@stll/folio-core 0.44.0 → 0.46.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/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/apply.js +59 -19
  3. package/dist/ai-edits/headless.js +2 -1
  4. package/dist/compare/compare.js +2 -5
  5. package/dist/compare/content-alignment.js +78 -53
  6. package/dist/compare/inline-atoms.js +34 -20
  7. package/dist/compare/scenario.js +2 -5
  8. package/dist/compare/types.d.ts +9 -1
  9. package/dist/compare/types.js +12 -1
  10. package/dist/compat/eigenpal.d.ts +2 -1
  11. package/dist/compat/eigenpal.js +2 -1
  12. package/dist/content-controls/checkboxDisplay.d.ts +11 -0
  13. package/dist/content-controls/checkboxDisplay.js +40 -0
  14. package/dist/content-controls/findContentControls.js +3 -1
  15. package/dist/content-controls/mutateContentControls.js +21 -4
  16. package/dist/controller/contentControlWidgetController.d.ts +1 -0
  17. package/dist/controller/contentControlWidgetController.js +3 -0
  18. package/dist/controller/hiddenEditorApi.js +3 -1
  19. package/dist/controller/hiddenEditorManager.d.ts +1 -1
  20. package/dist/controller/hiddenEditorManager.js +6 -3
  21. package/dist/display-list/build/buildDisplayList.js +20 -1
  22. package/dist/display-list/build/imagePrimitives.d.ts +1 -1
  23. package/dist/display-list/build/imagePrimitives.js +3 -1
  24. package/dist/display-list/build/paragraphPrimitives.js +21 -2
  25. package/dist/display-list/dom/renderDisplayListToDom.js +18 -10
  26. package/dist/display-list/types.d.ts +17 -0
  27. package/dist/document-operations.js +14 -3
  28. package/dist/docx/attributeRemainder.d.ts +43 -0
  29. package/dist/docx/attributeRemainder.js +65 -0
  30. package/dist/docx/blockContentParser.d.ts +1 -1
  31. package/dist/docx/blockContentParser.js +87 -63
  32. package/dist/docx/blockPlainText.d.ts +3 -4
  33. package/dist/docx/blockPlainText.js +1 -0
  34. package/dist/docx/bookmarkPlacement.js +2 -0
  35. package/dist/docx/borderParser.js +5 -5
  36. package/dist/docx/commentAnchorIndex.d.ts +30 -0
  37. package/dist/docx/commentAnchorIndex.js +50 -0
  38. package/dist/docx/commentParser.d.ts +1 -1
  39. package/dist/docx/commentParser.js +78 -50
  40. package/dist/docx/commentRangeIntegrity.d.ts +16 -1
  41. package/dist/docx/commentRangeIntegrity.js +42 -1
  42. package/dist/docx/commentRangeJoin.d.ts +9 -0
  43. package/dist/docx/commentRangeJoin.js +33 -0
  44. package/dist/docx/commentReferenceCompletion.d.ts +9 -0
  45. package/dist/docx/commentReferenceCompletion.js +47 -0
  46. package/dist/docx/commentReferenceNormalization.js +1 -0
  47. package/dist/docx/commentReplyMarkers.js +11 -19
  48. package/dist/docx/commentThreadKey.d.ts +18 -0
  49. package/dist/docx/commentThreadKey.js +22 -0
  50. package/dist/docx/compatibility.js +1 -0
  51. package/dist/docx/containerChildren.d.ts +111 -0
  52. package/dist/docx/containerChildren.gen.d.ts +28 -0
  53. package/dist/docx/containerChildren.gen.js +246 -0
  54. package/dist/docx/containerChildren.js +103 -0
  55. package/dist/docx/diagramPreview.js +87 -27
  56. package/dist/docx/documentParser.d.ts +1 -1
  57. package/dist/docx/documentParser.js +3 -3
  58. package/dist/docx/ensureParaIds.js +17 -7
  59. package/dist/docx/fieldParser.d.ts +1 -1
  60. package/dist/docx/fieldParser.js +4 -4
  61. package/dist/docx/fieldState.d.ts +26 -0
  62. package/dist/docx/fieldState.js +53 -0
  63. package/dist/docx/footnoteParser.d.ts +1 -1
  64. package/dist/docx/graphicFrameLocks.d.ts +16 -4
  65. package/dist/docx/graphicFrameLocks.js +19 -6
  66. package/dist/docx/groupDrawingParser.js +3 -3
  67. package/dist/docx/headerFooterParser.js +1 -1
  68. package/dist/docx/headerFooterReferenceNormalization.js +1 -0
  69. package/dist/docx/hyperlinkParser.d.ts +27 -8
  70. package/dist/docx/hyperlinkParser.js +78 -20
  71. package/dist/docx/imageParser.d.ts +9 -1
  72. package/dist/docx/imageParser.js +91 -20
  73. package/dist/docx/imageRawXml.d.ts +14 -1
  74. package/dist/docx/imageRawXml.js +30 -6
  75. package/dist/docx/inlineWrapperContent.d.ts +85 -0
  76. package/dist/docx/inlineWrapperContent.js +84 -0
  77. package/dist/docx/mathToMathml.js +12 -14
  78. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  79. package/dist/docx/nonVisualDrawingProps.js +46 -0
  80. package/dist/docx/normalizeBaseDirection.js +10 -1
  81. package/dist/docx/paraIdAttribute.d.ts +21 -0
  82. package/dist/docx/paraIdAttribute.js +64 -0
  83. package/dist/docx/paragraphParser.d.ts +1 -1
  84. package/dist/docx/paragraphParser.js +277 -168
  85. package/dist/docx/paragraphPropertySource.js +5 -1
  86. package/dist/docx/paragraphTextBoxEnrichment.d.ts +1 -1
  87. package/dist/docx/paragraphTextBoxEnrichment.js +9 -52
  88. package/dist/docx/paragraphTraversal.js +7 -4
  89. package/dist/docx/parseWarningMessage.js +2 -1
  90. package/dist/docx/parser.js +2 -2
  91. package/dist/docx/preservedRunContent.d.ts +32 -0
  92. package/dist/docx/preservedRunContent.js +86 -0
  93. package/dist/docx/previewBudget.d.ts +64 -0
  94. package/dist/docx/previewBudget.js +88 -0
  95. package/dist/docx/renderedPageBreakNormalization.js +2 -4
  96. package/dist/docx/revisionIdNormalization.js +17 -5
  97. package/dist/docx/rezip.js +91 -36
  98. package/dist/docx/runParser.d.ts +1 -1
  99. package/dist/docx/runParser.js +16 -12
  100. package/dist/docx/sdtPropertiesPatch.js +24 -18
  101. package/dist/docx/sectionParser.js +8 -1
  102. package/dist/docx/sectionReferenceHistory.js +2 -2
  103. package/dist/docx/selectiveSave.js +6 -6
  104. package/dist/docx/selectiveXmlPatch.d.ts +46 -2
  105. package/dist/docx/selectiveXmlPatch.js +86 -39
  106. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  107. package/dist/docx/serializer/borderSerializer.d.ts +1 -1
  108. package/dist/docx/serializer/borderSerializer.js +15 -14
  109. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  110. package/dist/docx/serializer/commentSerializer.js +87 -92
  111. package/dist/docx/serializer/documentSerializer.js +8 -11
  112. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  113. package/dist/docx/serializer/headerFooterSerializer.js +12 -14
  114. package/dist/docx/serializer/markupRangeAttributes.js +2 -2
  115. package/dist/docx/serializer/noteSerializer.js +8 -8
  116. package/dist/docx/serializer/numberingSerializer.js +7 -6
  117. package/dist/docx/serializer/paragraphSerializer.js +93 -72
  118. package/dist/docx/serializer/partNamespaces.js +2 -2
  119. package/dist/docx/serializer/runSerializer.js +73 -44
  120. package/dist/docx/serializer/sectionPropertiesSerializer.js +19 -14
  121. package/dist/docx/serializer/settingsSerializer.js +4 -3
  122. package/dist/docx/serializer/stylesSerializer.js +6 -6
  123. package/dist/docx/serializer/tableSerializer.js +28 -17
  124. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  125. package/dist/docx/serializer/themeSerializer.js +6 -6
  126. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  127. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  128. package/dist/docx/serializer/xmlUtils.js +1 -13
  129. package/dist/docx/server/boundedArchive.d.ts +12 -0
  130. package/dist/docx/server/boundedArchive.js +20 -1
  131. package/dist/docx/server/createBilingualDocument.js +3 -1
  132. package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
  133. package/dist/docx/server/materializeYjsDocx.js +9 -1
  134. package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
  135. package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
  136. package/dist/docx/server/validateDocxConformance.js +22 -1
  137. package/dist/docx/shapeParser.js +12 -10
  138. package/dist/docx/tableParser.d.ts +1 -1
  139. package/dist/docx/tableParser.js +206 -85
  140. package/dist/docx/textBoxParser.d.ts +25 -2
  141. package/dist/docx/textBoxParser.js +59 -4
  142. package/dist/docx/unzip.d.ts +23 -0
  143. package/dist/docx/unzip.js +37 -26
  144. package/dist/docx/verbatimCapture.js +1 -1
  145. package/dist/docx/vmlImageParser.d.ts +17 -1
  146. package/dist/docx/vmlImageParser.js +20 -3
  147. package/dist/docx/vmlPreview.d.ts +1 -3
  148. package/dist/docx/vmlPreview.js +2 -30
  149. package/dist/docx/xmlEncoding.d.ts +5 -0
  150. package/dist/docx/xmlEncoding.js +12 -0
  151. package/dist/docx/xmlParser.d.ts +26 -2
  152. package/dist/docx/xmlParser.js +70 -27
  153. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  154. package/dist/docx/xmlResourceLimits.js +105 -24
  155. package/dist/headless-layout.js +10 -1
  156. package/dist/index.d.ts +2 -1
  157. package/dist/index.js +2 -1
  158. package/dist/internal/pageBreakRunSourceDescendantIndex.js +5 -2
  159. package/dist/internal/paragraphFormattingSerialization.js +3 -2
  160. package/dist/layout-bridge/convert/toFlowBlocks.js +102 -12
  161. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  162. package/dist/layout-engine/measure/tableCellFloating.js +2 -0
  163. package/dist/layout-engine/types.d.ts +23 -1
  164. package/dist/layout-painter/renderImage.d.ts +5 -1
  165. package/dist/layout-painter/renderImage.js +14 -6
  166. package/dist/layout-painter/renderPage.d.ts +2 -0
  167. package/dist/layout-painter/renderPage.js +4 -0
  168. package/dist/layout-painter/renderParagraph.d.ts +13 -1
  169. package/dist/layout-painter/renderParagraph.js +26 -6
  170. package/dist/managers/autoSaveCodec.d.ts +9 -1
  171. package/dist/managers/autoSaveCodec.js +14 -12
  172. package/dist/markdown/images.js +1 -4
  173. package/dist/markdown/renderBlock.js +1 -0
  174. package/dist/markdown/renderRuns.d.ts +1 -1
  175. package/dist/markdown/renderRuns.js +28 -5
  176. package/dist/markdown/renderTable.js +22 -5
  177. package/dist/markdown/trailers.js +4 -1
  178. package/dist/pdf/images.js +142 -6
  179. package/dist/prosemirror/attrs/index.d.ts +14 -3
  180. package/dist/prosemirror/attrs/index.js +224 -2
  181. package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
  182. package/dist/prosemirror/authoredTransformAttrs.js +64 -0
  183. package/dist/prosemirror/commands/contentControls.js +10 -2
  184. package/dist/prosemirror/commands/image.d.ts +9 -2
  185. package/dist/prosemirror/commands/image.js +43 -28
  186. package/dist/prosemirror/commands/pageBreak.js +1 -1
  187. package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
  188. package/dist/prosemirror/commentReferenceAttrs.js +33 -0
  189. package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
  190. package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
  191. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  192. package/dist/prosemirror/conversion/fromProseDoc.js +338 -99
  193. package/dist/prosemirror/conversion/index.d.ts +2 -2
  194. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
  195. package/dist/prosemirror/conversion/toProseDoc.js +551 -327
  196. package/dist/prosemirror/extensions/StarterKit.js +8 -58
  197. package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
  198. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
  199. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +45 -27
  200. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
  201. package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
  202. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
  203. package/dist/prosemirror/extensions/markRegistry.d.ts +41 -0
  204. package/dist/prosemirror/extensions/markRegistry.js +75 -0
  205. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  206. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
  207. package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +63 -0
  208. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  209. package/dist/prosemirror/extensions/marks/markUtils.js +47 -4
  210. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
  211. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +87 -0
  212. package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
  213. package/dist/prosemirror/extensions/nodes/ImageExtension.js +20 -0
  214. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
  215. package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
  216. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
  217. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
  218. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +10 -2
  219. package/dist/prosemirror/extensions/nodes/TableExtension.js +3 -2
  220. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +13 -5
  221. package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
  222. package/dist/prosemirror/inlineWrapperStack.js +75 -0
  223. package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
  224. package/dist/prosemirror/pageBreakRunProjection.js +19 -9
  225. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +6 -3
  226. package/dist/prosemirror/paragraphFormattingProvenance.js +12 -3
  227. package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
  228. package/dist/prosemirror/replacedAnnotations.js +165 -0
  229. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +3 -1
  230. package/dist/prosemirror/runFormattingInlineCarriers.js +5 -1
  231. package/dist/prosemirror/schema/index.d.ts +3 -3
  232. package/dist/prosemirror/schema/marks.d.ts +25 -1
  233. package/dist/prosemirror/schema/nodes.d.ts +139 -3
  234. package/dist/prosemirror/schema/nodes.js +16 -1
  235. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +2 -0
  236. package/dist/prosemirror/trackedRunInlineAtoms.js +2 -0
  237. package/dist/prosemirror/validation.js +16 -2
  238. package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
  239. package/dist/prosemirror/yjsDocumentMetadata.js +171 -0
  240. package/dist/prosemirror/zeroWidthAnchors.js +2 -0
  241. package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
  242. package/dist/render-dom/commentAnchorAttributes.js +54 -0
  243. package/dist/server.d.ts +3 -1
  244. package/dist/server.js +3 -1
  245. package/dist/types/content.d.ts +2 -2
  246. package/dist/utils/base64.d.ts +36 -0
  247. package/dist/utils/base64.js +40 -0
  248. package/dist/utils/clipboard.d.ts +9 -1
  249. package/dist/utils/clipboard.js +29 -2
  250. package/dist/utils/findReplace.js +1 -1
  251. package/dist/utils/imageLuminance.d.ts +15 -0
  252. package/dist/utils/imageLuminance.js +32 -0
  253. package/dist/utils/mergeDocumentContent.js +7 -17
  254. package/dist/utils/replaceText.js +1 -0
  255. package/dist/utils/units.d.ts +10 -1
  256. package/dist/utils/units.js +12 -1
  257. package/dist/utils/urlSecurity.d.ts +8 -2
  258. package/dist/utils/urlSecurity.js +21 -3
  259. package/package.json +2 -2
  260. package/dist/docx/blockRangeMarkers.d.ts +0 -36
  261. package/dist/docx/blockRangeMarkers.js +0 -59
  262. package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
  263. package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
@@ -1,11 +1,13 @@
1
+ import { serializePreservedAttributes } from "../attributeRemainder.js";
1
2
  import { getUnserializedSectionPropertyChildNames } from "../sectionParser.js";
2
3
  import { serializeSectionReferenceHistory } from "../sectionReferenceHistory.js";
3
4
  import { serializeBorder } from "./borderSerializer.js";
4
5
  import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
5
- import { escapeXml, intAttr } from "./xmlUtils.js";
6
+ import { intAttr } from "./xmlUtils.js";
7
+ import { escapeXmlAttribute } from "@stll/docx-core";
6
8
  //#region src/docx/serializer/sectionPropertiesSerializer.ts
7
- const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${escapeXml(ref.rId)}"/>`;
8
- const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${escapeXml(ref.rId)}"/>`;
9
+ const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${escapeXmlAttribute(ref.rId)}"/>`;
10
+ const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${escapeXmlAttribute(ref.rId)}"/>`;
9
11
  function serializeFootnoteProperties(props) {
10
12
  if (!props) return "";
11
13
  const parts = [];
@@ -52,12 +54,12 @@ function serializePaperSource(props) {
52
54
  return attrs.length > 0 ? `<w:paperSrc ${attrs.join(" ")}/>` : "";
53
55
  }
54
56
  function serializeColumns(props) {
55
- if (props.columnCount === void 0 && !props.columns?.length && props.columnSpace === void 0 && props.equalWidth === void 0) return "";
57
+ if (props.columnCount === void 0 && !props.columns?.length && props.columnSpace === void 0 && props.equalWidth === void 0 && props.separator === void 0) return "";
56
58
  const attrs = [];
57
59
  if (props.columnCount !== void 0 && props.columnCount >= 1) attrs.push(`w:num="${intAttr(props.columnCount)}"`);
58
60
  if (props.columnSpace !== void 0) attrs.push(`w:space="${intAttr(props.columnSpace)}"`);
59
61
  if (props.equalWidth !== void 0) attrs.push(`w:equalWidth="${props.equalWidth ? "1" : "0"}"`);
60
- if (props.separator) attrs.push("w:sep=\"1\"");
62
+ if (props.separator !== void 0) attrs.push(`w:sep="${props.separator ? "1" : "0"}"`);
61
63
  const colElements = (props.columns ?? []).map((col) => {
62
64
  const colAttrs = [];
63
65
  if (col.width !== void 0) colAttrs.push(`w:w="${intAttr(col.width)}"`);
@@ -84,7 +86,7 @@ function serializePageNumbering(props) {
84
86
  if (pageNumbering.format) attrs.push(`w:fmt="${pageNumbering.format}"`);
85
87
  if (pageNumbering.start !== void 0) attrs.push(`w:start="${intAttr(pageNumbering.start)}"`);
86
88
  if (pageNumbering.chapterStyle !== void 0) attrs.push(`w:chapStyle="${intAttr(pageNumbering.chapterStyle)}"`);
87
- if (pageNumbering.chapterSeparator) attrs.push(`w:chapSep="${escapeXml(pageNumbering.chapterSeparator)}"`);
89
+ if (pageNumbering.chapterSeparator) attrs.push(`w:chapSep="${escapeXmlAttribute(pageNumbering.chapterSeparator)}"`);
88
90
  return attrs.length > 0 ? `<w:pgNumType ${attrs.join(" ")}/>` : "";
89
91
  }
90
92
  function serializePageBorders(props) {
@@ -114,10 +116,10 @@ function serializeBackground(props) {
114
116
  const attrs = [];
115
117
  const { background } = props;
116
118
  if (background.color?.auto) attrs.push("w:color=\"auto\"");
117
- else if (background.color?.rgb) attrs.push(`w:color="${escapeXml(background.color.rgb)}"`);
118
- if (background.themeColor ?? background.color?.themeColor) attrs.push(`w:themeColor="${escapeXml(background.themeColor ?? background.color?.themeColor ?? "")}"`);
119
- if (background.themeTint ?? background.color?.themeTint) attrs.push(`w:themeTint="${escapeXml(background.themeTint ?? background.color?.themeTint ?? "")}"`);
120
- if (background.themeShade ?? background.color?.themeShade) attrs.push(`w:themeShade="${escapeXml(background.themeShade ?? background.color?.themeShade ?? "")}"`);
119
+ else if (background.color?.rgb) attrs.push(`w:color="${escapeXmlAttribute(background.color.rgb)}"`);
120
+ if (background.themeColor ?? background.color?.themeColor) attrs.push(`w:themeColor="${escapeXmlAttribute(background.themeColor ?? background.color?.themeColor ?? "")}"`);
121
+ if (background.themeTint ?? background.color?.themeTint) attrs.push(`w:themeTint="${escapeXmlAttribute(background.themeTint ?? background.color?.themeTint ?? "")}"`);
122
+ if (background.themeShade ?? background.color?.themeShade) attrs.push(`w:themeShade="${escapeXmlAttribute(background.themeShade ?? background.color?.themeShade ?? "")}"`);
121
123
  return attrs.length > 0 ? `<w:background ${attrs.join(" ")}/>` : "";
122
124
  }
123
125
  function serializeDocGrid(props) {
@@ -127,7 +129,7 @@ function serializeDocGrid(props) {
127
129
  if (dg.type) attrs.push(`w:type="${dg.type}"`);
128
130
  if (dg.linePitch !== void 0) attrs.push(`w:linePitch="${intAttr(dg.linePitch)}"`);
129
131
  if (dg.charSpace !== void 0) attrs.push(`w:charSpace="${intAttr(dg.charSpace)}"`);
130
- return attrs.length > 0 ? `<w:docGrid ${attrs.join(" ")}/>` : "";
132
+ return attrs.length > 0 ? `<w:docGrid ${attrs.join(" ")}/>` : "<w:docGrid/>";
131
133
  }
132
134
  function serializeOnOffElement(value, name) {
133
135
  if (value === void 0) return "";
@@ -173,12 +175,15 @@ function serializeSectionProperties(props) {
173
175
  serializeOnOffElement(props.noEndnote, "noEndnote"),
174
176
  serializeOnOffElement(props.rtlGutter, "rtlGutter")
175
177
  ]) if (xml) parts.push(xml);
176
- if (props.printerSettingsRelationshipId) parts.push(`<w:printerSettings r:id="${escapeXml(props.printerSettingsRelationshipId)}"/>`);
178
+ if (props.printerSettingsRelationshipId) parts.push(`<w:printerSettings r:id="${escapeXmlAttribute(props.printerSettingsRelationshipId)}"/>`);
177
179
  for (const change of props.propertyChanges ?? []) parts.push(serializeSectionPropertyChange(change));
178
180
  const unserializedChildNames = getUnserializedSectionPropertyChildNames(props);
179
181
  if (unserializedChildNames && unserializedChildNames.length > 0) return "";
180
- if (parts.length > 0) return `<w:sectPr>${parts.join("")}</w:sectPr>`;
181
- if (Object.keys(props).length > 0) return "";
182
+ const attributes = serializePreservedAttributes([], props.preservedAttributes);
183
+ const startTag = attributes.length > 0 ? `<w:sectPr ${attributes.join(" ")}` : "<w:sectPr";
184
+ if (parts.length > 0) return `${startTag}>${parts.join("")}</w:sectPr>`;
185
+ if (Object.keys(props).some((key) => key !== "preservedAttributes")) return "";
186
+ if (attributes.length > 0) return `${startTag}/>`;
182
187
  return "<w:sectPr/>";
183
188
  }
184
189
  //#endregion
@@ -1,5 +1,6 @@
1
1
  import { serializePartElement } from "./partNamespaces.js";
2
- import { escapeXml, intAttr } from "./xmlUtils.js";
2
+ import { intAttr } from "./xmlUtils.js";
3
+ import { escapeXmlAttribute } from "@stll/docx-core";
3
4
  //#region src/docx/serializer/settingsSerializer.ts
4
5
  const serializeSettingsXml = (settings) => {
5
6
  const parts = [`<w:defaultTabStop w:val="${intAttr(settings.defaultTabStop)}"/>`];
@@ -7,8 +8,8 @@ const serializeSettingsXml = (settings) => {
7
8
  if (settings.updateFields) parts.push("<w:updateFields w:val=\"true\"/>");
8
9
  if (settings.themeFontLang) {
9
10
  const attrs = [];
10
- if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${escapeXml(settings.themeFontLang.eastAsia)}"`);
11
- if (settings.themeFontLang.bidi) attrs.push(`w:bidi="${escapeXml(settings.themeFontLang.bidi)}"`);
11
+ if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${escapeXmlAttribute(settings.themeFontLang.eastAsia)}"`);
12
+ if (settings.themeFontLang.bidi) attrs.push(`w:bidi="${escapeXmlAttribute(settings.themeFontLang.bidi)}"`);
12
13
  if (attrs.length > 0) parts.push(`<w:themeFontLang ${attrs.join(" ")}/>`);
13
14
  }
14
15
  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
@@ -2,7 +2,7 @@ import { serializeParagraphFormatting } from "./paragraphSerializer.js";
2
2
  import { serializePartElement } from "./partNamespaces.js";
3
3
  import { serializeTableCellFormatting, serializeTableFormatting, serializeTableRowFormatting } from "./tableSerializer.js";
4
4
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
5
- import { escapeXml } from "./xmlUtils.js";
5
+ import { escapeXmlAttribute } from "@stll/docx-core";
6
6
  //#region src/docx/serializer/stylesSerializer.ts
7
7
  const serializeStylesXml = (definitions) => {
8
8
  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
@@ -28,12 +28,12 @@ const serializeLatentStyles = (definitions) => {
28
28
  return `<w:latentStyles${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}/>`;
29
29
  };
30
30
  const serializeStyle = (style) => {
31
- const attrs = [`w:type="${style.type}"`, `w:styleId="${escapeXml(style.styleId)}"`];
31
+ const attrs = [`w:type="${style.type}"`, `w:styleId="${escapeXmlAttribute(style.styleId)}"`];
32
32
  if (style.default) attrs.push("w:default=\"1\"");
33
- const parts = [`<w:name w:val="${escapeXml(style.name ?? style.styleId)}"/>`];
34
- if (style.basedOn) parts.push(`<w:basedOn w:val="${escapeXml(style.basedOn)}"/>`);
35
- if (style.next) parts.push(`<w:next w:val="${escapeXml(style.next)}"/>`);
36
- if (style.link) parts.push(`<w:link w:val="${escapeXml(style.link)}"/>`);
33
+ const parts = [`<w:name w:val="${escapeXmlAttribute(style.name ?? style.styleId)}"/>`];
34
+ if (style.basedOn) parts.push(`<w:basedOn w:val="${escapeXmlAttribute(style.basedOn)}"/>`);
35
+ if (style.next) parts.push(`<w:next w:val="${escapeXmlAttribute(style.next)}"/>`);
36
+ if (style.link) parts.push(`<w:link w:val="${escapeXmlAttribute(style.link)}"/>`);
37
37
  if (style.uiPriority !== void 0) parts.push(`<w:uiPriority w:val="${style.uiPriority}"/>`);
38
38
  pushBooleanElement(parts, "hidden", style.hidden);
39
39
  pushBooleanElement(parts, "semiHidden", style.semiHidden);
@@ -1,13 +1,15 @@
1
1
  import { canonicalJson } from "../../utils/canonicalJson.js";
2
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
3
- import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
3
+ import { serializePreservedAttributes } from "../attributeRemainder.js";
4
+ import { serializeWithPreservedChildren } from "../containerChildren.js";
4
5
  import { TABLE_LOOK_FLAGS } from "../tableLook.js";
5
6
  import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTableRowProperties } from "../tableParser.js";
6
7
  import { sanitizeCapturedXmlElement } from "../verbatimCapture.js";
7
8
  import { NAMESPACES, OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
8
9
  import { serializeBorder } from "./borderSerializer.js";
9
10
  import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
10
- import { escapeXml, intAttr } from "./xmlUtils.js";
11
+ import { intAttr } from "./xmlUtils.js";
12
+ import { escapeXmlAttribute } from "@stll/docx-core";
11
13
  //#region src/docx/serializer/tableSerializer.ts
12
14
  /**
13
15
  * The captured element with the revision children written back into it.
@@ -130,15 +132,15 @@ function serializeCellMargins(margins, elementName) {
130
132
  function serializeShading(shading) {
131
133
  if (!shading) return "";
132
134
  const attrs = [];
133
- if (shading.pattern) attrs.push(`w:val="${escapeXml(shading.pattern)}"`);
135
+ if (shading.pattern) attrs.push(`w:val="${escapeXmlAttribute(shading.pattern)}"`);
134
136
  else attrs.push("w:val=\"clear\"");
135
- if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXml(shading.color.rgb)}"`);
137
+ if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXmlAttribute(shading.color.rgb)}"`);
136
138
  else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
137
- if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXml(shading.fill.rgb)}"`);
139
+ if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXmlAttribute(shading.fill.rgb)}"`);
138
140
  else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
139
- if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXml(shading.fill.themeColor)}"`);
140
- if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXml(shading.fill.themeTint)}"`);
141
- if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXml(shading.fill.themeShade)}"`);
141
+ if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(shading.fill.themeColor)}"`);
142
+ if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(shading.fill.themeTint)}"`);
143
+ if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(shading.fill.themeShade)}"`);
142
144
  if (attrs.length === 0) return "";
143
145
  return `<w:shd ${attrs.join(" ")}/>`;
144
146
  }
@@ -154,7 +156,7 @@ function serializeShading(shading) {
154
156
  function serializeTableLook(look) {
155
157
  if (!look) return "";
156
158
  const attrs = [];
157
- if (look.val !== void 0) attrs.push(`w:val="${escapeXml(look.val)}"`);
159
+ if (look.val !== void 0) attrs.push(`w:val="${escapeXmlAttribute(look.val)}"`);
158
160
  for (const flag of TABLE_LOOK_FLAGS) {
159
161
  const stated = look[flag];
160
162
  if (stated !== void 0) attrs.push(`w:${flag}="${stated ? "1" : "0"}"`);
@@ -189,7 +191,7 @@ function serializeTableFormatting(formatting, propertyChanges) {
189
191
  if (tableSource !== null) return withRevisionChildren(tableSource, (propertyChanges ?? []).map((change) => serializeTablePropertyChange(change)));
190
192
  const parts = [];
191
193
  if (formatting) {
192
- if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXml(formatting.styleId)}"/>`);
194
+ if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXmlAttribute(formatting.styleId)}"/>`);
193
195
  const floatingXml = serializeFloatingTableProperties(formatting.floating);
194
196
  if (floatingXml) parts.push(floatingXml);
195
197
  if (formatting.overlap) parts.push(`<w:tblOverlap w:val="${formatting.overlap}"/>`);
@@ -368,8 +370,14 @@ const replayableGridChangeXml = (gridChangeXml) => sanitizeCapturedXmlElement(gr
368
370
  * Serialize cell content (paragraphs, nested tables)
369
371
  */
370
372
  function serializeCellContent(content, serializeParagraph) {
371
- const parts = [];
372
- for (const item of content) parts.push(withBlockRangeMarkers(item, item.type === "paragraph" ? serializeParagraph(item) : serializeTable(item, serializeParagraph)));
373
+ const parts = content.map((block) => {
374
+ switch (block.type) {
375
+ case "paragraph": return serializeParagraph(block);
376
+ case "table": return serializeTable(block, serializeParagraph);
377
+ case "preservedBlock": return block.xml;
378
+ default: return block;
379
+ }
380
+ });
373
381
  if (parts.length === 0) parts.push("<w:p/>");
374
382
  return parts.join("");
375
383
  }
@@ -390,8 +398,9 @@ function serializeTableRow(row, serializeParagraph) {
390
398
  const parts = [];
391
399
  const trPrXml = serializeTableRowFormatting(row.formatting, row.propertyChanges, row.structuralChange);
392
400
  if (trPrXml) parts.push(trPrXml);
393
- for (const cell of row.cells) parts.push(serializeTableCell(cell, serializeParagraph));
394
- return `<w:tr>${parts.join("")}</w:tr>`;
401
+ parts.push(serializeWithPreservedChildren(row.cells.map((cell) => serializeTableCell(cell, serializeParagraph)), row.preserved));
402
+ const attrs = serializePreservedAttributes([], row.preservedAttributes);
403
+ return `<w:tr${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${parts.join("")}</w:tr>`;
395
404
  }
396
405
  /**
397
406
  * Serialize a table to OOXML XML (w:tbl)
@@ -400,9 +409,11 @@ function serializeTableRow(row, serializeParagraph) {
400
409
  * @returns XML string for the table
401
410
  */
402
411
  function serializeTable(table, serializeParagraph) {
403
- const parts = [serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>", serializeTableGrid(table)];
404
- for (const row of table.rows) parts.push(serializeTableRow(row, serializeParagraph));
405
- return `<w:tbl>${parts.join("")}</w:tbl>`;
412
+ return `<w:tbl>${[
413
+ serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>",
414
+ serializeTableGrid(table),
415
+ serializeWithPreservedChildren(table.rows.map((row) => serializeTableRow(row, serializeParagraph)), table.preserved)
416
+ ].join("")}</w:tbl>`;
406
417
  }
407
418
  /**
408
419
  * Serialize multiple tables to OOXML XML
@@ -1,7 +1,8 @@
1
1
  import { HIGHLIGHT_COLOR_VALUES } from "../../types/documentEnumValues.js";
2
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
3
3
  import { roundHorizontalScalePercentForSerialization } from "../../utils/horizontalScale.js";
4
- import { escapeXml, intAttr } from "./xmlUtils.js";
4
+ import { intAttr } from "./xmlUtils.js";
5
+ import { escapeXmlAttribute } from "@stll/docx-core";
5
6
  //#region src/docx/serializer/textFormattingSerializer.ts
6
7
  const VALID_HIGHLIGHT_COLORS = new Set(HIGHLIGHT_COLOR_VALUES);
7
8
  /**
@@ -12,10 +13,10 @@ function serializeColorElement(color) {
12
13
  const { auto, rgb, themeColor, themeTint, themeShade } = color;
13
14
  const attrs = [];
14
15
  if (auto) attrs.push("w:val=\"auto\"");
15
- else if (rgb && isValidHexColor(rgb)) attrs.push(`w:val="${escapeXml(rgb)}"`);
16
- if (themeColor) attrs.push(`w:themeColor="${escapeXml(themeColor)}"`);
17
- if (themeTint) attrs.push(`w:themeTint="${escapeXml(themeTint)}"`);
18
- if (themeShade) attrs.push(`w:themeShade="${escapeXml(themeShade)}"`);
16
+ else if (rgb && isValidHexColor(rgb)) attrs.push(`w:val="${escapeXmlAttribute(rgb)}"`);
17
+ if (themeColor) attrs.push(`w:themeColor="${escapeXmlAttribute(themeColor)}"`);
18
+ if (themeTint) attrs.push(`w:themeTint="${escapeXmlAttribute(themeTint)}"`);
19
+ if (themeShade) attrs.push(`w:themeShade="${escapeXmlAttribute(themeShade)}"`);
19
20
  return attrs.length === 0 ? "" : `<w:color ${attrs.join(" ")}/>`;
20
21
  }
21
22
  /**
@@ -25,20 +26,20 @@ function serializeShading(shading) {
25
26
  if (!shading) return "";
26
27
  const { pattern, color, fill } = shading;
27
28
  const attrs = [];
28
- if (pattern) attrs.push(`w:val="${escapeXml(pattern)}"`);
29
+ if (pattern) attrs.push(`w:val="${escapeXmlAttribute(pattern)}"`);
29
30
  else attrs.push("w:val=\"clear\"");
30
31
  if (color) {
31
32
  const { rgb, auto, themeColor: _themeColor, themeTint: _themeTint, themeShade: _themeShade } = color;
32
- if (rgb && isValidHexColor(rgb)) attrs.push(`w:color="${escapeXml(rgb)}"`);
33
+ if (rgb && isValidHexColor(rgb)) attrs.push(`w:color="${escapeXmlAttribute(rgb)}"`);
33
34
  else if (auto) attrs.push("w:color=\"auto\"");
34
35
  }
35
36
  if (fill) {
36
37
  const { rgb, auto, themeColor, themeTint, themeShade } = fill;
37
- if (rgb && isValidHexColor(rgb)) attrs.push(`w:fill="${escapeXml(rgb)}"`);
38
+ if (rgb && isValidHexColor(rgb)) attrs.push(`w:fill="${escapeXmlAttribute(rgb)}"`);
38
39
  else if (auto) attrs.push("w:fill=\"auto\"");
39
- if (themeColor) attrs.push(`w:themeFill="${escapeXml(themeColor)}"`);
40
- if (themeTint) attrs.push(`w:themeFillTint="${escapeXml(themeTint)}"`);
41
- if (themeShade) attrs.push(`w:themeFillShade="${escapeXml(themeShade)}"`);
40
+ if (themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(themeColor)}"`);
41
+ if (themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(themeTint)}"`);
42
+ if (themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(themeShade)}"`);
42
43
  }
43
44
  return attrs.length === 0 ? "" : `<w:shd ${attrs.join(" ")}/>`;
44
45
  }
@@ -49,19 +50,19 @@ function serializeTextFormatting(input) {
49
50
  if (!input) return "";
50
51
  const { bold, boldCs, italic, italicCs, underline, strike, doubleStrike, vertAlign, smallCaps, allCaps, hidden, noProof, color, highlight, shading, fontSize, fontSizeCs, fontFamily, language, spacing, position, scale, kerning, effect, emphasisMark, emboss, imprint, outline, shadow, rtl, cs, styleId } = input;
51
52
  const parts = [];
52
- if (styleId) parts.push(`<w:rStyle w:val="${escapeXml(styleId)}"/>`);
53
+ if (styleId) parts.push(`<w:rStyle w:val="${escapeXmlAttribute(styleId)}"/>`);
53
54
  if (fontFamily) {
54
55
  const { ascii, hAnsi, eastAsia, cs: complexScript, hint, asciiTheme, hAnsiTheme, eastAsiaTheme, csTheme } = fontFamily;
55
56
  const fontAttrs = [];
56
- if (ascii) fontAttrs.push(`w:ascii="${escapeXml(ascii)}"`);
57
- if (hAnsi) fontAttrs.push(`w:hAnsi="${escapeXml(hAnsi)}"`);
58
- if (eastAsia) fontAttrs.push(`w:eastAsia="${escapeXml(eastAsia)}"`);
59
- if (complexScript) fontAttrs.push(`w:cs="${escapeXml(complexScript)}"`);
60
- if (hint) fontAttrs.push(`w:hint="${escapeXml(hint)}"`);
61
- if (asciiTheme) fontAttrs.push(`w:asciiTheme="${escapeXml(asciiTheme)}"`);
62
- if (hAnsiTheme) fontAttrs.push(`w:hAnsiTheme="${escapeXml(hAnsiTheme)}"`);
63
- if (eastAsiaTheme) fontAttrs.push(`w:eastAsiaTheme="${escapeXml(eastAsiaTheme)}"`);
64
- if (csTheme) fontAttrs.push(`w:cstheme="${escapeXml(csTheme)}"`);
57
+ if (ascii) fontAttrs.push(`w:ascii="${escapeXmlAttribute(ascii)}"`);
58
+ if (hAnsi) fontAttrs.push(`w:hAnsi="${escapeXmlAttribute(hAnsi)}"`);
59
+ if (eastAsia) fontAttrs.push(`w:eastAsia="${escapeXmlAttribute(eastAsia)}"`);
60
+ if (complexScript) fontAttrs.push(`w:cs="${escapeXmlAttribute(complexScript)}"`);
61
+ if (hint) fontAttrs.push(`w:hint="${escapeXmlAttribute(hint)}"`);
62
+ if (asciiTheme) fontAttrs.push(`w:asciiTheme="${escapeXmlAttribute(asciiTheme)}"`);
63
+ if (hAnsiTheme) fontAttrs.push(`w:hAnsiTheme="${escapeXmlAttribute(hAnsiTheme)}"`);
64
+ if (eastAsiaTheme) fontAttrs.push(`w:eastAsiaTheme="${escapeXmlAttribute(eastAsiaTheme)}"`);
65
+ if (csTheme) fontAttrs.push(`w:cstheme="${escapeXmlAttribute(csTheme)}"`);
65
66
  if (fontAttrs.length > 0) parts.push(`<w:rFonts ${fontAttrs.join(" ")}/>`);
66
67
  }
67
68
  if (bold === true) parts.push("<w:b/>");
@@ -114,10 +115,10 @@ function serializeTextFormatting(input) {
114
115
  const uAttrs = [`w:val="${style}"`];
115
116
  if (underlineColor) {
116
117
  const { rgb, themeColor, themeTint, themeShade, auto: _auto } = underlineColor;
117
- if (rgb && isValidHexColor(rgb)) uAttrs.push(`w:color="${escapeXml(rgb)}"`);
118
- if (themeColor) uAttrs.push(`w:themeColor="${escapeXml(themeColor)}"`);
119
- if (themeTint) uAttrs.push(`w:themeTint="${escapeXml(themeTint)}"`);
120
- if (themeShade) uAttrs.push(`w:themeShade="${escapeXml(themeShade)}"`);
118
+ if (rgb && isValidHexColor(rgb)) uAttrs.push(`w:color="${escapeXmlAttribute(rgb)}"`);
119
+ if (themeColor) uAttrs.push(`w:themeColor="${escapeXmlAttribute(themeColor)}"`);
120
+ if (themeTint) uAttrs.push(`w:themeTint="${escapeXmlAttribute(themeTint)}"`);
121
+ if (themeShade) uAttrs.push(`w:themeShade="${escapeXmlAttribute(themeShade)}"`);
121
122
  }
122
123
  parts.push(`<w:u ${uAttrs.join(" ")}/>`);
123
124
  }
@@ -133,9 +134,9 @@ function serializeTextFormatting(input) {
133
134
  if (language) {
134
135
  const { val, eastAsia, bidi } = language;
135
136
  const languageAttrs = [];
136
- if (val) languageAttrs.push(`w:val="${escapeXml(val)}"`);
137
- if (eastAsia) languageAttrs.push(`w:eastAsia="${escapeXml(eastAsia)}"`);
138
- if (bidi) languageAttrs.push(`w:bidi="${escapeXml(bidi)}"`);
137
+ if (val) languageAttrs.push(`w:val="${escapeXmlAttribute(val)}"`);
138
+ if (eastAsia) languageAttrs.push(`w:eastAsia="${escapeXmlAttribute(eastAsia)}"`);
139
+ if (bidi) languageAttrs.push(`w:bidi="${escapeXmlAttribute(bidi)}"`);
139
140
  if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
140
141
  }
141
142
  return parts.length === 0 ? "" : `<w:rPr>${parts.join("")}</w:rPr>`;
@@ -1,8 +1,8 @@
1
1
  import { serializePartElement } from "./partNamespaces.js";
2
- import { escapeXml } from "./xmlUtils.js";
2
+ import { escapeXmlAttribute } from "@stll/docx-core";
3
3
  //#region src/docx/serializer/themeSerializer.ts
4
4
  const serializeThemeXml = (theme) => {
5
- const name = escapeXml(theme.name ?? "Folio Theme");
5
+ const name = escapeXmlAttribute(theme.name ?? "Folio Theme");
6
6
  const elements = `${serializeColorScheme(theme.colorScheme)}${serializeFontScheme(theme)}${serializeFormatScheme(theme)}`;
7
7
  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
8
8
  partPath: "word/theme/theme1.xml",
@@ -28,17 +28,17 @@ const serializeColorScheme = (colors) => {
28
28
  hlink: colors?.hlink ?? "0563C1",
29
29
  folHlink: colors?.folHlink ?? "954F72"
30
30
  };
31
- return `<a:clrScheme name="Folio">${Object.entries(values).map(([slot, value]) => `<a:${slot}><a:srgbClr val="${escapeXml(value)}"/></a:${slot}>`).join("")}</a:clrScheme>`;
31
+ return `<a:clrScheme name="Folio">${Object.entries(values).map(([slot, value]) => `<a:${slot}><a:srgbClr val="${escapeXmlAttribute(value)}"/></a:${slot}>`).join("")}</a:clrScheme>`;
32
32
  };
33
33
  const serializeFontScheme = (theme) => {
34
34
  return `<a:fontScheme name="Folio">${serializeThemeFont(theme.fontScheme?.majorFont, "majorFont", "Arial")}${serializeThemeFont(theme.fontScheme?.minorFont, "minorFont", "Arial")}</a:fontScheme>`;
35
35
  };
36
36
  const serializeThemeFont = (font, element, fallback) => {
37
- const scriptFonts = Object.entries(font?.fonts ?? {}).map(([script, typeface]) => `<a:font script="${escapeXml(script)}" typeface="${escapeXml(typeface)}"/>`).join("");
38
- return `<a:${element}><a:latin typeface="${escapeXml(font?.latin ?? fallback)}"/><a:ea typeface="${escapeXml(font?.ea ?? "")}"/><a:cs typeface="${escapeXml(font?.cs ?? "")}"/>${scriptFonts}</a:${element}>`;
37
+ const scriptFonts = Object.entries(font?.fonts ?? {}).map(([script, typeface]) => `<a:font script="${escapeXmlAttribute(script)}" typeface="${escapeXmlAttribute(typeface)}"/>`).join("");
38
+ return `<a:${element}><a:latin typeface="${escapeXmlAttribute(font?.latin ?? fallback)}"/><a:ea typeface="${escapeXmlAttribute(font?.ea ?? "")}"/><a:cs typeface="${escapeXmlAttribute(font?.cs ?? "")}"/>${scriptFonts}</a:${element}>`;
39
39
  };
40
40
  const serializeFormatScheme = (theme) => {
41
- return `<a:fmtScheme name="${escapeXml(theme.formatScheme?.name ?? "Folio")}"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst></a:fmtScheme>`;
41
+ return `<a:fmtScheme name="${escapeXmlAttribute(theme.formatScheme?.name ?? "Folio")}"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst></a:fmtScheme>`;
42
42
  };
43
43
  //#endregion
44
44
  export { serializeThemeXml };
@@ -1,6 +1,6 @@
1
1
  import { DATE_UTC_ATTRIBUTE } from "../trackedChangeInfo.js";
2
- import { escapeXml } from "./xmlUtils.js";
3
2
  import { panic } from "better-result";
3
+ import { escapeXmlAttribute } from "@stll/docx-core";
4
4
  import { normalizeRevisionId } from "@stll/docx-core/model";
5
5
  //#region src/docx/serializer/trackedChangeAttributes.ts
6
6
  /** Enforces the singular `w:rPrChange` child in a run-property container. */
@@ -26,6 +26,6 @@ const trackedChangeAttributeEntries = (info) => {
26
26
  return entries;
27
27
  };
28
28
  const trackedChangeAttributeRecord = (info) => Object.fromEntries(trackedChangeAttributeEntries(info));
29
- const serializeTrackedChangeAttributes = (info) => trackedChangeAttributeEntries(info).map(([name, value]) => `${name}="${escapeXml(value)}"`).join(" ");
29
+ const serializeTrackedChangeAttributes = (info) => trackedChangeAttributeEntries(info).map(([name, value]) => `${name}="${escapeXmlAttribute(value)}"`).join(" ");
30
30
  //#endregion
31
31
  export { getSingularRunPropertyChange, serializeTrackedChangeAttributes, trackedChangeAttributeEntries, trackedChangeAttributeRecord };
@@ -2,7 +2,6 @@
2
2
  /**
3
3
  * Shared XML utility functions for serializers.
4
4
  */
5
- declare function escapeXml(text: string): string;
6
5
  /**
7
6
  * Format a numeric value as an integer XML attribute.
8
7
  *
@@ -29,4 +28,4 @@ declare function intAttr(value: number | undefined | null): string;
29
28
  */
30
29
  declare function isSingleWellFormedElement(xml: string, expectedLocalName: string): boolean;
31
30
  //#endregion
32
- export { escapeXml, intAttr, isSingleWellFormedElement };
31
+ export { intAttr, isSingleWellFormedElement };
@@ -3,18 +3,6 @@ import { getLocalName, parseXml } from "../xmlParser.js";
3
3
  /**
4
4
  * Shared XML utility functions for serializers.
5
5
  */
6
- const XML_SPECIAL_CHARACTER_PATTERN = /[&<>"']/u;
7
- const XML_SPECIAL_CHARACTER_GLOBAL_PATTERN = /[&<>"']/gu;
8
- function escapeXml(text) {
9
- if (!XML_SPECIAL_CHARACTER_PATTERN.test(text)) return text;
10
- return text.replace(XML_SPECIAL_CHARACTER_GLOBAL_PATTERN, (character) => {
11
- if (character === "&") return "&amp;";
12
- if (character === "<") return "&lt;";
13
- if (character === ">") return "&gt;";
14
- if (character === "\"") return "&quot;";
15
- return "&apos;";
16
- });
17
- }
18
6
  /**
19
7
  * Format a numeric value as an integer XML attribute.
20
8
  *
@@ -57,4 +45,4 @@ function isSingleWellFormedElement(xml, expectedLocalName) {
57
45
  return root?.name !== void 0 && getLocalName(root.name) === expectedLocalName;
58
46
  }
59
47
  //#endregion
60
- export { escapeXml, intAttr, isSingleWellFormedElement };
48
+ export { intAttr, isSingleWellFormedElement };
@@ -1,3 +1,4 @@
1
+ import { XmlResourceLimits } from "../xmlResourceLimits.js";
1
2
  import JSZip from "jszip";
2
3
  //#region src/docx/server/boundedArchive.d.ts
3
4
  declare module "jszip" {
@@ -27,6 +28,17 @@ type DocxArchiveOptions = {
27
28
  maxEntryBytes?: number;
28
29
  maxTotalBytes?: number;
29
30
  maxEntries?: number;
31
+ /**
32
+ * Bounds on parsed XML structure, applied to every XML part this archive
33
+ * hands out as a string.
34
+ *
35
+ * Every consumer of `readEntryString` parses the result into an object tree,
36
+ * and the byte caps above bound the markup, not the tree. Enforcing the
37
+ * element and attribute bounds here rather than at each parse site means a
38
+ * new consumer is bounded by construction: there is no way to obtain a part
39
+ * string from this archive that has not been counted.
40
+ */
41
+ xmlLimits?: Partial<XmlResourceLimits>;
30
42
  };
31
43
  type DocxArchiveEntry = {
32
44
  readonly path: string;
@@ -1,3 +1,4 @@
1
+ import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits, createXmlPackageBudget } from "../xmlResourceLimits.js";
1
2
  import { TaggedError } from "better-result";
2
3
  import JSZip from "jszip";
3
4
  //#region src/docx/server/boundedArchive.ts
@@ -117,6 +118,12 @@ const loadDocxArchive = async (bytes, options = {}) => {
117
118
  message: `DOCX archive declares ${declaredTotalBytes} cumulative bytes (max ${maxTotalBytes})`,
118
119
  reason: "total-too-large"
119
120
  });
121
+ const xmlLimits = {
122
+ ...FOLIO_XML_RESOURCE_LIMITS,
123
+ ...options.xmlLimits
124
+ };
125
+ const xmlBudget = createXmlPackageBudget();
126
+ const countedParts = /* @__PURE__ */ new Set();
120
127
  let totalBytesRead = 0;
121
128
  let readChain = Promise.resolve();
122
129
  const readEntry = async (path, readOptions = {}) => {
@@ -151,7 +158,19 @@ const loadDocxArchive = async (bytes, options = {}) => {
151
158
  }))),
152
159
  async readEntryString(path) {
153
160
  const content = await readEntry(path);
154
- return content === null ? null : new TextDecoder("utf-8", { ignoreBOM: true }).decode(content);
161
+ if (content === null) return null;
162
+ const xml = new TextDecoder("utf-8", { ignoreBOM: true }).decode(content);
163
+ const lower = path.toLowerCase();
164
+ if (!lower.endsWith(".xml") && !lower.endsWith(".rels")) return xml;
165
+ const charged = countedParts.has(path);
166
+ countedParts.add(path);
167
+ assertXmlResourceLimits({
168
+ xml,
169
+ limits: xmlLimits,
170
+ partPath: path,
171
+ ...charged ? {} : { budget: xmlBudget }
172
+ });
173
+ return xml;
155
174
  },
156
175
  readEntryUint8: readEntry
157
176
  };
@@ -203,6 +203,7 @@ const flattenBlocks = (content) => {
203
203
  out.push(block);
204
204
  return;
205
205
  }
206
+ if (block.type === "preservedBlock") return;
206
207
  for (const child of block.content) visit(child);
207
208
  };
208
209
  for (const block of content) visit(block);
@@ -462,7 +463,7 @@ const remapListRendering = (rendering, cloner) => {
462
463
  const collectTableParagraphs = (table) => {
463
464
  const out = [];
464
465
  for (const row of table.rows) for (const cell of row.cells) for (const item of cell.content) if (item.type === "paragraph") out.push(item);
465
- else out.push(...collectTableParagraphs(item));
466
+ else if (item.type === "table") out.push(...collectTableParagraphs(item));
466
467
  return out;
467
468
  };
468
469
  const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner, bookmarkIds }) => {
@@ -475,6 +476,7 @@ const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner
475
476
  ...structuredClone(cell),
476
477
  content: cell.content.map((item) => {
477
478
  if (item.type === "table") return cloneTable(item);
479
+ if (item.type !== "paragraph") return structuredClone(item);
478
480
  const targetParaId = paraIds.mint(item.paraId);
479
481
  const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
480
482
  if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
@@ -4,7 +4,7 @@ declare const FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME = "prosemirror";
4
4
  /** Maximum accepted size of a complete Yjs collaboration state update. */
5
5
  declare const FOLIO_YJS_UPDATE_MAX_BYTES: number;
6
6
  /** Stable failure codes returned by server-side Yjs-to-DOCX materialization. */
7
- declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "update_too_large"];
7
+ declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "stale_attr_schema", "update_too_large"];
8
8
  /** Failure code for a rejected Yjs-to-DOCX materialization request. */
9
9
  type FolioYjsDocxMaterializationErrorCode = (typeof FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES)[number];
10
10
  declare const FolioYjsDocxMaterializationError_base: import("better-result").TaggedErrorClass<"FolioYjsDocxMaterializationError">;
@@ -1,6 +1,6 @@
1
1
  import { fromProseDoc } from "../../prosemirror/conversion/fromProseDoc.js";
2
2
  import { schema } from "../../prosemirror/schema/index.js";
3
- import { readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/yjsParagraphSourceContract.js";
3
+ import { applyAttrSchemaMigrations, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/yjsDocumentMetadata.js";
4
4
  import { ParagraphPropertySourceValidationError } from "../paragraphPropertySource.js";
5
5
  import { parseDocx } from "../parser.js";
6
6
  import { repackDocx } from "../rezip.js";
@@ -18,6 +18,7 @@ const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES = [
18
18
  "invalid_update",
19
19
  "missing_document",
20
20
  "source_mismatch",
21
+ "stale_attr_schema",
21
22
  "update_too_large"
22
23
  ];
23
24
  /** Typed failure raised when a collaboration snapshot cannot be materialized. */
@@ -40,6 +41,13 @@ const readProseMirrorDocument = (yjsUpdate) => {
40
41
  code: "missing_document",
41
42
  message: "Yjs update does not contain a Folio document."
42
43
  });
44
+ const attrSchemaVersion = readYjsAttrSchemaVersion(ydoc);
45
+ if (attrSchemaVersion.isErr()) throw new FolioYjsDocxMaterializationError({
46
+ cause: attrSchemaVersion.error,
47
+ code: "stale_attr_schema",
48
+ message: attrSchemaVersion.error.message
49
+ });
50
+ applyAttrSchemaMigrations(ydoc, fragment, attrSchemaVersion.value);
43
51
  const contract = readYjsParagraphSourceContract(ydoc);
44
52
  if (!contract) throw new FolioYjsDocxMaterializationError({
45
53
  code: "source_mismatch",
@@ -0,0 +1,55 @@
1
+ import { FolioYjsAttrSchemaVersion } from "../../prosemirror/yjsDocumentMetadata.js";
2
+ import { Result } from "better-result";
3
+ //#region src/docx/server/migrateYjsAttrSchema.d.ts
4
+ /** Stable failure codes returned by an offline snapshot migration. */
5
+ declare const FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES: readonly ["invalid_update", "unsupported_version", "update_too_large"];
6
+ /** Failure code for a snapshot that could not be migrated. */
7
+ type FolioYjsSnapshotMigrationErrorCode = (typeof FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES)[number];
8
+ declare const FolioYjsSnapshotMigrationError_base: import("better-result").TaggedErrorClass<"FolioYjsSnapshotMigrationError">;
9
+ /** Typed failure raised when a collaboration snapshot cannot be migrated. */
10
+ declare class FolioYjsSnapshotMigrationError extends FolioYjsSnapshotMigrationError_base<{
11
+ code: FolioYjsSnapshotMigrationErrorCode;
12
+ message: string;
13
+ cause?: unknown;
14
+ }> {}
15
+ /** What an offline migration did to one snapshot. */
16
+ type FolioYjsSnapshotMigrationResult = {
17
+ /** The complete state update to store in place of the input. */
18
+ update: Uint8Array;
19
+ /** The attr-schema version the input was written under. */
20
+ fromVersion: FolioYjsAttrSchemaVersion;
21
+ /** The attr-schema version the output carries. */
22
+ toVersion: FolioYjsAttrSchemaVersion;
23
+ /** Paragraphs whose attrs the migration rewrote. */
24
+ paragraphsRewritten: number;
25
+ };
26
+ /**
27
+ * Carry a stored collaboration snapshot forward to the attr schema this build
28
+ * writes, without a ProseMirror schema and without rebuilding a single node.
29
+ *
30
+ * A Yjs XML element's attributes are ordinary CRDT map entries holding JSON, so
31
+ * {@link applyAttrSchemaMigrations} rewrites values in the fragment directly.
32
+ * Nothing here can trip y-prosemirror's node rebuild, which deletes an element
33
+ * it fails to construct. The function is pure and offline: no network, no
34
+ * DOCX, no editor.
35
+ *
36
+ * A snapshot already at the current version is returned byte for byte, so a
37
+ * host may run this over its whole corpus repeatedly.
38
+ *
39
+ * **The contract a reseed must respect.** This function never reseeds and never
40
+ * touches the paragraph-source contract: it preserves whichever source binding
41
+ * the snapshot already carries. A reseed does the opposite, and Folio holds it
42
+ * to one rule: *the DOCX handed to the seeding editor must be the same bytes
43
+ * the host will later pass to `materializeYjsDocx` as that room's source*.
44
+ * The contract is a digest of those bytes, so seeding from one document while
45
+ * recording another as the room's source makes every later save fail with
46
+ * `source_mismatch`. The reverse mistake is worse and Folio cannot see it: a
47
+ * host that reseeds from the document a room was *created* from, rather than
48
+ * from the latest content it published, produces a perfectly well-formed
49
+ * snapshot whose collaborative history has been replaced by an older document.
50
+ * Rebind the recorded source in the same statement that clears the seed, or do
51
+ * not clear it.
52
+ */
53
+ declare const migrateFolioYjsSnapshot: (update: Uint8Array) => Result<FolioYjsSnapshotMigrationResult, FolioYjsSnapshotMigrationError>;
54
+ //#endregion
55
+ export { FOLIO_YJS_SNAPSHOT_MIGRATION_ERROR_CODES, FolioYjsSnapshotMigrationError, FolioYjsSnapshotMigrationErrorCode, FolioYjsSnapshotMigrationResult, migrateFolioYjsSnapshot };