@stll/folio-core 0.55.0 → 0.57.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 (225) hide show
  1. package/dist/ai-edits/apply.js +214 -52
  2. package/dist/ai-edits/batch-claims.d.ts +3 -1
  3. package/dist/ai-edits/batch-claims.js +25 -1
  4. package/dist/ai-edits/headless.js +2 -2
  5. package/dist/ai-edits/table-row-column-mutations.js +17 -3
  6. package/dist/ai-edits/types.d.ts +7 -1
  7. package/dist/ai-suggestions/apply.js +20 -1
  8. package/dist/compare/compare.js +1 -0
  9. package/dist/compare/inline-atoms.js +40 -16
  10. package/dist/compare/inline-provenance.js +12 -5
  11. package/dist/compare/reproducible-package.d.ts +0 -17
  12. package/dist/compare/reproducible-package.js +12 -18
  13. package/dist/compat/eigenpal.d.ts +2 -1
  14. package/dist/compat/eigenpal.js +2 -1
  15. package/dist/controller/canonicalClipboard.d.ts +23 -0
  16. package/dist/controller/canonicalClipboard.js +482 -0
  17. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  18. package/dist/controller/canonicalClipboardResources.js +382 -0
  19. package/dist/controller/canonicalComments.d.ts +41 -0
  20. package/dist/controller/canonicalComments.js +152 -0
  21. package/dist/controller/canonicalComposition.d.ts +24 -3
  22. package/dist/controller/canonicalComposition.js +208 -48
  23. package/dist/controller/canonicalInlineProjection.d.ts +118 -0
  24. package/dist/controller/canonicalInlineProjection.js +476 -0
  25. package/dist/controller/canonicalInput.d.ts +13 -4
  26. package/dist/controller/canonicalInput.js +62 -14
  27. package/dist/controller/canonicalInputTimer.d.ts +16 -0
  28. package/dist/controller/canonicalInputTimer.js +34 -0
  29. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  30. package/dist/controller/canonicalPublicOperations.js +648 -0
  31. package/dist/controller/canonicalSelection.d.ts +18 -0
  32. package/dist/controller/canonicalSelection.js +32 -0
  33. package/dist/controller/canonicalSession.d.ts +27 -6
  34. package/dist/controller/canonicalSession.js +298 -116
  35. package/dist/controller/canonicalStoryEditor.d.ts +7 -3
  36. package/dist/controller/canonicalStoryEditor.js +27 -2
  37. package/dist/controller/canonicalStructure.js +57 -4
  38. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  39. package/dist/controller/canonicalTocSelection.js +43 -0
  40. package/dist/controller/folioEditor.d.ts +2 -9
  41. package/dist/controller/folioEditor.js +18 -4
  42. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  43. package/dist/controller/headerFooterEditorManager.js +26 -11
  44. package/dist/controller/hiddenEditorApi.d.ts +24 -3
  45. package/dist/controller/hiddenEditorApi.js +8 -0
  46. package/dist/controller/hiddenEditorManager.d.ts +5 -1
  47. package/dist/controller/hiddenEditorManager.js +378 -43
  48. package/dist/controller/noteEditorManager.d.ts +2 -1
  49. package/dist/controller/noteEditorManager.js +29 -13
  50. package/dist/document-operations.d.ts +8 -1
  51. package/dist/document-operations.js +6 -4
  52. package/dist/docx/blockPlainText.d.ts +1 -1
  53. package/dist/docx/blockPlainText.js +11 -3
  54. package/dist/docx/builtInStyles.js +13 -6
  55. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  56. package/dist/docx/canonicalResourceSave.js +27 -0
  57. package/dist/docx/canonicalSave.d.ts +29 -0
  58. package/dist/docx/canonicalSave.js +101 -0
  59. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  60. package/dist/docx/canonicalStoryRepack.js +38 -5
  61. package/dist/docx/containerChildren.gen.d.ts +3 -1
  62. package/dist/docx/containerChildren.gen.js +33 -1
  63. package/dist/docx/documentParser.d.ts +1 -13
  64. package/dist/docx/documentParser.js +1 -47
  65. package/dist/docx/ensureParaIds.js +10 -4
  66. package/dist/docx/fieldParser.js +14 -2
  67. package/dist/docx/footnoteParser.d.ts +1 -11
  68. package/dist/docx/footnoteParser.js +1 -16
  69. package/dist/docx/headerFooterParser.d.ts +1 -9
  70. package/dist/docx/headerFooterParser.js +1 -12
  71. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  72. package/dist/docx/headerFooterVerbatim.js +81 -3
  73. package/dist/docx/hyperlinkParser.d.ts +1 -1
  74. package/dist/docx/hyperlinkParser.js +20 -1
  75. package/dist/docx/imageParser.d.ts +13 -1
  76. package/dist/docx/imageParser.js +20 -11
  77. package/dist/docx/inlineWrapperContent.d.ts +4 -4
  78. package/dist/docx/inlineWrapperContent.js +4 -4
  79. package/dist/docx/linkedSdtPreservation.d.ts +10 -0
  80. package/dist/docx/linkedSdtPreservation.js +37 -0
  81. package/dist/docx/metadataPrivacy.js +6 -1
  82. package/dist/docx/packageParts.js +9 -3
  83. package/dist/docx/paragraphParser.js +88 -67
  84. package/dist/docx/paragraphPropertySource.d.ts +22 -1
  85. package/dist/docx/paragraphPropertySource.js +109 -2
  86. package/dist/docx/parser.js +2 -1
  87. package/dist/docx/preservedRunContent.js +2 -1
  88. package/dist/docx/relsParser.js +26 -2
  89. package/dist/docx/removeHeaderFooterParts.js +17 -6
  90. package/dist/docx/rezip.d.ts +49 -5
  91. package/dist/docx/rezip.js +754 -235
  92. package/dist/docx/saveDiagnostics.d.ts +11 -0
  93. package/dist/docx/selectiveSave.d.ts +2 -1
  94. package/dist/docx/selectiveSave.js +42 -11
  95. package/dist/docx/selectiveXmlPatch.js +7 -5
  96. package/dist/docx/serializer/commentSerializer.d.ts +4 -1
  97. package/dist/docx/serializer/commentSerializer.js +7 -3
  98. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  99. package/dist/docx/serializer/documentSerializer.js +29 -10
  100. package/dist/docx/serializer/paragraphSerializer.js +207 -94
  101. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  102. package/dist/docx/server/materializeYjsDocx.js +14 -6
  103. package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
  104. package/dist/docx/storyBlockReplay.d.ts +21 -1
  105. package/dist/docx/storyBlockReplay.js +113 -9
  106. package/dist/docx/storyPlainText.d.ts +20 -0
  107. package/dist/docx/storyPlainText.js +23 -0
  108. package/dist/docx/structuralXmlPatch.js +10 -22
  109. package/dist/docx/styleParser.d.ts +5 -1
  110. package/dist/docx/styleParser.js +1 -1
  111. package/dist/docx/tableParser.d.ts +1 -8
  112. package/dist/docx/tableParser.js +1 -28
  113. package/dist/docx/textBoxParser.d.ts +1 -5
  114. package/dist/docx/textBoxParser.js +1 -20
  115. package/dist/docx/unzip.js +6 -1
  116. package/dist/i18n/messages/ar.gen.js +4 -0
  117. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  118. package/dist/i18n/messages/catalogs.gen.js +68 -0
  119. package/dist/i18n/messages/cs.gen.js +4 -0
  120. package/dist/i18n/messages/de.gen.js +4 -0
  121. package/dist/i18n/messages/en.gen.js +4 -0
  122. package/dist/i18n/messages/es.gen.js +4 -0
  123. package/dist/i18n/messages/et.gen.js +4 -0
  124. package/dist/i18n/messages/fr.gen.js +4 -0
  125. package/dist/i18n/messages/he.gen.js +4 -0
  126. package/dist/i18n/messages/hi.gen.js +4 -0
  127. package/dist/i18n/messages/hu.gen.js +4 -0
  128. package/dist/i18n/messages/lt.gen.js +4 -0
  129. package/dist/i18n/messages/lv.gen.js +4 -0
  130. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  131. package/dist/i18n/messages/pl.gen.js +4 -0
  132. package/dist/i18n/messages/pt-BR.gen.js +4 -0
  133. package/dist/i18n/messages/sk.gen.js +4 -0
  134. package/dist/i18n/messages/tr.gen.js +4 -0
  135. package/dist/i18n/messages/zh-CN.gen.js +4 -0
  136. package/dist/index.d.ts +2 -1
  137. package/dist/index.js +2 -1
  138. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  139. package/dist/internal/acceptedBlockProjection.js +45 -0
  140. package/dist/internal/revisionResolutionInline.js +25 -3
  141. package/dist/managers/DocumentLoaderManager.js +2 -1
  142. package/dist/markdown/escape.d.ts +3 -1
  143. package/dist/markdown/escape.js +9 -2
  144. package/dist/markdown/renderBlock.js +5 -120
  145. package/dist/markdown/renderParagraph.js +4 -2
  146. package/dist/markdown/renderRuns.d.ts +7 -1
  147. package/dist/markdown/renderRuns.js +33 -24
  148. package/dist/panel-layout.d.ts +11 -9
  149. package/dist/panel-layout.js +13 -9
  150. package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
  151. package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
  152. package/dist/prosemirror/attrs/index.js +5 -0
  153. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  154. package/dist/prosemirror/canonicalCommands.js +11 -4
  155. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  156. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  157. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  158. package/dist/prosemirror/clipboardIntent.js +9 -0
  159. package/dist/prosemirror/commands/comments.js +60 -35
  160. package/dist/prosemirror/commands/pageBreak.js +2 -2
  161. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  162. package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
  163. package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
  164. package/dist/prosemirror/commentOps.js +15 -1
  165. package/dist/prosemirror/conversion/fromProseDoc.js +22 -3
  166. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  167. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  168. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  169. package/dist/prosemirror/conversion/toProseDoc.js +124 -22
  170. package/dist/prosemirror/extensions/core/ParagraphExtension.js +61 -35
  171. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
  172. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
  173. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +65 -29
  174. package/dist/prosemirror/extensions/marks/markUtils.js +11 -8
  175. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
  176. package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
  177. package/dist/prosemirror/fieldRepresentation.js +18 -0
  178. package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
  179. package/dist/prosemirror/hyperlinkRemoval.js +28 -9
  180. package/dist/prosemirror/insertOperations.js +2 -1
  181. package/dist/prosemirror/listNumbering.js +11 -5
  182. package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
  183. package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
  184. package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
  185. package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
  186. package/dist/prosemirror/noteReferenceReview.js +14 -1
  187. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  188. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  189. package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
  190. package/dist/prosemirror/plugins/documentStyles.js +17 -10
  191. package/dist/prosemirror/plugins/suggestionMode.js +48 -48
  192. package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
  193. package/dist/prosemirror/preservedXmlReview.js +35 -0
  194. package/dist/prosemirror/runFormattingFromMarks.js +2 -2
  195. package/dist/prosemirror/runFormattingReconciliation.js +5 -4
  196. package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
  197. package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
  198. package/dist/prosemirror/runStyleFormatting.d.ts +3 -1
  199. package/dist/prosemirror/runStyleFormatting.js +3 -1
  200. package/dist/prosemirror/schema/marks.d.ts +1 -0
  201. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  202. package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
  203. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
  204. package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
  205. package/dist/prosemirror/textInput.d.ts +5 -1
  206. package/dist/prosemirror/textInput.js +3 -3
  207. package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
  208. package/dist/prosemirror/trackedRevisionPath.js +39 -1
  209. package/dist/prosemirror/validation.js +3 -5
  210. package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
  211. package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
  212. package/dist/types/canonicalCapabilities.d.ts +201 -0
  213. package/dist/types/canonicalCapabilities.js +198 -0
  214. package/dist/types/canonicalComments.d.ts +51 -0
  215. package/dist/types/canonicalComments.js +0 -0
  216. package/dist/types/canonicalSave.d.ts +11 -0
  217. package/dist/types/canonicalSave.js +0 -0
  218. package/dist/types/canonicalSections.d.ts +12 -0
  219. package/dist/types/canonicalSections.js +0 -0
  220. package/dist/types/docxSerialization.d.ts +14 -0
  221. package/dist/types/docxSerialization.js +7 -0
  222. package/dist/utils/headingCollector.js +45 -4
  223. package/dist/utils/outlineDepth.d.ts +9 -0
  224. package/dist/utils/outlineDepth.js +9 -0
  225. package/package.json +4 -4
@@ -1,5 +1,7 @@
1
1
  import { consumeTrackedSectionEndpointRemoval } from "../internal/sectionEndpointResolution.js";
2
2
  import { consumeSectionReferenceResolution } from "../internal/sectionReferenceResolution.js";
3
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
4
+ import { canonicalJson } from "../utils/canonicalJson.js";
3
5
  import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
4
6
  import { appVersionInSchemaForm, normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
5
7
  import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
@@ -9,6 +11,7 @@ import { readDocumentSectionFacts } from "./documentSectionFacts.js";
9
11
  import { normalizeDrawingIds } from "./drawingIdNormalization.js";
10
12
  import { rebindDrawingImageRelationship } from "./drawingRelationships.js";
11
13
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
14
+ import { getDocumentSourceBaseline } from "./headerFooterVerbatim.js";
12
15
  import { assertValidFolioDocumentModel } from "./modelValidation.js";
13
16
  import { isNewDataUrlDrawing } from "./newImage.js";
14
17
  import { missingNoteReferenceStyles, noteReferenceNeeds } from "./noteReferenceStyles.js";
@@ -16,13 +19,13 @@ import { parseNumbering } from "./numberingParser.js";
16
19
  import { paragraphNumberingReferenceId as paragraphNumberingReferenceId$1 } from "./numberingReference.js";
17
20
  import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
18
21
  import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
19
- import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
20
- import { BLOCK_TREE_DESCENT, visitBlockTreeRecords } from "./paragraphTraversal.js";
22
+ import { cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, copyDocumentParagraphPropertySourceContract } from "./paragraphPropertySource.js";
23
+ import { BLOCK_TREE_DESCENT, visitBlockTreeRecords, visitInlineContentSlots, visitParagraphRuns } from "./paragraphTraversal.js";
21
24
  import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
22
25
  import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
23
26
  import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
24
- import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
25
- import { planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
27
+ import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions, spliceXml } from "./selectiveXmlPatch.js";
28
+ import { hasOwnedCommentsPart, planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
26
29
  import { serializeDocument } from "./serializer/documentSerializer.js";
27
30
  import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
28
31
  import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
@@ -34,11 +37,14 @@ import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerialize
34
37
  import { serializeThemeXml } from "./serializer/themeSerializer.js";
35
38
  import { updateEvenAndOddHeaders } from "./settingsHeaderFooterUpdate.js";
36
39
  import { parseSettings } from "./settingsParser.js";
40
+ import { getXmlSourceRange, parseStreamingXmlWithSourceRanges } from "./streamingXmlParser.js";
41
+ import { captureVerbatimXml } from "./verbatimCapture.js";
37
42
  import { hasCanonicalWordprocessingPrefixes } from "./wordprocessingPrefixes.js";
38
- import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
39
- import { TaggedError, panic } from "better-result";
43
+ import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, findAttributeByNamespaceUri, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
44
+ import { Result, TaggedError, panic } from "better-result";
40
45
  import { assertValidOoxmlNumericIds, escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
41
46
  import { mintRelationshipId } from "@stll/docx-core/model";
47
+ import { writeZipPart } from "@stll/docx-core/zip";
42
48
  import JSZip from "jszip";
43
49
  //#region src/docx/rezip.ts
44
50
  /**
@@ -196,13 +202,19 @@ async function serializeCommentsToZip(doc, zip, compressionLevel) {
196
202
  const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
197
203
  const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
198
204
  if (comments.length === 0) {
199
- if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
205
+ const ownsCommentsPart = hasOwnedCommentsPart(doc);
206
+ if (sourceCommentsXml === void 0 ? !ownsCommentsPart : !hasCommentEntries(sourceCommentsXml)) return;
200
207
  }
201
208
  const plan = planCommentParts(comments);
202
209
  const commentsXml = serializeComments(plan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
203
- zip.file(sourceCommentsFile?.name ?? "word/comments.xml", commentsXml, {
204
- compression: "DEFLATE",
205
- compressionOptions: { level: compressionLevel }
210
+ writeZipPart({
211
+ zip,
212
+ path: sourceCommentsFile?.name ?? "word/comments.xml",
213
+ data: commentsXml,
214
+ options: {
215
+ compression: "DEFLATE",
216
+ compressionOptions: { level: compressionLevel }
217
+ }
206
218
  });
207
219
  await ensureCommentsContentType(zip, compressionLevel);
208
220
  await ensureCommentsRelationship(zip, compressionLevel);
@@ -231,9 +243,14 @@ async function syncCommentsExtendedPart(plan, zip, compressionLevel) {
231
243
  await transformPackagingFile(zip, "word/_rels/document.xml.rels", removeCommentsExtendedRelationship, compressionLevel);
232
244
  return;
233
245
  }
234
- zip.file(existing?.name ?? "word/commentsExtended.xml", xml, {
235
- compression: "DEFLATE",
236
- compressionOptions: { level: compressionLevel }
246
+ writeZipPart({
247
+ zip,
248
+ path: existing?.name ?? "word/commentsExtended.xml",
249
+ data: xml,
250
+ options: {
251
+ compression: "DEFLATE",
252
+ compressionOptions: { level: compressionLevel }
253
+ }
237
254
  });
238
255
  await transformPackagingFile(zip, "[Content_Types].xml", addCommentsExtendedOverride, compressionLevel);
239
256
  await transformPackagingFile(zip, "word/_rels/document.xml.rels", addCommentsExtendedRelationship, compressionLevel);
@@ -244,9 +261,14 @@ async function transformPackagingFile(zip, path, transform, compressionLevel) {
244
261
  if (!file) return;
245
262
  const xml = await file.async("text");
246
263
  const next = transform(xml);
247
- if (next !== xml) zip.file(path, next, {
248
- compression: "DEFLATE",
249
- compressionOptions: { level: compressionLevel }
264
+ if (next !== xml) writeZipPart({
265
+ zip,
266
+ path,
267
+ data: next,
268
+ options: {
269
+ compression: "DEFLATE",
270
+ compressionOptions: { level: compressionLevel }
271
+ }
250
272
  });
251
273
  }
252
274
  function findZipEntryCaseInsensitive(zip, lowerPath) {
@@ -255,7 +277,9 @@ function findZipEntryCaseInsensitive(zip, lowerPath) {
255
277
  for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.toLowerCase() === lowerPath) return file;
256
278
  return null;
257
279
  }
258
- const EMPTY_RELS_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"></Relationships>";
280
+ const PACKAGE_RELATIONSHIPS_NAMESPACE_URI = "http://schemas.openxmlformats.org/package/2006/relationships";
281
+ const EMPTY_RELS_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
282
+ <Relationships xmlns="${PACKAGE_RELATIONSHIPS_NAMESPACE_URI}"></Relationships>`;
259
283
  function headerFooterFilename(target) {
260
284
  return target.startsWith("/") ? target.slice(1) : `word/${target}`;
261
285
  }
@@ -352,9 +376,14 @@ async function registerImageExtensions(zip, extensions, compressionLevel) {
352
376
  changed = true;
353
377
  }
354
378
  if (!changed) return;
355
- zip.file("[Content_Types].xml", ctXml, {
356
- compression: "DEFLATE",
357
- compressionOptions: { level: compressionLevel }
379
+ writeZipPart({
380
+ zip,
381
+ path: "[Content_Types].xml",
382
+ data: ctXml,
383
+ options: {
384
+ compression: "DEFLATE",
385
+ compressionOptions: { level: compressionLevel }
386
+ }
358
387
  });
359
388
  }
360
389
  function collectNewImages(blocks) {
@@ -425,9 +454,14 @@ async function processNewImages(parts, zip, compressionLevel) {
425
454
  maxId++;
426
455
  const mediaPath = `word/media/${`image${maxImageNum}.${extension}`}`;
427
456
  const newRId = mintRelationshipId(maxId);
428
- zip.file(mediaPath, data, {
429
- compression: "DEFLATE",
430
- compressionOptions: { level: compressionLevel }
457
+ writeZipPart({
458
+ zip,
459
+ path: mediaPath,
460
+ data,
461
+ options: {
462
+ compression: "DEFLATE",
463
+ compressionOptions: { level: compressionLevel }
464
+ }
431
465
  });
432
466
  relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXmlAttribute(relativeTargetForPart(partPath, mediaPath))}"/>`);
433
467
  extensionsAdded.add(extension);
@@ -438,13 +472,15 @@ async function processNewImages(parts, zip, compressionLevel) {
438
472
  }) ?? panic("A detached drawing lost its embedded image relationship");
439
473
  image.rId = newRId;
440
474
  }
441
- if (relEntries.length > 0) {
442
- const updatedRelsXml = relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`);
443
- zip.file(relsPath, updatedRelsXml, {
475
+ if (relEntries.length > 0) writeZipPart({
476
+ zip,
477
+ path: relsPath,
478
+ data: relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`),
479
+ options: {
444
480
  compression: "DEFLATE",
445
481
  compressionOptions: { level: compressionLevel }
446
- });
447
- }
482
+ }
483
+ });
448
484
  }
449
485
  await registerImageExtensions(zip, extensionsAdded, compressionLevel);
450
486
  }
@@ -456,67 +492,259 @@ const materializeEmbeddedMedia = async ({ document, zip, compressionLevel }) =>
456
492
  if (isUnsafePackagePath(media.path) || !media.path.startsWith("word/media/")) panic("Detached media has an invalid package path");
457
493
  const extension = media.path.split(".").at(-1)?.toLowerCase();
458
494
  if (!extension || !/^[a-z0-9]+$/u.test(extension)) panic("Embedded media has an invalid extension");
459
- zip.file(media.path, media.data, {
460
- compression: "DEFLATE",
461
- compressionOptions: { level: compressionLevel }
495
+ writeZipPart({
496
+ zip,
497
+ path: media.path,
498
+ data: media.data,
499
+ options: {
500
+ compression: "DEFLATE",
501
+ compressionOptions: { level: compressionLevel }
502
+ }
462
503
  });
463
504
  extensions.add(extension);
464
505
  }
465
506
  await registerImageExtensions(zip, extensions, compressionLevel);
466
507
  };
467
- /**
468
- * Collect all hyperlinks that have an href but no rId from block content.
469
- * These are newly created hyperlinks that need relationship entries.
470
- */
471
- function collectHyperlinksWithoutRId(blocks) {
472
- const hyperlinks = [];
473
- const collectInlineHyperlinks = (content) => {
474
- for (const item of content) switch (item.type) {
475
- case "hyperlink":
476
- if (item.href && !item.rId && !item.anchor) hyperlinks.push(item);
477
- break;
478
- case "simpleField":
479
- case "inlineSdt":
480
- case "inlineWrapper":
481
- case "insertion":
482
- case "deletion":
483
- case "moveFrom":
484
- case "moveTo":
485
- collectInlineHyperlinks(item.content);
486
- break;
487
- default: break;
508
+ var CanonicalImageRelationshipRefusalError = class extends TaggedError("CanonicalImageRelationshipRefusalError") {};
509
+ /** Publish allocated pictures into each owning story without rebinding canonical ids. */
510
+ const publishCanonicalImageResources = async ({ document, zip, compressionLevel }) => {
511
+ const baseline = getDocumentSourceBaseline(document);
512
+ for (const { relsPath, blocks } of collectDocxParts(document, zip)) {
513
+ const images = [];
514
+ visitBlockTreeRecords(blocks, (record) => {
515
+ if (record.type !== "paragraph") return BLOCK_TREE_DESCENT.descend;
516
+ visitParagraphRuns(record, (run) => {
517
+ for (const content of run.content) if (content.type === "drawing" && content.image.rId !== void 0) images.push(content.image);
518
+ });
519
+ return BLOCK_TREE_DESCENT.descend;
520
+ });
521
+ if (images.length === 0) continue;
522
+ let xml = await readRelsOrStub(zip, relsPath);
523
+ const local = parseRelationships(xml);
524
+ const partPath = relsPath.replace("/_rels/", "/").replace(/\.rels$/u, "");
525
+ let changed = false;
526
+ for (const image of images) {
527
+ const id = image.rId;
528
+ if (id === void 0) continue;
529
+ const refuse = (reason, message) => {
530
+ throw new CanonicalImageRelationshipRefusalError({
531
+ message,
532
+ gap: CANONICAL_GAP.save,
533
+ part: relsPath,
534
+ relationshipId: id,
535
+ reason
536
+ });
537
+ };
538
+ const source = local.get(id);
539
+ const allocated = document.package.relationships?.get(id);
540
+ const allocatedMediaPath = allocated?.targetMode === "External" || allocated?.type !== RELATIONSHIP_TYPES.image ? void 0 : resolveRelativePath("word/_rels/document.xml.rels", allocated.target);
541
+ const allocatedMedia = allocatedMediaPath === void 0 ? void 0 : document.package.media?.get(allocatedMediaPath);
542
+ const newlyAllocated = allocated?.type === RELATIONSHIP_TYPES.image && (baseline.type !== "captured" || canonicalJson(allocated) !== canonicalJson(baseline.resourceRelationships?.get(id)));
543
+ const desired = relsPath === "word/_rels/document.xml.rels" || newlyAllocated || source === void 0 && image.src !== void 0 && (allocated?.targetMode === "External" ? image.src === allocated.target : image.src === allocatedMedia?.dataUrl) ? allocated : source;
544
+ if (desired?.type !== RELATIONSHIP_TYPES.image) return refuse("unresolved", "A canonical inline picture has no owning image relationship.");
545
+ const desiredRelsPath = desired === allocated ? "word/_rels/document.xml.rels" : relsPath;
546
+ const absolute = desired.targetMode === "External" ? void 0 : resolveRelativePath(desiredRelsPath, desired.target);
547
+ if (desired.targetMode === "External" && !isAllowedExternalWatermarkImageUrl(desired.target)) return refuse("unsafeTarget", "A canonical inline picture has an unsafe external target.");
548
+ if (absolute !== void 0 && isUnsafePackagePath(absolute)) return refuse("unsafeTarget", "A canonical inline picture has an unsafe media target.");
549
+ if (source) {
550
+ const sourceAbsolute = source.targetMode === "External" ? void 0 : resolveRelativePath(relsPath, source.target);
551
+ if (source.type !== RELATIONSHIP_TYPES.image || source.targetMode === "External" !== (desired.targetMode === "External") || (absolute === void 0 ? source.target !== desired.target : sourceAbsolute !== absolute)) return refuse("conflict", "A canonical inline picture relationship conflicts with its owning source binding.");
552
+ }
553
+ if (absolute !== void 0 && !findZipEntryCaseInsensitive(zip, absolute.toLowerCase())) {
554
+ const media = document.package.media?.get(absolute);
555
+ if (!media) return refuse("missingMedia", "A canonical inline picture is missing its media bytes.");
556
+ if (isUnsafePackagePath(media.path) || media.path !== absolute) return refuse("unsafeTarget", "A canonical inline picture media path does not match its relationship.");
557
+ writeZipPart({
558
+ zip,
559
+ path: absolute,
560
+ data: media.data,
561
+ options: {
562
+ compression: "DEFLATE",
563
+ compressionOptions: { level: compressionLevel }
564
+ }
565
+ });
566
+ const extension = absolute.split(".").at(-1)?.toLowerCase();
567
+ if (!extension || !/^[a-z0-9]+$/u.test(extension)) return refuse("unsafeTarget", "A canonical inline picture media extension is invalid.");
568
+ await registerImageExtensions(zip, /* @__PURE__ */ new Set([extension]), compressionLevel);
569
+ }
570
+ if (source) continue;
571
+ const root = parseXmlDocument(xml);
572
+ const type = root !== null && getChildElements(root).some((element) => getAttribute(element, null, "Type")?.startsWith("http://purl.oclc.org/ooxml/officeDocument/relationships/")) ? "http://purl.oclc.org/ooxml/officeDocument/relationships/image" : RELATIONSHIP_TYPES.image;
573
+ const target = absolute === void 0 ? desired.target : relativeTargetForPart(partPath, absolute);
574
+ xml = appendCanonicalRelationship({
575
+ xml,
576
+ partPath: relsPath,
577
+ relationshipXml: `<Relationship Id="${escapeXmlAttribute(id)}" Type="${type}" Target="${escapeXmlAttribute(target)}"${desired.targetMode === "External" ? " TargetMode=\"External\"" : ""}/>`
578
+ });
579
+ local.set(id, {
580
+ id,
581
+ type: RELATIONSHIP_TYPES.image,
582
+ target,
583
+ ...desired.targetMode === "External" ? { targetMode: "External" } : {}
584
+ });
585
+ changed = true;
488
586
  }
489
- };
490
- for (const block of blocks) if (block.type === "paragraph") collectInlineHyperlinks(block.content);
491
- else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
492
- else if (block.type === "blockSdt" || block.type === "blockCustomXml") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
493
- return hyperlinks;
587
+ if (changed) writeZipPart({
588
+ zip,
589
+ path: relsPath,
590
+ data: xml,
591
+ options: {
592
+ compression: "DEFLATE",
593
+ compressionOptions: { level: compressionLevel }
594
+ }
595
+ });
596
+ }
597
+ };
598
+ const collectExternalHyperlinkResources = (blocks) => {
599
+ const resources = [];
600
+ visitBlockTreeRecords(blocks, (record) => {
601
+ if (record.type !== "paragraph") return BLOCK_TREE_DESCENT.descend;
602
+ visitInlineContentSlots(record, ({ item }) => {
603
+ if (item.type === "hyperlink" && item.href && !item.anchor) resources.push({
604
+ type: "text",
605
+ hyperlink: item
606
+ });
607
+ });
608
+ visitParagraphRuns(record, (run) => {
609
+ for (const drawing of run.content) if (drawing.type === "drawing" && drawing.rawXmlMode === void 0 && drawing.image.hlinkHref) resources.push({
610
+ type: "drawing",
611
+ image: drawing.image,
612
+ drawing
613
+ });
614
+ });
615
+ return BLOCK_TREE_DESCENT.descend;
616
+ });
617
+ return resources;
618
+ };
619
+ const externalHyperlinkTarget = (resource) => {
620
+ switch (resource.type) {
621
+ case "text": return {
622
+ id: resource.hyperlink.rId,
623
+ href: resource.hyperlink.href
624
+ };
625
+ case "drawing": return {
626
+ id: resource.image.hlinkRId,
627
+ href: resource.image.hlinkHref
628
+ };
629
+ default: return resource;
630
+ }
631
+ };
632
+ /** Newly authored text links; the census shares the complete owning-part traversal. */
633
+ function collectHyperlinksWithoutRId(blocks) {
634
+ return collectExternalHyperlinkResources(blocks).flatMap((resource) => resource.type === "text" && !resource.hyperlink.rId ? [resource.hyperlink] : []);
494
635
  }
495
636
  /** The selective save boundary must use the same resource census as full repack. */
496
- const hasUnmaterializedInlineResources = (blocks) => collectNewImages(blocks).length > 0 || collectHyperlinksWithoutRId(blocks).length > 0;
497
- /**
498
- * Process newly created hyperlinks in every part: assign rIds and add
499
- * relationship entries to the owning part's rels. Mutates the hyperlinks' rId
500
- * fields in-place.
501
- */
637
+ const hasUnmaterializedInlineResources = (blocks) => collectNewImages(blocks).length > 0 || collectExternalHyperlinkResources(blocks).some((resource) => externalHyperlinkTarget(resource).id === void 0);
638
+ const matchesExternalHyperlink = (relation, href) => relation?.type === RELATIONSHIP_TYPES.hyperlink && relation.targetMode === "External" && relation.target === href;
639
+ /** Compare against the already loaded source ZIP, without mutating model or package. */
640
+ const hasUnmaterializedHyperlinkBindings = async (document, zip) => {
641
+ for (const { blocks, relsPath } of collectDocxParts(document, zip)) {
642
+ const resources = collectExternalHyperlinkResources(blocks);
643
+ if (resources.length === 0) continue;
644
+ const relationships = parseRelationships(await readRelsOrStub(zip, relsPath));
645
+ for (const resource of resources) {
646
+ const { id, href } = externalHyperlinkTarget(resource);
647
+ if (href && (id === void 0 || !matchesExternalHyperlink(relationships.get(id), href))) return true;
648
+ }
649
+ }
650
+ return false;
651
+ };
652
+ const rebindCapturedDrawingLink = ({ xml, previousId, nextId }) => {
653
+ const parsed = Result.try({
654
+ try: () => parseXml(xml, OOXML_NAMESPACE_SCOPE),
655
+ catch: () => new DocxPackageFidelityError("Cannot rebind invalid captured drawing hyperlink XML.")
656
+ });
657
+ if (parsed.isErr()) throw parsed.error;
658
+ let rebound = 0;
659
+ const visit = (element) => {
660
+ const attribute = findAttributeByNamespaceUri(element, OFFICE_RELATIONSHIP_NAMESPACE_URIS, "id");
661
+ if (attribute?.value === previousId && element.attributes !== void 0) {
662
+ element.attributes[attribute.name] = nextId;
663
+ rebound += 1;
664
+ }
665
+ for (const child of element.elements ?? []) visit(child);
666
+ };
667
+ visit(parsed.value);
668
+ if (rebound !== 1) throw new DocxPackageFidelityError("Captured drawing hyperlink must own exactly one click relationship.");
669
+ return (parsed.value.elements ?? []).map(captureVerbatimXml).join("");
670
+ };
671
+ /** Materialize each modeled hyperlink against the actual owning ZIP relationship table. */
502
672
  async function processNewHyperlinks(parts, zip, compressionLevel) {
503
673
  for (const { relsPath, blocks } of parts) {
504
- const newHyperlinks = collectHyperlinksWithoutRId(blocks);
505
- if (newHyperlinks.length === 0) continue;
674
+ const resources = collectExternalHyperlinkResources(blocks);
675
+ if (resources.length === 0) continue;
506
676
  const relsXml = await readRelsOrStub(zip, relsPath);
677
+ const relationships = parseRelationships(relsXml);
678
+ const root = parseXmlDocument(relsXml);
679
+ if (root?.name === void 0 || getLocalName(root.name) !== "Relationships" || getNamespaceUri(root) !== PACKAGE_RELATIONSHIPS_NAMESPACE_URI) throw new DocxPackageFidelityError("Cannot materialize hyperlinks into an invalid relationship part.");
680
+ const strictRelationshipNamespace = "http://purl.oclc.org/ooxml/officeDocument/relationships";
681
+ const relationshipType = getChildElements(root).some((child) => getAttribute(child, null, "Type")?.startsWith(`${strictRelationshipNamespace}/`)) ? `${strictRelationshipNamespace}/hyperlink` : RELATIONSHIP_TYPES.hyperlink;
682
+ const byTarget = /* @__PURE__ */ new Map();
683
+ for (const relation of relationships.values()) if (relation.type === RELATIONSHIP_TYPES.hyperlink && relation.targetMode === "External" && !byTarget.has(relation.target)) byTarget.set(relation.target, relation.id);
507
684
  let maxId = findMaxRId(relsXml);
508
685
  const relEntries = [];
509
- for (const hyperlink of newHyperlinks) {
510
- if (!hyperlink.href) continue;
511
- maxId++;
512
- const newRId = `rId${maxId}`;
513
- relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.hyperlink}" Target="${escapeXmlAttribute(hyperlink.href)}" TargetMode="External"/>`);
514
- hyperlink.rId = newRId;
686
+ for (const resource of resources) {
687
+ const { id, href } = externalHyperlinkTarget(resource);
688
+ if (!href || id !== void 0 && matchesExternalHyperlink(relationships.get(id), href)) continue;
689
+ let nextId = byTarget.get(href);
690
+ if (nextId === void 0) {
691
+ if (id !== void 0 && !relationships.has(id)) nextId = id;
692
+ else do {
693
+ maxId += 1;
694
+ nextId = `rId${maxId}`;
695
+ } while (relationships.has(nextId));
696
+ relationships.set(nextId, {
697
+ id: nextId,
698
+ type: RELATIONSHIP_TYPES.hyperlink,
699
+ target: href,
700
+ targetMode: "External"
701
+ });
702
+ byTarget.set(href, nextId);
703
+ relEntries.push({
704
+ type: "element",
705
+ name: `${root.name.slice(0, -13)}Relationship`,
706
+ attributes: {
707
+ Id: nextId,
708
+ Type: relationshipType,
709
+ Target: href,
710
+ TargetMode: "External"
711
+ }
712
+ });
713
+ }
714
+ switch (resource.type) {
715
+ case "text":
716
+ resource.hyperlink.rId = nextId;
717
+ break;
718
+ case "drawing": {
719
+ const source = resource.image.hlinkClickSource;
720
+ if (source !== void 0 && source.rId === id && id !== void 0) resource.image.hlinkClickSource = {
721
+ xml: rebindCapturedDrawingLink({
722
+ xml: source.xml,
723
+ previousId: id,
724
+ nextId
725
+ }),
726
+ rId: nextId
727
+ };
728
+ resource.image.hlinkRId = nextId;
729
+ delete resource.drawing.rawXml;
730
+ delete resource.drawing.rawImageFingerprint;
731
+ break;
732
+ }
733
+ default: panic(`Unsupported hyperlink resource ${JSON.stringify(resource)}`);
734
+ }
735
+ }
736
+ if (relEntries.length > 0) {
737
+ (root.elements ??= []).push(...relEntries);
738
+ writeZipPart({
739
+ zip,
740
+ path: relsPath,
741
+ data: captureVerbatimXml(root),
742
+ options: {
743
+ compression: "DEFLATE",
744
+ compressionOptions: { level: compressionLevel }
745
+ }
746
+ });
515
747
  }
516
- zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
517
- compression: "DEFLATE",
518
- compressionOptions: { level: compressionLevel }
519
- });
520
748
  }
521
749
  }
522
750
  /**
@@ -541,9 +769,14 @@ const normalizePackageIdsInZip = async (zip, compressionLevel) => {
541
769
  const normalizedParts = normalizeParaIdRangeInXmlParts(normalizeRevisionIdsInXmlParts(xmlParts));
542
770
  for (const [path, xml] of normalizedParts) if (xml !== xmlParts.get(path)) {
543
771
  assertValidOoxmlNumericIds(xml, path);
544
- zip.file(path, xml, {
545
- compression: "DEFLATE",
546
- compressionOptions: { level: compressionLevel }
772
+ writeZipPart({
773
+ zip,
774
+ path,
775
+ data: xml,
776
+ options: {
777
+ compression: "DEFLATE",
778
+ compressionOptions: { level: compressionLevel }
779
+ }
547
780
  });
548
781
  }
549
782
  };
@@ -558,9 +791,14 @@ const normalizeAppVersionInZip = async (zip, compressionLevel) => {
558
791
  if (!extendedProperties) return;
559
792
  const xml = await extendedProperties.async("text");
560
793
  const normalized = normalizeAppVersionInExtendedProperties(xml);
561
- if (normalized !== xml) zip.file(EXTENDED_PROPERTIES_PATH, normalized, {
562
- compression: "DEFLATE",
563
- compressionOptions: { level: compressionLevel }
794
+ if (normalized !== xml) writeZipPart({
795
+ zip,
796
+ path: EXTENDED_PROPERTIES_PATH,
797
+ data: normalized,
798
+ options: {
799
+ compression: "DEFLATE",
800
+ compressionOptions: { level: compressionLevel }
801
+ }
564
802
  });
565
803
  };
566
804
  /**
@@ -639,20 +877,37 @@ const normalizeExportDrawingIds = ({ package: docxPackage }) => {
639
877
  ...docxPackage.endnotes !== void 0 ? { endnotes: docxPackage.endnotes } : {}
640
878
  });
641
879
  };
642
- const finishRepack = async ({ document, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, onDiagnostic, sectionEndpointRemoval, sectionReferenceRemovals }) => {
643
- document = await materializeNewHeaderFooterParts(document, outputZip, compressionLevel);
880
+ /** Keep export mutations off authority while retaining the source-buffer cache key. */
881
+ const cloneExportDocument = (sourceDocument) => {
882
+ const { originalBuffer, ...graph } = sourceDocument;
883
+ const document = cloneDocumentWithParagraphPropertySources(graph);
884
+ if (originalBuffer !== void 0) document.originalBuffer = originalBuffer;
885
+ copyDocumentParagraphPropertySourceContract(document, sourceDocument);
886
+ return document;
887
+ };
888
+ const finishRepack = async ({ document: sourceDocument, originalZip, outputZip, originalDocument, originalCorePropertiesXml, compressionLevel, updateModifiedDate, modifiedBy, changedNoteParaIds, onDiagnostic, bodyAuthority, sectionEndpointRemoval, sectionReferenceRemovals }) => {
889
+ const document = await materializeNewHeaderFooterParts(cloneExportDocument(sourceDocument), outputZip, compressionLevel);
644
890
  await materializeEmbeddedMedia({
645
891
  document,
646
892
  zip: outputZip,
647
893
  compressionLevel
648
894
  });
895
+ if (bodyAuthority === "canonical") await publishCanonicalImageResources({
896
+ document,
897
+ zip: outputZip,
898
+ compressionLevel
899
+ });
649
900
  const parts = collectDocxParts(document, outputZip);
650
901
  normalizeExportDrawingIds(document);
651
902
  await processNewImages(parts, outputZip, compressionLevel);
652
903
  await processNewHyperlinks(parts, outputZip, compressionLevel);
653
904
  assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
654
905
  applyReplyThreadMarkers(document);
655
- const documentXml = serializeDocument(document, originalDocument === void 0 ? void 0 : readRootNamespaceBindings(originalDocument.xml));
906
+ const documentXml = serializeDocument(document, originalDocument === void 0 ? void 0 : readRootNamespaceBindings(originalDocument.xml), {
907
+ xml: originalDocument?.xml,
908
+ onDiagnostic,
909
+ bodyAuthority
910
+ });
656
911
  if (originalDocument?.xml) assertDocumentPackageFidelity({
657
912
  originalDocumentFacts: originalDocument.sectionFacts(),
658
913
  serializedDocumentXml: documentXml,
@@ -666,11 +921,21 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
666
921
  removedReferences: [...sectionReferenceRemovals ?? [], ...sectionEndpointRemoval?.removedReferences ?? []],
667
922
  compressionLevel
668
923
  });
669
- outputZip.file("word/document.xml", documentXml, {
670
- compression: "DEFLATE",
671
- compressionOptions: { level: compressionLevel }
924
+ writeZipPart({
925
+ zip: outputZip,
926
+ path: "word/document.xml",
927
+ data: documentXml,
928
+ options: {
929
+ compression: "DEFLATE",
930
+ compressionOptions: { level: compressionLevel }
931
+ }
932
+ });
933
+ await rebindWatermarkRelIds({
934
+ document,
935
+ zip: outputZip,
936
+ compressionLevel,
937
+ bodyAuthority
672
938
  });
673
- await rebindWatermarkRelIds(document, outputZip, compressionLevel);
674
939
  await serializeHeadersFootersToZip({
675
940
  doc: document,
676
941
  zip: outputZip,
@@ -689,16 +954,18 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
689
954
  await serializeHeaderFooterSettingsIntoZip(document, outputZip, compressionLevel);
690
955
  await serializeCommentsToZip(document, outputZip, compressionLevel);
691
956
  await dropAttachedTemplateReference(outputZip, compressionLevel);
692
- if (updateModifiedDate && originalCorePropertiesXml) {
693
- const updatedCoreProperties = updateCoreProperties(originalCorePropertiesXml, {
957
+ if (updateModifiedDate && originalCorePropertiesXml) writeZipPart({
958
+ zip: outputZip,
959
+ path: "docProps/core.xml",
960
+ data: updateCoreProperties(originalCorePropertiesXml, {
694
961
  updateModifiedDate,
695
962
  ...modifiedBy !== void 0 ? { modifiedBy } : {}
696
- });
697
- outputZip.file("docProps/core.xml", updatedCoreProperties, {
963
+ }),
964
+ options: {
698
965
  compression: "DEFLATE",
699
966
  compressionOptions: { level: compressionLevel }
700
- });
701
- }
967
+ }
968
+ });
702
969
  return generateDocxZip(outputZip, compressionLevel);
703
970
  };
704
971
  async function repackDocxWithSectionEndpointRemoval({ document: doc, options, sectionEndpointRemoval, sectionReferenceRemovals }) {
@@ -718,6 +985,7 @@ async function repackDocxWithSectionEndpointRemoval({ document: doc, options, se
718
985
  compressionLevel,
719
986
  updateModifiedDate,
720
987
  onDiagnostic: options.onDiagnostic,
988
+ bodyAuthority: options.bodyAuthority,
721
989
  ...modifiedBy !== void 0 ? { modifiedBy } : {},
722
990
  ...changedNoteParaIds !== void 0 ? { changedNoteParaIds } : {},
723
991
  ...sectionEndpointRemoval !== void 0 ? { sectionEndpointRemoval } : {},
@@ -744,18 +1012,30 @@ function repackDocx(doc, options = {}) {
744
1012
  */
745
1013
  async function repackDocxFromRaw(doc, rawContent, options = {}) {
746
1014
  const { compressionLevel = 6, updateModifiedDate = true, modifiedBy, changedNoteParaIds } = options;
747
- let exportDocument = withoutOrphanCommentRanges(doc);
1015
+ let exportDocument = withoutOrphanCommentRanges(cloneExportDocument(doc));
748
1016
  const newZip = new JSZip();
749
1017
  for (const [path, file] of Object.entries(rawContent.originalZip.files)) {
750
1018
  if (file.dir) {
751
- newZip.folder(path.replace(/\/$/u, ""));
1019
+ writeZipPart({
1020
+ zip: newZip,
1021
+ path,
1022
+ data: "",
1023
+ options: {
1024
+ dir: true,
1025
+ date: file.date
1026
+ }
1027
+ });
752
1028
  continue;
753
1029
  }
754
1030
  if (isUnsafePackagePath(path)) continue;
755
- const content = await file.async("arraybuffer");
756
- newZip.file(path, content, {
757
- compression: "DEFLATE",
758
- compressionOptions: { level: compressionLevel }
1031
+ writeZipPart({
1032
+ zip: newZip,
1033
+ path,
1034
+ data: await file.async("arraybuffer"),
1035
+ options: {
1036
+ compression: "DEFLATE",
1037
+ compressionOptions: { level: compressionLevel }
1038
+ }
759
1039
  });
760
1040
  }
761
1041
  exportDocument = await materializeNewHeaderFooterParts(exportDocument, newZip, compressionLevel);
@@ -764,23 +1044,42 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
764
1044
  zip: newZip,
765
1045
  compressionLevel
766
1046
  });
1047
+ if (options.bodyAuthority === "canonical") await publishCanonicalImageResources({
1048
+ document: exportDocument,
1049
+ zip: newZip,
1050
+ compressionLevel
1051
+ });
767
1052
  const parts = collectDocxParts(exportDocument, newZip);
768
1053
  normalizeExportDrawingIds(exportDocument);
769
1054
  await processNewImages(parts, newZip, compressionLevel);
770
1055
  await processNewHyperlinks(parts, newZip, compressionLevel);
771
1056
  assertValidFolioDocumentModel(exportDocument, "Cannot repack invalid DOCX document model");
772
1057
  applyReplyThreadMarkers(exportDocument);
773
- const documentXml = serializeDocument(exportDocument, rawContent.documentXml ? readRootNamespaceBindings(rawContent.documentXml) : void 0);
1058
+ const documentXml = serializeDocument(exportDocument, rawContent.documentXml ? readRootNamespaceBindings(rawContent.documentXml) : void 0, {
1059
+ xml: rawContent.documentXml ?? void 0,
1060
+ onDiagnostic: options.onDiagnostic,
1061
+ bodyAuthority: options.bodyAuthority
1062
+ });
774
1063
  if (rawContent.documentXml) assertDocumentPackageFidelity({
775
1064
  originalDocumentFacts: readDocumentSectionFacts(rawContent.documentXml),
776
1065
  serializedDocumentXml: documentXml,
777
1066
  doc: exportDocument
778
1067
  });
779
- newZip.file("word/document.xml", documentXml, {
780
- compression: "DEFLATE",
781
- compressionOptions: { level: compressionLevel }
1068
+ writeZipPart({
1069
+ zip: newZip,
1070
+ path: "word/document.xml",
1071
+ data: documentXml,
1072
+ options: {
1073
+ compression: "DEFLATE",
1074
+ compressionOptions: { level: compressionLevel }
1075
+ }
1076
+ });
1077
+ await rebindWatermarkRelIds({
1078
+ document: exportDocument,
1079
+ zip: newZip,
1080
+ compressionLevel,
1081
+ bodyAuthority: options.bodyAuthority
782
1082
  });
783
- await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
784
1083
  await serializeHeadersFootersToZip({
785
1084
  doc: exportDocument,
786
1085
  zip: newZip,
@@ -799,16 +1098,18 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
799
1098
  await serializeAddedStylesIntoZip(exportDocument, rawContent.originalZip, newZip, compressionLevel);
800
1099
  await serializeCommentsToZip(exportDocument, newZip, compressionLevel);
801
1100
  await dropAttachedTemplateReference(newZip, compressionLevel);
802
- if (updateModifiedDate && rawContent.corePropsXml) {
803
- const updatedCoreProps = updateCoreProperties(rawContent.corePropsXml, {
1101
+ if (updateModifiedDate && rawContent.corePropsXml) writeZipPart({
1102
+ zip: newZip,
1103
+ path: "docProps/core.xml",
1104
+ data: updateCoreProperties(rawContent.corePropsXml, {
804
1105
  updateModifiedDate,
805
1106
  ...modifiedBy !== void 0 ? { modifiedBy } : {}
806
- });
807
- newZip.file("docProps/core.xml", updatedCoreProps, {
1107
+ }),
1108
+ options: {
808
1109
  compression: "DEFLATE",
809
1110
  compressionOptions: { level: compressionLevel }
810
- });
811
- }
1111
+ }
1112
+ });
812
1113
  return generateDocxZip(newZip, compressionLevel);
813
1114
  }
814
1115
  const COMMENTS_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml";
@@ -897,13 +1198,23 @@ async function dropAttachedTemplateReference(zip, compressionLevel) {
897
1198
  if (!settingsFile) return;
898
1199
  const relsFile = zip.file(SETTINGS_RELS_PART);
899
1200
  const filtered = withoutAttachedTemplate(await settingsFile.async("text"), await relsFile?.async("text"));
900
- if (filtered.settingsXml !== void 0) zip.file(SETTINGS_PART, filtered.settingsXml, {
901
- compression: "DEFLATE",
902
- compressionOptions: { level: compressionLevel }
1201
+ if (filtered.settingsXml !== void 0) writeZipPart({
1202
+ zip,
1203
+ path: SETTINGS_PART,
1204
+ data: filtered.settingsXml,
1205
+ options: {
1206
+ compression: "DEFLATE",
1207
+ compressionOptions: { level: compressionLevel }
1208
+ }
903
1209
  });
904
- if (filtered.relsXml !== void 0) zip.file(SETTINGS_RELS_PART, filtered.relsXml, {
905
- compression: "DEFLATE",
906
- compressionOptions: { level: compressionLevel }
1210
+ if (filtered.relsXml !== void 0) writeZipPart({
1211
+ zip,
1212
+ path: SETTINGS_RELS_PART,
1213
+ data: filtered.relsXml,
1214
+ options: {
1215
+ compression: "DEFLATE",
1216
+ compressionOptions: { level: compressionLevel }
1217
+ }
907
1218
  });
908
1219
  }
909
1220
  /**
@@ -914,11 +1225,17 @@ async function ensureCommentsContentType(zip, compressionLevel) {
914
1225
  const ctFile = zip.file("[Content_Types].xml");
915
1226
  if (!ctFile) return;
916
1227
  let ctXml = await ctFile.async("text");
917
- if (ctXml.includes("/word/comments.xml")) return;
918
- ctXml = ctXml.replace("</Types>", `<Override PartName="/word/comments.xml" ContentType="${COMMENTS_CONTENT_TYPE}"/></Types>`);
919
- zip.file("[Content_Types].xml", ctXml, {
920
- compression: "DEFLATE",
921
- compressionOptions: { level: compressionLevel }
1228
+ if (ctXml.toLowerCase().includes("/word/comments.xml")) return;
1229
+ const commentsPartPath = findZipEntryCaseInsensitive(zip, "word/comments.xml")?.name ?? "word/comments.xml";
1230
+ ctXml = ctXml.replace("</Types>", `<Override PartName="/${commentsPartPath}" ContentType="${COMMENTS_CONTENT_TYPE}"/></Types>`);
1231
+ writeZipPart({
1232
+ zip,
1233
+ path: "[Content_Types].xml",
1234
+ data: ctXml,
1235
+ options: {
1236
+ compression: "DEFLATE",
1237
+ compressionOptions: { level: compressionLevel }
1238
+ }
922
1239
  });
923
1240
  }
924
1241
  /**
@@ -930,12 +1247,18 @@ async function ensureCommentsRelationship(zip, compressionLevel) {
930
1247
  const relsFile = zip.file(relsPath);
931
1248
  if (!relsFile) return;
932
1249
  let relsXml = await relsFile.async("text");
933
- if (relsXml.includes("comments.xml")) return;
1250
+ if (relsXml.toLowerCase().includes("comments.xml")) return;
1251
+ const commentsPartPath = findZipEntryCaseInsensitive(zip, "word/comments.xml")?.name ?? "word/comments.xml";
934
1252
  const newRId = `rId${findMaxRId(relsXml) + 1}`;
935
- relsXml = relsXml.replace("</Relationships>", `<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.comments}" Target="comments.xml"/></Relationships>`);
936
- zip.file(relsPath, relsXml, {
937
- compression: "DEFLATE",
938
- compressionOptions: { level: compressionLevel }
1253
+ relsXml = relsXml.replace("</Relationships>", `<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.comments}" Target="${commentsPartPath.slice(5)}"/></Relationships>`);
1254
+ writeZipPart({
1255
+ zip,
1256
+ path: relsPath,
1257
+ data: relsXml,
1258
+ options: {
1259
+ compression: "DEFLATE",
1260
+ compressionOptions: { level: compressionLevel }
1261
+ }
939
1262
  });
940
1263
  }
941
1264
  /**
@@ -949,9 +1272,14 @@ async function ensureCommentsRelationship(zip, compressionLevel) {
949
1272
  async function updateDocumentXml(originalBuffer, newDocumentXml, options = {}) {
950
1273
  const { compressionLevel = 6 } = options;
951
1274
  const zip = await JSZip.loadAsync(originalBuffer);
952
- zip.file("word/document.xml", newDocumentXml, {
953
- compression: "DEFLATE",
954
- compressionOptions: { level: compressionLevel }
1275
+ writeZipPart({
1276
+ zip,
1277
+ path: "word/document.xml",
1278
+ data: newDocumentXml,
1279
+ options: {
1280
+ compression: "DEFLATE",
1281
+ compressionOptions: { level: compressionLevel }
1282
+ }
955
1283
  });
956
1284
  return zip.generateAsync({
957
1285
  type: "arraybuffer",
@@ -971,9 +1299,14 @@ async function updateDocumentXml(originalBuffer, newDocumentXml, options = {}) {
971
1299
  async function updateXmlFile(originalBuffer, path, content, options = {}) {
972
1300
  const { compressionLevel = 6 } = options;
973
1301
  const zip = await JSZip.loadAsync(originalBuffer);
974
- zip.file(path, content, {
975
- compression: "DEFLATE",
976
- compressionOptions: { level: compressionLevel }
1302
+ writeZipPart({
1303
+ zip,
1304
+ path,
1305
+ data: content,
1306
+ options: {
1307
+ compression: "DEFLATE",
1308
+ compressionOptions: { level: compressionLevel }
1309
+ }
977
1310
  });
978
1311
  return zip.generateAsync({
979
1312
  type: "arraybuffer",
@@ -1004,9 +1337,14 @@ async function updateMultipleFiles(originalBuffer, updates, options = {}) {
1004
1337
  */
1005
1338
  async function applyUpdatesToZip(zip, updates, options = {}) {
1006
1339
  const { compressionLevel = 6 } = options;
1007
- for (const [path, content] of updates) zip.file(path, content, {
1008
- compression: "DEFLATE",
1009
- compressionOptions: { level: compressionLevel }
1340
+ for (const [path, content] of updates) writeZipPart({
1341
+ zip,
1342
+ path,
1343
+ data: content,
1344
+ options: {
1345
+ compression: "DEFLATE",
1346
+ compressionOptions: { level: compressionLevel }
1347
+ }
1010
1348
  });
1011
1349
  await normalizePackageOnSave(zip, compressionLevel);
1012
1350
  return await zip.generateAsync({
@@ -1031,8 +1369,11 @@ async function addRelationship(originalBuffer, relationship) {
1031
1369
  const newRId = `rId${findMaxRId(relsXml) + 1}`;
1032
1370
  const targetModeAttr = relationship.targetMode === "External" ? " TargetMode=\"External\"" : "";
1033
1371
  const newRelElement = `<Relationship Id="${newRId}" Type="${relationship.type}" Target="${escapeXmlAttribute(relationship.target)}"${targetModeAttr}/>`;
1034
- const updatedRelsXml = relsXml.replace("</Relationships>", `${newRelElement}</Relationships>`);
1035
- zip.file(relsPath, updatedRelsXml);
1372
+ writeZipPart({
1373
+ zip,
1374
+ path: relsPath,
1375
+ data: relsXml.replace("</Relationships>", `${newRelElement}</Relationships>`)
1376
+ });
1036
1377
  return {
1037
1378
  buffer: await zip.generateAsync({
1038
1379
  type: "arraybuffer",
@@ -1054,7 +1395,11 @@ async function addRelationship(originalBuffer, relationship) {
1054
1395
  async function addMedia(originalBuffer, filename, data, mimeType) {
1055
1396
  const zip = await JSZip.loadAsync(originalBuffer);
1056
1397
  const mediaPath = `word/media/${filename}`;
1057
- zip.file(mediaPath, data);
1398
+ writeZipPart({
1399
+ zip,
1400
+ path: mediaPath,
1401
+ data
1402
+ });
1058
1403
  const relResult = await addRelationship(await zip.generateAsync({ type: "arraybuffer" }), {
1059
1404
  type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
1060
1405
  target: `media/${filename}`
@@ -1067,7 +1412,11 @@ async function addMedia(originalBuffer, filename, data, mimeType) {
1067
1412
  const extensionElement = `<Default Extension="${extension}" ContentType="${getContentTypeForExtension(extension, mimeType)}"/>`;
1068
1413
  const updatedContentTypes = contentTypesXml.replace("</Types>", `${extensionElement}</Types>`);
1069
1414
  const finalZip = await JSZip.loadAsync(relResult.buffer);
1070
- finalZip.file("[Content_Types].xml", updatedContentTypes);
1415
+ writeZipPart({
1416
+ zip: finalZip,
1417
+ path: "[Content_Types].xml",
1418
+ data: updatedContentTypes
1419
+ });
1071
1420
  return {
1072
1421
  buffer: await finalZip.generateAsync({
1073
1422
  type: "arraybuffer",
@@ -1213,17 +1562,27 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
1213
1562
  materialize(doc.package.headers, RELATIONSHIP_TYPES.header, "header", HEADER_CONTENT_TYPE, true);
1214
1563
  materialize(doc.package.footers, RELATIONSHIP_TYPES.footer, "footer", FOOTER_CONTENT_TYPE, false);
1215
1564
  const compressionOptions = { level: compressionLevel };
1216
- if (relEntries.length > 0) zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
1217
- compression: "DEFLATE",
1218
- compressionOptions
1565
+ if (relEntries.length > 0) writeZipPart({
1566
+ zip,
1567
+ path: relsPath,
1568
+ data: relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`),
1569
+ options: {
1570
+ compression: "DEFLATE",
1571
+ compressionOptions
1572
+ }
1219
1573
  });
1220
1574
  const ctFile = zip.file("[Content_Types].xml");
1221
1575
  if (ctFile) {
1222
1576
  const original = await ctFile.async("text");
1223
1577
  const updated = registerContentTypeParts(original, contentTypeParts);
1224
- if (updated !== original) zip.file("[Content_Types].xml", updated, {
1225
- compression: "DEFLATE",
1226
- compressionOptions
1578
+ if (updated !== original) writeZipPart({
1579
+ zip,
1580
+ path: "[Content_Types].xml",
1581
+ data: updated,
1582
+ options: {
1583
+ compression: "DEFLATE",
1584
+ compressionOptions
1585
+ }
1227
1586
  });
1228
1587
  }
1229
1588
  return doc;
@@ -1242,25 +1601,46 @@ function hasModelDrivenPictureWatermark(doc) {
1242
1601
  for (const hf of headers.values()) if (hf.watermark?.kind === "picture" && !hf.rawWatermarkXml) return true;
1243
1602
  return false;
1244
1603
  }
1245
- /**
1246
- * Rebind each picture watermark's `imageRId` so it resolves in its own header
1247
- * part's rels. A watermark propagated across headers (or onto a header created
1248
- * by coverage) carries the source header's rId, which is meaningless in a
1249
- * sibling header's `word/_rels/header*.xml.rels`. Per header: keep the rId if
1250
- * it already resolves; otherwise reuse an existing relationship to the same
1251
- * media target, or mint a new one. Media bytes are materialized before this step. Raw watermark XML
1252
- * changes only when its image relationship must be rebound.
1253
- */
1254
- async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1604
+ const appendCanonicalRelationship = ({ xml, relationshipXml, partPath }) => {
1605
+ const parsed = parseStreamingXmlWithSourceRanges(xml);
1606
+ if (parsed.status !== "parsed") throw new DocxPackageFidelityError(`Cannot read canonical relationships in ${partPath}`);
1607
+ const root = getChildElements(parsed.value).find((element) => getLocalName(element.name ?? "") === "Relationships" && getNamespaceUri(element) === PACKAGE_RELATIONSHIPS_NAMESPACE_URI);
1608
+ const range = root ? getXmlSourceRange(root) : void 0;
1609
+ if (!root?.name || !range) throw new DocxPackageFidelityError(`Cannot find canonical relationship root in ${partPath}`);
1610
+ const sourceRoot = xml.slice(range.start, range.end);
1611
+ const bound = relationshipXml.replace("<Relationship ", `<Relationship xmlns="${PACKAGE_RELATIONSHIPS_NAMESPACE_URI}" `);
1612
+ const selfClosing = sourceRoot.endsWith("/>");
1613
+ const close = xml.lastIndexOf("</", range.end);
1614
+ if (!selfClosing && close < range.start) throw new DocxPackageFidelityError(`Cannot extend canonical relationships in ${partPath}`);
1615
+ const result = spliceXml(xml, [selfClosing ? {
1616
+ start: range.end - 2,
1617
+ end: range.end,
1618
+ newXml: ">" + bound + `</${root.name}>`
1619
+ } : {
1620
+ start: close,
1621
+ end: close,
1622
+ newXml: bound
1623
+ }]);
1624
+ if (result === null) throw new DocxPackageFidelityError(`Cannot splice canonical relationships in ${partPath}`);
1625
+ return result;
1626
+ };
1627
+ async function rebindWatermarkRelIds({ document: doc, zip, compressionLevel, bodyAuthority }) {
1255
1628
  const rels = doc.package.relationships;
1256
1629
  const headers = doc.package.headers;
1257
- if (!rels || !headers) return;
1630
+ if (!headers) return;
1631
+ if (!rels) {
1632
+ if (bodyAuthority === "canonical" && [...headers.values()].some(({ watermark }) => watermark?.kind === "picture")) throw new DocxPackageFidelityError("A canonical picture watermark has no header relationship map");
1633
+ return;
1634
+ }
1258
1635
  const pending = [];
1259
1636
  for (const [rId, hf] of headers) {
1260
1637
  const watermark = hf.watermark;
1261
1638
  if (!watermark || watermark.kind !== "picture") continue;
1262
1639
  const rel = rels.get(rId);
1263
- if (!rel?.target) continue;
1640
+ if (!rel?.target) {
1641
+ if (bodyAuthority === "canonical") throw new DocxPackageFidelityError(`A canonical picture watermark has no header target for ${rId}`);
1642
+ continue;
1643
+ }
1264
1644
  pending.push({
1265
1645
  watermark,
1266
1646
  header: hf,
@@ -1293,20 +1673,37 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1293
1673
  const changedPaths = /* @__PURE__ */ new Set();
1294
1674
  for (const { watermark, header, relsPath, partPath } of pending) {
1295
1675
  let canonical;
1296
- if (watermark.imageTarget !== void 0) if (watermark.imageTargetExternal) canonical = isAllowedExternalWatermarkImageUrl(watermark.imageTarget) ? {
1297
- mode: "external",
1298
- url: watermark.imageTarget
1299
- } : resolveCanonical(watermark.imageRId);
1300
- else canonical = {
1676
+ if (watermark.imageTarget !== void 0) if (watermark.imageTargetExternal) {
1677
+ if (bodyAuthority === "canonical" && !isAllowedExternalWatermarkImageUrl(watermark.imageTarget)) throw new DocxPackageFidelityError("A canonical picture watermark has an unsafe external target");
1678
+ canonical = isAllowedExternalWatermarkImageUrl(watermark.imageTarget) ? {
1679
+ mode: "external",
1680
+ url: watermark.imageTarget
1681
+ } : resolveCanonical(watermark.imageRId);
1682
+ } else canonical = {
1301
1683
  mode: "internal",
1302
1684
  absolute: watermark.imageTarget
1303
1685
  };
1304
1686
  else canonical = resolveCanonical(watermark.imageRId);
1305
- if (!canonical) continue;
1687
+ if (!canonical) {
1688
+ if (bodyAuthority === "canonical") throw new DocxPackageFidelityError("A canonical picture watermark image cannot be resolved");
1689
+ continue;
1690
+ }
1691
+ if (bodyAuthority === "canonical" && canonical.mode === "internal" && !zip.file(canonical.absolute)) throw new DocxPackageFidelityError("A canonical picture watermark image part is missing");
1306
1692
  const relsXml = relsXmlByPath.get(relsPath) ?? EMPTY_RELS_XML;
1307
1693
  const localRels = parseRelationships(relsXml);
1308
1694
  const local = relImage(localRels.get(watermark.imageRId), relsPath);
1309
1695
  if (local && sameImage(local, canonical)) continue;
1696
+ if (bodyAuthority === "canonical") {
1697
+ if (localRels.has(watermark.imageRId)) throw new DocxPackageFidelityError(`A canonical picture watermark relationship ${watermark.imageRId} has a conflicting target`);
1698
+ const relXml = canonical.mode === "external" ? `<Relationship Id="${escapeXmlAttribute(watermark.imageRId)}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXmlAttribute(canonical.url)}" TargetMode="External"/>` : `<Relationship Id="${escapeXmlAttribute(watermark.imageRId)}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXmlAttribute(relativeTargetForPart(partPath, canonical.absolute))}"/>`;
1699
+ relsXmlByPath.set(relsPath, appendCanonicalRelationship({
1700
+ xml: relsXml,
1701
+ relationshipXml: relXml,
1702
+ partPath: relsPath
1703
+ }));
1704
+ changedPaths.add(relsPath);
1705
+ continue;
1706
+ }
1310
1707
  let resolvedRId;
1311
1708
  for (const [id, rel] of localRels) {
1312
1709
  const image = relImage(rel, relsPath);
@@ -1331,9 +1728,14 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
1331
1728
  const compressionOptions = { level: compressionLevel };
1332
1729
  for (const path of changedPaths) {
1333
1730
  const xml = relsXmlByPath.get(path);
1334
- if (xml) zip.file(path, xml, {
1335
- compression: "DEFLATE",
1336
- compressionOptions
1731
+ if (xml) writeZipPart({
1732
+ zip,
1733
+ path,
1734
+ data: xml,
1735
+ options: {
1736
+ compression: "DEFLATE",
1737
+ compressionOptions
1738
+ }
1337
1739
  });
1338
1740
  }
1339
1741
  }
@@ -1373,9 +1775,14 @@ async function serializeHeadersFootersToZip({ doc, zip, compressionLevel, onDiag
1373
1775
  for (const [filename, xml] of await collectHeaderFooterUpdates(doc, {
1374
1776
  sourceZip: zip,
1375
1777
  onDiagnostic
1376
- })) zip.file(filename, xml, {
1377
- compression: "DEFLATE",
1378
- compressionOptions
1778
+ })) writeZipPart({
1779
+ zip,
1780
+ path: filename,
1781
+ data: xml,
1782
+ options: {
1783
+ compression: "DEFLATE",
1784
+ compressionOptions
1785
+ }
1379
1786
  });
1380
1787
  }
1381
1788
  async function serializeNotesToZip({ doc, originalZip, newZip, compressionLevel, changedNoteParaIds }) {
@@ -1429,22 +1836,37 @@ async function materializeNewNotePart({ contentType, newZip, partPath, relations
1429
1836
  const compressionOptions = { level: compressionLevel };
1430
1837
  const contentTypesXml = await contentTypesFile.async("text");
1431
1838
  const partName = `/${partPath}`;
1432
- if (!contentTypesXml.toLowerCase().includes(partName.toLowerCase())) newZip.file(contentTypesFile.name, contentTypesXml.replace("</Types>", () => `<Override PartName="${partName}" ContentType="${contentType}"/></Types>`), {
1433
- compression: "DEFLATE",
1434
- compressionOptions
1839
+ if (!contentTypesXml.toLowerCase().includes(partName.toLowerCase())) writeZipPart({
1840
+ zip: newZip,
1841
+ path: contentTypesFile.name,
1842
+ data: contentTypesXml.replace("</Types>", () => `<Override PartName="${partName}" ContentType="${contentType}"/></Types>`),
1843
+ options: {
1844
+ compression: "DEFLATE",
1845
+ compressionOptions
1846
+ }
1435
1847
  });
1436
1848
  const relationshipsXml = await relationshipsFile.async("text");
1437
1849
  if (!relationshipsXml.includes(relationshipType)) {
1438
1850
  const relationshipId = `rId${findMaxRId(relationshipsXml) + 1}`;
1439
1851
  const target = partPath.slice(5);
1440
- newZip.file(relationshipsFile.name, relationshipsXml.replace("</Relationships>", () => `<Relationship Id="${relationshipId}" Type="${relationshipType}" Target="${target}"/></Relationships>`), {
1441
- compression: "DEFLATE",
1442
- compressionOptions
1852
+ writeZipPart({
1853
+ zip: newZip,
1854
+ path: relationshipsFile.name,
1855
+ data: relationshipsXml.replace("</Relationships>", () => `<Relationship Id="${relationshipId}" Type="${relationshipType}" Target="${target}"/></Relationships>`),
1856
+ options: {
1857
+ compression: "DEFLATE",
1858
+ compressionOptions
1859
+ }
1443
1860
  });
1444
1861
  }
1445
- newZip.file(partPath, serializedPart, {
1446
- compression: "DEFLATE",
1447
- compressionOptions
1862
+ writeZipPart({
1863
+ zip: newZip,
1864
+ path: partPath,
1865
+ data: serializedPart,
1866
+ options: {
1867
+ compression: "DEFLATE",
1868
+ compressionOptions
1869
+ }
1448
1870
  });
1449
1871
  }
1450
1872
  /** Only this modeled switch changes on story lifecycle edits; preserve all other settings. */
@@ -1468,9 +1890,14 @@ async function serializeHeaderFooterSettingsIntoZip(doc, zip, compressionLevel)
1468
1890
  if (parseSettings(xml).evenAndOddHeaders === enabled) return;
1469
1891
  const patched = updateEvenAndOddHeaders(xml, enabled);
1470
1892
  if (patched === null) throw new DocxPackageFidelityError("Cannot update malformed header/footer settings");
1471
- zip.file(file.name, patched, {
1472
- compression: "DEFLATE",
1473
- compressionOptions: { level: compressionLevel }
1893
+ writeZipPart({
1894
+ zip,
1895
+ path: file.name,
1896
+ data: patched,
1897
+ options: {
1898
+ compression: "DEFLATE",
1899
+ compressionOptions: { level: compressionLevel }
1900
+ }
1474
1901
  });
1475
1902
  }
1476
1903
  const numberingBaselines = /* @__PURE__ */ new WeakMap();
@@ -1506,9 +1933,14 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
1506
1933
  currentXml
1507
1934
  });
1508
1935
  if (patched === null || patched === baseline.originalXml) return;
1509
- newZip.file(file.name, patched, {
1510
- compression: "DEFLATE",
1511
- compressionOptions: { level: compressionLevel }
1936
+ writeZipPart({
1937
+ zip: newZip,
1938
+ path: file.name,
1939
+ data: patched,
1940
+ options: {
1941
+ compression: "DEFLATE",
1942
+ compressionOptions: { level: compressionLevel }
1943
+ }
1512
1944
  });
1513
1945
  }
1514
1946
  const STYLES_PART_PATH = "word/styles.xml";
@@ -1616,24 +2048,18 @@ const styleDefinitionsToSerialize = (doc) => {
1616
2048
  styles: [...styles.styles, ...missing]
1617
2049
  };
1618
2050
  };
1619
- async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compressionLevel) {
2051
+ /** One append-only style owner for full repack and selective overlays. */
2052
+ async function planAddedStyles(doc, originalZip) {
1620
2053
  const styles = doc.package.styles;
1621
- if (!styles || styles.styles.length === 0) return;
2054
+ if (!styles || styles.styles.length === 0) return { type: "unchanged" };
1622
2055
  const file = findNotePartEntry(originalZip, STYLES_PART_PATH);
1623
2056
  const originalXml = file ? await file.async("text") : null;
1624
2057
  const rootName = originalXml === null ? void 0 : /<(?![?!])(?<name>[^\s/>]+)/u.exec(originalXml)?.groups?.["name"];
1625
2058
  const rootClose = originalXml !== null && rootName !== void 0 && getLocalName(rootName) === "styles" ? originalXml.lastIndexOf(`</${rootName}>`) : -1;
1626
- if (file === null || originalXml === null || rootClose < 0) {
1627
- await materializeNewNotePart({
1628
- contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
1629
- newZip,
1630
- partPath: STYLES_PART_PATH,
1631
- relationshipType: RELATIONSHIP_TYPES.styles,
1632
- serializedPart: serializeStylesXml(styles),
1633
- compressionLevel
1634
- });
1635
- return;
1636
- }
2059
+ if (file === null || originalXml === null || rootClose < 0) return {
2060
+ type: "materialize",
2061
+ styles
2062
+ };
1637
2063
  const existing = new Set(definedStyleIds(originalXml));
1638
2064
  const added = [];
1639
2065
  for (const style of styles.styles) {
@@ -1641,13 +2067,42 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
1641
2067
  existing.add(style.styleId);
1642
2068
  added.push(style);
1643
2069
  }
1644
- if (added.length === 0) return;
2070
+ if (added.length === 0) return { type: "unchanged" };
1645
2071
  const canonical = hasCanonicalWordprocessingPrefixes(originalXml);
1646
2072
  const patched = originalXml.slice(0, rootClose) + added.map((style) => canonical ? serializeStyle(style) : selfBoundFragment(serializeStyle(style), file.name)).join("") + originalXml.slice(rootClose);
1647
- newZip.file(file.name, patched, {
1648
- compression: "DEFLATE",
1649
- compressionOptions: { level: compressionLevel }
1650
- });
2073
+ return {
2074
+ type: "patch",
2075
+ path: file.name,
2076
+ xml: patched
2077
+ };
2078
+ }
2079
+ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compressionLevel) {
2080
+ const plan = await planAddedStyles(doc, originalZip);
2081
+ switch (plan.type) {
2082
+ case "unchanged": return;
2083
+ case "materialize":
2084
+ await materializeNewNotePart({
2085
+ contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
2086
+ newZip,
2087
+ partPath: STYLES_PART_PATH,
2088
+ relationshipType: RELATIONSHIP_TYPES.styles,
2089
+ serializedPart: serializeStylesXml(plan.styles),
2090
+ compressionLevel
2091
+ });
2092
+ return;
2093
+ case "patch":
2094
+ writeZipPart({
2095
+ zip: newZip,
2096
+ path: plan.path,
2097
+ data: plan.xml,
2098
+ options: {
2099
+ compression: "DEFLATE",
2100
+ compressionOptions: { level: compressionLevel }
2101
+ }
2102
+ });
2103
+ return;
2104
+ default: return plan;
2105
+ }
1651
2106
  }
1652
2107
  /**
1653
2108
  * The XML to write for a patched note part, or null to keep the original.
@@ -1702,9 +2157,14 @@ async function patchNotePartIntoZip({ conventionalLowerPath, currentXml, replace
1702
2157
  partName: file.name
1703
2158
  });
1704
2159
  if (patchedXml === null || patchedXml === originalXml) return;
1705
- newZip.file(file.name, patchedXml, {
1706
- compression: "DEFLATE",
1707
- compressionOptions: { level: compressionLevel }
2160
+ writeZipPart({
2161
+ zip: newZip,
2162
+ path: file.name,
2163
+ data: patchedXml,
2164
+ options: {
2165
+ compression: "DEFLATE",
2166
+ compressionOptions: { level: compressionLevel }
2167
+ }
1708
2168
  });
1709
2169
  }
1710
2170
  /** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
@@ -1802,7 +2262,10 @@ function createEmptyDocx(properties = {}) {
1802
2262
  }
1803
2263
  const createEmptyDocxZip = ({ creator, application }) => {
1804
2264
  const zip = new JSZip();
1805
- zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2265
+ writeZipPart({
2266
+ zip,
2267
+ path: "[Content_Types].xml",
2268
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1806
2269
  <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
1807
2270
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
1808
2271
  <Default Extension="xml" ContentType="application/xml"/>
@@ -1810,18 +2273,30 @@ const createEmptyDocxZip = ({ creator, application }) => {
1810
2273
  <Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
1811
2274
  <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
1812
2275
  <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
1813
- </Types>`);
1814
- zip.file("_rels/.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2276
+ </Types>`
2277
+ });
2278
+ writeZipPart({
2279
+ zip,
2280
+ path: "_rels/.rels",
2281
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1815
2282
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
1816
2283
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
1817
2284
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
1818
2285
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
1819
- </Relationships>`);
1820
- zip.file("word/_rels/document.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2286
+ </Relationships>`
2287
+ });
2288
+ writeZipPart({
2289
+ zip,
2290
+ path: "word/_rels/document.xml.rels",
2291
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1821
2292
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
1822
2293
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
1823
- </Relationships>`);
1824
- zip.file("word/document.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2294
+ </Relationships>`
2295
+ });
2296
+ writeZipPart({
2297
+ zip,
2298
+ path: "word/document.xml",
2299
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1825
2300
  <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
1826
2301
  <w:body>
1827
2302
  <w:p>
@@ -1834,19 +2309,30 @@ const createEmptyDocxZip = ({ creator, application }) => {
1834
2309
  <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>
1835
2310
  </w:sectPr>
1836
2311
  </w:body>
1837
- </w:document>`);
1838
- zip.file(STYLES_PART_PATH, SEED_STYLES_XML);
2312
+ </w:document>`
2313
+ });
2314
+ writeZipPart({
2315
+ zip,
2316
+ path: STYLES_PART_PATH,
2317
+ data: SEED_STYLES_XML
2318
+ });
1839
2319
  const now = (/* @__PURE__ */ new Date()).toISOString();
1840
- const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${escapeXmlText(creator)}</dc:creator>`;
1841
- zip.file("docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1842
- <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${creatorElement}
2320
+ writeZipPart({
2321
+ zip,
2322
+ path: "docProps/core.xml",
2323
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2324
+ <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${creator === void 0 ? "" : `\n <dc:creator>${escapeXmlText(creator)}</dc:creator>`}
1843
2325
  <dcterms:created xsi:type="dcterms:W3CDTF">${now}</dcterms:created>
1844
2326
  <dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>
1845
- </cp:coreProperties>`);
1846
- const applicationElements = application === void 0 ? "" : `\n <Application>${escapeXmlText(application)}</Application>\n <AppVersion>${CREATED_APP_VERSION}</AppVersion>`;
1847
- zip.file("docProps/app.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
1848
- <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">${applicationElements}
1849
- </Properties>`);
2327
+ </cp:coreProperties>`
2328
+ });
2329
+ writeZipPart({
2330
+ zip,
2331
+ path: "docProps/app.xml",
2332
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2333
+ <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">${application === void 0 ? "" : `\n <Application>${escapeXmlText(application)}</Application>\n <AppVersion>${CREATED_APP_VERSION}</AppVersion>`}
2334
+ </Properties>`
2335
+ });
1850
2336
  return zip;
1851
2337
  };
1852
2338
  /**
@@ -1868,7 +2354,8 @@ async function createDocx(doc, properties = {}) {
1868
2354
  originalCorePropertiesXml: source.corePropertiesXml,
1869
2355
  compressionLevel: 6,
1870
2356
  updateModifiedDate: true,
1871
- onDiagnostic: properties.onDiagnostic
2357
+ onDiagnostic: properties.onDiagnostic,
2358
+ bodyAuthority: properties.bodyAuthority
1872
2359
  });
1873
2360
  }
1874
2361
  const zip = await createDocumentSeedZip(doc, properties);
@@ -1880,7 +2367,8 @@ async function createDocx(doc, properties = {}) {
1880
2367
  originalCorePropertiesXml: await zip.file("docProps/core.xml")?.async("text"),
1881
2368
  compressionLevel: 6,
1882
2369
  updateModifiedDate: true,
1883
- onDiagnostic: properties.onDiagnostic
2370
+ onDiagnostic: properties.onDiagnostic,
2371
+ bodyAuthority: properties.bodyAuthority
1884
2372
  });
1885
2373
  }
1886
2374
  const createDocumentSeedZip = async (doc, properties) => {
@@ -1891,38 +2379,69 @@ const createDocumentSeedZip = async (doc, properties) => {
1891
2379
  const styleDefinitions = styleDefinitionsToSerialize(doc);
1892
2380
  if (styleDefinitions) {
1893
2381
  assertStyleNumberingReferences(doc);
1894
- zip.file(STYLES_PART_PATH, serializeStylesXml(styleDefinitions));
1895
- } else zip.file(STYLES_PART_PATH, seedStylesXmlWith(missingNoteReferenceStyles(void 0, noteReferenceNeeds(doc.package))));
2382
+ writeZipPart({
2383
+ zip,
2384
+ path: STYLES_PART_PATH,
2385
+ data: serializeStylesXml(styleDefinitions)
2386
+ });
2387
+ } else writeZipPart({
2388
+ zip,
2389
+ path: STYLES_PART_PATH,
2390
+ data: seedStylesXmlWith(missingNoteReferenceStyles(void 0, noteReferenceNeeds(doc.package)))
2391
+ });
1896
2392
  const numbering = doc.package.numbering;
1897
2393
  if (numbering && (numbering.abstractNums.length > 0 || numbering.nums.length > 0)) {
1898
- zip.file("word/numbering.xml", serializeNumberingXml(numbering));
2394
+ writeZipPart({
2395
+ zip,
2396
+ path: "word/numbering.xml",
2397
+ data: serializeNumberingXml(numbering)
2398
+ });
1899
2399
  relationships.push(relationshipXml(nextRelationshipId, "numbering", "numbering.xml"));
1900
2400
  overrides.push(overrideXml("/word/numbering.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"));
1901
2401
  nextRelationshipId += 1;
1902
2402
  }
1903
2403
  if (doc.package.theme) {
1904
- zip.file("word/theme/theme1.xml", serializeThemeXml(doc.package.theme));
2404
+ writeZipPart({
2405
+ zip,
2406
+ path: "word/theme/theme1.xml",
2407
+ data: serializeThemeXml(doc.package.theme)
2408
+ });
1905
2409
  relationships.push(relationshipXml(nextRelationshipId, "theme", "theme/theme1.xml"));
1906
2410
  overrides.push(overrideXml("/word/theme/theme1.xml", "application/vnd.openxmlformats-officedocument.theme+xml"));
1907
2411
  nextRelationshipId += 1;
1908
2412
  }
1909
2413
  if (doc.package.fontTable && doc.package.fontTable.fonts.length > 0) {
1910
- zip.file("word/fontTable.xml", serializeFontTableXml(doc.package.fontTable));
2414
+ writeZipPart({
2415
+ zip,
2416
+ path: "word/fontTable.xml",
2417
+ data: serializeFontTableXml(doc.package.fontTable)
2418
+ });
1911
2419
  relationships.push(relationshipXml(nextRelationshipId, "fontTable", "fontTable.xml"));
1912
2420
  overrides.push(overrideXml("/word/fontTable.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"));
1913
2421
  nextRelationshipId += 1;
1914
2422
  }
1915
2423
  if (doc.package.settings) {
1916
- zip.file("word/settings.xml", serializeSettingsXml(doc.package.settings));
2424
+ writeZipPart({
2425
+ zip,
2426
+ path: "word/settings.xml",
2427
+ data: serializeSettingsXml(doc.package.settings)
2428
+ });
1917
2429
  relationships.push(relationshipXml(nextRelationshipId, "settings", "settings.xml"));
1918
2430
  overrides.push(overrideXml("/word/settings.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"));
1919
2431
  }
1920
- zip.file("word/_rels/document.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${relationships.join("")}</Relationships>`);
2432
+ writeZipPart({
2433
+ zip,
2434
+ path: "word/_rels/document.xml.rels",
2435
+ data: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${relationships.join("")}</Relationships>`
2436
+ });
1921
2437
  if (overrides.length > 0) {
1922
2438
  const contentTypesFile = zip.file("[Content_Types].xml");
1923
2439
  if (!contentTypesFile) panic("Fresh DOCX seed is missing [Content_Types].xml");
1924
- const contentTypes = await contentTypesFile.async("string");
1925
- zip.file("[Content_Types].xml", contentTypes.replace("</Types>", `${overrides.join("")}\n</Types>`));
2440
+ writeZipPart({
2441
+ zip,
2442
+ path: "[Content_Types].xml",
2443
+ data: (await contentTypesFile.async("string")).replace("</Types>", `${overrides.join("")}\n</Types>`)
2444
+ });
1926
2445
  }
1927
2446
  return zip;
1928
2447
  };
@@ -1956,4 +2475,4 @@ const assertStyleNumberingReferences = (doc) => {
1956
2475
  }
1957
2476
  };
1958
2477
  //#endregion
1959
- export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, DocxDuplicateSectionCarrierError, 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 };
2478
+ export { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_CONTENT_TYPE, COMMENTS_EXTENDED_PART, COMMENTS_EXTENDED_PART_LOWER, CanonicalImageRelationshipRefusalError, DocxDuplicateSectionCarrierError, DocxPackageFidelityError, 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 };