@stll/folio-core 0.51.0 → 0.53.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 (316) hide show
  1. package/README.md +23 -0
  2. package/dist/ai-edits/apply.d.ts +26 -1
  3. package/dist/ai-edits/apply.js +1519 -105
  4. package/dist/ai-edits/batch-claims.d.ts +130 -0
  5. package/dist/ai-edits/batch-claims.js +244 -0
  6. package/dist/ai-edits/blockRange.js +9 -3
  7. package/dist/ai-edits/character-boundaries.d.ts +43 -0
  8. package/dist/ai-edits/character-boundaries.js +95 -0
  9. package/dist/ai-edits/clean-text.d.ts +33 -3
  10. package/dist/ai-edits/clean-text.js +52 -6
  11. package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
  12. package/dist/ai-edits/comment-lifecycle.js +52 -0
  13. package/dist/ai-edits/headless.d.ts +71 -7
  14. package/dist/ai-edits/headless.js +381 -41
  15. package/dist/ai-edits/index.d.ts +2 -1
  16. package/dist/ai-edits/index.js +2 -1
  17. package/dist/ai-edits/minimal-replacement.d.ts +21 -6
  18. package/dist/ai-edits/minimal-replacement.js +134 -15
  19. package/dist/ai-edits/newListNumbering.d.ts +23 -0
  20. package/dist/ai-edits/newListNumbering.js +94 -0
  21. package/dist/ai-edits/note-references.d.ts +96 -0
  22. package/dist/ai-edits/note-references.js +139 -0
  23. package/dist/ai-edits/pending-suggestions.d.ts +79 -0
  24. package/dist/ai-edits/pending-suggestions.js +306 -0
  25. package/dist/ai-edits/read.d.ts +1 -1
  26. package/dist/ai-edits/read.js +56 -43
  27. package/dist/ai-edits/result-validation.d.ts +29 -0
  28. package/dist/ai-edits/result-validation.js +164 -0
  29. package/dist/ai-edits/snapshot.d.ts +36 -3
  30. package/dist/ai-edits/snapshot.js +177 -23
  31. package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
  32. package/dist/ai-edits/table-cell-mutations.js +4 -1
  33. package/dist/ai-edits/table-geometry.d.ts +1 -1
  34. package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
  35. package/dist/ai-edits/table-row-column-mutations.js +61 -26
  36. package/dist/ai-edits/types.d.ts +155 -16
  37. package/dist/compare/compare.d.ts +1 -1
  38. package/dist/compare/compare.js +65 -16
  39. package/dist/compare/content-alignment.js +13 -2
  40. package/dist/compare/content-types.d.ts +7 -0
  41. package/dist/compare/content.d.ts +4 -1
  42. package/dist/compare/content.js +6 -1
  43. package/dist/compare/formatting.js +2 -1
  44. package/dist/compare/inline-atoms.d.ts +21 -4
  45. package/dist/compare/inline-atoms.js +138 -16
  46. package/dist/compare/inline-provenance.js +18 -22
  47. package/dist/compare/plan.js +10 -5
  48. package/dist/compare/scenario.js +14 -3
  49. package/dist/compare/section-boundary-properties.js +14 -1
  50. package/dist/compare/types.d.ts +4 -2
  51. package/dist/compare/types.js +2 -1
  52. package/dist/compare/verification.d.ts +1 -1
  53. package/dist/compare/verification.js +15 -3
  54. package/dist/controller/committedLayoutDiff.d.ts +25 -0
  55. package/dist/controller/committedLayoutDiff.js +71 -0
  56. package/dist/controller/contentControlWidgetController.d.ts +1 -1
  57. package/dist/controller/folioEditor.d.ts +1 -1
  58. package/dist/controller/fontReadiness.d.ts +70 -3
  59. package/dist/controller/fontReadiness.js +100 -10
  60. package/dist/controller/headerFooterEditorManager.js +6 -1
  61. package/dist/controller/layoutPipeline.d.ts +12 -10
  62. package/dist/controller/layoutPipeline.js +62 -9
  63. package/dist/controller/layoutRunOptions.d.ts +24 -0
  64. package/dist/controller/layoutRunOptions.js +17 -0
  65. package/dist/controller/layoutScheduler.d.ts +19 -14
  66. package/dist/controller/layoutScheduler.js +15 -12
  67. package/dist/controller/layoutSession.d.ts +26 -2
  68. package/dist/controller/layoutSession.js +1 -1
  69. package/dist/controller/noteEditorManager.js +8 -3
  70. package/dist/document-operations.d.ts +11 -2
  71. package/dist/document-operations.js +230 -21
  72. package/dist/docx/altChunk.d.ts +8 -0
  73. package/dist/docx/altChunk.js +17 -0
  74. package/dist/docx/attributeRemainder.d.ts +1 -1
  75. package/dist/docx/blockContentParser.d.ts +64 -1
  76. package/dist/docx/blockContentParser.js +17 -4
  77. package/dist/docx/blockCustomXmlShell.d.ts +7 -0
  78. package/dist/docx/blockCustomXmlShell.js +14 -0
  79. package/dist/docx/blockPlainText.js +7 -0
  80. package/dist/docx/commentAnchorIndex.js +1 -1
  81. package/dist/docx/commentRangeIntegrity.js +8 -3
  82. package/dist/docx/commentReferenceNormalization.js +1 -1
  83. package/dist/docx/commentReplyMarkers.js +20 -1
  84. package/dist/docx/compatibility.d.ts +1 -1
  85. package/dist/docx/compatibility.js +34 -13
  86. package/dist/docx/containerChildren.gen.d.ts +1 -1
  87. package/dist/docx/containerChildren.gen.js +1 -0
  88. package/dist/docx/documentParser.js +12 -12
  89. package/dist/docx/drawingIdNormalization.d.ts +3 -1
  90. package/dist/docx/drawingIdNormalization.js +6 -1
  91. package/dist/docx/ensureParaIds.js +119 -97
  92. package/dist/docx/footnoteParser.js +8 -4
  93. package/dist/docx/headerFooterParser.js +3 -3
  94. package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
  95. package/dist/docx/headerFooterReferenceNormalization.js +52 -27
  96. package/dist/docx/hyperlinkParser.d.ts +1 -1
  97. package/dist/docx/index.d.ts +1 -1
  98. package/dist/docx/listNumberingInstances.d.ts +59 -0
  99. package/dist/docx/listNumberingInstances.js +249 -0
  100. package/dist/docx/normalizeBaseDirection.js +2 -1
  101. package/dist/docx/opaqueCarrier.d.ts +7 -0
  102. package/dist/docx/opaqueCarrier.js +38 -0
  103. package/dist/docx/packageParts.js +4 -1
  104. package/dist/docx/paragraphParser.js +91 -24
  105. package/dist/docx/paragraphPropertySource.d.ts +4 -2
  106. package/dist/docx/paragraphPropertySource.js +7 -1
  107. package/dist/docx/paragraphTraversal.js +1 -0
  108. package/dist/docx/parseWarningMessage.js +4 -1
  109. package/dist/docx/parser.js +173 -20
  110. package/dist/docx/rezip.js +27 -5
  111. package/dist/docx/sdtProperties.js +1 -1
  112. package/dist/docx/sdtPropertiesPatch.js +3 -0
  113. package/dist/docx/selectiveSave.js +16 -9
  114. package/dist/docx/selectiveXmlPatch.js +106 -37
  115. package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
  116. package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
  117. package/dist/docx/serializer/documentSerializer.js +2 -0
  118. package/dist/docx/serializer/headerFooterSerializer.js +2 -0
  119. package/dist/docx/serializer/noteSerializer.js +3 -1
  120. package/dist/docx/serializer/paragraphSerializer.js +33 -9
  121. package/dist/docx/serializer/tableSerializer.js +25 -38
  122. package/dist/docx/server/createBilingualDocument.js +3 -3
  123. package/dist/docx/tableParser.d.ts +157 -1
  124. package/dist/docx/tableParser.js +89 -10
  125. package/dist/docx/textBoxParser.js +1 -1
  126. package/dist/docx/unzip.d.ts +2 -1
  127. package/dist/docx/unzip.js +1 -1
  128. package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
  129. package/dist/docx/wordprocessingPrefixes.js +286 -0
  130. package/dist/generated/text_shaper.js +26 -0
  131. package/dist/generated/text_shaper_bg.wasm +0 -0
  132. package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
  133. package/dist/i18n/messages/catalogs.gen.js +153 -17
  134. package/dist/i18n/messages/messages.gen.d.ts +8 -0
  135. package/dist/internal/compare/inline-presentation.d.ts +2 -1
  136. package/dist/internal/compare/inline-presentation.js +11 -10
  137. package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
  138. package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
  139. package/dist/internal/indexedPositionMap.d.ts +11 -0
  140. package/dist/internal/indexedPositionMap.js +42 -0
  141. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  142. package/dist/internal/revisionResolutionEdits.d.ts +13 -0
  143. package/dist/internal/revisionResolutionEdits.js +19 -0
  144. package/dist/internal/revisionResolutionInline.d.ts +24 -0
  145. package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
  146. package/dist/internal/revisionResolutionStep.d.ts +34 -0
  147. package/dist/internal/revisionResolutionStep.js +282 -0
  148. package/dist/internal/revisionResolutionTracking.d.ts +11 -0
  149. package/dist/internal/revisionResolutionTracking.js +124 -0
  150. package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
  151. package/dist/internal/wholeStoryRevisionResolution.js +530 -0
  152. package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
  153. package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
  154. package/dist/layout-bridge/convert/tableConversion.js +1 -1
  155. package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
  156. package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
  157. package/dist/layout-engine/layoutInstrumentation.js +4 -1
  158. package/dist/layout-engine/measure/cache.d.ts +18 -1
  159. package/dist/layout-engine/measure/cache.js +31 -1
  160. package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
  161. package/dist/layout-engine/measure/measureContainer.js +7 -47
  162. package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
  163. package/dist/layout-engine/measure/measureHelpers.js +22 -2
  164. package/dist/layout-engine/measure/measureWorker.js +2 -3
  165. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
  166. package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
  167. package/dist/layout-engine/types.d.ts +6 -0
  168. package/dist/layout-painter/renderPage.js +1 -0
  169. package/dist/layout-painter/renderParagraph.js +1 -1
  170. package/dist/managers/ContextMenuManager.js +5 -2
  171. package/dist/managers/types.d.ts +3 -0
  172. package/dist/markdown/fromMarkdown.js +2 -1
  173. package/dist/markdown/internals.d.ts +11 -1
  174. package/dist/markdown/internals.js +24 -3
  175. package/dist/markdown/renderBlock.js +39 -9
  176. package/dist/markdown/renderParagraph.d.ts +13 -1
  177. package/dist/markdown/renderParagraph.js +85 -38
  178. package/dist/markdown/renderRuns.js +3 -10
  179. package/dist/markdown/renderTable.js +24 -12
  180. package/dist/markdown/trailers.js +2 -2
  181. package/dist/markdown/types.d.ts +29 -7
  182. package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
  183. package/dist/paged-layout/incrementalMeasure.js +1 -9
  184. package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
  185. package/dist/paged-layout/transactionDirtyRange.js +29 -41
  186. package/dist/panel-layout.d.ts +83 -0
  187. package/dist/panel-layout.js +96 -0
  188. package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
  189. package/dist/prosemirror/anchoredTextBoxes.js +156 -0
  190. package/dist/prosemirror/attrs/index.d.ts +6 -2
  191. package/dist/prosemirror/attrs/index.js +172 -1
  192. package/dist/prosemirror/commands/comments.d.ts +7 -8
  193. package/dist/prosemirror/commands/comments.js +241 -207
  194. package/dist/prosemirror/commands/contentControls.d.ts +1 -1
  195. package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
  196. package/dist/prosemirror/commands/formatting.d.ts +1 -1
  197. package/dist/prosemirror/commands/index.d.ts +2 -2
  198. package/dist/prosemirror/commands/index.js +2 -2
  199. package/dist/prosemirror/commands/paragraph.d.ts +7 -1
  200. package/dist/prosemirror/commands/paragraph.js +9 -1
  201. package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
  202. package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
  203. package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
  204. package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
  205. package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
  206. package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
  207. package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
  208. package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
  209. package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
  210. package/dist/prosemirror/commands/sectionBreak.js +2 -1
  211. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
  212. package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
  213. package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
  214. package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
  215. package/dist/prosemirror/conversion/toProseDoc.js +39 -4
  216. package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
  217. package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
  218. package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
  219. package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
  220. package/dist/prosemirror/extensions/StarterKit.js +3 -1
  221. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  222. package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
  223. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
  224. package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
  225. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
  226. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
  227. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
  228. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
  229. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
  230. package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
  231. package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
  232. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
  233. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
  234. package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
  235. package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
  236. package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
  237. package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
  238. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
  239. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
  240. package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
  241. package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
  242. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
  243. package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
  244. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
  245. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  246. package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
  247. package/dist/prosemirror/extensions/types.d.ts +4 -1
  248. package/dist/prosemirror/findReplaceSelection.js +1 -1
  249. package/dist/prosemirror/index.d.ts +3 -2
  250. package/dist/prosemirror/index.js +3 -2
  251. package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
  252. package/dist/prosemirror/indexedNodeLookup.js +103 -0
  253. package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
  254. package/dist/prosemirror/listAutoformatMarkers.js +98 -0
  255. package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
  256. package/dist/prosemirror/listInstanceReferences.js +72 -0
  257. package/dist/prosemirror/listLabels.d.ts +24 -0
  258. package/dist/prosemirror/listLabels.js +111 -0
  259. package/dist/prosemirror/listNumbering.d.ts +110 -0
  260. package/dist/prosemirror/listNumbering.js +345 -0
  261. package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
  262. package/dist/prosemirror/listRenderingAttrs.js +8 -1
  263. package/dist/prosemirror/markupViewProjection.d.ts +95 -0
  264. package/dist/prosemirror/markupViewProjection.js +83 -0
  265. package/dist/prosemirror/numberingAttr.d.ts +9 -1
  266. package/dist/prosemirror/numberingAttr.js +9 -1
  267. package/dist/prosemirror/outlineLevelAttr.js +1 -1
  268. package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
  269. package/dist/prosemirror/paragraphIndentation.js +29 -7
  270. package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
  271. package/dist/prosemirror/paragraphMarkJoin.js +24 -0
  272. package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
  273. package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
  274. package/dist/prosemirror/plugins/documentNumbering.js +97 -11
  275. package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
  276. package/dist/prosemirror/plugins/documentStyles.js +14 -1
  277. package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
  278. package/dist/prosemirror/plugins/suggestionMode.js +116 -52
  279. package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
  280. package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
  281. package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
  282. package/dist/prosemirror/positionSweep.d.ts +24 -0
  283. package/dist/prosemirror/positionSweep.js +222 -0
  284. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
  285. package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
  286. package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
  287. package/dist/prosemirror/replacedAnnotations.js +170 -1
  288. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
  289. package/dist/prosemirror/runStyleFormatting.d.ts +7 -1
  290. package/dist/prosemirror/runStyleFormatting.js +13 -7
  291. package/dist/prosemirror/schema/index.d.ts +2 -2
  292. package/dist/prosemirror/schema/index.js +3 -0
  293. package/dist/prosemirror/schema/nodes.d.ts +42 -11
  294. package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
  295. package/dist/prosemirror/sectionMarkEdits.js +59 -0
  296. package/dist/prosemirror/storyListNumbering.d.ts +17 -0
  297. package/dist/prosemirror/storyListNumbering.js +102 -0
  298. package/dist/prosemirror/tableGridMutation.d.ts +48 -2
  299. package/dist/prosemirror/tableGridMutation.js +106 -1
  300. package/dist/prosemirror/validation.js +5 -1
  301. package/dist/redline.js +93 -9
  302. package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
  303. package/dist/render-dom/BodySelectionOverlay.js +3 -2
  304. package/dist/shaping/shaper.d.ts +47 -4
  305. package/dist/shaping/shaper.js +46 -15
  306. package/dist/text-shaping.d.ts +4 -0
  307. package/dist/text-shaping.js +4 -0
  308. package/dist/utils/findReplace.js +2 -2
  309. package/dist/utils/mergeDocumentContent.js +1 -1
  310. package/dist/utils/noteReferenceLabels.d.ts +36 -0
  311. package/dist/utils/noteReferenceLabels.js +32 -0
  312. package/dist/utils/replaceText.js +1 -1
  313. package/dist/version-comparison.d.ts +1 -0
  314. package/dist/version-comparison.js +1 -0
  315. package/package.json +8 -2
  316. package/dist/internal/headlessRevisionResolution.d.ts +0 -23
@@ -1,6 +1,7 @@
1
1
  import { toArrayBuffer } from "../utils/docxInput.js";
2
2
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
3
  import { MAX_PACKAGE_TIFF_PIXELS, convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
4
+ import { ALT_CHUNK_PLAIN_TEXT_LIMIT_BYTES, getAltChunkRelationshipId, isAltChunkMarkup } from "./altChunk.js";
4
5
  import { DUPLICATE_COMMENT_ID_WARNING, normalizeCommentIds } from "./commentIdNormalization.js";
5
6
  import { parseComments } from "./commentParser.js";
6
7
  import { DANGLING_COMMENT_REFERENCE_WARNING, UNBALANCED_COMMENT_RANGE_WARNING, normalizeCommentReferences } from "./commentReferenceNormalization.js";
@@ -13,13 +14,14 @@ import { DocxEncryptionError } from "./encryption/errors.js";
13
14
  import { parseFontTable } from "./fontTableParser.js";
14
15
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
15
16
  import { parseFooter, parseHeader } from "./headerFooterParser.js";
16
- import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
17
- import { assignHeaderFooterVerbatimXml, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
17
+ import { DANGLING_FOOTER_REFERENCE_WARNING, DANGLING_HEADER_REFERENCE_WARNING, assignHeaderFooterRoles, normalizeHeaderFooterReferences } from "./headerFooterReferenceNormalization.js";
18
+ import { assignHeaderFooterVerbatimXml, canReplayHeaderFooterVerbatim, refreshHeaderFooterVerbatimFingerprint } from "./headerFooterVerbatim.js";
18
19
  import { extractMetafileRaster, isMetafileMimeType } from "./metafileRaster.js";
19
20
  import { renderEmfSvg } from "./metafileSvg.js";
20
21
  import { DocxModelValidationError, formatDocumentModelIssues, validateFolioDocumentModel } from "./modelValidation.js";
21
22
  import { parseNumbering } from "./numberingParser.js";
22
23
  import { UNNUMBERED_PARAGRAPH_WARNING, UNNUMBERED_STYLE_WARNING, normalizeNumberingReferences, normalizeStyleNumberingReferences } from "./numberingReferenceNormalization.js";
24
+ import { countOpaqueRevisionWrappers } from "./opaqueCarrier.js";
23
25
  import { assignDocumentParagraphPropertySourceContract } from "./paragraphPropertySource.js";
24
26
  import { createParseWarningCollector } from "./parseContext.js";
25
27
  import { formatParseWarnings } from "./parseWarningMessage.js";
@@ -30,9 +32,10 @@ import { parseSettings } from "./settingsParser.js";
30
32
  import { parseStylesPackage } from "./styleParser.js";
31
33
  import { applyThemeFontLang, parseTheme } from "./themeParser.js";
32
34
  import { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges } from "./trackedMoveRangeNormalization.js";
33
- import { getMediaMimeType, mediaToDataUrl, unzipDocx } from "./unzip.js";
35
+ import { getEntryUncompressedSize, getMediaMimeType, mediaToDataUrl, unzipDocx } from "./unzip.js";
36
+ import { getAttribute, getChildElements, getLocalName, parseXmlDocument } from "./xmlParser.js";
34
37
  import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
35
- import { TaggedError } from "better-result";
38
+ import { TaggedError, panic } from "better-result";
36
39
  import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
37
40
  //#region src/docx/parser.ts
38
41
  /**
@@ -63,6 +66,101 @@ const sha256Hex = async (buffer) => {
63
66
  for (const byte of bytes) digest += byte.toString(16).padStart(2, "0");
64
67
  return digest;
65
68
  };
69
+ const countAltChunks = (blocks) => {
70
+ let count = 0;
71
+ for (const block of blocks) switch (block.type) {
72
+ case "preservedBlock":
73
+ count += isAltChunkMarkup(block.xml) ? 1 : 0;
74
+ break;
75
+ case "table":
76
+ for (const row of block.rows) for (const cell of row.cells) count += countAltChunks(cell.content);
77
+ break;
78
+ case "blockSdt":
79
+ case "blockCustomXml":
80
+ count += countAltChunks(block.content);
81
+ break;
82
+ case "paragraph":
83
+ case "bookmarkStart":
84
+ case "bookmarkEnd": break;
85
+ default: panic(`Unsupported block while counting altChunks: ${JSON.stringify(block)}`);
86
+ }
87
+ return count;
88
+ };
89
+ const ALT_CHUNK_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk";
90
+ const ALT_CHUNK_STRICT_RELATIONSHIP_TYPE = "http://purl.oclc.org/ooxml/officeDocument/relationships/aFChunk";
91
+ const CONTENT_TYPES_NAMESPACE_URI = "http://schemas.openxmlformats.org/package/2006/content-types";
92
+ const contentTypeOf = (contentTypesXml, partPath) => {
93
+ if (!contentTypesXml) return;
94
+ const root = parseXmlDocument(contentTypesXml);
95
+ if (!root || root.namespaceUri !== CONTENT_TYPES_NAMESPACE_URI) return;
96
+ const extension = partPath.slice(partPath.lastIndexOf(".") + 1).toLowerCase();
97
+ let defaultContentType;
98
+ for (const child of getChildElements(root)) {
99
+ if (child.namespaceUri !== CONTENT_TYPES_NAMESPACE_URI) continue;
100
+ const name = getLocalName(child.name);
101
+ const contentType = getAttribute(child, null, "ContentType");
102
+ if (name === "Override" && getAttribute(child, null, "PartName") === `/${partPath}`) return contentType ?? void 0;
103
+ if (name === "Default" && getAttribute(child, null, "Extension")?.toLowerCase() === extension) defaultContentType = contentType ?? void 0;
104
+ }
105
+ return defaultContentType;
106
+ };
107
+ const extractAltChunkPlainText = async (xml, context) => {
108
+ const relationshipId = getAltChunkRelationshipId(xml);
109
+ const relationship = relationshipId === void 0 ? void 0 : context.relationships.get(relationshipId);
110
+ if (!relationship || relationship.targetMode === "External" || relationship.type !== ALT_CHUNK_RELATIONSHIP_TYPE && relationship.type !== ALT_CHUNK_STRICT_RELATIONSHIP_TYPE) return;
111
+ const partPath = resolveRelativePath(context.relationshipsPath, relationship.target);
112
+ if (contentTypeOf(context.raw.contentTypesXml, partPath)?.split(";")[0]?.trim().toLowerCase() !== "text/plain") return;
113
+ const file = context.raw.originalZip.file(partPath);
114
+ if (!file) return;
115
+ const declaredSize = getEntryUncompressedSize(file);
116
+ if (declaredSize === null || declaredSize > context.budget.remainingBytes) return void 0;
117
+ const bytes = await file.async("uint8array");
118
+ if (bytes.byteLength > context.budget.remainingBytes) return;
119
+ context.budget.remainingBytes -= bytes.byteLength;
120
+ try {
121
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
122
+ } catch {
123
+ return;
124
+ }
125
+ };
126
+ const attachAltChunkPlainText = async (blocks, context) => {
127
+ for (const block of blocks) {
128
+ if (block.type === "preservedBlock" && isAltChunkMarkup(block.xml)) {
129
+ const readerText = await extractAltChunkPlainText(block.xml, context);
130
+ if (readerText !== void 0) block.readerText = readerText;
131
+ continue;
132
+ }
133
+ if (block.type === "table") {
134
+ for (const row of block.rows) for (const cell of row.cells) await attachAltChunkPlainText(cell.content, context);
135
+ continue;
136
+ }
137
+ if (block.type === "blockSdt" || block.type === "blockCustomXml") await attachAltChunkPlainText(block.content, context);
138
+ }
139
+ };
140
+ const countOpaqueRevisionCarriers = (blocks) => {
141
+ let count = 0;
142
+ for (const block of blocks) switch (block.type) {
143
+ case "preservedBlock":
144
+ count += countOpaqueRevisionWrappers(block.xml);
145
+ break;
146
+ case "table":
147
+ count += block.preserved?.children?.reduce((total, child) => total + countOpaqueRevisionWrappers(child.xml), 0) ?? 0;
148
+ for (const row of block.rows) {
149
+ count += row.preserved?.children?.reduce((total, child) => total + countOpaqueRevisionWrappers(child.xml), 0) ?? 0;
150
+ for (const cell of row.cells) count += countOpaqueRevisionCarriers(cell.content);
151
+ }
152
+ break;
153
+ case "blockSdt":
154
+ case "blockCustomXml":
155
+ count += countOpaqueRevisionCarriers(block.content);
156
+ break;
157
+ case "paragraph":
158
+ case "bookmarkStart":
159
+ case "bookmarkEnd": break;
160
+ default: panic(`Unsupported block while counting opaque revisions: ${JSON.stringify(block)}`);
161
+ }
162
+ return count;
163
+ };
66
164
  /**
67
165
  * Parse a DOCX file into a complete Document model
68
166
  *
@@ -210,6 +308,11 @@ async function parseDocxWithPreviewBudget(input, options, previewBudget) {
210
308
  code: DANGLING_FOOTER_REFERENCE_WARNING,
211
309
  count: headerFooterReferenceNormalization.removedDanglingFooterReferences
212
310
  });
311
+ assignHeaderFooterRoles({
312
+ documentBody,
313
+ ...headers !== void 0 ? { headers } : {},
314
+ ...footers !== void 0 ? { footers } : {}
315
+ });
213
316
  const numberingReferenceNormalization = normalizeNumberingReferences({
214
317
  documentBody,
215
318
  numbering,
@@ -272,27 +375,77 @@ async function parseDocxWithPreviewBudget(input, options, previewBudget) {
272
375
  } else onProgress("Skipping font loading", 95);
273
376
  onProgress("Assembling document...", 95);
274
377
  const properties = timeStage("coreProperties", () => parseCoreProperties(raw.corePropsXml));
378
+ const pkg = {
379
+ conformanceClass: detectDocxConformanceClass(raw.documentXml),
380
+ document: documentBody,
381
+ settings,
382
+ ...styleDefinitions !== void 0 ? { styles: styleDefinitions } : {},
383
+ theme,
384
+ numbering: numbering.definitions,
385
+ ...fontTable ? { fontTable } : {},
386
+ ...headers !== void 0 ? { headers } : {},
387
+ ...footers !== void 0 ? { footers } : {},
388
+ ...footnotes !== void 0 ? { footnotes } : {},
389
+ ...endnotes !== void 0 ? { endnotes } : {},
390
+ relationships: rels,
391
+ media,
392
+ ...properties !== void 0 ? { properties } : {}
393
+ };
275
394
  const document = {
276
- package: {
277
- conformanceClass: detectDocxConformanceClass(raw.documentXml),
278
- document: documentBody,
279
- settings,
280
- ...styleDefinitions !== void 0 ? { styles: styleDefinitions } : {},
281
- theme,
282
- numbering: numbering.definitions,
283
- ...fontTable ? { fontTable } : {},
284
- ...headers !== void 0 ? { headers } : {},
285
- ...footers !== void 0 ? { footers } : {},
286
- ...footnotes !== void 0 ? { footnotes } : {},
287
- ...endnotes !== void 0 ? { endnotes } : {},
288
- relationships: rels,
289
- media,
290
- ...properties !== void 0 ? { properties } : {}
291
- },
395
+ package: pkg,
292
396
  originalBuffer: raw.originalBuffer,
293
397
  ...templateVariables !== void 0 ? { templateVariables } : {},
294
398
  ...requiredFonts.length > 0 ? { requiredFonts } : {}
295
399
  };
400
+ const altChunkBudget = { remainingBytes: ALT_CHUNK_PLAIN_TEXT_LIMIT_BYTES };
401
+ await attachAltChunkPlainText(pkg.document.content, {
402
+ relationships: rels,
403
+ relationshipsPath: DOCUMENT_RELATIONSHIPS_PATH,
404
+ raw,
405
+ budget: altChunkBudget
406
+ });
407
+ const altChunkContextForPart = (partPath) => {
408
+ const relationshipsPath = getRelationshipsPathForPart(partPath);
409
+ const relationshipsXml = getMapCaseInsensitive(raw.allXml, relationshipsPath);
410
+ return {
411
+ relationships: relationshipsXml ? parseRelationships(relationshipsXml) : /* @__PURE__ */ new Map(),
412
+ relationshipsPath,
413
+ raw,
414
+ budget: altChunkBudget
415
+ };
416
+ };
417
+ for (const [rId, header] of pkg.headers ?? []) {
418
+ const relationship = rels.get(rId);
419
+ if (relationship?.target) {
420
+ const replayable = canReplayHeaderFooterVerbatim(header);
421
+ await attachAltChunkPlainText(header.content, altChunkContextForPart(getRelationshipPartPath(relationship.target)));
422
+ if (replayable) refreshHeaderFooterVerbatimFingerprint(header);
423
+ }
424
+ }
425
+ for (const [rId, footer] of pkg.footers ?? []) {
426
+ const relationship = rels.get(rId);
427
+ if (relationship?.target) {
428
+ const replayable = canReplayHeaderFooterVerbatim(footer);
429
+ await attachAltChunkPlainText(footer.content, altChunkContextForPart(getRelationshipPartPath(relationship.target)));
430
+ if (replayable) refreshHeaderFooterVerbatimFingerprint(footer);
431
+ }
432
+ }
433
+ const footnoteContext = altChunkContextForPart("word/footnotes.xml");
434
+ for (const footnote of pkg.footnotes ?? []) await attachAltChunkPlainText(footnote.content, footnoteContext);
435
+ const endnoteContext = altChunkContextForPart("word/endnotes.xml");
436
+ for (const endnote of pkg.endnotes ?? []) await attachAltChunkPlainText(endnote.content, endnoteContext);
437
+ const altChunkCount = countAltChunks(pkg.document.content) + [...pkg.headers?.values() ?? []].reduce((total, part) => total + countAltChunks(part.content), 0) + [...pkg.footers?.values() ?? []].reduce((total, part) => total + countAltChunks(part.content), 0) + (pkg.footnotes ?? []).reduce((total, part) => total + countAltChunks(part.content), 0) + (pkg.endnotes ?? []).reduce((total, part) => total + countAltChunks(part.content), 0);
438
+ if (altChunkCount > 0) parseContext.warn({
439
+ code: PARSE_WARNING_CODES.altChunkUnsupported,
440
+ element: "w:altChunk",
441
+ count: altChunkCount
442
+ });
443
+ const opaqueRevisionCount = countOpaqueRevisionCarriers(pkg.document.content) + [...pkg.headers?.values() ?? []].reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0) + [...pkg.footers?.values() ?? []].reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0) + (pkg.footnotes ?? []).reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0) + (pkg.endnotes ?? []).reduce((total, part) => total + countOpaqueRevisionCarriers(part.content), 0);
444
+ if (opaqueRevisionCount > 0) parseContext.warn({
445
+ code: PARSE_WARNING_CODES.revisionCarrierOpaque,
446
+ element: "w:ins/w:del/w:moveFrom/w:moveTo",
447
+ count: opaqueRevisionCount
448
+ });
296
449
  assignDocumentParagraphPropertySourceContract(document, await paragraphPropertySourceDigest);
297
450
  previews.enforce(previewBudget);
298
451
  const validation = validateFolioDocumentModel(document);
@@ -26,10 +26,11 @@ import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
26
26
  import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
27
27
  import { serializeEndnotes, serializeFootnotes, serializeNewEndnotesPart, serializeNewFootnotesPart } from "./serializer/noteSerializer.js";
28
28
  import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
29
- import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
29
+ import { readRootNamespaceBindings, serializePartElement } from "./serializer/partNamespaces.js";
30
30
  import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
31
31
  import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
32
32
  import { serializeThemeXml } from "./serializer/themeSerializer.js";
33
+ import { hasCanonicalWordprocessingPrefixes } from "./wordprocessingPrefixes.js";
33
34
  import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
34
35
  import { TaggedError, panic } from "better-result";
35
36
  import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
@@ -363,7 +364,7 @@ function collectNewImages(blocks) {
363
364
  for (const sub of item.content) if (sub.type === "run") collectFromRun(sub);
364
365
  }
365
366
  } else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) images.push(...collectNewImages(cell.content));
366
- else if (block.type === "blockSdt") images.push(...collectNewImages(block.content));
367
+ else if (block.type === "blockSdt" || block.type === "blockCustomXml") images.push(...collectNewImages(block.content));
367
368
  return images;
368
369
  }
369
370
  /** Map MIME type to file extension (inverse of getContentTypeForExtension) */
@@ -484,7 +485,7 @@ function collectHyperlinksWithoutRId(blocks) {
484
485
  };
485
486
  for (const block of blocks) if (block.type === "paragraph") collectInlineHyperlinks(block.content);
486
487
  else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
487
- else if (block.type === "blockSdt") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
488
+ else if (block.type === "blockSdt" || block.type === "blockCustomXml") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
488
489
  return hyperlinks;
489
490
  }
490
491
  /** The selective save boundary must use the same resource census as full repack. */
@@ -1444,6 +1445,25 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
1444
1445
  const STYLES_PART_PATH = "word/styles.xml";
1445
1446
  const STYLES_CLOSE_ROOT = "</w:styles>";
1446
1447
  /**
1448
+ * One serialized element with the namespace declarations its own prefixes
1449
+ * need, for splicing into a part whose root binds them differently (or not
1450
+ * at all). A prefix no table knows throws rather than being written unbound.
1451
+ */
1452
+ const selfBoundFragment = (fragment, partPath) => {
1453
+ const openEnd = fragment.indexOf(">");
1454
+ const selfClosing = fragment[openEnd - 1] === "/";
1455
+ const rootName = /^<(?<name>[^\s/>]+)/u.exec(fragment)?.groups?.["name"];
1456
+ if (openEnd === -1 || rootName === void 0) throw new DocxPackageFidelityError(`Cannot splice a malformed element into ${partPath}`);
1457
+ return serializePartElement({
1458
+ partPath,
1459
+ rootName,
1460
+ rootAttributes: fragment.slice(rootName.length + 1, selfClosing ? openEnd - 1 : openEnd).trim(),
1461
+ baselinePrefixes: [],
1462
+ sourceBindings: void 0,
1463
+ body: selfClosing ? "" : fragment.slice(openEnd + 1, fragment.lastIndexOf("</"))
1464
+ });
1465
+ };
1466
+ /**
1447
1467
  * The style table a package folio creates starts from: `docDefaults` and
1448
1468
  * `Normal`, and nothing else. A document that carries its own style table
1449
1469
  * replaces this part wholesale.
@@ -1532,7 +1552,8 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
1532
1552
  if (!styles || styles.styles.length === 0) return;
1533
1553
  const file = findNotePartEntry(originalZip, STYLES_PART_PATH);
1534
1554
  const originalXml = file ? await file.async("text") : null;
1535
- const rootClose = originalXml?.lastIndexOf(STYLES_CLOSE_ROOT) ?? -1;
1555
+ const rootName = originalXml === null ? void 0 : /<(?![?!])(?<name>[^\s/>]+)/u.exec(originalXml)?.groups?.["name"];
1556
+ const rootClose = originalXml !== null && rootName !== void 0 && getLocalName(rootName) === "styles" ? originalXml.lastIndexOf(`</${rootName}>`) : -1;
1536
1557
  if (file === null || originalXml === null || rootClose < 0) {
1537
1558
  await materializeNewNotePart({
1538
1559
  contentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
@@ -1552,7 +1573,8 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
1552
1573
  added.push(style);
1553
1574
  }
1554
1575
  if (added.length === 0) return;
1555
- const patched = originalXml.slice(0, rootClose) + added.map(serializeStyle).join("") + originalXml.slice(rootClose);
1576
+ const canonical = hasCanonicalWordprocessingPrefixes(originalXml);
1577
+ const patched = originalXml.slice(0, rootClose) + added.map((style) => canonical ? serializeStyle(style) : selfBoundFragment(serializeStyle(style), file.name)).join("") + originalXml.slice(rootClose);
1556
1578
  newZip.file(file.name, patched, {
1557
1579
  compression: "DEFLATE",
1558
1580
  compressionOptions: { level: compressionLevel }
@@ -6,7 +6,7 @@ import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByLocalName, getAt
6
6
  //#region src/docx/sdtProperties.ts
7
7
  function parseListItems(el) {
8
8
  const items = [];
9
- for (const child of el.elements ?? []) if (child.type === "element" && (child.name === "w:listItem" || child.name?.endsWith(":listItem"))) {
9
+ for (const child of el.elements ?? []) if (child.type === "element" && getLocalName(child.name ?? "") === "listItem") {
10
10
  const displayText = getAttributeAnyPrefix(child, "displayText");
11
11
  const value = getAttributeAnyPrefix(child, "value");
12
12
  if (displayText === null && value === null) continue;
@@ -1,3 +1,4 @@
1
+ import { OOXML_NAMESPACE_SCOPE, findChild, getAttribute, parseXml } from "./xmlParser.js";
1
2
  import { escapeXmlAttribute } from "@stll/docx-core";
2
3
  //#region src/docx/sdtPropertiesPatch.ts
3
4
  /**
@@ -66,6 +67,8 @@ const withDateState = (xml, props) => {
66
67
  const fullDate = props.dateValueISO;
67
68
  const format = props.dateFormat;
68
69
  if (fullDate === void 0 && format === void 0) return xml;
70
+ const capturedDate = parseXml(xml, OOXML_NAMESPACE_SCOPE).elements?.at(0);
71
+ if ((fullDate === void 0 || getAttribute(capturedDate, "w", "fullDate") === fullDate) && (format === void 0 || getAttribute(findChild(capturedDate, "w", "dateFormat"), "w", "val") === format)) return xml;
69
72
  const attributes = (prefix, matched) => fullDate === void 0 ? matched : `${withoutAttr(matched, "fullDate")} ${prefix}:fullDate="${escapeXmlAttribute(fullDate)}"`;
70
73
  const formatChild = (prefix) => format === void 0 ? "" : `<${prefix}:dateFormat ${prefix}:val="${escapeXmlAttribute(format)}"/>`;
71
74
  const opened = /<(?<prefix>\w+):date\b(?<attrs>[^>]*)>(?<inner>[\s\S]*?)<\/\w+:date>/iu;
@@ -126,12 +126,15 @@ async function ensureCommentsExtendedPackaging(zip, updates) {
126
126
  * (`w:nsid`/`w:tmpl`, custom formats, level sub-elements), so only the changed
127
127
  * definitions are spliced by id; every other definition stays byte-exact.
128
128
  *
129
- * Any absent numbering part, unchanged numbering, or unsplittable edit leaves
130
- * the original part untouched — the file is simply not added to `updates`.
129
+ * Unchanged numbering leaves the original part untouched. Numbering the
130
+ * splice cannot write (a package without a numbering part, or an edit it
131
+ * cannot split) answers `false`: the paragraphs may reference those
132
+ * definitions, so the caller falls back to a full repack rather than save a
133
+ * package whose `w:numPr` names an instance it does not define.
131
134
  */
132
135
  async function patchNumberingPart(zip, doc, updates) {
133
136
  const numbering = doc.package.numbering;
134
- if (!numbering || numbering.abstractNums.length === 0 && numbering.nums.length === 0) return;
137
+ if (!numbering || numbering.abstractNums.length === 0 && numbering.nums.length === 0) return true;
135
138
  const conventionalLowerPath = "word/numbering.xml";
136
139
  let file = zip.file(conventionalLowerPath);
137
140
  if (!file) {
@@ -140,15 +143,19 @@ async function patchNumberingPart(zip, doc, updates) {
140
143
  break;
141
144
  }
142
145
  }
143
- if (!file) return;
146
+ if (!file) return false;
144
147
  const originalXml = await file.async("text");
148
+ const baselineXml = serializeNumberingXml(parseNumbering(originalXml).definitions);
149
+ const currentXml = serializeNumberingXml(numbering);
150
+ if (currentXml === baselineXml) return true;
145
151
  const patched = patchNumberingDefinitions({
146
152
  originalXml,
147
- baselineXml: serializeNumberingXml(parseNumbering(originalXml).definitions),
148
- currentXml: serializeNumberingXml(numbering)
153
+ baselineXml,
154
+ currentXml
149
155
  });
150
- if (patched === null || patched === originalXml) return;
151
- updates.set(file.name, patched);
156
+ if (patched === null) return false;
157
+ if (patched !== originalXml) updates.set(file.name, patched);
158
+ return true;
152
159
  }
153
160
  /**
154
161
  * Attempt a selective save — patch only changed paragraphs in document.xml.
@@ -250,7 +257,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
250
257
  }
251
258
  }
252
259
  if (!await patchCommentsExtended(zip, commentPlan, updates)) return null;
253
- await patchNumberingPart(zip, doc, updates);
260
+ if (!await patchNumberingPart(zip, doc, updates)) return null;
254
261
  for (const [path, xml] of await collectHeaderFooterUpdates(doc, zip)) updates.set(path, xml);
255
262
  await queueSettingsUpdates(zip, updates);
256
263
  const corePropsFile = zip.file("docProps/core.xml");
@@ -1,6 +1,7 @@
1
1
  import { patchBreaksCommentRangeBalance } from "./commentRangeIntegrity.js";
2
2
  import { resolveParagraphIdentities } from "./paraIdAttribute.js";
3
3
  import { captureVerbatimXml } from "./verbatimCapture.js";
4
+ import { PARAGRAPH_SCAN_NAMES, hasCanonicalWordprocessingPrefixes, matchCloseTag, matchOpenTag, resolveSelectiveScanPrefixes, splicesAsCanonical } from "./wordprocessingPrefixes.js";
4
5
  import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findAttributeByNamespaceUri, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
5
6
  //#region src/docx/selectiveXmlPatch.ts
6
7
  /**
@@ -108,29 +109,6 @@ function buildParagraphOffsetIndex(xml) {
108
109
  for (const [id, range] of ranges) if (seenCount.get(id) === 1) index.set(id, range);
109
110
  return index;
110
111
  }
111
- /** The containers {@link scanParagraphs} tracks, by the literal tag the part writes. */
112
- const TRACKED_CONTAINERS = [
113
- {
114
- name: "mc:Fallback",
115
- key: "fallback"
116
- },
117
- {
118
- name: "mc:AlternateContent",
119
- key: "alternateContent"
120
- },
121
- {
122
- name: "w:txbxContent",
123
- key: "textBox"
124
- },
125
- {
126
- name: "w:tc",
127
- key: "tableCell"
128
- }
129
- ].map(({ name, key }) => ({
130
- key,
131
- open: `<${name}`,
132
- close: `</${name}>`
133
- }));
134
112
  /**
135
113
  * Every `<w:p>` element of `xml`, in the document order of its opening tags,
136
114
  * with the `w14:paraId` that tag carries and the containers around it.
@@ -145,6 +123,8 @@ const TRACKED_CONTAINERS = [
145
123
  * no splice can be built from it.
146
124
  */
147
125
  function scanParagraphs(xml) {
126
+ const resolution = resolveSelectiveScanPrefixes(xml);
127
+ const mcPrefixes = resolution.type === "resolved" ? resolution.prefixes.mc : ["mc"];
148
128
  const paragraphs = [];
149
129
  const open = [];
150
130
  const depth = {
@@ -153,6 +133,28 @@ function scanParagraphs(xml) {
153
133
  textBox: 0,
154
134
  tableCell: 0
155
135
  };
136
+ const containers = [
137
+ {
138
+ key: "fallback",
139
+ prefixes: mcPrefixes,
140
+ name: "Fallback"
141
+ },
142
+ {
143
+ key: "alternateContent",
144
+ prefixes: mcPrefixes,
145
+ name: "AlternateContent"
146
+ },
147
+ {
148
+ key: "textBox",
149
+ prefixes: ["w"],
150
+ name: "txbxContent"
151
+ },
152
+ {
153
+ key: "tableCell",
154
+ prefixes: ["w"],
155
+ name: "tc"
156
+ }
157
+ ];
156
158
  let pos = 0;
157
159
  scan: while (pos < xml.length) {
158
160
  const tagStart = xml.indexOf("<", pos);
@@ -165,13 +167,14 @@ function scanParagraphs(xml) {
165
167
  pos = end;
166
168
  continue;
167
169
  }
168
- for (const { key, open: openLiteral, close } of TRACKED_CONTAINERS) {
169
- if (xml.startsWith(close, tagStart)) {
170
+ for (const { key, prefixes, name } of containers) {
171
+ const closeLength = matchCloseTag(xml, tagStart, prefixes, name);
172
+ if (closeLength !== -1) {
170
173
  depth[key] = Math.max(0, depth[key] - 1);
171
- pos = tagStart + close.length;
174
+ pos = tagStart + closeLength;
172
175
  continue scan;
173
176
  }
174
- if (xml.startsWith(openLiteral, tagStart) && isXmlNameBoundary(xml[tagStart + openLiteral.length])) {
177
+ if (matchOpenTag(xml, tagStart, prefixes, name) !== -1) {
175
178
  const tagEnd = xml.indexOf(">", tagStart);
176
179
  if (tagEnd === -1) break scan;
177
180
  if (xml[tagEnd - 1] !== "/") depth[key] += 1;
@@ -333,10 +336,9 @@ const nearestSharedParaId = (own, other, paragraph, step) => {
333
336
  * - an id-less paragraph whose story's ordinals do not line up.
334
337
  */
335
338
  const routeChangedParagraphs = (originalXml, serializedXml, changedIds) => {
336
- const rootName = rootElementName(originalXml);
337
- if (rootName !== void 0 && !rootName.startsWith("w:")) return {
339
+ if (!splicesAsCanonical(originalXml, PARAGRAPH_SCAN_NAMES)) return {
338
340
  type: "refused",
339
- reason: `non-canonical-wordprocessingml-prefix: ${rootName}`
341
+ reason: `non-canonical-wordprocessingml-prefix: ${rootElementName(originalXml) ?? "unknown"}`
340
342
  };
341
343
  const original = addressableParagraphs(originalXml);
342
344
  const serialized = addressableParagraphs(serializedXml);
@@ -423,9 +425,14 @@ const routeChangedParagraphs = (originalXml, serializedXml, changedIds) => {
423
425
  newXml: authored ? newXml : withoutMintedIds(newXml, sourceXml)
424
426
  });
425
427
  }
428
+ const candidate = spliceXml(originalXml, splices);
429
+ if (candidate === null || !splicesAsCanonical(candidate, PARAGRAPH_SCAN_NAMES)) return {
430
+ type: "refused",
431
+ reason: "replacement-namespace-conflict"
432
+ };
426
433
  return {
427
434
  type: "routed",
428
- splices
435
+ xml: candidate
429
436
  };
430
437
  };
431
438
  /**
@@ -459,7 +466,7 @@ function validatePatchSafety(originalXml, serializedXml, changedIds) {
459
466
  function buildPatchedDocumentXml(originalXml, serializedXml, changedIds) {
460
467
  if (changedIds.size === 0) return originalXml;
461
468
  const routing = routeChangedParagraphs(originalXml, serializedXml, changedIds);
462
- return routing.type === "refused" ? null : spliceXml(originalXml, routing.splices);
469
+ return routing.type === "refused" ? null : routing.xml;
463
470
  }
464
471
  /**
465
472
  * Build a patched note part (word/footnotes.xml / word/endnotes.xml) by
@@ -647,6 +654,40 @@ const paragraphRanges = (xml, wordPrefix) => {
647
654
  }
648
655
  return ranges;
649
656
  };
657
+ const OPENING_PARA_ID = /^<[^>]*?\sw14:paraId="(?<id>[^"]*)"/u;
658
+ const openingParaId = (paragraph) => OPENING_PARA_ID.exec(paragraph)?.groups?.["id"];
659
+ /** A note's XML outside its paragraphs, one entry per gap between them. */
660
+ const noteGaps = (note, ranges) => {
661
+ const gaps = [];
662
+ let cursor = 0;
663
+ for (const range of ranges) {
664
+ gaps.push(note.slice(cursor, range.start));
665
+ cursor = range.end;
666
+ }
667
+ gaps.push(note.slice(cursor));
668
+ return gaps;
669
+ };
670
+ /**
671
+ * Whether a note's block structure differs between two serializations of it:
672
+ * a paragraph added, removed or replaced by another, or anything written
673
+ * between paragraphs changed — a table, a row, a cell's properties, a
674
+ * row-level revision. Paragraph-by-paragraph splicing pairs paragraphs by
675
+ * ordinal, so it is only faithful when neither happened; a note whose shape
676
+ * changed is rewritten whole. A paragraph the baseline has no id for pairs
677
+ * with any id, because that id was minted after reading, not changed.
678
+ */
679
+ const noteShapeChanged = (baseline, current) => {
680
+ const baselineRanges = paragraphRanges(baseline.note, baseline.prefix);
681
+ const currentRanges = paragraphRanges(current.note, current.prefix);
682
+ if (baselineRanges.length !== currentRanges.length) return true;
683
+ const currentGaps = noteGaps(current.note, currentRanges);
684
+ if (noteGaps(baseline.note, baselineRanges).some((gap, index) => gap !== currentGaps[index])) return true;
685
+ return baselineRanges.some((range, index) => {
686
+ const baselineId = openingParaId(baseline.note.slice(range.start, range.end));
687
+ const currentRange = currentRanges[index];
688
+ return baselineId !== void 0 && baselineId !== openingParaId(current.note.slice(currentRange.start, currentRange.end));
689
+ });
690
+ };
650
691
  const collectChangedNoteParaIds = (baselineXml, currentXml) => {
651
692
  const changed = /* @__PURE__ */ new Set();
652
693
  const baselineIds = collectParaIds(baselineXml);
@@ -681,6 +722,7 @@ const collectChangedNoteParaIds = (baselineXml, currentXml) => {
681
722
  */
682
723
  function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }) {
683
724
  const currentElements = collectNoteElementSyntax(serializedXml, elementName);
725
+ const baselineElements = collectNoteElementSyntax(baselineXml, elementName);
684
726
  const originalElements = collectNoteElementSyntax(originalXml, elementName);
685
727
  const replacementElements = collectNoteElementSyntax(replacementXml, elementName);
686
728
  const replacementXmlnsDeclarations = collectXmlnsFromOpeningTag(replacementXml);
@@ -716,7 +758,17 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
716
758
  const originalParagraphs = paragraphRanges(originalNote, originalSyntax.elementPrefix);
717
759
  const replacementParagraphs = paragraphRanges(replacementNote, replacementSyntax.elementPrefix);
718
760
  const noteChangedParaIds = [...collectParaIds(currentNote).keys()].filter((paraId) => unroutedChangedParaIds.has(paraId));
719
- if (noteChangedParaIds.length === 0) continue;
761
+ const baselineSyntaxEntries = baselineElements.get(id);
762
+ const baselineSyntax = baselineSyntaxEntries?.length === 1 ? baselineSyntaxEntries[0] : void 0;
763
+ const baselineNote = baselineSyntax ? extractNoteElement(baselineXml, baselineSyntax, id) : null;
764
+ const reshaped = baselineSyntax !== void 0 && baselineNote !== null && baselineNote !== currentNote && noteShapeChanged({
765
+ note: baselineNote,
766
+ prefix: baselineSyntax.elementPrefix
767
+ }, {
768
+ note: currentNote,
769
+ prefix: currentSyntax.elementPrefix
770
+ });
771
+ if (noteChangedParaIds.length === 0 && !reshaped) continue;
720
772
  const wholeNoteSplice = {
721
773
  start: originalOffsets.start,
722
774
  end: originalOffsets.end,
@@ -727,7 +779,7 @@ function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, repl
727
779
  })
728
780
  };
729
781
  noteSplices.push(wholeNoteSplice);
730
- if (currentParagraphs.length !== originalParagraphs.length || currentParagraphs.length !== replacementParagraphs.length) {
782
+ if (reshaped || currentParagraphs.length !== originalParagraphs.length || currentParagraphs.length !== replacementParagraphs.length) {
731
783
  for (const paraId of noteChangedParaIds) unroutedChangedParaIds.delete(paraId);
732
784
  paragraphSplices.push(wholeNoteSplice);
733
785
  continue;
@@ -892,8 +944,24 @@ function originalCustomNumFmtFormat(numFmtElement) {
892
944
  */
893
945
  function collectXmlnsFromOpeningTag(elementXml) {
894
946
  const out = {};
895
- const tagEnd = elementXml.indexOf(">");
896
- const openTag = tagEnd === -1 ? elementXml : elementXml.slice(0, tagEnd);
947
+ let tagStart = elementXml.indexOf("<");
948
+ while (tagStart !== -1 && (elementXml.startsWith("<?", tagStart) || elementXml.startsWith("<!--", tagStart))) {
949
+ const endMarker = elementXml.startsWith("<!--", tagStart) ? "-->" : "?>";
950
+ const end = elementXml.indexOf(endMarker, tagStart);
951
+ if (end === -1) return out;
952
+ tagStart = elementXml.indexOf("<", end + endMarker.length);
953
+ }
954
+ if (tagStart === -1) return out;
955
+ let tagEnd = tagStart + 1;
956
+ let quote = null;
957
+ while (tagEnd < elementXml.length) {
958
+ const character = elementXml[tagEnd];
959
+ if (character === quote) quote = null;
960
+ else if (quote === null && (character === "\"" || character === "'")) quote = character;
961
+ else if (quote === null && character === ">") break;
962
+ tagEnd += 1;
963
+ }
964
+ const openTag = elementXml.slice(tagStart, tagEnd);
897
965
  const pattern = /\s(?<name>xmlns(?::[\w.-]+)?)="(?<uri>[^"]*)"/gu;
898
966
  let match;
899
967
  while ((match = pattern.exec(openTag)) !== null) out[match.groups["name"]] = match.groups["uri"];
@@ -1056,9 +1124,10 @@ const patchNumberingByNamespace = ({ original, currentXml, changed, added }) =>
1056
1124
  const patchNumberingDefinitions = ({ originalXml, baselineXml, currentXml }) => {
1057
1125
  const changed = collectChangedNumberingDefs(baselineXml, currentXml);
1058
1126
  const added = collectAddedNumberingDefs(baselineXml, currentXml);
1127
+ if (changed.abstractNums.size === 0 && changed.nums.size === 0 && added.abstractNums.size === 0 && added.nums.size === 0) return originalXml;
1059
1128
  const original = parseXmlDocument(originalXml);
1060
1129
  if (!original || getLocalName(original.name) !== "numbering" || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(original) ?? "")) return null;
1061
- if (getNamespacePrefix(original.name ?? "") !== "w") return patchNumberingByNamespace({
1130
+ if (getNamespacePrefix(original.name ?? "") !== "w" || !hasCanonicalWordprocessingPrefixes(originalXml)) return patchNumberingByNamespace({
1062
1131
  original,
1063
1132
  currentXml,
1064
1133
  changed,
@@ -0,0 +1,5 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ //#region src/docx/serializer/blockCustomXmlSerializer.d.ts
3
+ declare const serializeBlockCustomXml: (block: document_d_exports.BlockCustomXml, serializeChild: (child: document_d_exports.BlockContent) => string) => string;
4
+ //#endregion
5
+ export { serializeBlockCustomXml };
@@ -0,0 +1,4 @@
1
+ //#region src/docx/serializer/blockCustomXmlSerializer.ts
2
+ const serializeBlockCustomXml = (block, serializeChild) => block.openingXml + block.content.map(serializeChild).join("") + block.closingXml;
3
+ //#endregion
4
+ export { serializeBlockCustomXml };