@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.
Files changed (119) hide show
  1. package/dist/ai-edits/apply.js +169 -38
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/ai-edits/types.d.ts +7 -1
  4. package/dist/compare/compare.js +1 -0
  5. package/dist/compare/reproducible-package.d.ts +0 -17
  6. package/dist/compare/reproducible-package.js +12 -18
  7. package/dist/controller/canonicalClipboard.d.ts +23 -0
  8. package/dist/controller/canonicalClipboard.js +476 -0
  9. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  10. package/dist/controller/canonicalClipboardResources.js +382 -0
  11. package/dist/controller/canonicalComposition.d.ts +20 -3
  12. package/dist/controller/canonicalComposition.js +166 -38
  13. package/dist/controller/canonicalInlineProjection.d.ts +113 -0
  14. package/dist/controller/canonicalInlineProjection.js +393 -0
  15. package/dist/controller/canonicalInput.d.ts +13 -4
  16. package/dist/controller/canonicalInput.js +52 -13
  17. package/dist/controller/canonicalInputTimer.d.ts +7 -0
  18. package/dist/controller/canonicalInputTimer.js +11 -0
  19. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  20. package/dist/controller/canonicalPublicOperations.js +556 -0
  21. package/dist/controller/canonicalSession.d.ts +24 -6
  22. package/dist/controller/canonicalSession.js +259 -111
  23. package/dist/controller/canonicalStoryEditor.d.ts +5 -3
  24. package/dist/controller/canonicalStoryEditor.js +7 -2
  25. package/dist/controller/canonicalStructure.js +57 -4
  26. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  27. package/dist/controller/canonicalTocSelection.js +43 -0
  28. package/dist/controller/folioEditor.d.ts +2 -9
  29. package/dist/controller/folioEditor.js +5 -4
  30. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  31. package/dist/controller/headerFooterEditorManager.js +8 -7
  32. package/dist/controller/hiddenEditorApi.d.ts +12 -2
  33. package/dist/controller/hiddenEditorApi.js +4 -0
  34. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  35. package/dist/controller/hiddenEditorManager.js +216 -34
  36. package/dist/controller/noteEditorManager.d.ts +2 -1
  37. package/dist/controller/noteEditorManager.js +8 -7
  38. package/dist/document-operations.d.ts +8 -1
  39. package/dist/document-operations.js +6 -4
  40. package/dist/docx/blockPlainText.d.ts +1 -1
  41. package/dist/docx/blockPlainText.js +11 -3
  42. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  43. package/dist/docx/canonicalResourceSave.js +27 -0
  44. package/dist/docx/canonicalSave.d.ts +29 -0
  45. package/dist/docx/canonicalSave.js +101 -0
  46. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  47. package/dist/docx/canonicalStoryRepack.js +38 -5
  48. package/dist/docx/documentParser.d.ts +1 -13
  49. package/dist/docx/documentParser.js +1 -47
  50. package/dist/docx/ensureParaIds.js +10 -4
  51. package/dist/docx/footnoteParser.d.ts +1 -11
  52. package/dist/docx/footnoteParser.js +1 -16
  53. package/dist/docx/headerFooterParser.d.ts +1 -9
  54. package/dist/docx/headerFooterParser.js +1 -12
  55. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  56. package/dist/docx/headerFooterVerbatim.js +81 -3
  57. package/dist/docx/imageParser.d.ts +13 -1
  58. package/dist/docx/imageParser.js +20 -11
  59. package/dist/docx/metadataPrivacy.js +6 -1
  60. package/dist/docx/packageParts.js +9 -3
  61. package/dist/docx/paragraphPropertySource.js +2 -1
  62. package/dist/docx/parser.js +2 -1
  63. package/dist/docx/removeHeaderFooterParts.js +17 -6
  64. package/dist/docx/rezip.d.ts +49 -5
  65. package/dist/docx/rezip.js +745 -229
  66. package/dist/docx/saveDiagnostics.d.ts +11 -0
  67. package/dist/docx/selectiveSave.d.ts +2 -1
  68. package/dist/docx/selectiveSave.js +32 -3
  69. package/dist/docx/selectiveXmlPatch.js +7 -5
  70. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  71. package/dist/docx/serializer/documentSerializer.js +29 -10
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  73. package/dist/docx/storyBlockReplay.d.ts +21 -1
  74. package/dist/docx/storyBlockReplay.js +113 -9
  75. package/dist/docx/storyPlainText.d.ts +20 -0
  76. package/dist/docx/storyPlainText.js +23 -0
  77. package/dist/docx/structuralXmlPatch.js +10 -22
  78. package/dist/docx/styleParser.d.ts +5 -1
  79. package/dist/docx/styleParser.js +1 -1
  80. package/dist/docx/tableParser.d.ts +1 -8
  81. package/dist/docx/tableParser.js +1 -28
  82. package/dist/docx/textBoxParser.d.ts +1 -5
  83. package/dist/docx/textBoxParser.js +1 -20
  84. package/dist/docx/unzip.js +6 -1
  85. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  86. package/dist/internal/acceptedBlockProjection.js +45 -0
  87. package/dist/managers/DocumentLoaderManager.js +2 -1
  88. package/dist/markdown/escape.d.ts +3 -1
  89. package/dist/markdown/escape.js +9 -2
  90. package/dist/markdown/renderBlock.js +5 -120
  91. package/dist/markdown/renderParagraph.js +4 -2
  92. package/dist/markdown/renderRuns.d.ts +7 -1
  93. package/dist/markdown/renderRuns.js +32 -13
  94. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  95. package/dist/prosemirror/canonicalCommands.js +11 -4
  96. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  97. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  98. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  99. package/dist/prosemirror/clipboardIntent.js +9 -0
  100. package/dist/prosemirror/commands/pageBreak.js +2 -2
  101. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  102. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  103. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  104. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  105. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  106. package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
  107. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
  108. package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
  109. package/dist/prosemirror/insertOperations.js +2 -1
  110. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  111. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  112. package/dist/prosemirror/plugins/suggestionMode.js +7 -5
  113. package/dist/types/canonicalCapabilities.d.ts +215 -0
  114. package/dist/types/canonicalCapabilities.js +212 -0
  115. package/dist/types/canonicalSave.d.ts +11 -0
  116. package/dist/types/canonicalSave.js +0 -0
  117. package/dist/types/docxSerialization.d.ts +14 -0
  118. package/dist/types/docxSerialization.js +7 -0
  119. 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 collectBlockTexts = (blocks) => {
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) => collectBlockTexts(cell.content).join("\n")).join(" "));
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(...collectBlockTexts(block.content));
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 only references missing from the committed canonical snapshot, with exact multiplicity. */
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
- /** Authorize only references missing from the committed canonical snapshot, with exact multiplicity. */
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
- if (!document.originalBuffer) return repack();
11
- const xml = await (await JSZip.loadAsync(document.originalBuffer)).file("word/document.xml")?.async("text");
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 after = readDocumentSectionFacts(serializeDocument(document));
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, getCharacterCount, getDocumentText, getParagraphCount, getSectionCount, getWordCount, hasTemplateVariables, parseDocumentBody, parseDocumentBodyTree };
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, getCharacterCount, getDocumentText, getParagraphCount, getSectionCount, getWordCount, hasTemplateVariables, parseDocumentBody, parseDocumentBodyTree };
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
- zip.file(partPath, content, {
390
- compression: "DEFLATE",
391
- compressionOptions: { level: 6 },
392
- date: sourceEntry.date
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, getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote, mergeEndnoteMaps, mergeFootnoteMaps, parseEndnoteProperties, parseEndnotes, parseFootnoteProperties, parseFootnotes };
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, getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote, mergeEndnoteMaps, mergeFootnoteMaps, parseEndnoteProperties, parseEndnotes, parseFootnoteProperties, parseFootnotes };
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, getHeaderFooterText, getHeaderForPage, hasImages, hasPageNumberField, hasTables, headerFooterMapToTypeMap, isEmptyHeaderFooter, parseFooter, parseFooterReference, parseFooterReferences, parseHeader, parseHeaderFooter, parseHeaderReference, parseHeaderReferences };
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, getHeaderFooterText, getHeaderForPage, hasImages, hasPageNumberField, hasTables, headerFooterMapToTypeMap, isEmptyHeaderFooter, parseFooter, parseFooterReference, parseFooterReferences, parseHeader, parseHeaderFooter, parseHeaderReference, parseHeaderReferences };
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 };