@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
@@ -74,6 +74,66 @@ function extractParagraphXml(serializedXml, paraId) {
74
74
  return serializedXml.slice(offsets.start, offsets.end);
75
75
  }
76
76
  /**
77
+ * Single linear-scan index of every `<w:p>` element's start/end offsets,
78
+ * keyed by `w14:paraId`. Generalizes {@link findParagraphOffsets} (one
79
+ * regex-scan-plus-depth-walk per lookup) to build every paragraph's offsets
80
+ * in a single pass, so a caller that needs many paragraphs from the same XML
81
+ * (e.g. `collectChangedNoteParaIds` in rezip.ts, walking every note paraId)
82
+ * does O(1) map lookups afterward instead of re-scanning the whole XML once
83
+ * per id — O(ids * XML length) collapses to O(XML length).
84
+ *
85
+ * A `<w:p>` nested inside another (e.g. inside `mc:AlternateContent`) is
86
+ * indexed too via a depth stack, matching every element `findParagraphOffsets`
87
+ * can resolve. A paraId that appears on more than one element is ambiguous —
88
+ * mirroring {@link findParagraphOffsets}'s single-match requirement — and is
89
+ * omitted from the index, so a lookup misses exactly where the per-id
90
+ * function would return null.
91
+ */
92
+ function buildParagraphOffsetIndex(xml) {
93
+ const seenCount = /* @__PURE__ */ new Map();
94
+ const ranges = /* @__PURE__ */ new Map();
95
+ const stack = [];
96
+ let pos = 0;
97
+ const noteOpen = (paraId) => {
98
+ if (paraId) seenCount.set(paraId, (seenCount.get(paraId) ?? 0) + 1);
99
+ };
100
+ const noteRange = (paraId, start, end) => {
101
+ if (paraId) ranges.set(paraId, {
102
+ start,
103
+ end
104
+ });
105
+ };
106
+ while (pos < xml.length) {
107
+ const tagStart = xml.indexOf("<", pos);
108
+ if (tagStart === -1) break;
109
+ if (xml.startsWith("</w:p>", tagStart)) {
110
+ const frame = stack.pop();
111
+ const end = tagStart + 6;
112
+ if (frame) noteRange(frame.paraId, frame.start, end);
113
+ pos = end;
114
+ continue;
115
+ }
116
+ if (!xml.startsWith("<w:p", tagStart) || !isXmlNameBoundary(xml[tagStart + 4])) {
117
+ pos = tagStart + 1;
118
+ continue;
119
+ }
120
+ const tagEnd = xml.indexOf(">", tagStart);
121
+ if (tagEnd === -1) break;
122
+ const openTag = xml.slice(tagStart, tagEnd + 1);
123
+ const paraId = /\bw14:paraId="(?<id>[^"]*)"/u.exec(openTag)?.groups?.["id"];
124
+ noteOpen(paraId);
125
+ if (xml[tagEnd - 1] === "/") noteRange(paraId, tagStart, tagEnd + 1);
126
+ else stack.push({
127
+ start: tagStart,
128
+ paraId
129
+ });
130
+ pos = tagEnd + 1;
131
+ }
132
+ const index = /* @__PURE__ */ new Map();
133
+ for (const [id, range] of ranges) if (seenCount.get(id) === 1) index.set(id, range);
134
+ return index;
135
+ }
136
+ /**
77
137
  * Count <w:p> elements in an XML string (top-level paragraph count).
78
138
  * Counts opening <w:p tags that are NOT self-closing.
79
139
  */
@@ -484,4 +544,4 @@ function escapeRegExp(str) {
484
544
  return str.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
485
545
  }
486
546
  //#endregion
487
- export { buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
547
+ export { buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, validatePatchSafety };
@@ -1,3 +1,4 @@
1
+ import { isSingleWellFormedElement } from "./xmlUtils.js";
1
2
  import { reconcileRawSdtPr } from "../sdtPropertiesPatch.js";
2
3
  //#region src/docx/serializer/blockSdtSerializer.ts
3
4
  function escapeXmlAttr(value) {
@@ -73,14 +74,14 @@ function extractDateFullDate(blockSdt) {
73
74
  }
74
75
  function serializeBlockSdt(blockSdt, serializeChild) {
75
76
  const props = blockSdt.properties;
76
- const baseSdtPr = props.rawPropertiesXml ?? serializeFallbackSdtPr(props);
77
+ const baseSdtPr = props.rawPropertiesXml && isSingleWellFormedElement(props.rawPropertiesXml, "sdtPr") ? props.rawPropertiesXml : serializeFallbackSdtPr(props);
77
78
  const dateFullDate = extractDateFullDate(blockSdt);
78
79
  const dropdownLastValue = extractDropdownLastValue(blockSdt);
79
80
  const sdtPrXml = reconcileRawSdtPr(baseSdtPr, props, {
80
81
  ...dateFullDate !== void 0 ? { dateFullDate } : {},
81
82
  ...dropdownLastValue !== void 0 ? { dropdownLastValue } : {}
82
83
  });
83
- const sdtEndPrXml = props.rawEndPropertiesXml ?? "";
84
+ const sdtEndPrXml = props.rawEndPropertiesXml && isSingleWellFormedElement(props.rawEndPropertiesXml, "sdtEndPr") ? props.rawEndPropertiesXml : "";
84
85
  const contentXml = blockSdt.content.map(serializeChild).join("");
85
86
  return `<w:sdt>${sdtPrXml}${sdtEndPrXml}${props.rawSdtChildrenBeforeContent ?? ""}<w:sdtContent>${contentXml}</w:sdtContent>${props.rawSdtChildrenAfterContent ?? ""}</w:sdt>`;
86
87
  }
@@ -27,8 +27,8 @@ function serializeRunContent(run) {
27
27
  */
28
28
  function commentParagraphOpenTag(p) {
29
29
  const attrs = [];
30
- if (p.paraId) attrs.push(`w14:paraId="${p.paraId}"`);
31
- if (p.textId) attrs.push(`w14:textId="${p.textId}"`);
30
+ if (p.paraId) attrs.push(`w14:paraId="${escapeXml(p.paraId)}"`);
31
+ if (p.textId) attrs.push(`w14:textId="${escapeXml(p.textId)}"`);
32
32
  return attrs.length > 0 ? `<w:p ${attrs.join(" ")}>` : "<w:p>";
33
33
  }
34
34
  function serializeParagraph(p) {
@@ -46,8 +46,7 @@ function serializeParagraphWithAnnotationRef(p) {
46
46
  return xml;
47
47
  }
48
48
  function serializeComment(comment) {
49
- const attrs = [`w:id="${comment.id}"`];
50
- if (comment.author) attrs.push(`w:author="${escapeXml(comment.author)}"`);
49
+ const attrs = [`w:id="${comment.id}"`, `w:author="${escapeXml(comment.author)}"`];
51
50
  if (comment.initials) attrs.push(`w:initials="${escapeXml(comment.initials)}"`);
52
51
  if (comment.date) attrs.push(`w:date="${escapeXml(comment.date)}"`);
53
52
  let xml = `<w:comment ${attrs.join(" ")}>`;
@@ -174,8 +173,8 @@ function serializeCommentsExtended(comments) {
174
173
  if (!entries) return null;
175
174
  let xml = COMMENTS_EXTENDED_HEADER;
176
175
  for (const entry of entries) {
177
- const parentAttr = entry.paraIdParent !== void 0 ? ` w15:paraIdParent="${entry.paraIdParent}"` : "";
178
- xml += `<w15:commentEx w15:paraId="${entry.paraId}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
176
+ const parentAttr = entry.paraIdParent !== void 0 ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
177
+ xml += `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parentAttr} w15:done="${entry.done ? "1" : "0"}"/>`;
179
178
  }
180
179
  xml += "</w15:commentsEx>";
181
180
  return xml;
@@ -1,8 +1,8 @@
1
1
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
2
+ import { serializeTable } from "./tableSerializer.js";
2
3
  import { resetAutoIdCounter } from "./runSerializer.js";
3
4
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
4
5
  import { serializeParagraph } from "./paragraphSerializer.js";
5
- import { serializeTable } from "./tableSerializer.js";
6
6
  //#region src/docx/serializer/documentSerializer.ts
7
7
  /**
8
8
  * Standard OOXML namespaces for document.xml
@@ -74,7 +74,7 @@ function buildNamespaceDeclarations() {
74
74
  */
75
75
  function serializeBlockContent(block) {
76
76
  if (block.type === "paragraph") return serializeParagraph(block);
77
- if (block.type === "table") return serializeTable(block);
77
+ if (block.type === "table") return serializeTable(block, serializeParagraph);
78
78
  return serializeBlockSdt(block, serializeBlockContent);
79
79
  }
80
80
  /**
@@ -1,8 +1,8 @@
1
1
  import { canReplayHeaderFooterVerbatim, getHeaderFooterVerbatimXml } from "../headerFooterVerbatim.js";
2
2
  import { escapeXml } from "./xmlUtils.js";
3
3
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
4
- import { serializeParagraph } from "./paragraphSerializer.js";
5
4
  import { serializeTable } from "./tableSerializer.js";
5
+ import { serializeParagraph } from "./paragraphSerializer.js";
6
6
  //#region src/docx/serializer/headerFooterSerializer.ts
7
7
  const NAMESPACES = {
8
8
  wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
@@ -36,7 +36,7 @@ function buildNamespaceDeclarations() {
36
36
  */
37
37
  function serializeBlock(block) {
38
38
  if (block.type === "paragraph") return serializeParagraph(block);
39
- if (block.type === "table") return serializeTable(block);
39
+ if (block.type === "table") return serializeTable(block, serializeParagraph);
40
40
  return serializeBlockSdt(block, serializeBlock);
41
41
  }
42
42
  /**
@@ -1,6 +1,6 @@
1
1
  import { serializeBlockSdt } from "./blockSdtSerializer.js";
2
- import { serializeParagraph } from "./paragraphSerializer.js";
3
2
  import { serializeTable } from "./tableSerializer.js";
3
+ import { serializeParagraph } from "./paragraphSerializer.js";
4
4
  //#region src/docx/serializer/noteSerializer.ts
5
5
  const NAMESPACES = {
6
6
  wpc: "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
@@ -34,7 +34,7 @@ function buildNamespaceDeclarations() {
34
34
  */
35
35
  function serializeBlock(block) {
36
36
  if (block.type === "paragraph") return serializeParagraph(block);
37
- if (block.type === "table") return serializeTable(block);
37
+ if (block.type === "table") return serializeTable(block, serializeParagraph);
38
38
  return serializeBlockSdt(block, serializeBlock);
39
39
  }
40
40
  /**
@@ -1,9 +1,11 @@
1
1
  import { numPrEqual } from "../numberingParser.js";
2
- import { escapeXml, intAttr } from "./xmlUtils.js";
2
+ import { isValidHexColor } from "../../utils/colorResolver.js";
3
+ import { escapeXml, intAttr, isSingleWellFormedElement } from "./xmlUtils.js";
3
4
  import { reconcileRawSdtPr } from "../sdtPropertiesPatch.js";
4
5
  import { serializeBorder } from "./borderSerializer.js";
5
6
  import { serializeRun, serializeTextFormatting } from "./runSerializer.js";
6
7
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
8
+ import { normalizeRevisionId } from "@stll/docx-core/model";
7
9
  //#region src/docx/serializer/paragraphSerializer.ts
8
10
  /**
9
11
  * Serialize paragraph borders (w:pBdr)
@@ -44,15 +46,15 @@ function serializeParagraphBorders(borders) {
44
46
  function serializeShading(shading) {
45
47
  if (!shading) return "";
46
48
  const attrs = [];
47
- if (shading.pattern) attrs.push(`w:val="${shading.pattern}"`);
49
+ if (shading.pattern) attrs.push(`w:val="${escapeXml(shading.pattern)}"`);
48
50
  else attrs.push("w:val=\"clear\"");
49
- if (shading.color?.rgb) attrs.push(`w:color="${shading.color.rgb}"`);
51
+ if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXml(shading.color.rgb)}"`);
50
52
  else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
51
- if (shading.fill?.rgb) attrs.push(`w:fill="${shading.fill.rgb}"`);
53
+ if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXml(shading.fill.rgb)}"`);
52
54
  else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
53
- if (shading.fill?.themeColor) attrs.push(`w:themeFill="${shading.fill.themeColor}"`);
54
- if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${shading.fill.themeTint}"`);
55
- if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${shading.fill.themeShade}"`);
55
+ if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXml(shading.fill.themeColor)}"`);
56
+ if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXml(shading.fill.themeTint)}"`);
57
+ if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXml(shading.fill.themeShade)}"`);
56
58
  if (attrs.length === 0) return "";
57
59
  return `<w:shd ${attrs.join(" ")}/>`;
58
60
  }
@@ -132,7 +134,7 @@ function serializeFrameProperties(frame) {
132
134
  * Serialize paragraph formatting properties to w:pPr XML
133
135
  */
134
136
  function serializeTrackedChangeAttrs(info) {
135
- const parts = [`w:id="${info.id}"`, `w:author="${escapeXml(info.author)}"`];
137
+ const parts = [`w:id="${normalizeRevisionId(info.id)}"`, `w:author="${escapeXml(info.author)}"`];
136
138
  if (info.date !== void 0) parts.push(`w:date="${escapeXml(info.date)}"`);
137
139
  return parts.join(" ");
138
140
  }
@@ -155,6 +157,7 @@ function serializeParagraphFormatting(formatting, propertyChanges, pPrMark) {
155
157
  const frameXml = serializeFrameProperties(formatting.frame);
156
158
  if (frameXml) parts.push(frameXml);
157
159
  pushToggle("widowControl", formatting.widowControl);
160
+ pushToggle("snapToGrid", formatting.snapToGrid);
158
161
  const numPrXml = formatting.numPrFromStyle != null && numPrEqual(formatting.numPr, formatting.numPrFromStyle) ? "" : serializeNumbering(formatting.numPr);
159
162
  if (numPrXml) parts.push(numPrXml);
160
163
  const bordersXml = serializeParagraphBorders(formatting.borders);
@@ -197,7 +200,7 @@ function extractRPrInner(rPrXml) {
197
200
  return rPrXml.slice(7, -8);
198
201
  }
199
202
  function serializeParagraphPropertyChange(change) {
200
- const normalizedId = Number.isInteger(change.info.id) && change.info.id >= 0 ? change.info.id : 0;
203
+ const normalizedId = normalizeRevisionId(change.info.id);
201
204
  const authorCandidate = typeof change.info.author === "string" ? change.info.author.trim() : "";
202
205
  const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
203
206
  const normalizedDate = typeof change.info.date === "string" ? change.info.date.trim() : void 0;
@@ -354,17 +357,19 @@ function serializeInlineSdt(sdt) {
354
357
  case "inlineSdt": return serializeInlineSdt(item);
355
358
  case "insertion": return serializeTrackedChange("ins", item);
356
359
  case "deletion": return serializeTrackedChange("del", item);
360
+ case "moveFrom": return serializeTrackedChange("moveFrom", item);
361
+ case "moveTo": return serializeTrackedChange("moveTo", item);
357
362
  case "mathEquation": return item.ommlXml || "";
358
363
  default: return item;
359
364
  }
360
365
  }).join("");
361
- const baseSdtPr = props.rawPropertiesXml ?? synthesizeInlineSdtPr(props);
366
+ const baseSdtPr = props.rawPropertiesXml && isSingleWellFormedElement(props.rawPropertiesXml, "sdtPr") ? props.rawPropertiesXml : synthesizeInlineSdtPr(props);
362
367
  const dateFullDate = props.sdtType === "date" && props.dateValueISO ? props.dateValueISO : void 0;
363
368
  const dropdownLastValue = (props.sdtType === "dropdown" || props.sdtType === "comboBox") && typeof props.dropdownLastValue === "string" ? props.dropdownLastValue : void 0;
364
369
  return `<w:sdt>${reconcileRawSdtPr(baseSdtPr, props, {
365
370
  ...dateFullDate !== void 0 ? { dateFullDate } : {},
366
371
  ...dropdownLastValue !== void 0 ? { dropdownLastValue } : {}
367
- })}${props.rawEndPropertiesXml ?? ""}<w:sdtContent>${contentXml}</w:sdtContent></w:sdt>`;
372
+ })}${props.rawEndPropertiesXml && isSingleWellFormedElement(props.rawEndPropertiesXml, "sdtEndPr") ? props.rawEndPropertiesXml : ""}<w:sdtContent>${contentXml}</w:sdtContent></w:sdt>`;
368
373
  }
369
374
  function serializeMoveRangeStart(tag, marker) {
370
375
  return `<w:${tag} ${[`w:id="${marker.id}"`, `w:name="${escapeXml(marker.name)}"`].join(" ")}/>`;
@@ -377,7 +382,7 @@ function rewriteRunTextAsDeleted(xml) {
377
382
  }
378
383
  function serializeTrackedChange(tag, change) {
379
384
  const info = change.info;
380
- const normalizedId = Number.isInteger(info.id) && info.id >= 0 ? info.id : 0;
385
+ const normalizedId = normalizeRevisionId(info.id);
381
386
  const authorCandidate = typeof info.author === "string" ? info.author.trim() : "";
382
387
  const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
383
388
  const normalizedDate = typeof info.date === "string" ? info.date.trim() : void 0;
@@ -452,8 +457,8 @@ function serializeParagraphContent(content, explicitCommentReferenceIds) {
452
457
  function serializeParagraph(paragraph) {
453
458
  const parts = [];
454
459
  const attrs = [];
455
- if (paragraph.paraId) attrs.push(`w14:paraId="${paragraph.paraId}"`);
456
- if (paragraph.textId) attrs.push(`w14:textId="${paragraph.textId}"`);
460
+ if (paragraph.paraId) attrs.push(`w14:paraId="${escapeXml(paragraph.paraId)}"`);
461
+ if (paragraph.textId) attrs.push(`w14:textId="${escapeXml(paragraph.textId)}"`);
457
462
  const attrsStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
458
463
  const pPrXml = serializeParagraphFormatting(paragraph.formatting, paragraph.propertyChanges, paragraph.pPrMark);
459
464
  const sectionPropertiesXml = serializeSectionProperties(paragraph.sectionProperties);
@@ -478,7 +483,10 @@ function serializeParagraph(paragraph) {
478
483
  }
479
484
  function injectRenderedPageBreakIntoFirstRun(xml) {
480
485
  const runOpeningTag = /<w:r(?=[\s>/])[^>]*>/u;
481
- if (!runOpeningTag.test(xml)) return null;
486
+ const openingTag = runOpeningTag.exec(xml);
487
+ if (!openingTag) return null;
488
+ const runEnd = xml.indexOf("</w:r>", openingTag.index + openingTag[0].length);
489
+ if (runEnd !== -1 && xml.slice(openingTag.index + openingTag[0].length, runEnd).includes("<w:lastRenderedPageBreak/>")) return xml;
482
490
  return xml.replace(runOpeningTag, (match) => `${match}<w:lastRenderedPageBreak/>`);
483
491
  }
484
492
  /**
@@ -1,7 +1,10 @@
1
1
  import { HIGHLIGHT_COLOR_VALUES } from "../../types/documentEnumValues.js";
2
+ import { isValidHexColor } from "../../utils/colorResolver.js";
2
3
  import { escapeXml, intAttr } from "./xmlUtils.js";
4
+ import { serializeTable } from "./tableSerializer.js";
3
5
  import { serializeParagraph } from "./paragraphSerializer.js";
4
6
  import { panic } from "better-result";
7
+ import { normalizeRevisionId } from "@stll/docx-core/model";
5
8
  //#region src/docx/serializer/runSerializer.ts
6
9
  /**
7
10
  * Run Serializer - Serialize runs to OOXML XML
@@ -41,10 +44,10 @@ function serializeColorElement(color) {
41
44
  if (!color) return "";
42
45
  const attrs = [];
43
46
  if (color.auto) attrs.push("w:val=\"auto\"");
44
- else if (color.rgb) attrs.push(`w:val="${color.rgb}"`);
45
- if (color.themeColor) attrs.push(`w:themeColor="${color.themeColor}"`);
46
- if (color.themeTint) attrs.push(`w:themeTint="${color.themeTint}"`);
47
- if (color.themeShade) attrs.push(`w:themeShade="${color.themeShade}"`);
47
+ else if (color.rgb && isValidHexColor(color.rgb)) attrs.push(`w:val="${escapeXml(color.rgb)}"`);
48
+ if (color.themeColor) attrs.push(`w:themeColor="${escapeXml(color.themeColor)}"`);
49
+ if (color.themeTint) attrs.push(`w:themeTint="${escapeXml(color.themeTint)}"`);
50
+ if (color.themeShade) attrs.push(`w:themeShade="${escapeXml(color.themeShade)}"`);
48
51
  if (attrs.length === 0) return "";
49
52
  return `<w:color ${attrs.join(" ")}/>`;
50
53
  }
@@ -54,15 +57,15 @@ function serializeColorElement(color) {
54
57
  function serializeShading(shading) {
55
58
  if (!shading) return "";
56
59
  const attrs = [];
57
- if (shading.pattern) attrs.push(`w:val="${shading.pattern}"`);
60
+ if (shading.pattern) attrs.push(`w:val="${escapeXml(shading.pattern)}"`);
58
61
  else attrs.push("w:val=\"clear\"");
59
- if (shading.color?.rgb) attrs.push(`w:color="${shading.color.rgb}"`);
62
+ if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXml(shading.color.rgb)}"`);
60
63
  else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
61
- if (shading.fill?.rgb) attrs.push(`w:fill="${shading.fill.rgb}"`);
64
+ if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXml(shading.fill.rgb)}"`);
62
65
  else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
63
- if (shading.fill?.themeColor) attrs.push(`w:themeFill="${shading.fill.themeColor}"`);
64
- if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${shading.fill.themeTint}"`);
65
- if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${shading.fill.themeShade}"`);
66
+ if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXml(shading.fill.themeColor)}"`);
67
+ if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXml(shading.fill.themeTint)}"`);
68
+ if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXml(shading.fill.themeShade)}"`);
66
69
  if (attrs.length === 0) return "";
67
70
  return `<w:shd ${attrs.join(" ")}/>`;
68
71
  }
@@ -136,10 +139,10 @@ function serializeTextFormatting(formatting) {
136
139
  if (formatting.underline) {
137
140
  const uAttrs = [`w:val="${formatting.underline.style}"`];
138
141
  if (formatting.underline.color) {
139
- if (formatting.underline.color.rgb) uAttrs.push(`w:color="${formatting.underline.color.rgb}"`);
140
- if (formatting.underline.color.themeColor) uAttrs.push(`w:themeColor="${formatting.underline.color.themeColor}"`);
141
- if (formatting.underline.color.themeTint) uAttrs.push(`w:themeTint="${formatting.underline.color.themeTint}"`);
142
- if (formatting.underline.color.themeShade) uAttrs.push(`w:themeShade="${formatting.underline.color.themeShade}"`);
142
+ if (formatting.underline.color.rgb && isValidHexColor(formatting.underline.color.rgb)) uAttrs.push(`w:color="${escapeXml(formatting.underline.color.rgb)}"`);
143
+ if (formatting.underline.color.themeColor) uAttrs.push(`w:themeColor="${escapeXml(formatting.underline.color.themeColor)}"`);
144
+ if (formatting.underline.color.themeTint) uAttrs.push(`w:themeTint="${escapeXml(formatting.underline.color.themeTint)}"`);
145
+ if (formatting.underline.color.themeShade) uAttrs.push(`w:themeShade="${escapeXml(formatting.underline.color.themeShade)}"`);
143
146
  }
144
147
  parts.push(`<w:u ${uAttrs.join(" ")}/>`);
145
148
  }
@@ -159,7 +162,7 @@ function extractRPrInner(rPrXml) {
159
162
  return rPrXml.slice(7, -8);
160
163
  }
161
164
  function serializeRunPropertyChange(change) {
162
- const normalizedId = Number.isInteger(change.info.id) && change.info.id >= 0 ? change.info.id : 0;
165
+ const normalizedId = normalizeRevisionId(change.info.id);
163
166
  const authorCandidate = typeof change.info.author === "string" ? change.info.author.trim() : "";
164
167
  const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
165
168
  const normalizedDate = typeof change.info.date === "string" ? change.info.date.trim() : void 0;
@@ -245,11 +248,11 @@ function serializeNoBreakHyphen(_content) {
245
248
  /** Serialize a color value to DrawingML a:srgbClr or a:schemeClr */
246
249
  function serializeDrawingColor(color) {
247
250
  if (!color) return "";
248
- if (color.rgb) return `<a:srgbClr val="${color.rgb.replace("#", "")}"/>`;
251
+ if (color.rgb && isValidHexColor(color.rgb)) return `<a:srgbClr val="${escapeXml(color.rgb.replace("#", ""))}"/>`;
249
252
  if (color.themeColor) {
250
- let clr = `<a:schemeClr val="${color.themeColor}"`;
251
- if (color.themeTint) clr += `><a:tint val="${color.themeTint}"/></a:schemeClr>`;
252
- else if (color.themeShade) clr += `><a:shade val="${color.themeShade}"/></a:schemeClr>`;
253
+ let clr = `<a:schemeClr val="${escapeXml(color.themeColor)}"`;
254
+ if (color.themeTint) clr += `><a:tint val="${escapeXml(color.themeTint)}"/></a:schemeClr>`;
255
+ else if (color.themeShade) clr += `><a:shade val="${escapeXml(color.themeShade)}"/></a:schemeClr>`;
253
256
  else clr += `/>`;
254
257
  return clr;
255
258
  }
@@ -418,8 +421,8 @@ function serializeDrawingContent(content) {
418
421
  ].join("");
419
422
  }
420
423
  /** Serialize text body content for shapes/textboxes */
421
- function serializeShapeTextBody(paragraphs) {
422
- return paragraphs.map((p) => serializeParagraph(p)).join("");
424
+ function serializeShapeTextBody(blocks) {
425
+ return blocks.map((block) => block.type === "paragraph" ? serializeParagraph(block) : serializeTable(block, serializeParagraph)).join("");
423
426
  }
424
427
  /**
425
428
  * Serialize shape content to full DrawingML XML (wps:wsp inside w:drawing)
@@ -534,6 +537,7 @@ function serializeRunContent(content) {
534
537
  case "instrText": return serializeInstrText(content);
535
538
  case "softHyphen": return serializeSoftHyphen(content);
536
539
  case "noBreakHyphen": return serializeNoBreakHyphen(content);
540
+ case "renderedPageBreak": return "<w:lastRenderedPageBreak/>";
537
541
  case "drawing":
538
542
  if (content.rawXml) return content.rawXml;
539
543
  return serializeDrawingContent(content);
@@ -1,6 +1,7 @@
1
1
  import { getUnserializedSectionPropertyChildNames } from "../sectionParser.js";
2
2
  import { escapeXml, intAttr } from "./xmlUtils.js";
3
3
  import { serializeBorder } from "./borderSerializer.js";
4
+ import { normalizeRevisionId } from "@stll/docx-core/model";
4
5
  //#region src/docx/serializer/sectionPropertiesSerializer.ts
5
6
  const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${ref.rId}"/>`;
6
7
  const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${ref.rId}"/>`;
@@ -132,7 +133,7 @@ function serializeOnOffElement(value, name) {
132
133
  return value ? `<w:${name}/>` : `<w:${name} w:val="0"/>`;
133
134
  }
134
135
  function serializeSectionPropertyChange(change) {
135
- const normalizedId = Number.isInteger(change.info.id) && change.info.id >= 0 ? change.info.id : 0;
136
+ const normalizedId = normalizeRevisionId(change.info.id);
136
137
  const authorCandidate = typeof change.info.author === "string" ? change.info.author.trim() : "";
137
138
  const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
138
139
  const normalizedDate = typeof change.info.date === "string" ? change.info.date.trim() : void 0;
@@ -1,7 +1,7 @@
1
1
  import { escapeXml } from "./xmlUtils.js";
2
+ import { serializeTableCellFormatting, serializeTableFormatting, serializeTableRowFormatting } from "./tableSerializer.js";
2
3
  import { serializeTextFormatting } from "./runSerializer.js";
3
4
  import { serializeParagraphFormatting } from "./paragraphSerializer.js";
4
- import { serializeTableCellFormatting, serializeTableFormatting, serializeTableRowFormatting } from "./tableSerializer.js";
5
5
  //#region src/docx/serializer/stylesSerializer.ts
6
6
  const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
7
7
  const serializeStylesXml = (definitions) => {
@@ -1,6 +1,7 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
2
 
3
3
  //#region src/docx/serializer/tableSerializer.d.ts
4
+ type ParagraphSerializer = (paragraph: document_d_exports.Paragraph) => string;
4
5
  /**
5
6
  * Serialize table formatting properties (w:tblPr)
6
7
  */
@@ -16,25 +17,25 @@ declare function serializeTableCellFormatting(formatting: document_d_exports.Tab
16
17
  /**
17
18
  * Serialize a table cell (w:tc)
18
19
  */
19
- declare function serializeTableCell(cell: document_d_exports.TableCell): string;
20
+ declare function serializeTableCell(cell: document_d_exports.TableCell, serializeParagraph: ParagraphSerializer): string;
20
21
  /**
21
22
  * Serialize a table row (w:tr)
22
23
  */
23
- declare function serializeTableRow(row: document_d_exports.TableRow): string;
24
+ declare function serializeTableRow(row: document_d_exports.TableRow, serializeParagraph: ParagraphSerializer): string;
24
25
  /**
25
26
  * Serialize a table to OOXML XML (w:tbl)
26
27
  *
27
28
  * @param table - The table to serialize
28
29
  * @returns XML string for the table
29
30
  */
30
- declare function serializeTable(table: document_d_exports.Table): string;
31
+ declare function serializeTable(table: document_d_exports.Table, serializeParagraph: ParagraphSerializer): string;
31
32
  /**
32
33
  * Serialize multiple tables to OOXML XML
33
34
  *
34
35
  * @param tables - The tables to serialize
35
36
  * @returns XML string for all tables
36
37
  */
37
- declare function serializeTables(tables: document_d_exports.Table[]): string;
38
+ declare function serializeTables(tables: document_d_exports.Table[], serializeParagraph: ParagraphSerializer): string;
38
39
  /**
39
40
  * Check if a table has any rows
40
41
  */
@@ -1,9 +1,10 @@
1
+ import { isValidHexColor } from "../../utils/colorResolver.js";
1
2
  import { escapeXml, intAttr } from "./xmlUtils.js";
2
3
  import { serializeBorder } from "./borderSerializer.js";
3
- import { serializeParagraph } from "./paragraphSerializer.js";
4
+ import { normalizeRevisionId } from "@stll/docx-core/model";
4
5
  //#region src/docx/serializer/tableSerializer.ts
5
6
  function normalizeTrackedChangeInfo(info) {
6
- const normalizedId = Number.isInteger(info.id) && info.id >= 0 ? info.id : 0;
7
+ const normalizedId = normalizeRevisionId(info.id);
7
8
  const authorCandidate = typeof info.author === "string" ? info.author.trim() : "";
8
9
  const normalizedAuthor = authorCandidate.length > 0 ? authorCandidate : "Unknown";
9
10
  const normalizedDate = typeof info.date === "string" ? info.date.trim() : void 0;
@@ -78,15 +79,15 @@ function serializeCellMargins(margins, elementName) {
78
79
  function serializeShading(shading) {
79
80
  if (!shading) return "";
80
81
  const attrs = [];
81
- if (shading.pattern) attrs.push(`w:val="${shading.pattern}"`);
82
+ if (shading.pattern) attrs.push(`w:val="${escapeXml(shading.pattern)}"`);
82
83
  else attrs.push("w:val=\"clear\"");
83
- if (shading.color?.rgb) attrs.push(`w:color="${shading.color.rgb}"`);
84
+ if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXml(shading.color.rgb)}"`);
84
85
  else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
85
- if (shading.fill?.rgb) attrs.push(`w:fill="${shading.fill.rgb}"`);
86
+ if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXml(shading.fill.rgb)}"`);
86
87
  else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
87
- if (shading.fill?.themeColor) attrs.push(`w:themeFill="${shading.fill.themeColor}"`);
88
- if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${shading.fill.themeTint}"`);
89
- if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${shading.fill.themeShade}"`);
88
+ if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXml(shading.fill.themeColor)}"`);
89
+ if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXml(shading.fill.themeTint)}"`);
90
+ if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXml(shading.fill.themeShade)}"`);
90
91
  if (attrs.length === 0) return "";
91
92
  return `<w:shd ${attrs.join(" ")}/>`;
92
93
  }
@@ -247,12 +248,17 @@ function serializeTableCellFormatting(formatting, propertyChanges, structuralCha
247
248
  if (formatting.textDirection) parts.push(`<w:textDirection w:val="${formatting.textDirection}"/>`);
248
249
  if (formatting.fitText) parts.push("<w:tcFitText/>");
249
250
  if (formatting.verticalAlign) parts.push(`<w:vAlign w:val="${formatting.verticalAlign}"/>`);
250
- if (formatting.hideMark) parts.push("<w:hideMark/>");
251
+ if (formatting.hideMark !== void 0) parts.push(formatting.hideMark ? "<w:hideMark/>" : "<w:hideMark w:val=\"false\"/>");
251
252
  }
252
253
  if (structuralChange) {
253
254
  if (structuralChange.type === "tableCellInsertion") parts.push(`<w:cellIns ${serializeTrackedChangeAttributes(structuralChange.info)}/>`);
254
255
  else if (structuralChange.type === "tableCellDeletion") parts.push(`<w:cellDel ${serializeTrackedChangeAttributes(structuralChange.info)}/>`);
255
- else if (structuralChange.type === "tableCellMerge") parts.push(`<w:cellMerge ${serializeTrackedChangeAttributes(structuralChange.info)}/>`);
256
+ else if (structuralChange.type === "tableCellMerge") {
257
+ const attrs = [serializeTrackedChangeAttributes(structuralChange.info)];
258
+ if (structuralChange.verticalMerge) attrs.push(`w:vMerge="${structuralChange.verticalMerge === "continue" ? "cont" : "rest"}"`);
259
+ if (structuralChange.verticalMergeOriginal) attrs.push(`w:vMergeOrig="${structuralChange.verticalMergeOriginal === "continue" ? "cont" : "rest"}"`);
260
+ parts.push(`<w:cellMerge ${attrs.join(" ")}/>`);
261
+ }
256
262
  }
257
263
  if (propertyChanges && propertyChanges.length > 0) parts.push(...propertyChanges.map((change) => serializeTableCellPropertyChange(change)));
258
264
  if (parts.length === 0) return "";
@@ -277,31 +283,31 @@ function serializeTableGrid(columnWidths) {
277
283
  /**
278
284
  * Serialize cell content (paragraphs, nested tables)
279
285
  */
280
- function serializeCellContent(content) {
286
+ function serializeCellContent(content, serializeParagraph) {
281
287
  const parts = [];
282
288
  for (const item of content) if (item.type === "paragraph") parts.push(serializeParagraph(item));
283
- else parts.push(serializeTable(item));
289
+ else parts.push(serializeTable(item, serializeParagraph));
284
290
  if (parts.length === 0) parts.push("<w:p/>");
285
291
  return parts.join("");
286
292
  }
287
293
  /**
288
294
  * Serialize a table cell (w:tc)
289
295
  */
290
- function serializeTableCell(cell) {
296
+ function serializeTableCell(cell, serializeParagraph) {
291
297
  const parts = [];
292
298
  const tcPrXml = serializeTableCellFormatting(cell.formatting, cell.propertyChanges, cell.structuralChange);
293
299
  if (tcPrXml) parts.push(tcPrXml);
294
- parts.push(serializeCellContent(cell.content));
300
+ parts.push(serializeCellContent(cell.content, serializeParagraph));
295
301
  return `<w:tc>${parts.join("")}</w:tc>`;
296
302
  }
297
303
  /**
298
304
  * Serialize a table row (w:tr)
299
305
  */
300
- function serializeTableRow(row) {
306
+ function serializeTableRow(row, serializeParagraph) {
301
307
  const parts = [];
302
308
  const trPrXml = serializeTableRowFormatting(row.formatting, row.propertyChanges, row.structuralChange);
303
309
  if (trPrXml) parts.push(trPrXml);
304
- for (const cell of row.cells) parts.push(serializeTableCell(cell));
310
+ for (const cell of row.cells) parts.push(serializeTableCell(cell, serializeParagraph));
305
311
  return `<w:tr>${parts.join("")}</w:tr>`;
306
312
  }
307
313
  /**
@@ -310,13 +316,13 @@ function serializeTableRow(row) {
310
316
  * @param table - The table to serialize
311
317
  * @returns XML string for the table
312
318
  */
313
- function serializeTable(table) {
319
+ function serializeTable(table, serializeParagraph) {
314
320
  const parts = [];
315
321
  const tblPrXml = serializeTableFormatting(table.formatting, table.propertyChanges);
316
322
  if (tblPrXml) parts.push(tblPrXml);
317
323
  const tblGridXml = serializeTableGrid(table.columnWidths);
318
324
  if (tblGridXml) parts.push(tblGridXml);
319
- for (const row of table.rows) parts.push(serializeTableRow(row));
325
+ for (const row of table.rows) parts.push(serializeTableRow(row, serializeParagraph));
320
326
  return `<w:tbl>${parts.join("")}</w:tbl>`;
321
327
  }
322
328
  /**
@@ -325,8 +331,8 @@ function serializeTable(table) {
325
331
  * @param tables - The tables to serialize
326
332
  * @returns XML string for all tables
327
333
  */
328
- function serializeTables(tables) {
329
- return tables.map(serializeTable).join("");
334
+ function serializeTables(tables, serializeParagraph) {
335
+ return tables.map((table) => serializeTable(table, serializeParagraph)).join("");
330
336
  }
331
337
  /**
332
338
  * Check if a table has any rows
@@ -16,5 +16,17 @@ declare function escapeXml(text: string): string;
16
16
  * literal `"NaN"` or `"Infinity"` into the XML.
17
17
  */
18
18
  declare function intAttr(value: number | undefined | null): string;
19
+ /**
20
+ * Verify that `xml` parses to exactly one root element named `expectedLocalName`
21
+ * (namespace prefix ignored). Used to gate raw XML snapshots — e.g. an SDT's
22
+ * `rawPropertiesXml`/`rawEndPropertiesXml` — before splicing them verbatim
23
+ * into a serialized document. Those snapshots are normally produced by our
24
+ * own parser, but they can also arrive from an untrusted surface (a
25
+ * programmatically constructed node, a collaboration payload); a value that
26
+ * is not a single well-formed `<w:sdtPr>`/`<w:sdtEndPr>` element could inject
27
+ * sibling markup or close the enclosing `<w:sdt>` early. Callers should fall
28
+ * back to a synthesized properties block when this returns `false`.
29
+ */
30
+ declare function isSingleWellFormedElement(xml: string, expectedLocalName: string): boolean;
19
31
  //#endregion
20
- export { escapeXml, intAttr };
32
+ export { escapeXml, intAttr, isSingleWellFormedElement };