@stll/folio-core 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.js +169 -38
- package/dist/ai-edits/headless.js +2 -2
- package/dist/ai-edits/types.d.ts +7 -1
- package/dist/compare/compare.js +1 -0
- package/dist/compare/reproducible-package.d.ts +0 -17
- package/dist/compare/reproducible-package.js +12 -18
- package/dist/controller/canonicalClipboard.d.ts +23 -0
- package/dist/controller/canonicalClipboard.js +476 -0
- package/dist/controller/canonicalClipboardResources.d.ts +21 -0
- package/dist/controller/canonicalClipboardResources.js +382 -0
- package/dist/controller/canonicalComposition.d.ts +20 -3
- package/dist/controller/canonicalComposition.js +166 -38
- package/dist/controller/canonicalInlineProjection.d.ts +113 -0
- package/dist/controller/canonicalInlineProjection.js +393 -0
- package/dist/controller/canonicalInput.d.ts +13 -4
- package/dist/controller/canonicalInput.js +52 -13
- package/dist/controller/canonicalInputTimer.d.ts +7 -0
- package/dist/controller/canonicalInputTimer.js +11 -0
- package/dist/controller/canonicalPublicOperations.d.ts +48 -0
- package/dist/controller/canonicalPublicOperations.js +556 -0
- package/dist/controller/canonicalSession.d.ts +24 -6
- package/dist/controller/canonicalSession.js +259 -111
- package/dist/controller/canonicalStoryEditor.d.ts +5 -3
- package/dist/controller/canonicalStoryEditor.js +7 -2
- package/dist/controller/canonicalStructure.js +57 -4
- package/dist/controller/canonicalTocSelection.d.ts +11 -0
- package/dist/controller/canonicalTocSelection.js +43 -0
- package/dist/controller/folioEditor.d.ts +2 -9
- package/dist/controller/folioEditor.js +5 -4
- package/dist/controller/headerFooterEditorManager.d.ts +2 -1
- package/dist/controller/headerFooterEditorManager.js +8 -7
- package/dist/controller/hiddenEditorApi.d.ts +12 -2
- package/dist/controller/hiddenEditorApi.js +4 -0
- package/dist/controller/hiddenEditorManager.d.ts +3 -1
- package/dist/controller/hiddenEditorManager.js +216 -34
- package/dist/controller/noteEditorManager.d.ts +2 -1
- package/dist/controller/noteEditorManager.js +8 -7
- package/dist/document-operations.d.ts +8 -1
- package/dist/document-operations.js +6 -4
- package/dist/docx/blockPlainText.d.ts +1 -1
- package/dist/docx/blockPlainText.js +11 -3
- package/dist/docx/canonicalResourceSave.d.ts +16 -0
- package/dist/docx/canonicalResourceSave.js +27 -0
- package/dist/docx/canonicalSave.d.ts +29 -0
- package/dist/docx/canonicalSave.js +101 -0
- package/dist/docx/canonicalStoryRepack.d.ts +1 -1
- package/dist/docx/canonicalStoryRepack.js +38 -5
- package/dist/docx/documentParser.d.ts +1 -13
- package/dist/docx/documentParser.js +1 -47
- package/dist/docx/ensureParaIds.js +10 -4
- package/dist/docx/footnoteParser.d.ts +1 -11
- package/dist/docx/footnoteParser.js +1 -16
- package/dist/docx/headerFooterParser.d.ts +1 -9
- package/dist/docx/headerFooterParser.js +1 -12
- package/dist/docx/headerFooterVerbatim.d.ts +26 -1
- package/dist/docx/headerFooterVerbatim.js +81 -3
- package/dist/docx/imageParser.d.ts +13 -1
- package/dist/docx/imageParser.js +20 -11
- package/dist/docx/metadataPrivacy.js +6 -1
- package/dist/docx/packageParts.js +9 -3
- package/dist/docx/paragraphPropertySource.js +2 -1
- package/dist/docx/parser.js +2 -1
- package/dist/docx/removeHeaderFooterParts.js +17 -6
- package/dist/docx/rezip.d.ts +49 -5
- package/dist/docx/rezip.js +745 -229
- package/dist/docx/saveDiagnostics.d.ts +11 -0
- package/dist/docx/selectiveSave.d.ts +2 -1
- package/dist/docx/selectiveSave.js +32 -3
- package/dist/docx/selectiveXmlPatch.js +7 -5
- package/dist/docx/serializer/documentSerializer.d.ts +9 -2
- package/dist/docx/serializer/documentSerializer.js +29 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
- package/dist/docx/storyBlockReplay.d.ts +21 -1
- package/dist/docx/storyBlockReplay.js +113 -9
- package/dist/docx/storyPlainText.d.ts +20 -0
- package/dist/docx/storyPlainText.js +23 -0
- package/dist/docx/structuralXmlPatch.js +10 -22
- package/dist/docx/styleParser.d.ts +5 -1
- package/dist/docx/styleParser.js +1 -1
- package/dist/docx/tableParser.d.ts +1 -8
- package/dist/docx/tableParser.js +1 -28
- package/dist/docx/textBoxParser.d.ts +1 -5
- package/dist/docx/textBoxParser.js +1 -20
- package/dist/docx/unzip.js +6 -1
- package/dist/internal/acceptedBlockProjection.d.ts +12 -0
- package/dist/internal/acceptedBlockProjection.js +45 -0
- package/dist/managers/DocumentLoaderManager.js +2 -1
- package/dist/markdown/escape.d.ts +3 -1
- package/dist/markdown/escape.js +9 -2
- package/dist/markdown/renderBlock.js +5 -120
- package/dist/markdown/renderParagraph.js +4 -2
- package/dist/markdown/renderRuns.d.ts +7 -1
- package/dist/markdown/renderRuns.js +32 -13
- package/dist/prosemirror/canonicalCommands.d.ts +28 -4
- package/dist/prosemirror/canonicalCommands.js +11 -4
- package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
- package/dist/prosemirror/canonicalSelectionRange.js +15 -0
- package/dist/prosemirror/clipboardIntent.d.ts +7 -0
- package/dist/prosemirror/clipboardIntent.js +9 -0
- package/dist/prosemirror/commands/pageBreak.js +2 -2
- package/dist/prosemirror/commands/pastePlainText.js +2 -1
- package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
- package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
- package/dist/prosemirror/conversion/toProseDoc.js +5 -4
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
- package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
- package/dist/prosemirror/insertOperations.js +2 -1
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
- package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
- package/dist/prosemirror/plugins/suggestionMode.js +7 -5
- package/dist/types/canonicalCapabilities.d.ts +215 -0
- package/dist/types/canonicalCapabilities.js +212 -0
- package/dist/types/canonicalSave.d.ts +11 -0
- package/dist/types/canonicalSave.js +0 -0
- package/dist/types/docxSerialization.d.ts +14 -0
- package/dist/types/docxSerialization.js +7 -0
- package/package.json +4 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectAcceptedBlocks } from "../internal/acceptedBlockProjection.js";
|
|
1
2
|
import { ALT_CHUNK_READER_DIAGNOSTIC, isAltChunkMarkup } from "./altChunk.js";
|
|
2
3
|
import { OPAQUE_REVISION_CARRIER_READER_DIAGNOSTIC, isOpaqueNestedRowMarkup, opaqueRevisionCarrierName } from "./opaqueCarrier.js";
|
|
3
4
|
import { getParagraphText } from "./paragraphParser.js";
|
|
@@ -18,7 +19,7 @@ import { panic } from "better-result";
|
|
|
18
19
|
* everything below block level and reads the accepted tracked-change view.
|
|
19
20
|
*/
|
|
20
21
|
/** One entry per block, so callers can join or count lines as they need. */
|
|
21
|
-
const
|
|
22
|
+
const collectResolvedBlockTexts = (blocks) => {
|
|
22
23
|
const texts = [];
|
|
23
24
|
for (const block of blocks) switch (block.type) {
|
|
24
25
|
case "paragraph":
|
|
@@ -27,12 +28,12 @@ const collectBlockTexts = (blocks) => {
|
|
|
27
28
|
case "table":
|
|
28
29
|
for (const row of block.rows) {
|
|
29
30
|
if (row.formatting?.hidden === true) continue;
|
|
30
|
-
texts.push(row.cells.map((cell) =>
|
|
31
|
+
texts.push(row.cells.map((cell) => collectResolvedBlockTexts(cell.content).join("\n")).join(" "));
|
|
31
32
|
}
|
|
32
33
|
break;
|
|
33
34
|
case "blockSdt":
|
|
34
35
|
case "blockCustomXml":
|
|
35
|
-
texts.push(...
|
|
36
|
+
texts.push(...collectResolvedBlockTexts(block.content));
|
|
36
37
|
break;
|
|
37
38
|
case "preservedBlock":
|
|
38
39
|
if (isAltChunkMarkup(block.xml)) texts.push(block.readerText === void 0 ? ALT_CHUNK_READER_DIAGNOSTIC : `${ALT_CHUNK_READER_DIAGNOSTIC}\n${block.readerText}`);
|
|
@@ -45,6 +46,13 @@ const collectBlockTexts = (blocks) => {
|
|
|
45
46
|
}
|
|
46
47
|
return texts;
|
|
47
48
|
};
|
|
49
|
+
/** One entry per accepted block; deleted breaks and table structure resolve together. */
|
|
50
|
+
const collectBlockTexts = (blocks) => {
|
|
51
|
+
const projection = projectAcceptedBlocks(blocks, void 0);
|
|
52
|
+
const texts = collectResolvedBlockTexts(projection?.blocks ?? blocks);
|
|
53
|
+
if (projection?.completeness === "partial") texts.push("[Unresolved tracked structural changes]");
|
|
54
|
+
return texts;
|
|
55
|
+
};
|
|
48
56
|
const blockPlainText = (blocks) => collectBlockTexts(blocks).join("\n");
|
|
49
57
|
//#endregion
|
|
50
58
|
export { blockPlainText, collectBlockTexts };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
|
|
3
|
+
import { SaveDiagnostic } from "./saveDiagnostics.js";
|
|
4
|
+
//#region src/docx/canonicalResourceSave.d.ts
|
|
5
|
+
declare const CanonicalResourceSaveRefusalError_base: import("better-result").TaggedErrorClass<"CanonicalResourceSaveRefusalError">;
|
|
6
|
+
declare class CanonicalResourceSaveRefusalError extends CanonicalResourceSaveRefusalError_base<{
|
|
7
|
+
message: string;
|
|
8
|
+
gap: typeof CANONICAL_GAP.resourceReplacement;
|
|
9
|
+
diagnostic: Extract<SaveDiagnostic, {
|
|
10
|
+
type: "canonicalResourceReplacement";
|
|
11
|
+
}>;
|
|
12
|
+
}> {}
|
|
13
|
+
/** Full and selective package writers preserve existing style/media source entries. */
|
|
14
|
+
declare const canonicalResourceReplacementOf: (document: document_d_exports.Document) => string | undefined;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CanonicalResourceSaveRefusalError, canonicalResourceReplacementOf };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
|
+
import { getDocumentSourceBaseline } from "./headerFooterVerbatim.js";
|
|
3
|
+
import { TaggedError } from "better-result";
|
|
4
|
+
//#region src/docx/canonicalResourceSave.ts
|
|
5
|
+
var CanonicalResourceSaveRefusalError = class extends TaggedError("CanonicalResourceSaveRefusalError") {};
|
|
6
|
+
/** Full and selective package writers preserve existing style/media source entries. */
|
|
7
|
+
const canonicalResourceReplacementOf = (document) => {
|
|
8
|
+
const source = getDocumentSourceBaseline(document);
|
|
9
|
+
if (source.type !== "captured" || !document.originalBuffer) return void 0;
|
|
10
|
+
const baseline = source.resourceStyles;
|
|
11
|
+
const current = document.package.styles;
|
|
12
|
+
if (baseline) {
|
|
13
|
+
const { styles: sourceStyles, ...sourceDefaults } = baseline;
|
|
14
|
+
const { styles: currentStyles, ...currentDefaults } = current ?? { styles: [] };
|
|
15
|
+
const currentById = new Map(currentStyles.map((style) => [style.styleId, style]));
|
|
16
|
+
if (canonicalJson(sourceDefaults) !== canonicalJson(currentDefaults) || sourceStyles.some((style) => canonicalJson(style) !== canonicalJson(currentById.get(style.styleId)))) return "word/styles.xml";
|
|
17
|
+
}
|
|
18
|
+
for (const [path, media] of source.resourceMedia) {
|
|
19
|
+
const currentMedia = document.package.media?.get(path);
|
|
20
|
+
if (!currentMedia || media.mimeType !== currentMedia.mimeType) return path;
|
|
21
|
+
const sourceBytes = new Uint8Array(media.data);
|
|
22
|
+
const currentBytes = new Uint8Array(currentMedia.data);
|
|
23
|
+
if (sourceBytes.length !== currentBytes.length || sourceBytes.some((byte, index) => byte !== currentBytes[index])) return path;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CanonicalResourceSaveRefusalError, canonicalResourceReplacementOf };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
|
|
2
|
+
import { SaveDiagnostic } from "./saveDiagnostics.js";
|
|
3
|
+
import { CanonicalSaveSnapshot } from "../types/canonicalSave.js";
|
|
4
|
+
import { FolioGetDocxOptions } from "../types/docxSerialization.js";
|
|
5
|
+
import { TripwireResult } from "./selectiveSaveTripwire.js";
|
|
6
|
+
import { FolioSelectiveSaveFlags } from "./selectiveSaveFlags.js";
|
|
7
|
+
//#region src/docx/canonicalSave.d.ts
|
|
8
|
+
declare const CanonicalSaveDiagnosticError_base: import("better-result").TaggedErrorClass<"CanonicalSaveDiagnosticError">;
|
|
9
|
+
/** Forward a fidelity fallback through the adapters' existing error channel. */
|
|
10
|
+
declare class CanonicalSaveDiagnosticError extends CanonicalSaveDiagnosticError_base<{
|
|
11
|
+
message: string;
|
|
12
|
+
gap: typeof CANONICAL_GAP.save;
|
|
13
|
+
diagnostic: SaveDiagnostic;
|
|
14
|
+
}> {}
|
|
15
|
+
type SerializeCanonicalSaveOptions = {
|
|
16
|
+
snapshot: CanonicalSaveSnapshot;
|
|
17
|
+
options?: FolioGetDocxOptions | undefined;
|
|
18
|
+
featureFlags?: FolioSelectiveSaveFlags | undefined;
|
|
19
|
+
};
|
|
20
|
+
/** Model and change signals come from one committed snapshot, never from PM. */
|
|
21
|
+
declare const serializeCanonicalSave: ({ snapshot, options, featureFlags }: SerializeCanonicalSaveOptions) => Promise<{
|
|
22
|
+
buffer: ArrayBuffer;
|
|
23
|
+
document: import("@stll/docx-core").Document;
|
|
24
|
+
version: number;
|
|
25
|
+
diagnostics: SaveDiagnostic[];
|
|
26
|
+
tripwireResult: TripwireResult | null;
|
|
27
|
+
}>;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { CanonicalSaveDiagnosticError, serializeCanonicalSave };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { withoutUnreferencedNotes } from "../prosemirror/noteReferenceReview.js";
|
|
2
|
+
import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
|
|
3
|
+
import { FOLIO_DOCX_SERIALIZATION_MODE } from "../types/docxSerialization.js";
|
|
4
|
+
import { CanonicalResourceSaveRefusalError, canonicalResourceReplacementOf } from "./canonicalResourceSave.js";
|
|
5
|
+
import { repackWithCanonicalStoryRemovals } from "./canonicalStoryRepack.js";
|
|
6
|
+
import { getDocumentSourceBaseline } from "./headerFooterVerbatim.js";
|
|
7
|
+
import { resolveSelectiveSaveFlags } from "./selectiveSaveFlags.js";
|
|
8
|
+
import { Result, TaggedError } from "better-result";
|
|
9
|
+
//#region src/docx/canonicalSave.ts
|
|
10
|
+
/** Forward a fidelity fallback through the adapters' existing error channel. */
|
|
11
|
+
var CanonicalSaveDiagnosticError = class extends TaggedError("CanonicalSaveDiagnosticError") {};
|
|
12
|
+
/** Model and change signals come from one committed snapshot, never from PM. */
|
|
13
|
+
const serializeCanonicalSave = async ({ snapshot, options, featureFlags }) => {
|
|
14
|
+
const document = withoutUnreferencedNotes(snapshot.document);
|
|
15
|
+
const replacementPart = snapshot.structure === "changed" ? canonicalResourceReplacementOf(document) : void 0;
|
|
16
|
+
if (replacementPart) {
|
|
17
|
+
const diagnostic = {
|
|
18
|
+
type: "canonicalResourceReplacement",
|
|
19
|
+
gap: CANONICAL_GAP.resourceReplacement,
|
|
20
|
+
part: replacementPart
|
|
21
|
+
};
|
|
22
|
+
options?.onDiagnostic?.(diagnostic);
|
|
23
|
+
throw new CanonicalResourceSaveRefusalError({
|
|
24
|
+
message: "Canonical save cannot preserve this package resource replacement.",
|
|
25
|
+
gap: CANONICAL_GAP.resourceReplacement,
|
|
26
|
+
diagnostic
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const baseline = document.originalBuffer;
|
|
30
|
+
const flags = resolveSelectiveSaveFlags(featureFlags);
|
|
31
|
+
const diagnostics = [];
|
|
32
|
+
const onDiagnostic = (diagnostic) => {
|
|
33
|
+
if (!diagnostics.some((existing) => existing.type === diagnostic.type && existing.part === diagnostic.part)) diagnostics.push(diagnostic);
|
|
34
|
+
};
|
|
35
|
+
const { repackDocx, createDocx } = await import("./rezip.js");
|
|
36
|
+
const useSelective = flags.selectiveSave && options?.mode !== FOLIO_DOCX_SERIALIZATION_MODE.full;
|
|
37
|
+
let selectiveBuffer = null;
|
|
38
|
+
if (baseline && (useSelective || flags.selectiveSaveTripwire)) {
|
|
39
|
+
const { attemptSelectiveSave } = await import("./selectiveSave.js");
|
|
40
|
+
selectiveBuffer = await attemptSelectiveSave(document, baseline, {
|
|
41
|
+
bodyAuthority: "canonical",
|
|
42
|
+
changedParaIds: new Set(snapshot.changedBlockIds),
|
|
43
|
+
structuralChange: snapshot.structure === "changed",
|
|
44
|
+
hasUntrackedChanges: false,
|
|
45
|
+
maxBytes: flags.selectiveSaveMaxBytes,
|
|
46
|
+
onDiagnostic
|
|
47
|
+
});
|
|
48
|
+
if (useSelective && !selectiveBuffer) onDiagnostic({
|
|
49
|
+
type: "selectiveSaveRefused",
|
|
50
|
+
part: "word/document.xml"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const repack = () => {
|
|
54
|
+
if (baseline && getDocumentSourceBaseline(document).type === "missing") onDiagnostic({
|
|
55
|
+
type: "sourceReplayUnavailable",
|
|
56
|
+
part: "word/document.xml"
|
|
57
|
+
});
|
|
58
|
+
return baseline ? repackWithCanonicalStoryRemovals({
|
|
59
|
+
document,
|
|
60
|
+
repack: () => repackDocx(document, {
|
|
61
|
+
onDiagnostic,
|
|
62
|
+
bodyAuthority: "canonical"
|
|
63
|
+
})
|
|
64
|
+
}) : createDocx(document, {
|
|
65
|
+
onDiagnostic,
|
|
66
|
+
bodyAuthority: "canonical"
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
let buffer = useSelective ? selectiveBuffer : null;
|
|
70
|
+
let fullBuffer = null;
|
|
71
|
+
if (!buffer) {
|
|
72
|
+
fullBuffer = await repack();
|
|
73
|
+
buffer = fullBuffer;
|
|
74
|
+
} else if (flags.selectiveSaveTripwire) {
|
|
75
|
+
const full = await Result.tryPromise({
|
|
76
|
+
try: repack,
|
|
77
|
+
catch: (error) => error
|
|
78
|
+
});
|
|
79
|
+
if (full.isOk()) fullBuffer = full.value;
|
|
80
|
+
}
|
|
81
|
+
let tripwireResult = null;
|
|
82
|
+
if (flags.selectiveSaveTripwire && fullBuffer) {
|
|
83
|
+
const { compareSelectiveVsFull } = await import("./selectiveSaveTripwire.js");
|
|
84
|
+
const capturedFullBuffer = fullBuffer;
|
|
85
|
+
const compared = await Result.tryPromise({
|
|
86
|
+
try: () => compareSelectiveVsFull(selectiveBuffer, capturedFullBuffer),
|
|
87
|
+
catch: (error) => error
|
|
88
|
+
});
|
|
89
|
+
if (compared.isOk()) tripwireResult = compared.value;
|
|
90
|
+
}
|
|
91
|
+
for (const diagnostic of diagnostics) options?.onDiagnostic?.(diagnostic);
|
|
92
|
+
return {
|
|
93
|
+
buffer,
|
|
94
|
+
document,
|
|
95
|
+
version: snapshot.version,
|
|
96
|
+
diagnostics,
|
|
97
|
+
tripwireResult
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
//#endregion
|
|
101
|
+
export { CanonicalSaveDiagnosticError, serializeCanonicalSave };
|
|
@@ -4,7 +4,7 @@ type CanonicalStoryRepackOptions<T> = {
|
|
|
4
4
|
document: document_d_exports.Document;
|
|
5
5
|
repack: () => Promise<T>;
|
|
6
6
|
};
|
|
7
|
-
/** Authorize
|
|
7
|
+
/** Authorize exact canonical section removals and references missing from the committed snapshot. */
|
|
8
8
|
declare const repackWithCanonicalStoryRemovals: <T>({ document, repack }: CanonicalStoryRepackOptions<T>) => Promise<T>;
|
|
9
9
|
//#endregion
|
|
10
10
|
export { repackWithCanonicalStoryRemovals };
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
+
import { withTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
|
|
1
2
|
import { withSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
|
|
2
3
|
import { readDocumentSectionFacts } from "./documentSectionFacts.js";
|
|
4
|
+
import { parseDocx } from "./parser.js";
|
|
5
|
+
import { assertSectionCarriersMatchModel } from "./rezip.js";
|
|
3
6
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
4
7
|
import JSZip from "jszip";
|
|
5
8
|
//#region src/docx/canonicalStoryRepack.ts
|
|
6
9
|
/** Canonical snapshots own explicit section references; the PM projection does not. */
|
|
7
10
|
const referenceKey = ({ element, type, rId }) => `${element}:${type}:${rId}`;
|
|
8
|
-
|
|
11
|
+
const xmlFingerprint = async (xml) => {
|
|
12
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(xml));
|
|
13
|
+
let fingerprint = "";
|
|
14
|
+
for (const byte of new Uint8Array(digest)) fingerprint += byte.toString(16).padStart(2, "0");
|
|
15
|
+
return fingerprint;
|
|
16
|
+
};
|
|
17
|
+
/** Authorize exact canonical section removals and references missing from the committed snapshot. */
|
|
9
18
|
const repackWithCanonicalStoryRemovals = async ({ document, repack }) => {
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
const originalBuffer = document.originalBuffer;
|
|
20
|
+
if (!originalBuffer) return repack();
|
|
21
|
+
const xml = await (await JSZip.loadAsync(originalBuffer)).file("word/document.xml")?.async("text");
|
|
12
22
|
if (!xml) return repack();
|
|
13
23
|
const before = readDocumentSectionFacts(xml);
|
|
14
|
-
const
|
|
24
|
+
const serializedXml = serializeDocument(document);
|
|
25
|
+
const after = readDocumentSectionFacts(serializedXml);
|
|
15
26
|
const remaining = /* @__PURE__ */ new Map();
|
|
16
27
|
for (const reference of after.headerFooterReferences) {
|
|
17
28
|
const key = referenceKey(reference);
|
|
@@ -34,7 +45,29 @@ const repackWithCanonicalStoryRemovals = async ({ document, repack }) => {
|
|
|
34
45
|
return withSectionReferenceResolution({
|
|
35
46
|
document,
|
|
36
47
|
removedReferences,
|
|
37
|
-
repack
|
|
48
|
+
repack: async () => {
|
|
49
|
+
if (after.sectionCount >= before.sectionCount) return repack();
|
|
50
|
+
const baseline = await parseDocx(originalBuffer, { preloadFonts: false });
|
|
51
|
+
const baselineXml = serializeDocument(baseline);
|
|
52
|
+
const baselineFacts = readDocumentSectionFacts(baselineXml);
|
|
53
|
+
if (baselineFacts.sectionCount !== before.sectionCount) return repack();
|
|
54
|
+
assertSectionCarriersMatchModel({
|
|
55
|
+
doc: baseline,
|
|
56
|
+
serializedSectionCount: baselineFacts.sectionCount
|
|
57
|
+
});
|
|
58
|
+
return withTrackedSectionEndpointRemoval({
|
|
59
|
+
document,
|
|
60
|
+
resolution: {
|
|
61
|
+
type: "tracked-section-endpoint-removal",
|
|
62
|
+
sourceParagraphEndpointCount: before.sectionCount,
|
|
63
|
+
expectedParagraphEndpointCount: after.sectionCount,
|
|
64
|
+
sourceEndpointFingerprint: await xmlFingerprint(baselineXml),
|
|
65
|
+
expectedEndpointFingerprint: await xmlFingerprint(serializedXml),
|
|
66
|
+
removedReferences
|
|
67
|
+
},
|
|
68
|
+
repack
|
|
69
|
+
});
|
|
70
|
+
}
|
|
38
71
|
});
|
|
39
72
|
};
|
|
40
73
|
//#endregion
|
|
@@ -54,22 +54,10 @@ declare function getAllParagraphs(body: document_d_exports.DocumentBody): docume
|
|
|
54
54
|
* Get all tables from document body
|
|
55
55
|
*/
|
|
56
56
|
declare function getAllTables(body: document_d_exports.DocumentBody): document_d_exports.Table[];
|
|
57
|
-
/**
|
|
58
|
-
* Get plain text from entire document body
|
|
59
|
-
*/
|
|
60
|
-
declare function getDocumentText(body: document_d_exports.DocumentBody): string;
|
|
61
57
|
/**
|
|
62
58
|
* Count total paragraphs in document
|
|
63
59
|
*/
|
|
64
60
|
declare function getParagraphCount(body: document_d_exports.DocumentBody): number;
|
|
65
|
-
/**
|
|
66
|
-
* Count total words in document (approximate)
|
|
67
|
-
*/
|
|
68
|
-
declare function getWordCount(body: document_d_exports.DocumentBody): number;
|
|
69
|
-
/**
|
|
70
|
-
* Count total characters in document
|
|
71
|
-
*/
|
|
72
|
-
declare function getCharacterCount(body: document_d_exports.DocumentBody): number;
|
|
73
61
|
/**
|
|
74
62
|
* Get section count
|
|
75
63
|
*/
|
|
@@ -79,4 +67,4 @@ declare function getSectionCount(body: document_d_exports.DocumentBody): number;
|
|
|
79
67
|
*/
|
|
80
68
|
declare function hasTemplateVariables(body: document_d_exports.DocumentBody): boolean;
|
|
81
69
|
//#endregion
|
|
82
|
-
export { extractAllTemplateVariables, extractTemplateVariables, getAllParagraphs, getAllTables,
|
|
70
|
+
export { extractAllTemplateVariables, extractTemplateVariables, getAllParagraphs, getAllTables, getParagraphCount, getSectionCount, hasTemplateVariables, parseDocumentBody, parseDocumentBodyTree };
|
|
@@ -239,58 +239,12 @@ function getNestedTables(table) {
|
|
|
239
239
|
return tables;
|
|
240
240
|
}
|
|
241
241
|
/**
|
|
242
|
-
* Get plain text from entire document body
|
|
243
|
-
*/
|
|
244
|
-
function getDocumentText(body) {
|
|
245
|
-
const lines = [];
|
|
246
|
-
for (const block of body.content) if (block.type === "paragraph") lines.push(getParagraphText(block));
|
|
247
|
-
else if (block.type === "table") lines.push(getTableText(block));
|
|
248
|
-
else if (block.type === "blockSdt" || block.type === "blockCustomXml") lines.push(getTextFromBlocks(block.content));
|
|
249
|
-
return lines.join("\n");
|
|
250
|
-
}
|
|
251
|
-
const getTextFromBlocks = (blocks) => blocks.flatMap((block) => {
|
|
252
|
-
if (block.type === "paragraph") return [getParagraphText(block)];
|
|
253
|
-
if (block.type === "table") return [getTableText(block)];
|
|
254
|
-
return block.type === "blockSdt" || block.type === "blockCustomXml" ? [getTextFromBlocks(block.content)] : [];
|
|
255
|
-
}).join("\n");
|
|
256
|
-
/**
|
|
257
|
-
* Get plain text from a table
|
|
258
|
-
*/
|
|
259
|
-
function getTableText(table) {
|
|
260
|
-
const lines = [];
|
|
261
|
-
for (const row of table.rows) {
|
|
262
|
-
const rowTexts = [];
|
|
263
|
-
for (const cell of row.cells) {
|
|
264
|
-
const cellTexts = [];
|
|
265
|
-
for (const content of cell.content) if (content.type === "paragraph") cellTexts.push(getParagraphText(content));
|
|
266
|
-
else if (content.type === "table") cellTexts.push(getTableText(content));
|
|
267
|
-
else if (content.type === "blockSdt" || content.type === "blockCustomXml") cellTexts.push(getTextFromBlocks(content.content));
|
|
268
|
-
rowTexts.push(cellTexts.join("\n"));
|
|
269
|
-
}
|
|
270
|
-
lines.push(rowTexts.join(" "));
|
|
271
|
-
}
|
|
272
|
-
return lines.join("\n");
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
242
|
* Count total paragraphs in document
|
|
276
243
|
*/
|
|
277
244
|
function getParagraphCount(body) {
|
|
278
245
|
return getAllParagraphs(body).length;
|
|
279
246
|
}
|
|
280
247
|
/**
|
|
281
|
-
* Count total words in document (approximate)
|
|
282
|
-
*/
|
|
283
|
-
function getWordCount(body) {
|
|
284
|
-
const words = getDocumentText(body).trim().split(/\s+/u);
|
|
285
|
-
return words.length > 0 && words[0] !== "" ? words.length : 0;
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Count total characters in document
|
|
289
|
-
*/
|
|
290
|
-
function getCharacterCount(body) {
|
|
291
|
-
return getDocumentText(body).length;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
248
|
* Get section count
|
|
295
249
|
*/
|
|
296
250
|
function getSectionCount(body) {
|
|
@@ -303,4 +257,4 @@ function hasTemplateVariables(body) {
|
|
|
303
257
|
return extractAllTemplateVariables(body.content).length > 0;
|
|
304
258
|
}
|
|
305
259
|
//#endregion
|
|
306
|
-
export { extractAllTemplateVariables, extractTemplateVariables, getAllParagraphs, getAllTables,
|
|
260
|
+
export { extractAllTemplateVariables, extractTemplateVariables, getAllParagraphs, getAllTables, getParagraphCount, getSectionCount, hasTemplateVariables, parseDocumentBody, parseDocumentBodyTree };
|
|
@@ -6,6 +6,7 @@ import { scanStreamingXmlElements } from "./streamingXmlParser.js";
|
|
|
6
6
|
import { resolveNamespaceUri } from "./xmlNamespaceContext.js";
|
|
7
7
|
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, getAttribute, getChildElements, getLocalName, getNamespaceUri, parseXmlDocument, resolveAttributeNamespaceUri } from "./xmlParser.js";
|
|
8
8
|
import { TaggedError } from "better-result";
|
|
9
|
+
import { writeZipPart } from "@stll/docx-core/zip";
|
|
9
10
|
import JSZip from "jszip";
|
|
10
11
|
//#region src/docx/ensureParaIds.ts
|
|
11
12
|
/**
|
|
@@ -386,10 +387,15 @@ const ensureParaIdsInternal = async (docx, options) => {
|
|
|
386
387
|
for (const [partPath, content] of updates) {
|
|
387
388
|
const sourceEntry = zip.file(partPath);
|
|
388
389
|
if (sourceEntry === null) throw createEnsureParaIdsError(`Package part disappeared during normalization: ${partPath}`);
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
390
|
+
writeZipPart({
|
|
391
|
+
zip,
|
|
392
|
+
path: partPath,
|
|
393
|
+
data: content,
|
|
394
|
+
options: {
|
|
395
|
+
compression: "DEFLATE",
|
|
396
|
+
compressionOptions: { level: 6 },
|
|
397
|
+
date: sourceEntry.date
|
|
398
|
+
}
|
|
393
399
|
});
|
|
394
400
|
}
|
|
395
401
|
return {
|
|
@@ -67,16 +67,6 @@ declare function parseFootnotes(footnotesXml: string | null, styles?: StyleMap |
|
|
|
67
67
|
* @returns EndnoteMap with all endnotes
|
|
68
68
|
*/
|
|
69
69
|
declare function parseEndnotes(endnotesXml: string | null, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null, context?: ParseContext, previews?: PreviewLedger): EndnoteMap;
|
|
70
|
-
/**
|
|
71
|
-
* Get plain text content of a footnote.
|
|
72
|
-
*
|
|
73
|
-
* Uses the accepted tracked-change view and recurses through every note block.
|
|
74
|
-
*/
|
|
75
|
-
declare function getFootnoteText(footnote: document_d_exports.Footnote): string;
|
|
76
|
-
/**
|
|
77
|
-
* Get plain text content of an endnote.
|
|
78
|
-
*/
|
|
79
|
-
declare function getEndnoteText(endnote: document_d_exports.Endnote): string;
|
|
80
70
|
/**
|
|
81
71
|
* Check if a footnote is a separator (not regular content)
|
|
82
72
|
*/
|
|
@@ -102,4 +92,4 @@ declare function mergeFootnoteMaps(...maps: FootnoteMap[]): FootnoteMap;
|
|
|
102
92
|
*/
|
|
103
93
|
declare function mergeEndnoteMaps(...maps: EndnoteMap[]): EndnoteMap;
|
|
104
94
|
//#endregion
|
|
105
|
-
export { EndnoteMap, FootnoteMap, createEmptyEndnoteMap, createEmptyFootnoteMap,
|
|
95
|
+
export { EndnoteMap, FootnoteMap, createEmptyEndnoteMap, createEmptyFootnoteMap, isSeparatorEndnote, isSeparatorFootnote, mergeEndnoteMaps, mergeFootnoteMaps, parseEndnoteProperties, parseEndnotes, parseFootnoteProperties, parseFootnotes };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { blockPlainText } from "./blockPlainText.js";
|
|
2
1
|
import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
|
|
3
2
|
import { parseParagraph } from "./paragraphParser.js";
|
|
4
3
|
import { standalonePreviewLedger } from "./previewBudget.js";
|
|
@@ -196,20 +195,6 @@ function createEndnoteMap(byId, endnotes) {
|
|
|
196
195
|
};
|
|
197
196
|
}
|
|
198
197
|
/**
|
|
199
|
-
* Get plain text content of a footnote.
|
|
200
|
-
*
|
|
201
|
-
* Uses the accepted tracked-change view and recurses through every note block.
|
|
202
|
-
*/
|
|
203
|
-
function getFootnoteText(footnote) {
|
|
204
|
-
return blockPlainText(footnote.content);
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Get plain text content of an endnote.
|
|
208
|
-
*/
|
|
209
|
-
function getEndnoteText(endnote) {
|
|
210
|
-
return blockPlainText(endnote.content);
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
198
|
* Check if a footnote is a separator (not regular content)
|
|
214
199
|
*/
|
|
215
200
|
function isSeparatorFootnote(footnote) {
|
|
@@ -258,4 +243,4 @@ function mergeEndnoteMaps(...maps) {
|
|
|
258
243
|
return createEndnoteMap(byId, endnotes);
|
|
259
244
|
}
|
|
260
245
|
//#endregion
|
|
261
|
-
export { createEmptyEndnoteMap, createEmptyFootnoteMap,
|
|
246
|
+
export { createEmptyEndnoteMap, createEmptyFootnoteMap, isSeparatorEndnote, isSeparatorFootnote, mergeEndnoteMaps, mergeFootnoteMaps, parseEndnoteProperties, parseEndnotes, parseFootnoteProperties, parseFootnotes };
|
|
@@ -81,14 +81,6 @@ declare function createEmptyHeaderFooterMap(): HeaderFooterMap;
|
|
|
81
81
|
* @returns HeaderFooterMap with all parsed headers/footers
|
|
82
82
|
*/
|
|
83
83
|
declare function buildHeaderFooterMap(references: (document_d_exports.HeaderReference | document_d_exports.FooterReference)[], xmlContents: Map<string, string>, isHeader: boolean, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null): HeaderFooterMap;
|
|
84
|
-
/**
|
|
85
|
-
* Get plain text content of a header/footer.
|
|
86
|
-
*
|
|
87
|
-
* Shares one walk with the note stories: this used to read only text runs, so a
|
|
88
|
-
* header's fields, hyperlinks, tabs and breaks were silently absent from its
|
|
89
|
-
* text while the same paragraph in a footnote read in full.
|
|
90
|
-
*/
|
|
91
|
-
declare function getHeaderFooterText(hf: document_d_exports.HeaderFooter): string;
|
|
92
84
|
/**
|
|
93
85
|
* Check if header/footer is empty (no content)
|
|
94
86
|
*/
|
|
@@ -129,4 +121,4 @@ declare function hasImages(hf: document_d_exports.HeaderFooter): boolean;
|
|
|
129
121
|
*/
|
|
130
122
|
declare function hasTables(hf: document_d_exports.HeaderFooter): boolean;
|
|
131
123
|
//#endregion
|
|
132
|
-
export { HeaderFooterMap, buildHeaderFooterMap, createEmptyHeaderFooterMap, getFooterForPage,
|
|
124
|
+
export { HeaderFooterMap, buildHeaderFooterMap, createEmptyHeaderFooterMap, getFooterForPage, getHeaderForPage, hasImages, hasPageNumberField, hasTables, headerFooterMapToTypeMap, isEmptyHeaderFooter, parseFooter, parseFooterReference, parseFooterReferences, parseHeader, parseHeaderFooter, parseHeaderReference, parseHeaderReferences };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { parseBlockContent } from "./blockContentParser.js";
|
|
2
|
-
import { blockPlainText } from "./blockPlainText.js";
|
|
3
2
|
import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
|
|
4
3
|
import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
|
|
5
4
|
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
@@ -150,16 +149,6 @@ function buildHeaderFooterMap(references, xmlContents, isHeader, styles = null,
|
|
|
150
149
|
return createHeaderFooterMap(byId);
|
|
151
150
|
}
|
|
152
151
|
/**
|
|
153
|
-
* Get plain text content of a header/footer.
|
|
154
|
-
*
|
|
155
|
-
* Shares one walk with the note stories: this used to read only text runs, so a
|
|
156
|
-
* header's fields, hyperlinks, tabs and breaks were silently absent from its
|
|
157
|
-
* text while the same paragraph in a footnote read in full.
|
|
158
|
-
*/
|
|
159
|
-
function getHeaderFooterText(hf) {
|
|
160
|
-
return blockPlainText(hf.content);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
152
|
* Check if header/footer is empty (no content)
|
|
164
153
|
*/
|
|
165
154
|
function isEmptyHeaderFooter(hf) {
|
|
@@ -254,4 +243,4 @@ function hasTables(hf) {
|
|
|
254
243
|
return false;
|
|
255
244
|
}
|
|
256
245
|
//#endregion
|
|
257
|
-
export { buildHeaderFooterMap, createEmptyHeaderFooterMap, getFooterForPage,
|
|
246
|
+
export { buildHeaderFooterMap, createEmptyHeaderFooterMap, getFooterForPage, getHeaderForPage, hasImages, hasPageNumberField, hasTables, headerFooterMapToTypeMap, isEmptyHeaderFooter, parseFooter, parseFooterReference, parseFooterReferences, parseHeader, parseHeaderFooter, parseHeaderReference, parseHeaderReferences };
|
|
@@ -9,6 +9,13 @@ type HeaderFooterSourceBaseline = {
|
|
|
9
9
|
content: readonly document_d_exports.BlockContent[];
|
|
10
10
|
};
|
|
11
11
|
declare const captureHeaderFooterPackageBaselines: (document: document_d_exports.Document) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Transfer body, header and footer capture handles across a trusted
|
|
14
|
+
* document graph clone. `structuredClone` drops symbol-keyed properties, so a
|
|
15
|
+
* clone would otherwise read an edited part as uncaptured ("missing") rather
|
|
16
|
+
* than as a mismatch against its source.
|
|
17
|
+
*/
|
|
18
|
+
declare const copyHeaderFooterBaselineHandles: (target: document_d_exports.Document, source: document_d_exports.Document) => void;
|
|
12
19
|
/** Transfer the parsed package registry only across a trusted document graph clone. */
|
|
13
20
|
declare const copyHeaderFooterPackageBaselines: (target: document_d_exports.Document, source: document_d_exports.Document) => void;
|
|
14
21
|
declare const getHeaderFooterSourceBaseline: (hf: document_d_exports.HeaderFooter, originalBuffer?: ArrayBuffer) => HeaderFooterSourceBaseline;
|
|
@@ -18,5 +25,23 @@ declare const canReplayHeaderFooterVerbatim: (hf: document_d_exports.HeaderFoote
|
|
|
18
25
|
declare const assignHeaderFooterVerbatimXml: (hf: document_d_exports.HeaderFooter, xml: string) => void;
|
|
19
26
|
declare const refreshHeaderFooterVerbatimFingerprint: (hf: document_d_exports.HeaderFooter) => void;
|
|
20
27
|
declare const clearHeaderFooterVerbatimXml: (hf: document_d_exports.HeaderFooter) => void;
|
|
28
|
+
/** Capture the body in the same trusted package registry as secondary stories. */
|
|
29
|
+
declare const captureDocumentSourceBaseline: (document: document_d_exports.Document, xml: string) => void;
|
|
30
|
+
declare const getDocumentSourceBaseline: (document: document_d_exports.Document) => {
|
|
31
|
+
type: "missing";
|
|
32
|
+
} | {
|
|
33
|
+
type: "mismatch";
|
|
34
|
+
} | {
|
|
35
|
+
type: "captured";
|
|
36
|
+
body: document_d_exports.Document["package"]["document"];
|
|
37
|
+
xml: string;
|
|
38
|
+
fingerprint: string;
|
|
39
|
+
resourceStyles: document_d_exports.Document["package"]["styles"];
|
|
40
|
+
resourceRelationships: document_d_exports.Document["package"]["relationships"];
|
|
41
|
+
resourceMedia: ReadonlyMap<string, {
|
|
42
|
+
data: ArrayBuffer;
|
|
43
|
+
mimeType: string;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
21
46
|
//#endregion
|
|
22
|
-
export { assignHeaderFooterVerbatimXml, canReplayHeaderFooterBlocks, canReplayHeaderFooterVerbatim, captureHeaderFooterPackageBaselines, clearHeaderFooterVerbatimXml, copyHeaderFooterPackageBaselines, getHeaderFooterSourceBaseline, getHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint };
|
|
47
|
+
export { assignHeaderFooterVerbatimXml, canReplayHeaderFooterBlocks, canReplayHeaderFooterVerbatim, captureDocumentSourceBaseline, captureHeaderFooterPackageBaselines, clearHeaderFooterVerbatimXml, copyHeaderFooterBaselineHandles, copyHeaderFooterPackageBaselines, getDocumentSourceBaseline, getHeaderFooterSourceBaseline, getHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint };
|