@stll/folio-core 0.11.0 → 0.13.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 (263) hide show
  1. package/dist/ai-edits/apply.d.ts +2 -1
  2. package/dist/ai-edits/apply.js +693 -63
  3. package/dist/ai-edits/block-identity.d.ts +4 -0
  4. package/dist/ai-edits/block-identity.js +9 -0
  5. package/dist/ai-edits/blockRange.d.ts +28 -1
  6. package/dist/ai-edits/blockRange.js +68 -2
  7. package/dist/ai-edits/clean-text.js +2 -1
  8. package/dist/ai-edits/headless.d.ts +4 -4
  9. package/dist/ai-edits/headless.js +9 -9
  10. package/dist/ai-edits/index.d.ts +2 -2
  11. package/dist/ai-edits/index.js +2 -2
  12. package/dist/ai-edits/read.d.ts +7 -7
  13. package/dist/ai-edits/read.js +110 -3
  14. package/dist/ai-edits/snapshot.js +21 -3
  15. package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
  16. package/dist/ai-edits/table-cell-mutations.js +238 -0
  17. package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
  18. package/dist/ai-edits/table-mutation-plan.js +90 -0
  19. package/dist/ai-edits/table-row-column-mutations.d.ts +108 -0
  20. package/dist/ai-edits/table-row-column-mutations.js +300 -0
  21. package/dist/ai-edits/table-targets.d.ts +32 -0
  22. package/dist/ai-edits/table-targets.js +70 -0
  23. package/dist/ai-edits/types.d.ts +62 -1
  24. package/dist/ai-edits/word-diff.js +21 -0
  25. package/dist/compat/eigenpal.d.ts +3 -3
  26. package/dist/compat/eigenpal.js +3 -3
  27. package/dist/content-controls/mutateContentControls.js +1 -1
  28. package/dist/controller/activeEditorStory.d.ts +36 -0
  29. package/dist/controller/activeEditorStory.js +30 -0
  30. package/dist/controller/contentControlWidgetController.d.ts +55 -0
  31. package/dist/controller/contentControlWidgetController.js +141 -0
  32. package/dist/controller/fontReadiness.js +2 -0
  33. package/dist/controller/headerFooterEditorManager.js +2 -2
  34. package/dist/controller/hiddenEditorApi.js +2 -2
  35. package/dist/controller/hiddenEditorManager.d.ts +12 -1
  36. package/dist/controller/hiddenEditorManager.js +44 -12
  37. package/dist/controller/layoutPipeline.d.ts +2 -0
  38. package/dist/controller/layoutPipeline.js +17 -2
  39. package/dist/controller/noteEditorManager.d.ts +36 -0
  40. package/dist/controller/noteEditorManager.js +210 -0
  41. package/dist/document-operations.d.ts +43 -11
  42. package/dist/document-operations.js +186 -18
  43. package/dist/docx/blockContentParser.js +21 -7
  44. package/dist/docx/commentParser.js +6 -1
  45. package/dist/docx/commentRangeIntegrity.d.ts +6 -0
  46. package/dist/docx/commentRangeIntegrity.js +186 -0
  47. package/dist/docx/conformance.js +62 -1
  48. package/dist/docx/documentGrid.d.ts +8 -0
  49. package/dist/docx/documentGrid.js +8 -0
  50. package/dist/docx/drawingUtils.js +1 -1
  51. package/dist/docx/encryption/agileDecryption.js +12 -2
  52. package/dist/docx/encryption/compoundFile.js +6 -1
  53. package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
  54. package/dist/docx/encryption/encryptionInfo.js +18 -2
  55. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  56. package/dist/docx/ensureParaIds.js +8 -7
  57. package/dist/docx/fieldParser.js +1 -1
  58. package/dist/docx/groupDrawingParser.js +10 -5
  59. package/dist/docx/hyperlinkParser.js +1 -1
  60. package/dist/docx/imageParser.js +10 -6
  61. package/dist/docx/metadataPrivacy.js +17 -3
  62. package/dist/docx/notePropertiesParser.js +1 -1
  63. package/dist/docx/numberingParser.js +1 -1
  64. package/dist/docx/paragraphParser.js +70 -61
  65. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  66. package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
  67. package/dist/docx/paragraphTraversal.js +1 -1
  68. package/dist/docx/parser.js +3 -3
  69. package/dist/docx/rezip.js +20 -9
  70. package/dist/docx/runParser.js +17 -6
  71. package/dist/docx/sdtProperties.js +1 -1
  72. package/dist/docx/sectionParser.js +18 -5
  73. package/dist/docx/selectiveSave.js +5 -1
  74. package/dist/docx/selectiveXmlPatch.d.ts +22 -1
  75. package/dist/docx/selectiveXmlPatch.js +61 -1
  76. package/dist/docx/serializer/blockSdtSerializer.js +3 -2
  77. package/dist/docx/serializer/commentSerializer.js +5 -6
  78. package/dist/docx/serializer/documentSerializer.js +2 -2
  79. package/dist/docx/serializer/headerFooterSerializer.js +2 -2
  80. package/dist/docx/serializer/noteSerializer.js +2 -2
  81. package/dist/docx/serializer/paragraphSerializer.js +23 -15
  82. package/dist/docx/serializer/runSerializer.js +25 -21
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
  84. package/dist/docx/serializer/stylesSerializer.js +1 -1
  85. package/dist/docx/serializer/tableSerializer.d.ts +5 -4
  86. package/dist/docx/serializer/tableSerializer.js +26 -20
  87. package/dist/docx/serializer/xmlUtils.d.ts +13 -1
  88. package/dist/docx/serializer/xmlUtils.js +27 -1
  89. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
  90. package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
  91. package/dist/docx/server/boundedArchive.d.ts +14 -3
  92. package/dist/docx/server/boundedArchive.js +56 -11
  93. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
  94. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
  95. package/dist/docx/server/extractDocxText.js +55 -8
  96. package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
  97. package/dist/docx/server/inspectDocxPackage.js +217 -0
  98. package/dist/docx/server/validateDocxConformance.d.ts +41 -0
  99. package/dist/docx/server/validateDocxConformance.js +336 -0
  100. package/dist/docx/settingsParser.js +26 -3
  101. package/dist/docx/shapeParser.js +1 -1
  102. package/dist/docx/styleParser.js +4 -2
  103. package/dist/docx/tableParser.d.ts +8 -1
  104. package/dist/docx/tableParser.js +31 -10
  105. package/dist/docx/textBoxParser.d.ts +3 -3
  106. package/dist/docx/textBoxParser.js +16 -12
  107. package/dist/docx/unzip.js +1 -0
  108. package/dist/docx/vmlImageParser.js +11 -3
  109. package/dist/docx/xmlParser.js +11 -1
  110. package/dist/docx/xmlSafety.d.ts +5 -0
  111. package/dist/docx/xmlSafety.js +9 -0
  112. package/dist/fields/resolveFieldValues.js +1 -3
  113. package/dist/fonts/embeddedFonts.d.ts +33 -5
  114. package/dist/fonts/embeddedFonts.js +37 -8
  115. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  116. package/dist/i18n/messages/catalogs.gen.js +17 -0
  117. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  118. package/dist/index.d.ts +3 -3
  119. package/dist/index.js +3 -3
  120. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  121. package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
  122. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  123. package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
  124. package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
  125. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  126. package/dist/layout-bridge/convert/toFlowBlocks.js +151 -20
  127. package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
  128. package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
  129. package/dist/layout-bridge/engine/clickToPosition.js +2 -2
  130. package/dist/layout-engine/index.d.ts +2 -2
  131. package/dist/layout-engine/index.js +58 -30
  132. package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
  133. package/dist/layout-engine/justificationCompatibility.js +8 -0
  134. package/dist/layout-engine/keep-together.js +11 -1
  135. package/dist/layout-engine/measure/cache.js +23 -5
  136. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
  137. package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
  138. package/dist/layout-engine/measure/lineBreakProvider.js +56 -37
  139. package/dist/layout-engine/measure/measureBlocks.js +15 -4
  140. package/dist/layout-engine/measure/measureContainer.js +1 -1
  141. package/dist/layout-engine/measure/measureHelpers.d.ts +11 -2
  142. package/dist/layout-engine/measure/measureHelpers.js +15 -4
  143. package/dist/layout-engine/measure/measureParagraph.js +85 -25
  144. package/dist/layout-engine/measure/measureProvider.js +4 -3
  145. package/dist/layout-engine/measure/tableCellFlow.js +5 -2
  146. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
  147. package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
  148. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
  149. package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
  150. package/dist/layout-engine/paginator.js +5 -1
  151. package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
  152. package/dist/layout-engine/paragraphSpacing.js +107 -13
  153. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  154. package/dist/layout-engine/types.d.ts +48 -15
  155. package/dist/layout-painter/registry/modules/textBox.js +5 -1
  156. package/dist/layout-painter/renderImage.d.ts +12 -1
  157. package/dist/layout-painter/renderImage.js +21 -4
  158. package/dist/layout-painter/renderPage.d.ts +5 -2
  159. package/dist/layout-painter/renderPage.js +59 -14
  160. package/dist/layout-painter/renderParagraph.js +55 -15
  161. package/dist/layout-painter/renderTable.d.ts +5 -1
  162. package/dist/layout-painter/renderTable.js +7 -3
  163. package/dist/layout-painter/renderTextBox.d.ts +2 -1
  164. package/dist/layout-painter/renderTextBox.js +25 -14
  165. package/dist/layout-painter/renderWatermark.d.ts +1 -1
  166. package/dist/layout-painter/renderWatermark.js +2 -1
  167. package/dist/managers/DocumentLoaderManager.js +1 -1
  168. package/dist/markdown/fromMarkdown.js +1 -1
  169. package/dist/model.js +1 -1
  170. package/dist/prosemirror/attrs/index.d.ts +4 -2
  171. package/dist/prosemirror/attrs/index.js +175 -18
  172. package/dist/prosemirror/commands/comments.d.ts +79 -4
  173. package/dist/prosemirror/commands/comments.js +622 -13
  174. package/dist/prosemirror/commands/contentControls.js +1 -1
  175. package/dist/prosemirror/commands/hyperlink.js +15 -1
  176. package/dist/prosemirror/commands/image.js +7 -1
  177. package/dist/prosemirror/commands/index.d.ts +2 -2
  178. package/dist/prosemirror/commands/index.js +2 -2
  179. package/dist/prosemirror/commands/pageBreak.js +1 -1
  180. package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
  181. package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
  182. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
  183. package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
  184. package/dist/prosemirror/commentIdAllocator.js +13 -3
  185. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
  186. package/dist/prosemirror/conversion/fromProseDoc.js +404 -62
  187. package/dist/prosemirror/conversion/index.js +1 -1
  188. package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
  189. package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
  190. package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
  191. package/dist/prosemirror/conversion/toProseDoc.js +364 -103
  192. package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
  193. package/dist/prosemirror/extensions/StarterKit.js +6 -1
  194. package/dist/prosemirror/extensions/core/ParagraphExtension.js +6 -2
  195. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
  196. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
  197. package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
  198. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
  199. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
  200. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
  201. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
  202. package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
  203. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
  204. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
  205. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
  206. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +70 -11
  207. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  208. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
  209. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
  210. package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
  211. package/dist/prosemirror/extensions/nodes/TableExtension.js +14 -6
  212. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
  213. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
  214. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
  215. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
  216. package/dist/prosemirror/index.d.ts +1 -1
  217. package/dist/prosemirror/index.js +1 -1
  218. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +36 -10
  219. package/dist/prosemirror/plugins/contentControlWidgets.js +128 -94
  220. package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
  221. package/dist/prosemirror/plugins/revisionIds.js +57 -0
  222. package/dist/prosemirror/plugins/selectionTracker.js +1 -0
  223. package/dist/prosemirror/plugins/suggestionMode.js +5 -4
  224. package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
  225. package/dist/prosemirror/runFormattingMarkNames.js +40 -0
  226. package/dist/prosemirror/schema/index.d.ts +2 -2
  227. package/dist/prosemirror/schema/marks.d.ts +30 -3
  228. package/dist/prosemirror/schema/marks.js +15 -0
  229. package/dist/prosemirror/schema/nodes.d.ts +111 -6
  230. package/dist/prosemirror/selectionState.js +1 -0
  231. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  232. package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
  233. package/dist/prosemirror/tableCellMergeRevision.js +22 -0
  234. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
  235. package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
  236. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
  237. package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
  238. package/dist/prosemirror/validation.js +9 -1
  239. package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
  240. package/dist/server.d.ts +7 -3
  241. package/dist/server.js +7 -3
  242. package/dist/utils/clipboard.d.ts +10 -1
  243. package/dist/utils/clipboard.js +33 -6
  244. package/dist/utils/colorResolver.d.ts +8 -1
  245. package/dist/utils/colorResolver.js +35 -5
  246. package/dist/utils/fontFamilyMerge.js +9 -11
  247. package/dist/utils/fontLoader.js +3 -2
  248. package/dist/utils/fontResolver.d.ts +2 -1
  249. package/dist/utils/fontResolver.js +31 -7
  250. package/dist/utils/fontWeights.d.ts +1 -1
  251. package/dist/utils/fontWeights.js +1 -1
  252. package/dist/utils/hexId.d.ts +11 -1
  253. package/dist/utils/hexId.js +11 -1
  254. package/dist/utils/paragraphFormattingMerge.js +1 -0
  255. package/dist/utils/sanitizeImageSrc.d.ts +21 -0
  256. package/dist/utils/sanitizeImageSrc.js +30 -0
  257. package/dist/utils/stripXmlDeclarations.d.ts +11 -0
  258. package/dist/utils/stripXmlDeclarations.js +35 -0
  259. package/dist/version-comparison.d.ts +22 -2
  260. package/dist/version-comparison.js +41 -12
  261. package/dist/watermark/index.d.ts +11 -1
  262. package/dist/watermark/index.js +22 -1
  263. package/package.json +3 -2
@@ -1,12 +1,14 @@
1
- import { elementToXml, findChild, findChildren, getAttribute, getChildElements, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
2
- import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
3
1
  import { BorderStyleSchema, FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, TabLeaderSchema, TabStopAlignmentSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
2
+ import { elementToXml, findChild, findChildren, getAttribute, getChildElements, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
4
3
  import { parseRun, parseRunProperties } from "./runParser.js";
5
4
  import { parseFieldType } from "./fieldParser.js";
5
+ import { isValidHexId } from "../utils/hexId.js";
6
+ import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
6
7
  import { parseHyperlink as parseHyperlink$1 } from "./hyperlinkParser.js";
7
8
  import { consolidateParagraphContent } from "./runConsolidator.js";
8
9
  import { parseSdtProperties } from "./sdtProperties.js";
9
10
  import { parseSectionProperties } from "./sectionParser.js";
11
+ import { normalizeRevisionId } from "@stll/docx-core/model";
10
12
  //#region src/docx/paragraphParser.ts
11
13
  /**
12
14
  * Extract plain text from a math element (recursive text content extraction)
@@ -191,6 +193,9 @@ function collectFirstParagraphPropertyChildren(pPr) {
191
193
  case "shd":
192
194
  children.shd ??= child;
193
195
  break;
196
+ case "snapToGrid":
197
+ children.snapToGrid ??= child;
198
+ break;
194
199
  case "spacing":
195
200
  children.spacing ??= child;
196
201
  break;
@@ -243,6 +248,8 @@ function parseParagraphProperties(pPr, theme, styles) {
243
248
  }
244
249
  const bidi = propertyChildren.bidi;
245
250
  if (bidi) formatting.bidi = parseBooleanElement(bidi);
251
+ const snapToGrid = propertyChildren.snapToGrid;
252
+ if (snapToGrid) formatting.snapToGrid = parseBooleanElement(snapToGrid);
246
253
  const spacing = propertyChildren.spacing;
247
254
  if (spacing) {
248
255
  const before = parseNumericAttribute(spacing, "w", "before");
@@ -369,64 +376,64 @@ function getLocalName(name) {
369
376
  const colonIndex = name.indexOf(":");
370
377
  return colonIndex !== -1 ? name.slice(colonIndex + 1) : name;
371
378
  }
379
+ const RENDERED_BREAK_INLINE_WRAPPERS = /* @__PURE__ */ new Set([
380
+ "hyperlink",
381
+ "smartTag",
382
+ "sdt",
383
+ "sdtContent",
384
+ "fldSimple",
385
+ "customXml",
386
+ "ins",
387
+ "del",
388
+ "moveFrom",
389
+ "moveTo"
390
+ ]);
391
+ const RENDERED_BREAK_NON_CONTENT_MARKERS = /* @__PURE__ */ new Set([
392
+ "pPr",
393
+ "proofErr",
394
+ "bookmarkStart",
395
+ "bookmarkEnd",
396
+ "commentRangeStart",
397
+ "commentRangeEnd",
398
+ "commentReference",
399
+ "permStart",
400
+ "permEnd",
401
+ "rsidR"
402
+ ]);
403
+ const RENDERED_BREAK_VISIBLE_RUN_CONTENT = /* @__PURE__ */ new Set([
404
+ "t",
405
+ "tab",
406
+ "br",
407
+ "cr",
408
+ "sym",
409
+ "drawing",
410
+ "pict",
411
+ "object",
412
+ "softHyphen",
413
+ "noBreakHyphen",
414
+ "fldChar",
415
+ "instrText",
416
+ "pgNum",
417
+ "separator",
418
+ "continuationSeparator",
419
+ "footnoteRef",
420
+ "endnoteRef",
421
+ "footnoteReference",
422
+ "endnoteReference",
423
+ "ptab",
424
+ "monthShort",
425
+ "monthLong",
426
+ "yearShort",
427
+ "yearLong",
428
+ "dayShort",
429
+ "dayLong"
430
+ ]);
372
431
  function paragraphStartsWithRenderedPageBreak(node) {
373
- const inlineWrappers = /* @__PURE__ */ new Set([
374
- "hyperlink",
375
- "smartTag",
376
- "sdt",
377
- "sdtContent",
378
- "fldSimple",
379
- "customXml",
380
- "ins",
381
- "del",
382
- "moveFrom",
383
- "moveTo"
384
- ]);
385
- const nonContentMarkers = /* @__PURE__ */ new Set([
386
- "pPr",
387
- "proofErr",
388
- "bookmarkStart",
389
- "bookmarkEnd",
390
- "commentRangeStart",
391
- "commentRangeEnd",
392
- "commentReference",
393
- "permStart",
394
- "permEnd",
395
- "rsidR"
396
- ]);
397
- const visibleRunContent = /* @__PURE__ */ new Set([
398
- "t",
399
- "tab",
400
- "br",
401
- "cr",
402
- "sym",
403
- "drawing",
404
- "pict",
405
- "object",
406
- "softHyphen",
407
- "noBreakHyphen",
408
- "fldChar",
409
- "instrText",
410
- "pgNum",
411
- "separator",
412
- "continuationSeparator",
413
- "footnoteRef",
414
- "endnoteRef",
415
- "footnoteReference",
416
- "endnoteReference",
417
- "ptab",
418
- "monthShort",
419
- "monthLong",
420
- "yearShort",
421
- "yearLong",
422
- "dayShort",
423
- "dayLong"
424
- ]);
425
432
  let sawRenderedPageBreak = false;
426
433
  const visit = (element) => {
427
434
  for (const child of getChildElements(element)) {
428
435
  const childName = getLocalName(child.name);
429
- if (nonContentMarkers.has(childName)) continue;
436
+ if (RENDERED_BREAK_NON_CONTENT_MARKERS.has(childName)) continue;
430
437
  if (childName === "lastRenderedPageBreak") {
431
438
  sawRenderedPageBreak = true;
432
439
  continue;
@@ -440,11 +447,11 @@ function paragraphStartsWithRenderedPageBreak(node) {
440
447
  continue;
441
448
  }
442
449
  if (runChildName === "br" && getAttribute(runChild, "w", "type") === "page") return "forced";
443
- if (visibleRunContent.has(runChildName)) return "visible";
450
+ if (RENDERED_BREAK_VISIBLE_RUN_CONTENT.has(runChildName)) return "visible";
444
451
  }
445
452
  continue;
446
453
  }
447
- if (inlineWrappers.has(childName)) {
454
+ if (RENDERED_BREAK_INLINE_WRAPPERS.has(childName)) {
448
455
  const result = visit(child);
449
456
  if (result !== "continue") return result;
450
457
  continue;
@@ -481,11 +488,13 @@ function parseTrackedChangeInfo(node) {
481
488
  const rawDate = getAttribute(node, "w", "date");
482
489
  const author = rawAuthor?.trim() ?? "";
483
490
  const date = rawDate?.trim() ?? "";
491
+ const initials = (getAttribute(node, "w", "initials") ?? "").trim();
484
492
  const info = {
485
- id: Number.isInteger(parsedId) && parsedId >= 0 ? parsedId : 0,
493
+ id: normalizeRevisionId(parsedId),
486
494
  author: author.length > 0 ? author : "Unknown"
487
495
  };
488
496
  if (date.length > 0) info.date = date;
497
+ if (initials.length > 0) info.initials = initials;
489
498
  return info;
490
499
  }
491
500
  function parsePropertyChangeInfo(node) {
@@ -535,7 +544,7 @@ function isTrackedChangeWrapperChild(content) {
535
544
  return content.type === "run" || content.type === "hyperlink";
536
545
  }
537
546
  function isInlineSdtContent(content) {
538
- return content.type === "run" || content.type === "hyperlink" || content.type === "simpleField" || content.type === "complexField" || content.type === "inlineSdt" || content.type === "insertion" || content.type === "deletion" || content.type === "mathEquation";
547
+ return content.type === "run" || content.type === "hyperlink" || content.type === "simpleField" || content.type === "complexField" || content.type === "inlineSdt" || content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo" || content.type === "mathEquation";
539
548
  }
540
549
  function pushTrackedChangeWrapper({ contents, type, info, content, preserveEmpty = false }) {
541
550
  if (content.length === 0 && !preserveEmpty) return;
@@ -934,9 +943,9 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
934
943
  content: []
935
944
  };
936
945
  const paraId = getAttribute(node, "w14", "paraId") ?? getAttribute(node, "w", "paraId");
937
- if (paraId) paragraph.paraId = paraId;
946
+ if (paraId && isValidHexId(paraId)) paragraph.paraId = paraId;
938
947
  const textId = getAttribute(node, "w14", "textId") ?? getAttribute(node, "w", "textId");
939
- if (textId) paragraph.textId = textId;
948
+ if (textId && isValidHexId(textId)) paragraph.textId = textId;
940
949
  if (!options?.inHeaderFooter && paragraphStartsWithRenderedPageBreak(node)) paragraph.renderedPageBreakBefore = true;
941
950
  const pPr = findChild(node, "w", "pPr");
942
951
  if (pPr) {
@@ -2,8 +2,9 @@ import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
3
  import { StyleMap } from "./styleParser.js";
4
4
  import { XmlElement } from "./xmlParser.js";
5
+ import { TableParserFn } from "./textBoxParser.js";
5
6
 
6
7
  //#region src/docx/paragraphTextBoxEnrichment.d.ts
7
- declare const enrichParagraphTextBoxes: (paragraph: document_d_exports.Paragraph, paraXml: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => void;
8
+ declare const enrichParagraphTextBoxes: (paragraph: document_d_exports.Paragraph, paraXml: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, parseTable: TableParserFn) => void;
8
9
  //#endregion
9
10
  export { enrichParagraphTextBoxes };
@@ -72,26 +72,80 @@ const vmlWrapType = (positioned, zIndex) => {
72
72
  if (Number.isFinite(zIndex) && zIndex < 0) return "behind";
73
73
  return "inFront";
74
74
  };
75
- const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering, rels, media) => {
76
- const xmlChildren = getChildElements(paraXml);
75
+ const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering, rels, media, parseTable) => {
76
+ enrichTextBoxRuns({
77
+ content: paragraph.content,
78
+ xmlChildren: getChildElements(paraXml),
79
+ styles,
80
+ theme,
81
+ numbering,
82
+ rels,
83
+ media,
84
+ parseTable
85
+ });
86
+ };
87
+ const trackedChangeTypeFromXml = (localName) => {
88
+ if (localName === "ins") return "insertion";
89
+ if (localName === "del") return "deletion";
90
+ if (localName === "moveFrom" || localName === "moveTo") return localName;
91
+ };
92
+ const enrichTextBoxRuns = ({ content, xmlChildren, styles, theme, numbering, rels, media, parseTable }) => {
77
93
  let parsedIndex = 0;
78
94
  let lastConsumedRun;
79
95
  for (const xmlChild of xmlChildren) {
80
- if (getLocalName(xmlChild.name ?? "") !== "r") {
81
- if (parsedIndex < paragraph.content.length && paragraph.content[parsedIndex]?.type !== "run") parsedIndex += 1;
96
+ const localName = getLocalName(xmlChild.name ?? "");
97
+ if (localName === "pPr") continue;
98
+ const trackedChangeType = trackedChangeTypeFromXml(localName);
99
+ const parsedContent = content[parsedIndex];
100
+ if (trackedChangeType && parsedContent?.type === trackedChangeType) enrichTextBoxRuns({
101
+ content: parsedContent.content,
102
+ xmlChildren: getChildElements(xmlChild),
103
+ styles,
104
+ theme,
105
+ numbering,
106
+ rels,
107
+ media,
108
+ parseTable
109
+ });
110
+ if (localName === "sdt" && parsedContent?.type === "inlineSdt") {
111
+ const properties = parsedContent.properties;
112
+ const nestedContent = [];
113
+ let lastSegmentIndex = parsedIndex;
114
+ for (let index = parsedIndex; index < content.length; index += 1) {
115
+ const candidate = content[index];
116
+ if (candidate?.type !== "inlineSdt" || candidate.properties !== properties) continue;
117
+ nestedContent.push(...candidate.content);
118
+ lastSegmentIndex = index;
119
+ }
120
+ const sdtContent = getChildElements(xmlChild).find((child) => getLocalName(child.name ?? "") === "sdtContent");
121
+ if (sdtContent) enrichTextBoxRuns({
122
+ content: nestedContent,
123
+ xmlChildren: getChildElements(sdtContent),
124
+ styles,
125
+ theme,
126
+ numbering,
127
+ rels,
128
+ media,
129
+ parseTable
130
+ });
131
+ parsedIndex = lastSegmentIndex + 1;
132
+ continue;
133
+ }
134
+ if (localName !== "r") {
135
+ if (parsedIndex < content.length && parsedContent?.type !== "run") parsedIndex += 1;
82
136
  continue;
83
137
  }
84
138
  const { textBoxDrawings, vmlTextBoxes, hasNonTextBoxContent } = scanRunForTextBoxDrawings(xmlChild);
85
- const parsedContent = paragraph.content[parsedIndex];
86
139
  const parsedRun = parsedContent?.type === "run" ? parsedContent : void 0;
87
140
  const targetRun = parsedRun ?? (hasNonTextBoxContent ? lastConsumedRun : void 0);
141
+ const targetRunMatchesXml = targetRun !== void 0 && (hasNonTextBoxContent || parsedRun?.content.length === 0);
88
142
  for (const runEl of textBoxDrawings) {
89
143
  const textBox = parseTextBox(runEl);
90
144
  if (!textBox) continue;
91
145
  const wsp = findDeep(runEl, "wps", "wsp");
92
146
  if (wsp) {
93
147
  const txbxContentEl = getTextBoxContentElement(wsp);
94
- if (txbxContentEl) textBox.content = parseTextBoxContent(txbxContentEl, parseParagraph, null, styles, theme, numbering, rels ?? void 0, media ?? void 0);
148
+ if (txbxContentEl) textBox.content = parseTextBoxContent(txbxContentEl, parseParagraph, parseTable, styles, theme, numbering, rels, media);
95
149
  }
96
150
  const shape = {
97
151
  type: "shape",
@@ -112,31 +166,31 @@ const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering,
112
166
  type: "shape",
113
167
  shape
114
168
  };
115
- if (targetRun && hasNonTextBoxContent) targetRun.content.push(shapeContent);
169
+ if (targetRunMatchesXml) targetRun.content.push(shapeContent);
116
170
  else {
117
171
  const newRun = {
118
172
  type: "run",
119
173
  content: [shapeContent]
120
174
  };
121
- paragraph.content.splice(parsedIndex, 0, newRun);
175
+ content.splice(parsedIndex, 0, newRun);
122
176
  lastConsumedRun = newRun;
123
177
  parsedIndex += 1;
124
178
  }
125
179
  }
126
180
  for (const pictEl of vmlTextBoxes) {
127
- const shape = parseVmlTextBoxShape(pictEl, styles, theme, numbering, rels, media);
181
+ const shape = parseVmlTextBoxShape(pictEl, styles, theme, numbering, rels, media, parseTable);
128
182
  if (!shape) continue;
129
183
  const shapeContent = {
130
184
  type: "shape",
131
185
  shape
132
186
  };
133
- if (targetRun && hasNonTextBoxContent) targetRun.content.push(shapeContent);
187
+ if (targetRunMatchesXml) targetRun.content.push(shapeContent);
134
188
  else {
135
189
  const newRun = {
136
190
  type: "run",
137
191
  content: [shapeContent]
138
192
  };
139
- paragraph.content.splice(parsedIndex, 0, newRun);
193
+ content.splice(parsedIndex, 0, newRun);
140
194
  lastConsumedRun = newRun;
141
195
  parsedIndex += 1;
142
196
  }
@@ -196,7 +250,7 @@ const scanRunForTextBoxDrawings = (xmlRun) => {
196
250
  hasNonTextBoxContent
197
251
  };
198
252
  };
199
- const parseVmlTextBoxShape = (pictEl, styles, theme, numbering, rels, media) => {
253
+ const parseVmlTextBoxShape = (pictEl, styles, theme, numbering, rels, media, parseTable) => {
200
254
  const shapeEl = findDeep(pictEl, "v", "shape");
201
255
  const textBoxEl = shapeEl ? findDeep(shapeEl, "v", "textbox") : null;
202
256
  const contentEl = textBoxEl ? findDeep(textBoxEl, "w", "txbxContent") : null;
@@ -221,7 +275,7 @@ const parseVmlTextBoxShape = (pictEl, styles, theme, numbering, rels, media) =>
221
275
  ...fill === void 0 ? {} : { fill },
222
276
  wrap: { type: vmlWrapType(positioned, zIndex) },
223
277
  textBody: {
224
- content: parseTextBoxContent(contentEl, parseParagraph, null, styles, theme, numbering, rels ?? void 0, media ?? void 0),
278
+ content: parseTextBoxContent(contentEl, parseParagraph, parseTable, styles, theme, numbering, rels, media),
225
279
  ...margins === void 0 ? {} : { margins }
226
280
  }
227
281
  };
@@ -10,7 +10,7 @@ const visitDocxParagraphs = ({ documentBody, headers, footers, footnotes, endnot
10
10
  const visitRun = (run) => {
11
11
  for (const content of run.content) {
12
12
  if (content.type !== "shape" || !content.shape.textBody) continue;
13
- for (const paragraph of content.shape.textBody.content) visitParagraph(paragraph);
13
+ for (const block of content.shape.textBody.content) visitBlock(block);
14
14
  }
15
15
  };
16
16
  const visitHyperlink = (hyperlink) => {
@@ -1,13 +1,13 @@
1
+ import { parseNumbering } from "./numberingParser.js";
2
+ import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
3
+ import { applyThemeFontLang, parseTheme } from "./themeParser.js";
1
4
  import { toArrayBuffer } from "../utils/docxInput.js";
2
5
  import { loadFontsWithMapping } from "../utils/fontLoader.js";
3
6
  import { convertTiffToPngDataUrl, isTiffMimeType } from "../utils/tiffConverter.js";
4
- import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
5
- import { applyThemeFontLang, parseTheme } from "./themeParser.js";
6
7
  import { parseComments } from "./commentParser.js";
7
8
  import { normalizeCommentReferences } from "./commentReferenceNormalization.js";
8
9
  import { detectDocxConformanceClass } from "./conformance.js";
9
10
  import { parseCoreProperties } from "./corePropertiesParser.js";
10
- import { parseNumbering } from "./numberingParser.js";
11
11
  import { extractAllTemplateVariables, parseDocumentBody } from "./documentParser.js";
12
12
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
13
13
  import { parseFooter, parseHeader } from "./headerFooterParser.js";
@@ -1,10 +1,11 @@
1
- import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
2
1
  import { parseNumbering } from "./numberingParser.js";
2
+ import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
3
3
  import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
4
4
  import { assertValidFolioDocumentModel } from "./modelValidation.js";
5
5
  import { isPreservableDocxEntry } from "./unzip.js";
6
6
  import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
7
- import { buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, extractParagraphXml } from "./selectiveXmlPatch.js";
7
+ import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
8
+ import { buildParagraphOffsetIndex, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
8
9
  import { escapeXml } from "./serializer/xmlUtils.js";
9
10
  import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
10
11
  import { serializeDocument } from "./serializer/documentSerializer.js";
@@ -15,6 +16,7 @@ import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
15
16
  import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
16
17
  import { serializeStylesXml } from "./serializer/stylesSerializer.js";
17
18
  import { serializeThemeXml } from "./serializer/themeSerializer.js";
19
+ import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
18
20
  import { panic } from "better-result";
19
21
  import JSZip from "jszip";
20
22
  //#region src/docx/rezip.ts
@@ -369,7 +371,7 @@ async function processNewHyperlinks(newHyperlinks, zip, compressionLevel) {
369
371
  async function repackDocx(doc, options = {}) {
370
372
  if (!doc.originalBuffer) panic("Cannot repack document: no original buffer for round-trip. Use createDocx() for new documents.");
371
373
  const { compressionLevel = 6, updateModifiedDate = true, modifiedBy } = options;
372
- const exportDocument = doc;
374
+ const exportDocument = withoutOrphanCommentRanges(doc);
373
375
  const originalZip = await JSZip.loadAsync(doc.originalBuffer);
374
376
  const newZip = new JSZip();
375
377
  for (const [path, file] of Object.entries(originalZip.files)) {
@@ -431,7 +433,7 @@ async function repackDocx(doc, options = {}) {
431
433
  */
432
434
  async function repackDocxFromRaw(doc, rawContent, options = {}) {
433
435
  const { compressionLevel = 6, updateModifiedDate = true, modifiedBy } = options;
434
- const exportDocument = doc;
436
+ const exportDocument = withoutOrphanCommentRanges(doc);
435
437
  const newZip = new JSZip();
436
438
  for (const [path, file] of Object.entries(rawContent.originalZip.files)) {
437
439
  if (file.dir) {
@@ -854,10 +856,11 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
854
856
  const changedPaths = /* @__PURE__ */ new Set();
855
857
  for (const { watermark, relsPath, partPath } of pending) {
856
858
  let canonical;
857
- if (watermark.imageTarget !== void 0) canonical = watermark.imageTargetExternal ? {
859
+ if (watermark.imageTarget !== void 0) if (watermark.imageTargetExternal) canonical = isAllowedExternalWatermarkImageUrl(watermark.imageTarget) ? {
858
860
  mode: "external",
859
861
  url: watermark.imageTarget
860
- } : {
862
+ } : resolveCanonical(watermark.imageRId);
863
+ else canonical = {
861
864
  mode: "internal",
862
865
  absolute: watermark.imageTarget
863
866
  };
@@ -1000,15 +1003,23 @@ async function patchNotePartIntoZip(conventionalLowerPath, currentXml, baselineF
1000
1003
  * (re-parsed original) serialization — i.e. the ones actually edited. A
1001
1004
  * paragraph is only considered when its `paraId` resolves uniquely in both,
1002
1005
  * so it can be spliced safely.
1006
+ *
1007
+ * Builds one {@link buildParagraphOffsetIndex} per side (a single linear scan
1008
+ * each) instead of calling `extractParagraphXml` per candidate id, which
1009
+ * re-scanned the whole XML per id — O(note count * XML size) for a document
1010
+ * with many footnotes/endnotes. The index turns each lookup below into O(1).
1003
1011
  */
1004
1012
  function collectChangedNoteParaIds(baselineXml, currentXml) {
1005
1013
  const changed = /* @__PURE__ */ new Set();
1006
1014
  const baselineIds = collectParaIds(baselineXml);
1015
+ const baselineOffsets = buildParagraphOffsetIndex(baselineXml);
1016
+ const currentOffsets = buildParagraphOffsetIndex(currentXml);
1007
1017
  for (const [id, count] of collectParaIds(currentXml)) {
1008
1018
  if (count !== 1 || baselineIds.get(id) !== 1) continue;
1009
- const before = extractParagraphXml(baselineXml, id);
1010
- const after = extractParagraphXml(currentXml, id);
1011
- if (before !== null && after !== null && before !== after) changed.add(id);
1019
+ const beforeRange = baselineOffsets.get(id);
1020
+ const afterRange = currentOffsets.get(id);
1021
+ if (!beforeRange || !afterRange) continue;
1022
+ if (baselineXml.slice(beforeRange.start, beforeRange.end) !== currentXml.slice(afterRange.start, afterRange.end)) changed.add(id);
1012
1023
  }
1013
1024
  return changed;
1014
1025
  }
@@ -1,12 +1,21 @@
1
- import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
2
1
  import { EmphasisMarkSchema, FontThemeSchema, HighlightColorSchema, ShadingPatternSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
2
+ import { cloneWithXmlnsDeclarations, elementToXml, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
3
3
  import { parseImage } from "./imageParser.js";
4
4
  import { parseGroupDrawing } from "./groupDrawingParser.js";
5
5
  import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
6
6
  import { parseVmlImageContent } from "./vmlImageParser.js";
7
7
  import { resolveThemeFontRef } from "./themeParser.js";
8
+ import { normalizeRevisionId } from "@stll/docx-core/model";
8
9
  //#region src/docx/runParser.ts
9
10
  /**
11
+ * Sanity cap on `w:lang` `@w:val`/`@w:eastAsia`/`@w:bidi` tag length. BCP-47
12
+ * tags top out well under this; a hostile/corrupt tag here would drive the
13
+ * hyphenation dictionary lookup and segmenter cache keying with an
14
+ * attacker-sized string per run.
15
+ */
16
+ const MAX_LANGUAGE_TAG_LENGTH = 35;
17
+ const truncateLanguageTag = (value) => value === void 0 ? void 0 : value.slice(0, MAX_LANGUAGE_TAG_LENGTH);
18
+ /**
10
19
  * Parse color value from attributes
11
20
  */
12
21
  function parseColorValue(rgb, themeColor, themeTint, themeShade) {
@@ -267,9 +276,9 @@ function parseRunProperties(rPr, theme, _styles) {
267
276
  }
268
277
  const lang = propertyChildren.lang;
269
278
  if (lang) {
270
- const val = getAttribute(lang, "w", "val") || void 0;
271
- const eastAsia = getAttribute(lang, "w", "eastAsia") || void 0;
272
- const bidi = getAttribute(lang, "w", "bidi") || void 0;
279
+ const val = truncateLanguageTag(getAttribute(lang, "w", "val") || void 0);
280
+ const eastAsia = truncateLanguageTag(getAttribute(lang, "w", "eastAsia") || void 0);
281
+ const bidi = truncateLanguageTag(getAttribute(lang, "w", "bidi") || void 0);
273
282
  if (val || eastAsia || bidi) formatting.language = {
274
283
  ...val ? { val } : {},
275
284
  ...eastAsia ? { eastAsia } : {},
@@ -332,7 +341,7 @@ function parsePropertyChangeInfo(changeElement) {
332
341
  const date = (getAttribute(changeElement, "w", "date") ?? "").trim();
333
342
  const rsid = (getAttribute(changeElement, "w", "rsid") ?? "").trim();
334
343
  const info = {
335
- id: Number.isInteger(parsedId) && parsedId >= 0 ? parsedId : 0,
344
+ id: normalizeRevisionId(parsedId),
336
345
  author: author.length > 0 ? author : "Unknown"
337
346
  };
338
347
  if (date.length > 0) info.date = date;
@@ -551,7 +560,9 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
551
560
  break;
552
561
  }
553
562
  case "rPr": break;
554
- case "lastRenderedPageBreak": break;
563
+ case "lastRenderedPageBreak":
564
+ contents.push({ type: "renderedPageBreak" });
565
+ break;
555
566
  case "cr":
556
567
  contents.push({
557
568
  type: "break",
@@ -1,5 +1,5 @@
1
- import { elementToXml, findChild, getAttributeAnyPrefix, getLocalName, parseBooleanElement } from "./xmlParser.js";
2
1
  import { SdtLockSchema, narrowEnum } from "./parserEnums.js";
2
+ import { elementToXml, findChild, getAttributeAnyPrefix, getLocalName, parseBooleanElement } from "./xmlParser.js";
3
3
  //#region src/docx/sdtProperties.ts
4
4
  function parseListItems(el) {
5
5
  const items = [];
@@ -1,8 +1,21 @@
1
- import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
2
1
  import { BorderStyleSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
2
+ import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
3
3
  import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
4
4
  import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
5
+ import { normalizeRevisionId } from "@stll/docx-core/model";
5
6
  //#region src/docx/sectionParser.ts
7
+ /**
8
+ * Sanity cap on `w:cols/@w:num`. Word's column picker tops out well below
9
+ * this; a hostile/corrupt value here would force the layout engine to
10
+ * generate a proportional band per column.
11
+ */
12
+ const MAX_SECTION_COLUMNS = 45;
13
+ /**
14
+ * Sanity cap on `w:pgSz` `@w:w`/`@w:h`, in twips (~22in). Matches the
15
+ * existing `w:defaultTabStop` cap (`settingsParser.ts`) and bounds the
16
+ * ruler tick generators, which are sized off the page dimensions.
17
+ */
18
+ const MAX_PAGE_DIMENSION_TWIPS = 31680;
6
19
  const serializedSectionPropertyChildNames = /* @__PURE__ */ new Set([
7
20
  "headerReference",
8
21
  "footerReference",
@@ -55,7 +68,7 @@ function parsePropertyChangeInfo(node) {
55
68
  const date = (getAttribute(node, "w", "date") ?? "").trim();
56
69
  const rsid = (getAttribute(node, "w", "rsid") ?? "").trim();
57
70
  const info = {
58
- id: Number.isInteger(parsedId) && parsedId >= 0 ? parsedId : 0,
71
+ id: normalizeRevisionId(parsedId),
59
72
  author: author.length > 0 ? author : "Unknown"
60
73
  };
61
74
  if (date.length > 0) info.date = date;
@@ -170,9 +183,9 @@ function parseSectionProperties(sectPr) {
170
183
  const pgSz = findChild(sectPr, "w", "pgSz");
171
184
  if (pgSz) {
172
185
  const w = parseNumericAttribute(pgSz, "w", "w");
173
- if (w !== void 0) props.pageWidth = w;
186
+ if (w !== void 0) props.pageWidth = Math.min(w, MAX_PAGE_DIMENSION_TWIPS);
174
187
  const h = parseNumericAttribute(pgSz, "w", "h");
175
- if (h !== void 0) props.pageHeight = h;
188
+ if (h !== void 0) props.pageHeight = Math.min(h, MAX_PAGE_DIMENSION_TWIPS);
176
189
  const orientation = parseOrientation(getAttribute(pgSz, "w", "orient"));
177
190
  if (orientation) props.orientation = orientation;
178
191
  }
@@ -196,7 +209,7 @@ function parseSectionProperties(sectPr) {
196
209
  const cols = findChild(sectPr, "w", "cols");
197
210
  if (cols) {
198
211
  const num = parseNumericAttribute(cols, "w", "num");
199
- if (num !== void 0) props.columnCount = num;
212
+ if (num !== void 0) props.columnCount = Math.min(num, MAX_SECTION_COLUMNS);
200
213
  const space = parseNumericAttribute(cols, "w", "space");
201
214
  if (space !== void 0) props.columnSpace = space;
202
215
  const equalWidth = getAttribute(cols, "w", "equalWidth");
@@ -1,8 +1,10 @@
1
+ import { parseNumbering } from "./numberingParser.js";
1
2
  import { RELATIONSHIP_TYPES } from "./relsParser.js";
2
3
  import { parseCommentsExtended } from "./commentParser.js";
3
- import { parseNumbering } from "./numberingParser.js";
4
4
  import { validateFolioDocumentModel } from "./modelValidation.js";
5
+ import { isPreservableDocxEntry } from "./unzip.js";
5
6
  import { hasUnsynthesizedReplyRanges } from "./commentReplyMarkers.js";
7
+ import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
6
8
  import { buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
7
9
  import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
8
10
  import { serializeDocument } from "./serializer/documentSerializer.js";
@@ -182,6 +184,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
182
184
  if (hasNewImagesOrHyperlinks(content)) return null;
183
185
  if (hasUnmaterializedHeaderFooter(doc)) return null;
184
186
  if (hasModelDrivenPictureWatermark(doc)) return null;
187
+ if (withoutOrphanCommentRanges(doc) !== doc) return null;
185
188
  if (hasUnsynthesizedReplyRanges(doc)) return null;
186
189
  if (!validateFolioDocumentModel(doc).valid) return null;
187
190
  const comments = doc.package.document.comments ?? [];
@@ -189,6 +192,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
189
192
  const headerFooterUpdates = collectHeaderFooterUpdates(doc);
190
193
  try {
191
194
  const zip = await (await import("jszip")).default.loadAsync(originalBuffer);
195
+ for (const [path, file] of Object.entries(zip.files)) if (!file.dir && !isPreservableDocxEntry(path)) return null;
192
196
  const updates = /* @__PURE__ */ new Map();
193
197
  if (changedParaIds.size > 0) {
194
198
  const docXmlFile = zip.file("word/document.xml");
@@ -33,6 +33,27 @@ declare function findParagraphOffsets(xml: string, paraId: string): {
33
33
  * from a fully serialized document.xml string.
34
34
  */
35
35
  declare function extractParagraphXml(serializedXml: string, paraId: string): string | null;
36
+ type ParagraphOffsets = {
37
+ start: number;
38
+ end: number;
39
+ };
40
+ /**
41
+ * Single linear-scan index of every `<w:p>` element's start/end offsets,
42
+ * keyed by `w14:paraId`. Generalizes {@link findParagraphOffsets} (one
43
+ * regex-scan-plus-depth-walk per lookup) to build every paragraph's offsets
44
+ * in a single pass, so a caller that needs many paragraphs from the same XML
45
+ * (e.g. `collectChangedNoteParaIds` in rezip.ts, walking every note paraId)
46
+ * does O(1) map lookups afterward instead of re-scanning the whole XML once
47
+ * per id — O(ids * XML length) collapses to O(XML length).
48
+ *
49
+ * A `<w:p>` nested inside another (e.g. inside `mc:AlternateContent`) is
50
+ * indexed too via a depth stack, matching every element `findParagraphOffsets`
51
+ * can resolve. A paraId that appears on more than one element is ambiguous —
52
+ * mirroring {@link findParagraphOffsets}'s single-match requirement — and is
53
+ * omitted from the index, so a lookup misses exactly where the per-id
54
+ * function would return null.
55
+ */
56
+ declare function buildParagraphOffsetIndex(xml: string): Map<string, ParagraphOffsets>;
36
57
  /**
37
58
  * Count <w:p> elements in an XML string (top-level paragraph count).
38
59
  * Counts opening <w:p tags that are NOT self-closing.
@@ -109,4 +130,4 @@ declare function collectChangedNumberingDefs(baselineXml: string, currentXml: st
109
130
  */
110
131
  declare function buildPatchedNumberingXml(originalXml: string, currentXml: string, changed: ChangedNumberingDefs): string | null;
111
132
  //#endregion
112
- export { ChangedNumberingDefs, PatchSafetyOptions, PatchValidationResult, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
133
+ export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };