@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
@@ -15,7 +15,7 @@ const readFingerprint = (hf) => {
15
15
  });
16
16
  return value;
17
17
  };
18
- const BASELINE_HANDLE = Symbol("header-footer-source-baseline");
18
+ const BASELINE_HANDLE = Symbol("package-source-baseline");
19
19
  const contentBaselines = /* @__PURE__ */ new WeakMap();
20
20
  const captureContentBaseline = (hf) => {
21
21
  const fingerprint = hf.verbatimFingerprint;
@@ -23,6 +23,7 @@ const captureContentBaseline = (hf) => {
23
23
  const handle = {};
24
24
  const content = structuredClone(hf.content);
25
25
  contentBaselines.set(handle, {
26
+ type: "headerFooter",
26
27
  fingerprint,
27
28
  xml: hf.verbatimXml,
28
29
  contentFingerprint: canonicalJson(content),
@@ -46,6 +47,33 @@ const captureHeaderFooterPackageBaselines = (document) => {
46
47
  }
47
48
  packageBaselines.set(document.originalBuffer, handles);
48
49
  };
50
+ /**
51
+ * Transfer body, header and footer capture handles across a trusted
52
+ * document graph clone. `structuredClone` drops symbol-keyed properties, so a
53
+ * clone would otherwise read an edited part as uncaptured ("missing") rather
54
+ * than as a mismatch against its source.
55
+ */
56
+ const copyHeaderFooterBaselineHandles = (target, source) => {
57
+ const sourceBody = source.package.document;
58
+ if (BASELINE_HANDLE in sourceBody) Object.defineProperty(target.package.document, BASELINE_HANDLE, {
59
+ value: sourceBody[BASELINE_HANDLE],
60
+ enumerable: true,
61
+ configurable: true
62
+ });
63
+ for (const kind of ["headers", "footers"]) {
64
+ const targets = target.package[kind];
65
+ for (const [rId, sourcePart] of source.package[kind] ?? []) {
66
+ if (!(BASELINE_HANDLE in sourcePart)) continue;
67
+ const targetPart = targets?.get(rId);
68
+ if (!targetPart) continue;
69
+ Object.defineProperty(targetPart, BASELINE_HANDLE, {
70
+ value: sourcePart[BASELINE_HANDLE],
71
+ enumerable: true,
72
+ configurable: true
73
+ });
74
+ }
75
+ }
76
+ };
49
77
  /** Transfer the parsed package registry only across a trusted document graph clone. */
50
78
  const copyHeaderFooterPackageBaselines = (target, source) => {
51
79
  if (!target.originalBuffer || !source.originalBuffer) return;
@@ -59,7 +87,7 @@ const getHeaderFooterSourceBaseline = (hf, originalBuffer) => {
59
87
  if (handle === void 0 && !(BASELINE_HANDLE in hf)) return { type: "missing" };
60
88
  if (typeof handle !== "object" || handle === null) return { type: "mismatch" };
61
89
  const baseline = contentBaselines.get(handle);
62
- if (!baseline || baseline.fingerprint !== hf.verbatimFingerprint || baseline.xml !== hf.verbatimXml || baseline.contentFingerprint !== canonicalJson(baseline.content)) return { type: "mismatch" };
90
+ if (!baseline || baseline.type !== "headerFooter" || baseline.fingerprint !== hf.verbatimFingerprint || baseline.xml !== hf.verbatimXml || baseline.contentFingerprint !== canonicalJson(baseline.content)) return { type: "mismatch" };
63
91
  return {
64
92
  type: "captured",
65
93
  content: baseline.content
@@ -119,5 +147,55 @@ const clearHeaderFooterVerbatimXml = (hf) => {
119
147
  delete ext.verbatimFingerprint;
120
148
  Reflect.deleteProperty(hf, BASELINE_HANDLE);
121
149
  };
150
+ /** Capture the body in the same trusted package registry as secondary stories. */
151
+ const captureDocumentSourceBaseline = (document, xml) => {
152
+ const body = structuredClone(document.package.document);
153
+ const handle = {};
154
+ contentBaselines.set(handle, {
155
+ type: "body",
156
+ fingerprint: { type: "pending" },
157
+ xml,
158
+ body,
159
+ resourceStyles: structuredClone(document.package.styles),
160
+ resourceRelationships: structuredClone(document.package.relationships),
161
+ resourceMedia: new Map([...document.package.media ?? []].map(([path, media]) => [path, {
162
+ data: media.data,
163
+ mimeType: media.mimeType
164
+ }]))
165
+ });
166
+ Object.defineProperty(document.package.document, BASELINE_HANDLE, {
167
+ value: handle,
168
+ enumerable: true,
169
+ configurable: true
170
+ });
171
+ if (document.originalBuffer) {
172
+ const registry = packageBaselines.get(document.originalBuffer) ?? /* @__PURE__ */ new Map();
173
+ registry.set("word/document.xml", /* @__PURE__ */ new Map([["body", handle]]));
174
+ packageBaselines.set(document.originalBuffer, registry);
175
+ }
176
+ };
177
+ const getDocumentSourceBaseline = (document) => {
178
+ const body = document.package.document;
179
+ const handle = BASELINE_HANDLE in body ? body[BASELINE_HANDLE] : document.originalBuffer && packageBaselines.get(document.originalBuffer)?.get("word/document.xml")?.get("body");
180
+ if (handle === void 0) return { type: "missing" };
181
+ if (typeof handle !== "object" || handle === null) return { type: "mismatch" };
182
+ const baseline = contentBaselines.get(handle);
183
+ if (!baseline || baseline.type !== "body") return { type: "mismatch" };
184
+ const fingerprint = JSON.stringify(baseline.body);
185
+ if (baseline.fingerprint.type === "pending") baseline.fingerprint = {
186
+ type: "captured",
187
+ value: fingerprint
188
+ };
189
+ if (baseline.fingerprint.value !== fingerprint) return { type: "mismatch" };
190
+ return {
191
+ type: "captured",
192
+ body: baseline.body,
193
+ xml: baseline.xml,
194
+ fingerprint,
195
+ resourceStyles: baseline.resourceStyles,
196
+ resourceRelationships: baseline.resourceRelationships,
197
+ resourceMedia: baseline.resourceMedia
198
+ };
199
+ };
122
200
  //#endregion
123
- export { assignHeaderFooterVerbatimXml, canReplayHeaderFooterBlocks, canReplayHeaderFooterVerbatim, captureHeaderFooterPackageBaselines, clearHeaderFooterVerbatimXml, copyHeaderFooterPackageBaselines, getHeaderFooterSourceBaseline, getHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint };
201
+ export { assignHeaderFooterVerbatimXml, canReplayHeaderFooterBlocks, canReplayHeaderFooterVerbatim, captureDocumentSourceBaseline, captureHeaderFooterPackageBaselines, clearHeaderFooterVerbatimXml, copyHeaderFooterBaselineHandles, copyHeaderFooterPackageBaselines, getDocumentSourceBaseline, getHeaderFooterSourceBaseline, getHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint };
@@ -22,6 +22,18 @@ declare function resolveImageData(rId: string | undefined, rels: document_d_expo
22
22
  mimeType?: string;
23
23
  filename?: string;
24
24
  };
25
+ type ImageDerivedFactsOptions = {
26
+ id: string | undefined;
27
+ pictureNames: NonNullable<document_d_exports.Image["pictureNames"]>;
28
+ imageData: ReturnType<typeof resolveImageData>;
29
+ };
30
+ /** Facts the parser obtains from exported drawing markup and image relationships. */
31
+ declare const imageDerivedFactsOf: ({ id, pictureNames, imageData }: ImageDerivedFactsOptions) => {
32
+ filename?: string;
33
+ mimeType?: string;
34
+ pictureNames?: import("@stll/docx-core/model").NonVisualDrawingNames;
35
+ id?: string;
36
+ };
25
37
  /**
26
38
  * Parse a w:drawing element
27
39
  *
@@ -97,4 +109,4 @@ declare function getWrapDistancesPx(image: document_d_exports.Image): {
97
109
  */
98
110
  declare function needsTextWrapping(image: document_d_exports.Image): boolean;
99
111
  //#endregion
100
- export { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE, getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
112
+ export { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE, getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, imageDerivedFactsOf, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
@@ -10,7 +10,7 @@ import { RELATIONSHIP_TYPES, resolveRelationshipIdOfType } from "./relsParser.js
10
10
  import { isTextBoxDrawing } from "./textBoxParser.js";
11
11
  import { percentageSpelling } from "./transitionalSpelling.js";
12
12
  import { captureVerbatimXml } from "./verbatimCapture.js";
13
- import { findByFullName, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getChildElements, getLocalName, getNamespaceUri, parseNumericAttribute, parseOnOffAttribute, parseOnOffValue } from "./xmlParser.js";
13
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, findAttributeByNamespaceUri, findByFullName, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getChildElements, getLocalName, getNamespaceUri, parseNumericAttribute, parseOnOffAttribute, parseOnOffValue } from "./xmlParser.js";
14
14
  import { relationshipIdOf } from "@stll/docx-core/model";
15
15
  //#region src/docx/imageParser.ts
16
16
  /**
@@ -129,7 +129,7 @@ const parseDocPropsExtensions = (docPr) => {
129
129
  const parseDocPrLink = (docPr, localName) => {
130
130
  const element = findChildByNamespaceUri(docPr, DRAWINGML_NAMESPACE_URIS, localName);
131
131
  if (!element) return;
132
- const rId = getAttribute(element, "r", "id");
132
+ const rId = findAttributeByNamespaceUri(element, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "id")?.value;
133
133
  return {
134
134
  xml: captureVerbatimXml(element),
135
135
  ...rId == null ? {} : { rId }
@@ -382,6 +382,13 @@ function resolveImageData(rId, rels, media) {
382
382
  ...filename !== void 0 ? { filename } : {}
383
383
  };
384
384
  }
385
+ /** Facts the parser obtains from exported drawing markup and image relationships. */
386
+ const imageDerivedFactsOf = ({ id, pictureNames, imageData }) => ({
387
+ ...id ? { id } : {},
388
+ ...Object.keys(pictureNames).length > 0 ? { pictureNames } : {},
389
+ ...imageData.mimeType ? { mimeType: imageData.mimeType } : {},
390
+ ...imageData.filename ? { filename: imageData.filename } : {}
391
+ });
385
392
  /**
386
393
  * Parse a wp:inline element (inline image)
387
394
  *
@@ -411,18 +418,19 @@ function parseInline(inlineEl, rels, media) {
411
418
  size,
412
419
  wrap
413
420
  };
414
- if (props.id) image.id = props.id;
421
+ Object.assign(image, imageDerivedFactsOf({
422
+ id: props.id,
423
+ pictureNames,
424
+ imageData
425
+ }));
415
426
  if (props.name !== void 0) image.docPrName = props.name;
416
427
  if (props.alt !== void 0) image.alt = props.alt;
417
428
  if (props.title !== void 0) image.title = props.title;
418
- if (Object.keys(pictureNames).length > 0) image.pictureNames = pictureNames;
419
429
  if (props.decorative !== void 0) image.decorative = props.decorative;
420
430
  if (props.hidden !== void 0) image.hidden = props.hidden;
421
431
  if (props.docPrExtensions !== void 0) image.docPrExtensions = props.docPrExtensions;
422
432
  const safeSrc = sanitizeImageSrc(imageData.src);
423
433
  if (safeSrc) image.src = safeSrc;
424
- if (imageData.mimeType) image.mimeType = imageData.mimeType;
425
- if (imageData.filename) image.filename = imageData.filename;
426
434
  if (padding) image.padding = padding;
427
435
  if (transform) image.transform = transform;
428
436
  if (crop) image.crop = crop;
@@ -492,18 +500,19 @@ function parseAnchor(anchorEl, rels, media) {
492
500
  size,
493
501
  wrap
494
502
  };
495
- if (props.id) image.id = props.id;
503
+ Object.assign(image, imageDerivedFactsOf({
504
+ id: props.id,
505
+ pictureNames,
506
+ imageData
507
+ }));
496
508
  if (props.name !== void 0) image.docPrName = props.name;
497
509
  if (props.alt !== void 0) image.alt = props.alt;
498
510
  if (props.title !== void 0) image.title = props.title;
499
- if (Object.keys(pictureNames).length > 0) image.pictureNames = pictureNames;
500
511
  if (props.decorative !== void 0) image.decorative = props.decorative;
501
512
  if (props.hidden !== void 0) image.hidden = props.hidden;
502
513
  if (props.docPrExtensions !== void 0) image.docPrExtensions = props.docPrExtensions;
503
514
  const safeSrc = sanitizeImageSrc(imageData.src);
504
515
  if (safeSrc) image.src = safeSrc;
505
- if (imageData.mimeType) image.mimeType = imageData.mimeType;
506
- if (imageData.filename) image.filename = imageData.filename;
507
516
  if (position) image.position = position;
508
517
  if (padding) image.padding = padding;
509
518
  if (transform) image.transform = transform;
@@ -628,4 +637,4 @@ function needsTextWrapping(image) {
628
637
  ].includes(image.wrap.type);
629
638
  }
630
639
  //#endregion
631
- export { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE, getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
640
+ export { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE, getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, imageDerivedFactsOf, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
@@ -1,6 +1,7 @@
1
1
  import { countCentralDirectoryRecords, createInflationBudget, inflateEntryWithinLimits } from "./archiveInflation.js";
2
2
  import { elementToXml, getLocalName, parseXmlDocument } from "./xmlParser.js";
3
3
  import { TaggedError } from "better-result";
4
+ import { writeZipPart } from "@stll/docx-core/zip";
4
5
  import JSZip from "jszip";
5
6
  //#region src/docx/metadataPrivacy.ts
6
7
  const FOLIO_DOCUMENT_METADATA_PROPERTIES = Object.freeze([
@@ -152,7 +153,11 @@ const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
152
153
  removedMetadataProperties: []
153
154
  }
154
155
  };
155
- zip.file(coreProperties.name, rewritten.xml);
156
+ writeZipPart({
157
+ zip,
158
+ path: coreProperties.name,
159
+ data: rewritten.xml
160
+ });
156
161
  return {
157
162
  buffer: await zip.generateAsync({
158
163
  type: "arraybuffer",
@@ -1,4 +1,5 @@
1
1
  import { Result } from "better-result";
2
+ import { writeZipPart } from "@stll/docx-core/zip";
2
3
  //#region src/docx/packageParts.ts
3
4
  /**
4
5
  * What a repack carries out of the source package, and the guarantee that
@@ -129,9 +130,14 @@ const readRelationshipParts = async (zip) => {
129
130
  return parts;
130
131
  };
131
132
  const writePart = ({ zip, path, xml, compressionLevel }) => {
132
- zip.file(path, xml, {
133
- compression: "DEFLATE",
134
- compressionOptions: { level: compressionLevel }
133
+ writeZipPart({
134
+ zip,
135
+ path,
136
+ data: xml,
137
+ options: {
138
+ compression: "DEFLATE",
139
+ compressionOptions: { level: compressionLevel }
140
+ }
135
141
  });
136
142
  };
137
143
  /**
@@ -1,6 +1,6 @@
1
1
  import { modelParagraphFormattingEmission } from "../internal/paragraphFormattingSerialization.js";
2
2
  import { canonicalJson } from "../utils/canonicalJson.js";
3
- import { copyHeaderFooterPackageBaselines } from "./headerFooterVerbatim.js";
3
+ import { copyHeaderFooterBaselineHandles, copyHeaderFooterPackageBaselines } from "./headerFooterVerbatim.js";
4
4
  import { visitDocxParagraphs } from "./paragraphTraversal.js";
5
5
  import { TaggedError, panic } from "better-result";
6
6
  import { PluginKey } from "prosemirror-state";
@@ -244,6 +244,7 @@ const cloneDocumentWithParagraphPropertySources = (document) => {
244
244
  copyParagraphPropertySource(target, source);
245
245
  }
246
246
  copyDocumentParagraphPropertySourceContract(cloned, document);
247
+ copyHeaderFooterBaselineHandles(cloned, document);
247
248
  copyHeaderFooterPackageBaselines(cloned, document);
248
249
  return cloned;
249
250
  };
@@ -15,7 +15,7 @@ import { parseFontTable } from "./fontTableParser.js";
15
15
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
16
16
  import { parseFooter, parseHeader } from "./headerFooterParser.js";
17
17
  import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, assignHeaderFooterRoles, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
18
- import { assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, captureHeaderFooterPackageBaselines, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
18
+ import { assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, captureDocumentSourceBaseline, captureHeaderFooterPackageBaselines, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
19
19
  import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
20
20
  import { renderEmfSvg } from "./metafileSvg.js";
21
21
  import { DocxModelValidationError, formatDocumentModelIssues, validateFolioDocumentModel } from "./modelValidation.js";
@@ -474,6 +474,7 @@ async function parseDocxWithPreviewBudget(input, options, previewBudget) {
474
474
  }
475
475
  onProgress("Complete", 100);
476
476
  captureHeaderFooterPackageBaselines(document);
477
+ if (raw.documentXml) captureDocumentSourceBaseline(document, raw.documentXml);
477
478
  return document;
478
479
  } catch (error) {
479
480
  if (error instanceof DocxEncryptionError) throw error;
@@ -4,6 +4,7 @@ import { captureVerbatimXml } from "./verbatimCapture.js";
4
4
  import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, cloneElement, getAttribute, getLocalName, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
5
5
  import { panic } from "better-result";
6
6
  import { OOXML_NS } from "@stll/docx-utils";
7
+ import { writeZipPart } from "@stll/docx-core/zip";
7
8
  //#region src/docx/removeHeaderFooterParts.ts
8
9
  const DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
9
10
  const officeRelationshipHasType = (type, kind) => [...OFFICE_RELATIONSHIP_NAMESPACE_URIS].some((namespace) => type === `${namespace}/${kind}`);
@@ -54,9 +55,14 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
54
55
  }
55
56
  }
56
57
  const compressionOptions = { level: compressionLevel };
57
- zip.file(DOCUMENT_RELS_PATH, withoutChildren(relsXml, (child) => getNamespaceUri(child) === OOXML_NS.pr && getLocalName(child.name) === "Relationship" && removedIds.has(getAttribute(child, null, "Id") ?? "")), {
58
- compression: "DEFLATE",
59
- compressionOptions
58
+ writeZipPart({
59
+ zip,
60
+ path: DOCUMENT_RELS_PATH,
61
+ data: withoutChildren(relsXml, (child) => getNamespaceUri(child) === OOXML_NS.pr && getLocalName(child.name) === "Relationship" && removedIds.has(getAttribute(child, null, "Id") ?? "")),
62
+ options: {
63
+ compression: "DEFLATE",
64
+ compressionOptions
65
+ }
60
66
  });
61
67
  if (document.package.relationships) {
62
68
  document.package.relationships = new Map(document.package.relationships);
@@ -78,9 +84,14 @@ const removeResolvedHeaderFooterParts = async ({ document, zip, removedReference
78
84
  const contentTypes = zip.file("[Content_Types].xml");
79
85
  if (!contentTypes) return panic("The package has no content types");
80
86
  const contentTypesXml = await contentTypes.async("text");
81
- zip.file("[Content_Types].xml", withoutChildren(contentTypesXml, (child) => getNamespaceUri(child) === OOXML_NS.ct && getLocalName(child.name) === "Override" && candidates.has((getAttribute(child, null, "PartName") ?? "").replace(/^\//u, ""))), {
82
- compression: "DEFLATE",
83
- compressionOptions
87
+ writeZipPart({
88
+ zip,
89
+ path: "[Content_Types].xml",
90
+ data: withoutChildren(contentTypesXml, (child) => getNamespaceUri(child) === OOXML_NS.ct && getLocalName(child.name) === "Override" && candidates.has((getAttribute(child, null, "PartName") ?? "").replace(/^\//u, ""))),
91
+ options: {
92
+ compression: "DEFLATE",
93
+ compressionOptions
94
+ }
84
95
  });
85
96
  };
86
97
  //#endregion
@@ -1,6 +1,8 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
2
3
  import { SaveDiagnosticOptions } from "./saveDiagnostics.js";
3
4
  import { BlockContent, Hyperlink } from "../types/content.js";
5
+ import { DocumentBodyAuthorityOptions } from "./serializer/documentSerializer.js";
4
6
  import { RawDocxContent } from "./unzip.js";
5
7
  import JSZip from "jszip";
6
8
  //#region src/docx/rezip.d.ts
@@ -38,16 +40,46 @@ declare class DocxDuplicateSectionCarrierError extends DocxPackageFidelityError
38
40
  */
39
41
  declare function findMaxRId(relsXml: string): number;
40
42
  /**
41
- * Collect all hyperlinks that have an href but no rId from block content.
42
- * These are newly created hyperlinks that need relationship entries.
43
+ * Every serialized section has exactly one model record, and vice versa.
44
+ *
45
+ * A split paragraph's duplicated `w:sectPr` is a section nobody added,
46
+ * repeating the real one's `w:rsidSect` and claiming its revision history too.
47
+ * Two carriers over one record are therefore invalid regardless of whether a
48
+ * separate deletion makes the total section count rise, fall, or stay equal.
49
+ *
50
+ * The count is a separate invariant: `serializeSectionProperties` fails closed
51
+ * to `""` for a record holding settings it cannot write, so carrier uniqueness
52
+ * alone does not prove the package states the sections the model holds.
43
53
  */
54
+ declare const assertSectionCarriersMatchModel: ({ doc, serializedSectionCount }: {
55
+ doc: document_d_exports.Document;
56
+ serializedSectionCount: number;
57
+ }) => void;
58
+ declare const CanonicalImageRelationshipRefusalError_base: import("better-result").TaggedErrorClass<"CanonicalImageRelationshipRefusalError">;
59
+ declare class CanonicalImageRelationshipRefusalError extends CanonicalImageRelationshipRefusalError_base<{
60
+ message: string;
61
+ gap: typeof CANONICAL_GAP.save;
62
+ part: string;
63
+ relationshipId: string;
64
+ reason: "conflict" | "unresolved" | "missingMedia" | "unsafeTarget";
65
+ }> {}
66
+ type PublishCanonicalImageResourcesOptions = {
67
+ document: document_d_exports.Document;
68
+ zip: JSZip;
69
+ compressionLevel: number;
70
+ };
71
+ /** Publish allocated pictures into each owning story without rebinding canonical ids. */
72
+ declare const publishCanonicalImageResources: ({ document, zip, compressionLevel }: PublishCanonicalImageResourcesOptions) => Promise<void>;
73
+ /** Newly authored text links; the census shares the complete owning-part traversal. */
44
74
  declare function collectHyperlinksWithoutRId(blocks: BlockContent[]): Hyperlink[];
45
75
  /** The selective save boundary must use the same resource census as full repack. */
46
76
  declare const hasUnmaterializedInlineResources: (blocks: BlockContent[]) => boolean;
77
+ /** Compare against the already loaded source ZIP, without mutating model or package. */
78
+ declare const hasUnmaterializedHyperlinkBindings: (document: document_d_exports.Document, zip: JSZip) => Promise<boolean>;
47
79
  /**
48
80
  * Options for repacking DOCX
49
81
  */
50
- type RepackOptions = SaveDiagnosticOptions & {
82
+ type RepackOptions = SaveDiagnosticOptions & DocumentBodyAuthorityOptions & {
51
83
  /** Compression level (0-9, default: 6) */
52
84
  compressionLevel?: number;
53
85
  /** Whether to update modification date in docProps/core.xml */
@@ -190,6 +222,18 @@ type CollectHeaderFooterUpdatesOptions = SaveDiagnosticOptions & {
190
222
  sourceZip: JSZip;
191
223
  };
192
224
  declare function collectHeaderFooterUpdates(doc: document_d_exports.Document, { sourceZip, onDiagnostic }: CollectHeaderFooterUpdatesOptions): Promise<Map<string, string>>;
225
+ type AddedStylesPlan = {
226
+ type: "unchanged";
227
+ } | {
228
+ type: "materialize";
229
+ styles: document_d_exports.StyleDefinitions;
230
+ } | {
231
+ type: "patch";
232
+ path: string;
233
+ xml: string;
234
+ };
235
+ /** One append-only style owner for full repack and selective overlays. */
236
+ declare function planAddedStyles(doc: document_d_exports.Document, originalZip: JSZip): Promise<AddedStylesPlan>;
193
237
  /** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
194
238
  declare function notePartRelsPath(partPath: string): string;
195
239
  /** Update existing core-property values without synthesizing absent metadata. */
@@ -223,7 +267,7 @@ declare function isDocxBuffer(buffer: ArrayBuffer): boolean;
223
267
  * ignored for a document that carries a source package, which keeps the
224
268
  * properties that package already states.
225
269
  */
226
- type DocumentPropertiesOptions = SaveDiagnosticOptions & {
270
+ type DocumentPropertiesOptions = SaveDiagnosticOptions & DocumentBodyAuthorityOptions & {
227
271
  /** `dc:creator` in `docProps/core.xml`. Omitted when absent. */
228
272
  creator?: string;
229
273
  /** `Application` in `docProps/app.xml`. Omitted, with `AppVersion`, when absent. */
@@ -246,4 +290,4 @@ declare function createEmptyDocx(properties?: DocumentPropertiesOptions): Promis
246
290
  */
247
291
  declare function createDocx(doc: document_d_exports.Document, properties?: DocumentPropertiesOptions): Promise<ArrayBuffer>;
248
292
  //#endregion
249
- export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocumentPropertiesOptions, DocxDuplicateSectionCarrierError, DocxPackageFidelityError, RepackOptions, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedInlineResources, isDocxBuffer, notePartRelsPath, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx, withoutAttachedTemplate };
293
+ export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, CanonicalImageRelationshipRefusalError, DocumentPropertiesOptions, DocxDuplicateSectionCarrierError, DocxPackageFidelityError, RepackOptions, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, assertSectionCarriersMatchModel, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedHyperlinkBindings, hasUnmaterializedInlineResources, isDocxBuffer, notePartRelsPath, planAddedStyles, publishCanonicalImageResources, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx, withoutAttachedTemplate };