@stll/folio-core 0.37.5 → 0.39.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 (144) hide show
  1. package/dist/ai-edits/apply.js +275 -88
  2. package/dist/ai-edits/clean-text.js +12 -1
  3. package/dist/ai-edits/headless.d.ts +110 -0
  4. package/dist/ai-edits/headless.js +569 -40
  5. package/dist/ai-edits/snapshot.d.ts +24 -1
  6. package/dist/ai-edits/snapshot.js +206 -9
  7. package/dist/ai-edits/table-geometry.d.ts +7 -7
  8. package/dist/ai-edits/table-geometry.js +43 -27
  9. package/dist/ai-edits/table-template.d.ts +2 -1
  10. package/dist/ai-edits/table-template.js +9 -3
  11. package/dist/ai-edits/types.d.ts +37 -15
  12. package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
  13. package/dist/compare/__fixtures__/body-sequence.js +5 -3
  14. package/dist/compare/compare.d.ts +26 -2
  15. package/dist/compare/compare.js +444 -16
  16. package/dist/compare/content-alignment.js +3 -2
  17. package/dist/compare/content-types.d.ts +34 -2
  18. package/dist/compare/content.d.ts +13 -10
  19. package/dist/compare/content.js +69 -23
  20. package/dist/compare/formatting.d.ts +4 -22
  21. package/dist/compare/formatting.js +8 -112
  22. package/dist/compare/inline-atom-resources.d.ts +6 -0
  23. package/dist/compare/inline-atom-resources.js +26 -0
  24. package/dist/compare/inline-atoms.d.ts +35 -0
  25. package/dist/compare/inline-atoms.js +383 -0
  26. package/dist/compare/inline-provenance.d.ts +36 -0
  27. package/dist/compare/inline-provenance.js +334 -0
  28. package/dist/compare/plan.d.ts +4 -1
  29. package/dist/compare/plan.js +32 -4
  30. package/dist/compare/scenario.d.ts +4 -6
  31. package/dist/compare/section-boundary-properties.d.ts +63 -0
  32. package/dist/compare/section-boundary-properties.js +235 -0
  33. package/dist/compare/style-resources.d.ts +30 -0
  34. package/dist/compare/style-resources.js +404 -0
  35. package/dist/compare/types.d.ts +50 -11
  36. package/dist/compare/types.js +3 -1
  37. package/dist/compare/verification.d.ts +3 -5
  38. package/dist/compare/verification.js +8 -39
  39. package/dist/compat/eigenpal.d.ts +4 -4
  40. package/dist/compat/eigenpal.js +3 -3
  41. package/dist/controller/layoutPipeline.js +4 -0
  42. package/dist/display-list/build/buildContext.d.ts +3 -1
  43. package/dist/display-list/build/buildDisplayList.d.ts +4 -1
  44. package/dist/display-list/build/buildDisplayList.js +38 -4
  45. package/dist/display-list/build/commentAnnotations.d.ts +10 -0
  46. package/dist/display-list/build/commentAnnotations.js +17 -0
  47. package/dist/display-list/build/paragraphPrimitives.js +39 -1
  48. package/dist/display-list/primitives.d.ts +1 -1
  49. package/dist/display-list/types.d.ts +16 -1
  50. package/dist/document-operations.d.ts +5 -3
  51. package/dist/document-operations.js +112 -5
  52. package/dist/docx/blockContentParser.js +14 -2
  53. package/dist/docx/diagramPreview.d.ts +7 -0
  54. package/dist/docx/diagramPreview.js +191 -0
  55. package/dist/docx/drawingIdNormalization.js +43 -2
  56. package/dist/docx/drawingRelationships.d.ts +10 -0
  57. package/dist/docx/drawingRelationships.js +28 -0
  58. package/dist/docx/fieldParser.js +1 -1
  59. package/dist/docx/hyperlinkParser.js +3 -5
  60. package/dist/docx/imageRawXml.d.ts +8 -0
  61. package/dist/docx/imageRawXml.js +13 -0
  62. package/dist/docx/newImage.d.ts +2 -2
  63. package/dist/docx/newImage.js +3 -3
  64. package/dist/docx/numberingParser.js +30 -27
  65. package/dist/docx/paragraphParser.js +11 -4
  66. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  67. package/dist/docx/parser.js +13 -0
  68. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  69. package/dist/docx/removeHeaderFooterParts.js +87 -0
  70. package/dist/docx/rezip.d.ts +3 -1
  71. package/dist/docx/rezip.js +143 -41
  72. package/dist/docx/runParser.js +24 -5
  73. package/dist/docx/sectionParser.js +3 -0
  74. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  75. package/dist/docx/sectionReferenceHistory.js +59 -0
  76. package/dist/docx/selectiveSave.js +11 -31
  77. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  78. package/dist/docx/selectiveXmlPatch.js +62 -2
  79. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  80. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  81. package/dist/docx/serializer/partNamespaces.js +4 -0
  82. package/dist/docx/serializer/runSerializer.js +5 -2
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  84. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  85. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  86. package/dist/docx/tableParser.js +21 -11
  87. package/dist/docx/textBoxParser.js +25 -5
  88. package/dist/docx/xmlParser.d.ts +10 -1
  89. package/dist/docx/xmlParser.js +23 -1
  90. package/dist/export-pdf.js +2 -0
  91. package/dist/headless-layout.js +1 -0
  92. package/dist/index.d.ts +4 -4
  93. package/dist/index.js +3 -3
  94. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  95. package/dist/internal/compare/inline-presentation.js +222 -0
  96. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  97. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  98. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  99. package/dist/internal/sectionReferenceResolution.js +72 -0
  100. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  101. package/dist/layout-engine/index.js +5 -5
  102. package/dist/layout-engine/sectionVerticalAlignment.d.ts +7 -0
  103. package/dist/layout-engine/sectionVerticalAlignment.js +52 -0
  104. package/dist/layout-engine/types.d.ts +3 -1
  105. package/dist/markdown/renderRuns.js +1 -0
  106. package/dist/markdown/renderTable.js +1 -0
  107. package/dist/pdf/writePdf.js +37 -2
  108. package/dist/prosemirror/attrs/index.js +55 -1
  109. package/dist/prosemirror/commands/comments.d.ts +11 -1
  110. package/dist/prosemirror/commands/comments.js +68 -6
  111. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  112. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  113. package/dist/prosemirror/conversion/fromProseDoc.js +17 -8
  114. package/dist/prosemirror/conversion/toProseDoc.js +18 -57
  115. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  116. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  117. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  118. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +39 -1
  119. package/dist/prosemirror/imageCommit.js +7 -9
  120. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  121. package/dist/prosemirror/paragraphIndentation.js +72 -0
  122. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  123. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  124. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  125. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  126. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  127. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  128. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  129. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  130. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  131. package/dist/prosemirror/runStyleFormatting.js +76 -2
  132. package/dist/prosemirror/schema/marks.d.ts +3 -1
  133. package/dist/prosemirror/schema/marks.js +2 -0
  134. package/dist/prosemirror/schema/nodes.d.ts +5 -0
  135. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  136. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  137. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  138. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  139. package/dist/redline.js +15 -2
  140. package/dist/server.d.ts +2 -2
  141. package/dist/server.js +2 -2
  142. package/dist/version-comparison.d.ts +2 -0
  143. package/dist/version-comparison.js +7 -2
  144. package/package.json +2 -2
@@ -1,8 +1,11 @@
1
1
  import { consumeTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
2
+ import { consumeSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
2
3
  import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
3
4
  import { normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
4
5
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
5
6
  import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
7
+ import { normalizeDrawingIds } from "./drawingIdNormalization.js";
8
+ import { rebindDrawingImageRelationship } from "./drawingRelationships.js";
6
9
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
7
10
  import { assertValidFolioDocumentModel } from "./modelValidation.js";
8
11
  import { isNewDataUrlDrawing } from "./newImage.js";
@@ -10,8 +13,9 @@ import { parseNumbering } from "./numberingParser.js";
10
13
  import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
11
14
  import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
12
15
  import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
16
+ import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
13
17
  import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
14
- import { appendNumberingDefs, buildPatchedNotePartXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
18
+ import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
15
19
  import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
16
20
  import { serializeDocument } from "./serializer/documentSerializer.js";
17
21
  import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
@@ -23,7 +27,7 @@ import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
23
27
  import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
24
28
  import { serializeThemeXml } from "./serializer/themeSerializer.js";
25
29
  import { escapeXml } from "./serializer/xmlUtils.js";
26
- import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, getAttribute, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
30
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChild, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
27
31
  import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
28
32
  import { panic } from "better-result";
29
33
  import { validateDocxPackage } from "@stll/docx-core";
@@ -99,17 +103,21 @@ const countDocumentSections = (xml) => {
99
103
  };
100
104
  const extractHeaderFooterReferences = (xml) => {
101
105
  const references = [];
102
- for (const match of xml.matchAll(/<w:(?<element>headerReference|footerReference)\b[^>]*>/gu)) {
103
- const tag = match[0];
104
- const type = /\bw:type="(?<type>[^"]+)"/u.exec(tag)?.groups?.["type"] ?? "default";
105
- const rId = /\br:id="(?<rId>[^"]+)"/u.exec(tag)?.groups?.["rId"];
106
- const element = match.groups?.["element"];
107
- if (!rId || element !== "headerReference" && element !== "footerReference") continue;
108
- references.push({
109
- element,
110
- type,
111
- rId
112
- });
106
+ assertXmlResourceLimits(xml);
107
+ const pending = [parseXml(xml)];
108
+ while (pending.length > 0) {
109
+ const node = pending.pop();
110
+ if (!node) break;
111
+ const element = getLocalName(node.name);
112
+ if ((element === "headerReference" || element === "footerReference") && isWordprocessingElement(node, element)) {
113
+ const rId = getAttributeByNamespaceUri(node, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "id");
114
+ if (rId) references.push({
115
+ element,
116
+ type: getAttributeByNamespaceUri(node, WORDPROCESSINGML_NAMESPACE_URIS, "type") ?? "default",
117
+ rId
118
+ });
119
+ }
120
+ pending.push(...getChildElements(node));
113
121
  }
114
122
  return references;
115
123
  };
@@ -126,7 +134,7 @@ const consumeReferenceCount = (counts, key) => {
126
134
  counts.set(key, count - 1);
127
135
  return true;
128
136
  };
129
- function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXml, doc, sectionEndpointRemoval) {
137
+ function assertDocumentPackageFidelity({ originalDocumentXml, serializedDocumentXml, doc, sectionEndpointRemoval, sectionReferenceRemovals = [] }) {
130
138
  const originalSectionCount = countDocumentSections(originalDocumentXml);
131
139
  const serializedSectionCount = countDocumentSections(serializedDocumentXml);
132
140
  const trackedRemovedSectionCount = sectionEndpointRemoval ? sectionEndpointRemoval.sourceParagraphEndpointCount - sectionEndpointRemoval.expectedParagraphEndpointCount : 0;
@@ -136,6 +144,9 @@ function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXm
136
144
  for (const reference of extractHeaderFooterReferences(serializedDocumentXml)) incrementReferenceCount(serializedReferenceCounts, headerFooterReferenceKey(reference));
137
145
  const removedReferenceCounts = /* @__PURE__ */ new Map();
138
146
  if (exactTrackedSectionRemoval && sectionEndpointRemoval) for (const { part, type, relationshipId } of sectionEndpointRemoval.removedReferences) incrementReferenceCount(removedReferenceCounts, `${part}Reference:${type}:${relationshipId}`);
147
+ const resolvedReferenceCounts = /* @__PURE__ */ new Map();
148
+ for (const { part, type, relationshipId } of sectionReferenceRemovals) incrementReferenceCount(resolvedReferenceCounts, `${part}Reference:${type}:${relationshipId}`);
149
+ for (const [key, count] of resolvedReferenceCounts) removedReferenceCounts.set(key, Math.max(count, removedReferenceCounts.get(key) ?? 0));
139
150
  const missingRefs = [];
140
151
  for (const reference of extractHeaderFooterReferences(originalDocumentXml)) {
141
152
  if (!hasParsedHeaderFooterPart(doc, reference)) continue;
@@ -314,7 +325,7 @@ async function registerImageExtensions(zip, extensions, compressionLevel) {
314
325
  function collectNewImages(blocks) {
315
326
  const images = [];
316
327
  const collectFromRun = (run) => {
317
- for (const c of run.content) if (isNewDataUrlDrawing(c)) images.push(c.image);
328
+ for (const c of run.content) if (isNewDataUrlDrawing(c)) images.push(c);
318
329
  };
319
330
  for (const block of blocks) if (block.type === "paragraph") {
320
331
  for (const item of block.content) if (item.type === "run") collectFromRun(item);
@@ -370,7 +381,8 @@ async function processNewImages(parts, zip, compressionLevel) {
370
381
  let maxId = findMaxRId(relsXml);
371
382
  const relEntries = [];
372
383
  const partPath = relsPath.replace("/_rels/", "/").replace(/\.rels$/u, "");
373
- for (const image of newImages) {
384
+ for (const drawing of newImages) {
385
+ const { image } = drawing;
374
386
  if (!image.src) continue;
375
387
  const { data, extension } = decodeDataUrl(image.src);
376
388
  maxImageNum++;
@@ -383,6 +395,11 @@ async function processNewImages(parts, zip, compressionLevel) {
383
395
  });
384
396
  relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXml(relativeTargetForPart(partPath, mediaPath))}"/>`);
385
397
  extensionsAdded.add(extension);
398
+ if (drawing.rawXml) drawing.rawXml = rebindDrawingImageRelationship({
399
+ xml: drawing.rawXml,
400
+ previousId: image.rId ?? "",
401
+ nextId: newRId
402
+ }) ?? panic("A detached drawing lost its embedded image relationship");
386
403
  image.rId = newRId;
387
404
  }
388
405
  if (relEntries.length > 0) {
@@ -395,18 +412,51 @@ async function processNewImages(parts, zip, compressionLevel) {
395
412
  }
396
413
  await registerImageExtensions(zip, extensionsAdded, compressionLevel);
397
414
  }
415
+ /** Materialize detached embedded media without replacing any existing package part. */
416
+ const materializeEmbeddedMedia = async ({ document, zip, compressionLevel }) => {
417
+ const extensions = /* @__PURE__ */ new Set();
418
+ for (const media of document.package.media?.values() ?? []) {
419
+ if (findZipEntryCaseInsensitive(zip, media.path.toLowerCase())) continue;
420
+ if (isUnsafePackagePath(media.path) || !media.path.startsWith("word/media/")) panic("Detached media has an invalid package path");
421
+ const extension = media.path.split(".").at(-1)?.toLowerCase();
422
+ if (!extension || !/^[a-z0-9]+$/u.test(extension)) panic("Embedded media has an invalid extension");
423
+ zip.file(media.path, media.data, {
424
+ compression: "DEFLATE",
425
+ compressionOptions: { level: compressionLevel }
426
+ });
427
+ extensions.add(extension);
428
+ }
429
+ await registerImageExtensions(zip, extensions, compressionLevel);
430
+ };
398
431
  /**
399
432
  * Collect all hyperlinks that have an href but no rId from block content.
400
433
  * These are newly created hyperlinks that need relationship entries.
401
434
  */
402
435
  function collectHyperlinksWithoutRId(blocks) {
403
436
  const hyperlinks = [];
404
- for (const block of blocks) if (block.type === "paragraph") {
405
- for (const item of block.content) if (item.type === "hyperlink" && item.href && !item.rId && !item.anchor) hyperlinks.push(item);
406
- } else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
437
+ const collectInlineHyperlinks = (content) => {
438
+ for (const item of content) switch (item.type) {
439
+ case "hyperlink":
440
+ if (item.href && !item.rId && !item.anchor) hyperlinks.push(item);
441
+ break;
442
+ case "simpleField":
443
+ case "inlineSdt":
444
+ case "insertion":
445
+ case "deletion":
446
+ case "moveFrom":
447
+ case "moveTo":
448
+ collectInlineHyperlinks(item.content);
449
+ break;
450
+ default: break;
451
+ }
452
+ };
453
+ for (const block of blocks) if (block.type === "paragraph") collectInlineHyperlinks(block.content);
454
+ else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
407
455
  else hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
408
456
  return hyperlinks;
409
457
  }
458
+ /** The selective save boundary must use the same resource census as full repack. */
459
+ const hasUnmaterializedInlineResources = (blocks) => collectNewImages(blocks).length > 0 || collectHyperlinksWithoutRId(blocks).length > 0;
410
460
  /**
411
461
  * Process newly created hyperlinks in every part: assign rIds and add
412
462
  * relationship entries to the owning part's rels. Mutates the hyperlinks' rId
@@ -512,15 +562,42 @@ const cloneDocxZip = (source) => {
512
562
  clone.files = { ...source.files };
513
563
  return clone;
514
564
  };
515
- const finishRepack = async ({ document, originalZip, outputZip, originalDocumentXml, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, sectionEndpointRemoval }) => {
565
+ const normalizeExportDrawingIds = ({ package: docxPackage }) => {
566
+ normalizeDrawingIds({
567
+ documentBody: docxPackage.document,
568
+ ...docxPackage.headers !== void 0 ? { headers: docxPackage.headers } : {},
569
+ ...docxPackage.footers !== void 0 ? { footers: docxPackage.footers } : {},
570
+ ...docxPackage.footnotes !== void 0 ? { footnotes: docxPackage.footnotes } : {},
571
+ ...docxPackage.endnotes !== void 0 ? { endnotes: docxPackage.endnotes } : {}
572
+ });
573
+ };
574
+ const finishRepack = async ({ document, originalZip, outputZip, originalDocumentXml, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, sectionEndpointRemoval, sectionReferenceRemovals }) => {
516
575
  await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
576
+ await materializeEmbeddedMedia({
577
+ document,
578
+ zip: outputZip,
579
+ compressionLevel
580
+ });
517
581
  const parts = collectDocxParts(document, outputZip);
582
+ normalizeExportDrawingIds(document);
518
583
  await processNewImages(parts, outputZip, compressionLevel);
519
584
  await processNewHyperlinks(parts, outputZip, compressionLevel);
520
585
  assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
521
586
  applyReplyThreadMarkers(document);
522
587
  const documentXml = serializeDocument(document, originalDocumentXml === void 0 ? void 0 : readRootNamespaceBindings(originalDocumentXml));
523
- if (originalDocumentXml) assertDocumentPackageFidelity(originalDocumentXml, documentXml, document, sectionEndpointRemoval);
588
+ if (originalDocumentXml) assertDocumentPackageFidelity({
589
+ originalDocumentXml,
590
+ serializedDocumentXml: documentXml,
591
+ doc: document,
592
+ ...sectionEndpointRemoval !== void 0 && { sectionEndpointRemoval },
593
+ ...sectionReferenceRemovals !== void 0 && { sectionReferenceRemovals }
594
+ });
595
+ await removeResolvedHeaderFooterParts({
596
+ document,
597
+ zip: outputZip,
598
+ removedReferences: [...sectionReferenceRemovals ?? [], ...sectionEndpointRemoval?.removedReferences ?? []],
599
+ compressionLevel
600
+ });
524
601
  outputZip.file("word/document.xml", documentXml, {
525
602
  compression: "DEFLATE",
526
603
  compressionOptions: { level: compressionLevel }
@@ -550,7 +627,7 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
550
627
  }
551
628
  return generateDocxZip(outputZip, compressionLevel);
552
629
  };
553
- async function repackDocxWithSectionEndpointRemoval({ document: doc, options, sectionEndpointRemoval }) {
630
+ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, sectionEndpointRemoval, sectionReferenceRemovals }) {
554
631
  if (!doc.originalBuffer) panic("Cannot repack document: no original buffer for round-trip. Use createDocx() for new documents.");
555
632
  const { compressionLevel = 6, updateModifiedDate = true, modifiedBy, changedNoteParaIds } = options;
556
633
  const exportDocument = withoutOrphanCommentRanges(doc);
@@ -568,15 +645,18 @@ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, se
568
645
  updateModifiedDate,
569
646
  ...modifiedBy !== void 0 ? { modifiedBy } : {},
570
647
  ...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {},
571
- ...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {}
648
+ ...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {},
649
+ ...sectionReferenceRemovals !== void 0 ? { sectionReferenceRemovals } : {}
572
650
  });
573
651
  }
574
652
  function repackDocx(doc, options = {}) {
575
653
  const sectionEndpointRemoval = consumeTrackedSectionEndpointRemoval(doc);
654
+ const sectionReferenceRemovals = consumeSectionReferenceResolution(doc);
576
655
  return repackDocxWithSectionEndpointRemoval({
577
656
  document: doc,
578
657
  options,
579
- ...sectionEndpointRemoval ? { sectionEndpointRemoval } : {}
658
+ ...sectionEndpointRemoval ? { sectionEndpointRemoval } : {},
659
+ sectionReferenceRemovals
580
660
  });
581
661
  }
582
662
  /**
@@ -604,13 +684,23 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
604
684
  });
605
685
  }
606
686
  await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
687
+ await materializeEmbeddedMedia({
688
+ document: exportDocument,
689
+ zip: newZip,
690
+ compressionLevel
691
+ });
607
692
  const parts = collectDocxParts(exportDocument, newZip);
693
+ normalizeExportDrawingIds(exportDocument);
608
694
  await processNewImages(parts, newZip, compressionLevel);
609
695
  await processNewHyperlinks(parts, newZip, compressionLevel);
610
696
  assertValidFolioDocumentModel(exportDocument, "Cannot repack invalid DOCX document model");
611
697
  applyReplyThreadMarkers(exportDocument);
612
698
  const documentXml = serializeDocument(exportDocument, rawContent.documentXml ? readRootNamespaceBindings(rawContent.documentXml) : void 0);
613
- if (rawContent.documentXml) assertDocumentPackageFidelity(rawContent.documentXml, documentXml, exportDocument);
699
+ if (rawContent.documentXml) assertDocumentPackageFidelity({
700
+ originalDocumentXml: rawContent.documentXml,
701
+ serializedDocumentXml: documentXml,
702
+ doc: exportDocument
703
+ });
614
704
  newZip.file("word/document.xml", documentXml, {
615
705
  compression: "DEFLATE",
616
706
  compressionOptions: { level: compressionLevel }
@@ -1056,9 +1146,8 @@ function hasModelDrivenPictureWatermark(doc) {
1056
1146
  * by coverage) carries the source header's rId, which is meaningless in a
1057
1147
  * sibling header's `word/_rels/header*.xml.rels`. Per header: keep the rId if
1058
1148
  * it already resolves; otherwise reuse an existing relationship to the same
1059
- * media target, or mint a new one. The media bytes are shared (preserved from
1060
- * the source), so no new media part is written. Raw-replay watermarks are
1061
- * byte-exact and skipped.
1149
+ * media target, or mint a new one. Media bytes are materialized before this step. Raw watermark XML
1150
+ * changes only when its image relationship must be rebound.
1062
1151
  */
1063
1152
  async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1064
1153
  const rels = doc.package.relationships;
@@ -1067,11 +1156,12 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1067
1156
  const pending = [];
1068
1157
  for (const [rId, hf] of headers) {
1069
1158
  const watermark = hf.watermark;
1070
- if (!watermark || watermark.kind !== "picture" || hf.rawWatermarkXml) continue;
1159
+ if (!watermark || watermark.kind !== "picture") continue;
1071
1160
  const rel = rels.get(rId);
1072
1161
  if (!rel?.target) continue;
1073
1162
  pending.push({
1074
1163
  watermark,
1164
+ header: hf,
1075
1165
  relsPath: headerFooterRelsPath(rel.target),
1076
1166
  partPath: headerFooterFilename(rel.target)
1077
1167
  });
@@ -1099,7 +1189,7 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1099
1189
  }
1100
1190
  };
1101
1191
  const changedPaths = /* @__PURE__ */ new Set();
1102
- for (const { watermark, relsPath, partPath } of pending) {
1192
+ for (const { watermark, header, relsPath, partPath } of pending) {
1103
1193
  let canonical;
1104
1194
  if (watermark.imageTarget !== void 0) if (watermark.imageTargetExternal) canonical = isAllowedExternalWatermarkImageUrl(watermark.imageTarget) ? {
1105
1195
  mode: "external",
@@ -1129,6 +1219,11 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1129
1219
  relsXmlByPath.set(relsPath, relsXml.replace("</Relationships>", `${relXml}</Relationships>`));
1130
1220
  changedPaths.add(relsPath);
1131
1221
  }
1222
+ if (header.rawWatermarkXml && watermark.imageRId !== resolvedRId) header.rawWatermarkXml = rebindDrawingImageRelationship({
1223
+ xml: header.rawWatermarkXml,
1224
+ previousId: watermark.imageRId,
1225
+ nextId: resolvedRId
1226
+ }) ?? panic("A watermark lost its embedded image relationship");
1132
1227
  watermark.imageRId = resolvedRId;
1133
1228
  }
1134
1229
  const compressionOptions = { level: compressionLevel };
@@ -1257,7 +1352,17 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
1257
1352
  const numbering = doc.package.numbering;
1258
1353
  if (!numbering || numbering.abstractNums.length === 0 && numbering.nums.length === 0) return;
1259
1354
  const file = findNotePartEntry(originalZip, "word/numbering.xml");
1260
- if (!file) return;
1355
+ if (!file) {
1356
+ await materializeNewNotePart({
1357
+ contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
1358
+ newZip,
1359
+ partPath: "word/numbering.xml",
1360
+ relationshipType: RELATIONSHIP_TYPES.numbering,
1361
+ serializedPart: serializeNumberingXml(numbering),
1362
+ compressionLevel
1363
+ });
1364
+ return;
1365
+ }
1261
1366
  let baseline = doc.originalBuffer ? numberingBaselines.get(doc) : void 0;
1262
1367
  if (!baseline || baseline.originalBuffer !== doc.originalBuffer) {
1263
1368
  const originalXml = await file.async("text");
@@ -1269,15 +1374,12 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
1269
1374
  if (doc.originalBuffer) numberingBaselines.set(doc, baseline);
1270
1375
  }
1271
1376
  const currentXml = serializeNumberingXml(numbering);
1272
- const changed = collectChangedNumberingDefs(baseline.serializedXml, currentXml);
1273
- const added = collectAddedNumberingDefs(baseline.serializedXml, currentXml);
1274
- const hasChanged = changed.abstractNums.size > 0 || changed.nums.size > 0;
1275
- const hasAdded = added.abstractNums.size > 0 || added.nums.size > 0;
1276
- if (!hasChanged && !hasAdded) return;
1277
- const spliced = buildPatchedNumberingXml(baseline.originalXml, currentXml, changed);
1278
- if (spliced === null) return;
1279
- const patched = appendNumberingDefs(spliced, currentXml, added);
1280
- if (patched === null) return;
1377
+ const patched = patchNumberingDefinitions({
1378
+ originalXml: baseline.originalXml,
1379
+ baselineXml: baseline.serializedXml,
1380
+ currentXml
1381
+ });
1382
+ if (patched === null || patched === baseline.originalXml) return;
1281
1383
  newZip.file(file.name, patched, {
1282
1384
  compression: "DEFLATE",
1283
1385
  compressionOptions: { level: compressionLevel }
@@ -1584,4 +1686,4 @@ const assertStyleNumberingReferences = (doc) => {
1584
1686
  for (const numbering of doc.package.numbering?.nums ?? []) if (!availableAbstract.has(numbering.abstractNumId)) panic(`Numbering definition ${numbering.numId} references missing abstract numbering`);
1585
1687
  };
1586
1688
  //#endregion
1587
- export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, addCommentsExtendedOverride, addCommentsExtendedRelationship, addMedia, addRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, collectHyperlinksWithoutRId, createDocx, createEmptyDocx, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, isDocxBuffer, notePartRelsPath, removeCommentsExtendedOverride, removeCommentsExtendedRelationship, repackDocx, repackDocxFromRaw, updateCoreProperties, updateDocumentXml, updateMultipleFiles, updateXmlFile, validateDocx, withoutAttachedTemplate };
1689
+ export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxPackageFidelityError, 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 };
@@ -1,7 +1,9 @@
1
1
  import { isValidHexColor } from "../utils/colorResolver.js";
2
2
  import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
3
+ import { parseDiagramPreview } from "./diagramPreview.js";
3
4
  import { parseGroupDrawing } from "./groupDrawingParser.js";
4
5
  import { parseImage } from "./imageParser.js";
6
+ import { imageRawXmlFingerprint } from "./imageRawXml.js";
5
7
  import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, ShadingPatternSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
6
8
  import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
7
9
  import { isTextBoxDrawing } from "./textBoxParser.js";
@@ -10,7 +12,7 @@ import { resolveThemeFontRef } from "./themeParser.js";
10
12
  import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
11
13
  import { captureVerbatimXml } from "./verbatimCapture.js";
12
14
  import { parseVmlImageContent } from "./vmlImageParser.js";
13
- import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
15
+ import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
14
16
  import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
15
17
  //#region src/docx/runParser.ts
16
18
  /**
@@ -76,6 +78,9 @@ function collectFirstRunPropertyChildren(rPr) {
76
78
  case "color":
77
79
  children.color ??= child;
78
80
  break;
81
+ case "noProof":
82
+ children.noProof ??= child;
83
+ break;
79
84
  case "cs":
80
85
  children.cs ??= child;
81
86
  break;
@@ -215,6 +220,8 @@ function parseRunProperties(rPr, theme, _styles) {
215
220
  if (caps) formatting.allCaps = parseBooleanElement(caps);
216
221
  const vanish = propertyChildren.vanish;
217
222
  if (vanish) formatting.hidden = parseBooleanElement(vanish);
223
+ const noProof = propertyChildren.noProof;
224
+ if (noProof) formatting.noProof = parseBooleanElement(noProof);
218
225
  const color = propertyChildren.color;
219
226
  if (color) formatting.color = parseColorValue(getAttribute(color, "w", "val"), getAttribute(color, "w", "themeColor"), getAttribute(color, "w", "themeTint"), getAttribute(color, "w", "themeShade"));
220
227
  const highlight = propertyChildren.highlight;
@@ -479,7 +486,15 @@ function parseDrawingContent(element, rels, media) {
479
486
  if (groupImage) return {
480
487
  type: "drawing",
481
488
  image: groupImage,
482
- rawXml: captureVerbatimXml(element)
489
+ rawXml: captureVerbatimXml(element),
490
+ rawImageFingerprint: imageRawXmlFingerprint(groupImage)
491
+ };
492
+ const diagramImage = parseDiagramPreview(element, rels ?? void 0, media ?? void 0);
493
+ if (diagramImage) return {
494
+ type: "drawing",
495
+ image: diagramImage,
496
+ rawXml: captureVerbatimXml(element),
497
+ rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
483
498
  };
484
499
  if (shouldPreserveRawShapeDrawing(element)) return {
485
500
  type: "drawing",
@@ -506,7 +521,8 @@ function parseDrawingContent(element, rels, media) {
506
521
  type: "drawing",
507
522
  image
508
523
  };
509
- if (!image.src) drawing.rawXml = captureVerbatimXml(element);
524
+ drawing.rawXml = captureVerbatimXml(element);
525
+ drawing.rawImageFingerprint = imageRawXmlFingerprint(image);
510
526
  return drawing;
511
527
  }
512
528
  /**
@@ -595,7 +611,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
595
611
  contents.push(fallbackVml);
596
612
  break;
597
613
  }
598
- const targetEl = choiceEl ?? fallbackEl;
614
+ const targetEl = selectAlternateContentBranch(child);
599
615
  if (targetEl) for (const innerChild of getChildElements(targetEl)) {
600
616
  const innerName = getLocalName(innerChild.name);
601
617
  if (innerName === "drawing") {
@@ -610,7 +626,10 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
610
626
  innerVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
611
627
  contents.push(innerVml);
612
628
  }
613
- }
629
+ } else contents.push(...parseRunContents({
630
+ ...targetEl,
631
+ elements: [innerChild]
632
+ }, rels, media, rootXmlns));
614
633
  }
615
634
  break;
616
635
  }
@@ -1,6 +1,7 @@
1
1
  import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
2
2
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
3
3
  import { BorderStyleSchema, NumberFormatSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
4
+ import { parseSectionReferenceHistory } from "./sectionReferenceHistory.js";
4
5
  import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
5
6
  import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
6
7
  //#region src/docx/sectionParser.ts
@@ -348,6 +349,8 @@ function parseSectionProperties(sectPr) {
348
349
  info: parsePropertyChangeInfo(changeElement)
349
350
  };
350
351
  if (previousSectPr) change.previousProperties = parseSectionProperties(previousSectPr);
352
+ const previousReferences = parseSectionReferenceHistory(changeElement);
353
+ if (previousReferences !== void 0) change.previousReferences = previousReferences;
351
354
  return change;
352
355
  });
353
356
  if (propertyChanges.length > 0) props.propertyChanges = propertyChanges;
@@ -0,0 +1,17 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/sectionReferenceHistory.d.ts
4
+ type PreviousReferences = NonNullable<document_d_exports.SectionPropertyChange["previousReferences"]>;
5
+ declare const InvalidSectionReferenceHistoryError_base: import("better-result").TaggedErrorClass<"InvalidSectionReferenceHistoryError">;
6
+ declare class InvalidSectionReferenceHistoryError extends InvalidSectionReferenceHistoryError_base<{
7
+ message: string;
8
+ }> {}
9
+ declare const parseSectionReferenceHistory: (change: XmlElement) => PreviousReferences | undefined;
10
+ declare const serializeSectionReferenceHistory: (references: PreviousReferences | undefined) => string;
11
+ declare const sectionReferenceSelection: (properties: document_d_exports.SectionProperties) => PreviousReferences;
12
+ declare const sectionReferenceHistory: ({ previous, target }: {
13
+ previous: document_d_exports.SectionProperties;
14
+ target: document_d_exports.SectionProperties;
15
+ }) => PreviousReferences | undefined;
16
+ //#endregion
17
+ export { InvalidSectionReferenceHistoryError, parseSectionReferenceHistory, sectionReferenceHistory, sectionReferenceSelection, serializeSectionReferenceHistory };
@@ -0,0 +1,59 @@
1
+ import { canonicalJson } from "../utils/canonicalJson.js";
2
+ import { escapeXml } from "./serializer/xmlUtils.js";
3
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChildrenByNamespaceUri, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri } from "./xmlParser.js";
4
+ import { TaggedError } from "better-result";
5
+ //#region src/docx/sectionReferenceHistory.ts
6
+ const SECTION_REFERENCE_HISTORY_NAMESPACE = "urn:stella:folio:section-reference-history:1";
7
+ const HISTORY_NAMESPACES = /* @__PURE__ */ new Set([SECTION_REFERENCE_HISTORY_NAMESPACE]);
8
+ var InvalidSectionReferenceHistoryError = class extends TaggedError("InvalidSectionReferenceHistoryError") {};
9
+ const invalidHistory = () => {
10
+ throw new InvalidSectionReferenceHistoryError({ message: "Invalid section reference revision history." });
11
+ };
12
+ const parseSectionReferenceHistory = (change) => {
13
+ const histories = findChildrenByNamespaceUri(change, HISTORY_NAMESPACES, "previousReferences");
14
+ const history = histories.at(0);
15
+ if (!history) return void 0;
16
+ if (histories.length !== 1) return invalidHistory();
17
+ const headers = [];
18
+ const footers = [];
19
+ for (const child of getChildElements(history)) {
20
+ if (!WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(child) ?? "")) return invalidHistory();
21
+ const name = getLocalName(child.name);
22
+ if (name !== "headerReference" && name !== "footerReference") return invalidHistory();
23
+ const type = getAttributeByNamespaceUri(child, WORDPROCESSINGML_NAMESPACE_URIS, "type");
24
+ const rId = getAttributeByNamespaceUri(child, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "id");
25
+ if (type !== "default" && type !== "first" && type !== "even" || !rId || rId.trim() !== rId) return invalidHistory();
26
+ const references = name === "headerReference" ? headers : footers;
27
+ if (references.some((reference) => reference.type === type)) return invalidHistory();
28
+ references.push({
29
+ type,
30
+ rId
31
+ });
32
+ }
33
+ return {
34
+ ...headers.length > 0 && { headerReferences: headers },
35
+ ...footers.length > 0 && { footerReferences: footers }
36
+ };
37
+ };
38
+ const serializeSectionReferenceHistory = (references) => {
39
+ if (references === void 0) return "";
40
+ const serializeReferences = (kind, values) => {
41
+ if (new Set(values.map(({ type }) => type)).size !== values.length) return invalidHistory();
42
+ return values.map(({ type, rId }) => {
43
+ if (!rId || rId.trim() !== rId) return invalidHistory();
44
+ return `<w:${kind}Reference w:type="${type}" r:id="${escapeXml(rId)}"/>`;
45
+ }).join("");
46
+ };
47
+ const content = serializeReferences("header", references.headerReferences ?? []) + serializeReferences("footer", references.footerReferences ?? []);
48
+ return `<frh:previousReferences xmlns:frh="${SECTION_REFERENCE_HISTORY_NAMESPACE}" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="frh" mc:PreserveElements="frh:previousReferences">${content}</frh:previousReferences>`;
49
+ };
50
+ const sectionReferenceSelection = (properties) => ({
51
+ ...properties.headerReferences !== void 0 && { headerReferences: properties.headerReferences },
52
+ ...properties.footerReferences !== void 0 && { footerReferences: properties.footerReferences }
53
+ });
54
+ const sectionReferenceHistory = ({ previous, target }) => {
55
+ const before = sectionReferenceSelection(previous);
56
+ return canonicalJson(before) === canonicalJson(sectionReferenceSelection(target)) ? void 0 : before;
57
+ };
58
+ //#endregion
59
+ export { InvalidSectionReferenceHistoryError, parseSectionReferenceHistory, sectionReferenceHistory, sectionReferenceSelection, serializeSectionReferenceHistory };
@@ -2,13 +2,12 @@ import { parseCommentsExtended } from "./commentParser.js";
2
2
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
3
3
  import { hasUnsynthesizedReplyRanges } from "./commentReplyMarkers.js";
4
4
  import { validateFolioDocumentModel } from "./modelValidation.js";
5
- import { isNewDataUrlDrawing } from "./newImage.js";
6
5
  import { parseNumbering } from "./numberingParser.js";
7
6
  import { isUnsafePackagePath } from "./packageParts.js";
8
7
  import { RELATIONSHIP_TYPES } from "./relsParser.js";
9
- import { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_PART_LOWER, addCommentsExtendedOverride, addCommentsExtendedRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, updateCoreProperties, withoutAttachedTemplate } from "./rezip.js";
8
+ import { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_PART_LOWER, addCommentsExtendedOverride, addCommentsExtendedRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedInlineResources, updateCoreProperties, withoutAttachedTemplate } from "./rezip.js";
10
9
  import "./selectiveSaveFlags.js";
11
- import { buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
10
+ import { buildPatchedDocumentXml, buildPatchedNoteXml, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
12
11
  import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
13
12
  import { serializeDocument } from "./serializer/documentSerializer.js";
14
13
  import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
@@ -16,25 +15,6 @@ import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
16
15
  import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
17
16
  //#region src/docx/selectiveSave.ts
18
17
  /**
19
- * Check if document content has new images (data: URL without rId) or
20
- * new hyperlinks (href without rId). Combined into a single traversal
21
- * to avoid walking the block tree twice.
22
- */
23
- function hasNewImagesOrHyperlinks(blocks) {
24
- const runHasNewImage = (run) => run.content.some(isNewDataUrlDrawing);
25
- for (const block of blocks) if (block.type === "paragraph") {
26
- for (const item of block.content) if (item.type === "run") {
27
- if (runHasNewImage(item)) return true;
28
- } else if (item.type === "hyperlink" && item.href && !item.rId && !item.anchor) return true;
29
- else if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") {
30
- for (const sub of item.content) if (sub.type === "run" && runHasNewImage(sub)) return true;
31
- }
32
- } else if (block.type === "table") {
33
- for (const row of block.rows) for (const cell of row.cells) if (hasNewImagesOrHyperlinks(cell.content)) return true;
34
- }
35
- return false;
36
- }
37
- /**
38
18
  * Splice edited footnote/endnote paragraphs into their parts and return the
39
19
  * candidate ids that could NOT be routed to a note part (an id that is neither
40
20
  * a body nor a note paragraph — the caller bails on those).
@@ -139,7 +119,7 @@ async function ensureCommentsExtendedPackaging(zip, updates) {
139
119
  * Numbering definitions carry no `paraId`, so this is NOT keyed off
140
120
  * `changedParaIds` — it always runs (like the comments/header updates) and uses
141
121
  * a re-parse+re-serialize baseline to detect which `w:abstractNum` / `w:num`
142
- * definitions actually changed. The model omits parts of numbering.xml
122
+ * definitions changed or were minted. The model omits parts of numbering.xml
143
123
  * (`w:nsid`/`w:tmpl`, custom formats, level sub-elements), so only the changed
144
124
  * definitions are spliced by id; every other definition stays byte-exact.
145
125
  *
@@ -159,12 +139,12 @@ async function patchNumberingPart(zip, doc, updates) {
159
139
  }
160
140
  if (!file) return;
161
141
  const originalXml = await file.async("text");
162
- const baselineXml = serializeNumberingXml(parseNumbering(originalXml).definitions);
163
- const currentXml = serializeNumberingXml(numbering);
164
- const changed = collectChangedNumberingDefs(baselineXml, currentXml);
165
- if (changed.abstractNums.size === 0 && changed.nums.size === 0) return;
166
- const patched = buildPatchedNumberingXml(originalXml, currentXml, changed);
167
- if (patched === null) return;
142
+ const patched = patchNumberingDefinitions({
143
+ originalXml,
144
+ baselineXml: serializeNumberingXml(parseNumbering(originalXml).definitions),
145
+ currentXml: serializeNumberingXml(numbering)
146
+ });
147
+ if (patched === null || patched === originalXml) return;
168
148
  updates.set(file.name, patched);
169
149
  }
170
150
  /**
@@ -196,13 +176,13 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
196
176
  if (hasUntrackedChanges) return null;
197
177
  if (originalBuffer.byteLength > maxBytes) return null;
198
178
  const content = doc.package.document.content;
199
- if (hasNewImagesOrHyperlinks(content)) return null;
179
+ if (hasUnmaterializedInlineResources(content)) return null;
200
180
  for (const part of [
201
181
  ...doc.package.headers?.values() ?? [],
202
182
  ...doc.package.footers?.values() ?? [],
203
183
  ...doc.package.footnotes ?? [],
204
184
  ...doc.package.endnotes ?? []
205
- ]) if (hasNewImagesOrHyperlinks(part.content)) return null;
185
+ ]) if (hasUnmaterializedInlineResources(part.content)) return null;
206
186
  if (hasUnmaterializedHeaderFooter(doc)) return null;
207
187
  if (hasModelDrivenPictureWatermark(doc)) return null;
208
188
  if (withoutOrphanCommentRanges(doc) !== doc) return null;
@@ -159,6 +159,13 @@ declare function buildPatchedNumberingXml(originalXml: string, currentXml: strin
159
159
  * because they cannot be spliced by id; they are appended instead.
160
160
  */
161
161
  declare function collectAddedNumberingDefs(baselineXml: string, currentXml: string): ChangedNumberingDefs;
162
+ type PatchNumberingDefinitionsOptions = {
163
+ originalXml: string;
164
+ baselineXml: string;
165
+ currentXml: string;
166
+ };
167
+ /** Both save paths must write changed definitions and newly referenced instances together. */
168
+ declare const patchNumberingDefinitions: ({ originalXml, baselineXml, currentXml }: PatchNumberingDefinitionsOptions) => string | null;
162
169
  /**
163
170
  * Append added `w:abstractNum` / `w:num` definitions from `currentXml` to
164
171
  * `xml`. ECMA-376 §17.9 orders every `w:abstractNum` before the first `w:num`,
@@ -171,4 +178,4 @@ declare function collectAddedNumberingDefs(baselineXml: string, currentXml: stri
171
178
  */
172
179
  declare function appendNumberingDefs(xml: string, currentXml: string, added: ChangedNumberingDefs): string | null;
173
180
  //#endregion
174
- export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
181
+ export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, validatePatchSafety };