@stll/folio-core 0.43.0 → 0.45.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 (237) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +7 -5
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/ai-edits/snapshot.js +13 -9
  6. package/dist/compare/content-alignment.js +94 -54
  7. package/dist/compare/inline-atoms.js +34 -20
  8. package/dist/compare/style-resources.js +6 -0
  9. package/dist/content-controls/mutateContentControls.js +4 -2
  10. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  11. package/dist/document-operations.js +14 -3
  12. package/dist/docx/appVersionNormalization.d.ts +0 -18
  13. package/dist/docx/blockContentParser.js +8 -0
  14. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  15. package/dist/docx/blockRangeMarkers.js +59 -0
  16. package/dist/docx/bookmarkParser.d.ts +2 -20
  17. package/dist/docx/bookmarkParser.js +6 -30
  18. package/dist/docx/borderParser.d.ts +13 -0
  19. package/dist/docx/borderParser.js +71 -0
  20. package/dist/docx/builtInStyles.d.ts +165 -0
  21. package/dist/docx/builtInStyles.js +239 -0
  22. package/dist/docx/commentIdNormalization.d.ts +3 -1
  23. package/dist/docx/commentIdNormalization.js +18 -1
  24. package/dist/docx/commentParser.d.ts +2 -1
  25. package/dist/docx/commentParser.js +80 -42
  26. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  27. package/dist/docx/commentReferenceNormalization.js +23 -14
  28. package/dist/docx/commentThreadKey.d.ts +18 -0
  29. package/dist/docx/commentThreadKey.js +22 -0
  30. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  31. package/dist/docx/danglingRelationshipReferences.js +30 -0
  32. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  33. package/dist/docx/defaultParagraphStyle.js +23 -1
  34. package/dist/docx/diagramPreview.js +87 -27
  35. package/dist/docx/documentParser.d.ts +2 -1
  36. package/dist/docx/documentParser.js +2 -2
  37. package/dist/docx/drawingUtils.d.ts +8 -1
  38. package/dist/docx/drawingUtils.js +12 -3
  39. package/dist/docx/fieldParser.js +3 -5
  40. package/dist/docx/footnoteParser.d.ts +3 -2
  41. package/dist/docx/footnoteParser.js +19 -4
  42. package/dist/docx/groupDrawingParser.js +4 -4
  43. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  44. package/dist/docx/headerFooterRefParser.js +42 -12
  45. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  46. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  47. package/dist/docx/hyperlinkParser.js +13 -17
  48. package/dist/docx/imageParser.d.ts +10 -2
  49. package/dist/docx/imageParser.js +80 -30
  50. package/dist/docx/imageRawXml.d.ts +14 -1
  51. package/dist/docx/imageRawXml.js +35 -11
  52. package/dist/docx/markupRangeMarker.d.ts +15 -0
  53. package/dist/docx/markupRangeMarker.js +44 -0
  54. package/dist/docx/mathToMathml.js +12 -14
  55. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  56. package/dist/docx/nonVisualDrawingProps.js +46 -0
  57. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  58. package/dist/docx/noteReferenceStyles.js +70 -0
  59. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  60. package/dist/docx/numberingReferenceNormalization.js +20 -1
  61. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  62. package/dist/docx/paragraphParser.js +66 -99
  63. package/dist/docx/paragraphPropertySource.js +1 -0
  64. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  65. package/dist/docx/paragraphTraversal.d.ts +37 -1
  66. package/dist/docx/paragraphTraversal.js +84 -1
  67. package/dist/docx/parseContext.d.ts +37 -0
  68. package/dist/docx/parseContext.js +67 -0
  69. package/dist/docx/parseWarningMessage.d.ts +6 -0
  70. package/dist/docx/parseWarningMessage.js +44 -0
  71. package/dist/docx/parser.js +83 -29
  72. package/dist/docx/previewBudget.d.ts +64 -0
  73. package/dist/docx/previewBudget.js +88 -0
  74. package/dist/docx/relsParser.d.ts +28 -11
  75. package/dist/docx/relsParser.js +26 -13
  76. package/dist/docx/revisionIdNormalization.js +96 -10
  77. package/dist/docx/rezip.js +80 -40
  78. package/dist/docx/runConsolidator.js +1 -2
  79. package/dist/docx/runParser.d.ts +8 -1
  80. package/dist/docx/runParser.js +30 -48
  81. package/dist/docx/sdtPropertiesPatch.js +24 -18
  82. package/dist/docx/sectionParser.d.ts +2 -1
  83. package/dist/docx/sectionParser.js +21 -65
  84. package/dist/docx/sectionReferenceHistory.js +2 -2
  85. package/dist/docx/selectiveSave.js +6 -6
  86. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  87. package/dist/docx/serializer/borderSerializer.d.ts +2 -3
  88. package/dist/docx/serializer/borderSerializer.js +13 -12
  89. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  90. package/dist/docx/serializer/commentSerializer.js +82 -72
  91. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  92. package/dist/docx/serializer/documentSerializer.js +6 -16
  93. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  94. package/dist/docx/serializer/headerFooterSerializer.js +10 -5
  95. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  96. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  97. package/dist/docx/serializer/noteSerializer.js +5 -0
  98. package/dist/docx/serializer/numberingSerializer.js +7 -6
  99. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  100. package/dist/docx/serializer/paragraphSerializer.js +47 -52
  101. package/dist/docx/serializer/partNamespaces.js +2 -2
  102. package/dist/docx/serializer/runSerializer.js +57 -31
  103. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  104. package/dist/docx/serializer/settingsSerializer.js +4 -3
  105. package/dist/docx/serializer/stylesSerializer.js +6 -6
  106. package/dist/docx/serializer/tableSerializer.js +37 -21
  107. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  108. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  109. package/dist/docx/serializer/themeSerializer.js +6 -6
  110. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  111. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  112. package/dist/docx/serializer/xmlUtils.js +1 -13
  113. package/dist/docx/server/boundedArchive.d.ts +12 -0
  114. package/dist/docx/server/boundedArchive.js +20 -1
  115. package/dist/docx/server/build.js +8 -1
  116. package/dist/docx/server/createBilingualDocument.js +10 -18
  117. package/dist/docx/server/extractDocxText.js +3 -4
  118. package/dist/docx/server/validateDocxConformance.js +22 -1
  119. package/dist/docx/shadingParser.d.ts +6 -0
  120. package/dist/docx/shadingParser.js +32 -0
  121. package/dist/docx/shapeParser.js +10 -8
  122. package/dist/docx/styleParser.js +13 -87
  123. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  124. package/dist/docx/styleReferenceResolution.js +51 -0
  125. package/dist/docx/tableLook.d.ts +57 -0
  126. package/dist/docx/tableLook.js +63 -0
  127. package/dist/docx/tableParser.d.ts +7 -9
  128. package/dist/docx/tableParser.js +64 -110
  129. package/dist/docx/textBoxParser.js +11 -6
  130. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  131. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  132. package/dist/docx/transitionalSpelling.d.ts +13 -2
  133. package/dist/docx/transitionalSpelling.js +23 -1
  134. package/dist/docx/unzip.d.ts +23 -0
  135. package/dist/docx/unzip.js +32 -22
  136. package/dist/docx/verbatimCapture.js +5 -12
  137. package/dist/docx/vmlImageParser.js +5 -4
  138. package/dist/docx/vmlPreview.d.ts +1 -3
  139. package/dist/docx/vmlPreview.js +2 -30
  140. package/dist/docx/watermarkParser.js +2 -2
  141. package/dist/docx/xmlParser.d.ts +38 -33
  142. package/dist/docx/xmlParser.js +92 -47
  143. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  144. package/dist/docx/xmlResourceLimits.js +105 -24
  145. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  146. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  147. package/dist/internal/paragraphFormattingSerialization.js +29 -8
  148. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  149. package/dist/layout-engine/index.d.ts +2 -2
  150. package/dist/layout-engine/index.js +2 -2
  151. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  152. package/dist/layout-engine/types.d.ts +8 -2
  153. package/dist/layout-engine/types.js +35 -2
  154. package/dist/layout-painter/renderImage.js +4 -3
  155. package/dist/layout-painter/renderParagraph.js +4 -3
  156. package/dist/managers/autoSaveCodec.js +2 -8
  157. package/dist/markdown/images.js +1 -4
  158. package/dist/markdown/index.js +1 -1
  159. package/dist/markdown/internals.d.ts +6 -1
  160. package/dist/markdown/internals.js +14 -1
  161. package/dist/markdown/renderBlock.js +35 -21
  162. package/dist/markdown/renderParagraph.js +14 -5
  163. package/dist/markdown/renderRuns.js +4 -3
  164. package/dist/markdown/renderTable.js +4 -3
  165. package/dist/markdown/trailers.js +41 -7
  166. package/dist/markdown/types.d.ts +3 -7
  167. package/dist/prosemirror/attrs/index.js +71 -5
  168. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  169. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  170. package/dist/prosemirror/commands/image.js +1 -0
  171. package/dist/prosemirror/commands/index.d.ts +3 -3
  172. package/dist/prosemirror/commands/index.js +2 -2
  173. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  174. package/dist/prosemirror/commands/paragraph.js +2 -2
  175. package/dist/prosemirror/commentIdAllocator.js +2 -7
  176. package/dist/prosemirror/conversion/fromProseDoc.js +197 -68
  177. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  178. package/dist/prosemirror/conversion/toProseDoc.js +458 -335
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  180. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  181. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  182. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  183. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  184. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  185. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  186. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  187. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  188. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  189. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  190. package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -1
  191. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -2
  192. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  193. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  194. package/dist/prosemirror/extensions/types.d.ts +2 -2
  195. package/dist/prosemirror/index.d.ts +3 -3
  196. package/dist/prosemirror/index.js +3 -3
  197. package/dist/prosemirror/insertOperations.d.ts +9 -2
  198. package/dist/prosemirror/insertOperations.js +9 -4
  199. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +162 -0
  200. package/dist/prosemirror/paragraphFormattingProvenance.js +115 -0
  201. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  202. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  203. package/dist/prosemirror/plugins/index.d.ts +2 -2
  204. package/dist/prosemirror/plugins/index.js +2 -2
  205. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  206. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  207. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  208. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  209. package/dist/prosemirror/schema/nodes.d.ts +81 -1
  210. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  211. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  212. package/dist/prosemirror/styles/styleResolver.js +12 -0
  213. package/dist/style-engine/styleEngine.d.ts +3 -0
  214. package/dist/style-engine/styleEngine.js +3 -0
  215. package/dist/style-sets/extract.js +1 -23
  216. package/dist/style-sets/stellaStyle.js +46 -39
  217. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  218. package/dist/style-sets/styleSetNormalization.js +99 -0
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/base64.d.ts +36 -0
  221. package/dist/utils/base64.js +40 -0
  222. package/dist/utils/clipboard.js +2 -1
  223. package/dist/utils/createDocument.js +145 -20
  224. package/dist/utils/headingCollector.d.ts +8 -5
  225. package/dist/utils/headingCollector.js +23 -25
  226. package/dist/utils/tableOfContentsStyle.js +9 -2
  227. package/dist/utils/units.d.ts +10 -1
  228. package/dist/utils/units.js +12 -1
  229. package/dist/utils/urlSecurity.d.ts +8 -2
  230. package/dist/utils/urlSecurity.js +21 -3
  231. package/package.json +2 -2
  232. package/dist/docx/textWhitespace.d.ts +0 -4
  233. package/dist/docx/textWhitespace.js +0 -4
  234. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  235. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  236. package/dist/markdown/headings.d.ts +0 -13
  237. package/dist/markdown/headings.js +0 -20
@@ -1,10 +1,14 @@
1
1
  import { canonicalJson } from "../../utils/canonicalJson.js";
2
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
3
+ import { withBlockRangeMarkers } from "../blockRangeMarkers.js";
4
+ import { TABLE_LOOK_FLAGS } from "../tableLook.js";
3
5
  import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTableRowProperties } from "../tableParser.js";
4
- import { OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
6
+ import { sanitizeCapturedXmlElement } from "../verbatimCapture.js";
7
+ import { NAMESPACES, OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
5
8
  import { serializeBorder } from "./borderSerializer.js";
6
9
  import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
7
- import { escapeXml, intAttr } from "./xmlUtils.js";
10
+ import { intAttr } from "./xmlUtils.js";
11
+ import { escapeXmlAttribute } from "@stll/docx-core";
8
12
  //#region src/docx/serializer/tableSerializer.ts
9
13
  /**
10
14
  * The captured element with the revision children written back into it.
@@ -127,30 +131,35 @@ function serializeCellMargins(margins, elementName) {
127
131
  function serializeShading(shading) {
128
132
  if (!shading) return "";
129
133
  const attrs = [];
130
- if (shading.pattern) attrs.push(`w:val="${escapeXml(shading.pattern)}"`);
134
+ if (shading.pattern) attrs.push(`w:val="${escapeXmlAttribute(shading.pattern)}"`);
131
135
  else attrs.push("w:val=\"clear\"");
132
- if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXml(shading.color.rgb)}"`);
136
+ if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXmlAttribute(shading.color.rgb)}"`);
133
137
  else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
134
- if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXml(shading.fill.rgb)}"`);
138
+ if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXmlAttribute(shading.fill.rgb)}"`);
135
139
  else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
136
- if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXml(shading.fill.themeColor)}"`);
137
- if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXml(shading.fill.themeTint)}"`);
138
- if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXml(shading.fill.themeShade)}"`);
140
+ if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(shading.fill.themeColor)}"`);
141
+ if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(shading.fill.themeTint)}"`);
142
+ if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(shading.fill.themeShade)}"`);
139
143
  if (attrs.length === 0) return "";
140
144
  return `<w:shd ${attrs.join(" ")}/>`;
141
145
  }
142
146
  /**
143
- * Serialize table look flags (w:tblLook)
147
+ * Serialize a `w:tblLook`: what the author wrote, in the order they wrote it.
148
+ *
149
+ * A flag is written when it was authored, true or false. Writing only the true
150
+ * ones drops an explicit `w:lastRow="0"`, and an absent flag is not an off one:
151
+ * it falls back to `w:val`'s bit, so the two spell different tables under the
152
+ * same table style. `w:val` leads, then the flags in `CT_TblLook` order, which
153
+ * is the order Word writes them.
144
154
  */
145
155
  function serializeTableLook(look) {
146
156
  if (!look) return "";
147
157
  const attrs = [];
148
- if (look.firstRow) attrs.push("w:firstRow=\"1\"");
149
- if (look.lastRow) attrs.push("w:lastRow=\"1\"");
150
- if (look.firstColumn) attrs.push("w:firstColumn=\"1\"");
151
- if (look.lastColumn) attrs.push("w:lastColumn=\"1\"");
152
- if (look.noHBand) attrs.push("w:noHBand=\"1\"");
153
- if (look.noVBand) attrs.push("w:noVBand=\"1\"");
158
+ if (look.val !== void 0) attrs.push(`w:val="${escapeXmlAttribute(look.val)}"`);
159
+ for (const flag of TABLE_LOOK_FLAGS) {
160
+ const stated = look[flag];
161
+ if (stated !== void 0) attrs.push(`w:${flag}="${stated ? "1" : "0"}"`);
162
+ }
154
163
  if (attrs.length === 0) return "";
155
164
  return `<w:tblLook ${attrs.join(" ")}/>`;
156
165
  }
@@ -181,7 +190,7 @@ function serializeTableFormatting(formatting, propertyChanges) {
181
190
  if (tableSource !== null) return withRevisionChildren(tableSource, (propertyChanges ?? []).map((change) => serializeTablePropertyChange(change)));
182
191
  const parts = [];
183
192
  if (formatting) {
184
- if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXml(formatting.styleId)}"/>`);
193
+ if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXmlAttribute(formatting.styleId)}"/>`);
185
194
  const floatingXml = serializeFloatingTableProperties(formatting.floating);
186
195
  if (floatingXml) parts.push(floatingXml);
187
196
  if (formatting.overlap) parts.push(`<w:tblOverlap w:val="${formatting.overlap}"/>`);
@@ -344,17 +353,24 @@ function serializeTableGrid(table) {
344
353
  if (gridSourceXml !== void 0) {
345
354
  if (canonicalJson(parseTableGrid(parseXml(gridSourceXml, OOXML_NAMESPACE_SCOPE).elements?.[0] ?? null)) === canonicalJson(columnWidths)) return gridSourceXml;
346
355
  }
347
- if (columnWidths && columnWidths.length > 0) return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}</w:tblGrid>`;
356
+ const gridChange = replayableGridChangeXml(table.formatting?.gridChangeXml) ?? "";
357
+ if (columnWidths && columnWidths.length > 0) return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}${gridChange}</w:tblGrid>`;
348
358
  const columns = gridColumnCount(table);
349
- return columns === 0 ? "<w:tblGrid/>" : `<w:tblGrid>${"<w:gridCol/>".repeat(columns)}</w:tblGrid>`;
350
- }
359
+ return columns === 0 && gridChange === "" ? "<w:tblGrid/>" : `<w:tblGrid>${"<w:gridCol/>".repeat(columns)}${gridChange}</w:tblGrid>`;
360
+ }
361
+ const GRID_CHANGE_ROOT_NAME = /* @__PURE__ */ new Set(["tblGridChange"]);
362
+ const WORDPROCESSINGML_NAMESPACE = /* @__PURE__ */ new Set([NAMESPACES.w]);
363
+ const replayableGridChangeXml = (gridChangeXml) => sanitizeCapturedXmlElement(gridChangeXml, {
364
+ allowedLocalNames: GRID_CHANGE_ROOT_NAME,
365
+ allowedNamespaceUris: WORDPROCESSINGML_NAMESPACE,
366
+ inheritedNamespaceScope: OOXML_NAMESPACE_SCOPE
367
+ });
351
368
  /**
352
369
  * Serialize cell content (paragraphs, nested tables)
353
370
  */
354
371
  function serializeCellContent(content, serializeParagraph) {
355
372
  const parts = [];
356
- for (const item of content) if (item.type === "paragraph") parts.push(serializeParagraph(item));
357
- else parts.push(serializeTable(item, serializeParagraph));
373
+ for (const item of content) parts.push(withBlockRangeMarkers(item, item.type === "paragraph" ? serializeParagraph(item) : serializeTable(item, serializeParagraph)));
358
374
  if (parts.length === 0) parts.push("<w:p/>");
359
375
  return parts.join("");
360
376
  }
@@ -1,10 +1,9 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
2
  //#region src/docx/serializer/textFormattingSerializer.d.ts
3
- type ExhaustiveFields<Source, Classified extends keyof Source> = Exclude<keyof Source, Classified> extends never ? Source : never;
4
3
  type ClassifiedShadingField = "color" | "fill" | "pattern";
5
- type ExhaustiveShadingProperties = ExhaustiveFields<document_d_exports.ShadingProperties, ClassifiedShadingField>;
4
+ type ExhaustiveShadingProperties = document_d_exports.ExhaustiveFields<document_d_exports.ShadingProperties, ClassifiedShadingField>;
6
5
  type ClassifiedTextFormattingField = "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";
7
- type ExhaustiveTextFormatting = ExhaustiveFields<document_d_exports.TextFormatting, ClassifiedTextFormattingField>;
6
+ type ExhaustiveTextFormatting = document_d_exports.ExhaustiveFields<document_d_exports.TextFormatting, ClassifiedTextFormattingField>;
8
7
  /**
9
8
  * Serialize shading properties (w:shd)
10
9
  */
@@ -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
  };
@@ -26,7 +26,14 @@ const HEADING_LEVELS = [
26
26
  5,
27
27
  6
28
28
  ];
29
- /** Outline levels a `TOC \o` switch may name (ECMA-376 `w:outlineLvl` 0-8). */
29
+ /**
30
+ * The heading levels a `TOC \o` field-argument may name (ECMA-376 17.16.5.68).
31
+ *
32
+ * `\o` selects by *style id* — "each integer corresponds to the style with a
33
+ * style ID of HeadingX" — not by outline level, which is what `\u` reads. The
34
+ * domain here is therefore the 1-9 of `Heading1`…`Heading9`, not the 0-8 of
35
+ * `w:outlineLvl`.
36
+ */
30
37
  const TOC_LEVEL_MIN = 1;
31
38
  const TOC_LEVEL_MAX = 9;
32
39
  /**
@@ -1,4 +1,5 @@
1
1
  import { deterministicHexId } from "../../utils/hexId.js";
2
+ import { createBuiltInStyleIndex, resolveHeadingLevel } from "../builtInStyles.js";
2
3
  import { isNumberingReference } from "../numberingReference.js";
3
4
  import { getParagraphText } from "../paragraphParser.js";
4
5
  import { cloneParagraphWithoutPropertySource } from "../paragraphPropertySource.js";
@@ -77,6 +78,7 @@ function createBilingualDocument(source, options) {
77
78
  const styles = source.package.styles;
78
79
  const numbering = source.package.numbering;
79
80
  const styleById = new Map((styles?.styles ?? []).map((style) => [style.styleId, style]));
81
+ const builtInStyles = createBuiltInStyleIndex(styleById.values(), styles?.docDefaults);
80
82
  const blocks = flattenBlocks(source.package.document.content);
81
83
  const cloner = createNumberingCloner({
82
84
  numbering,
@@ -124,7 +126,7 @@ function createBilingualDocument(source, options) {
124
126
  }
125
127
  const { copy, ref } = copyParagraph(block);
126
128
  rows.push({
127
- kind: classifyParagraph(block, styleById),
129
+ kind: classifyParagraph(block, styleById, builtInStyles),
128
130
  rowId: ref.targetParaId,
129
131
  ...ref
130
132
  });
@@ -235,26 +237,15 @@ const isFieldOnlyParagraph = (paragraph) => {
235
237
  * them would surface as a missing handle rather than as the rule it is.
236
238
  */
237
239
  const isTranslatableParagraph = (paragraph, editableParagraphIds) => paragraph.paraId !== void 0 && editableParagraphIds.has(paragraph.paraId) && !isFieldOnlyParagraph(paragraph);
238
- /** Heading style families across Word UI languages (en, cs/sk, de, fr, pl). */
239
- const HEADING_STYLE_PATTERN = /heading|nadpis|berschrift|titre|nag[łl]/iu;
240
- const classifyParagraph = (paragraph, styleById) => {
240
+ const classifyParagraph = (paragraph, styleById, builtInStyles) => {
241
241
  const formatting = paragraph.formatting;
242
- const style = formatting?.styleId ? styleById.get(formatting.styleId) : void 0;
243
- const outlineLevel = formatting?.outlineLevel ?? resolveInheritedOutlineLevel(style, styleById);
244
- if (outlineLevel !== void 0 && outlineLevel < 9) return "heading";
245
- if (style && (HEADING_STYLE_PATTERN.test(style.styleId) || HEADING_STYLE_PATTERN.test(style.name ?? ""))) return "heading";
242
+ if (resolveHeadingLevel({
243
+ outlineLevel: formatting?.outlineLevel,
244
+ styleId: formatting?.styleId
245
+ }, builtInStyles) !== void 0) return "heading";
246
246
  if (effectiveNumPr(paragraph, styleById) !== void 0) return "listItem";
247
247
  return "paragraph";
248
248
  };
249
- const resolveInheritedOutlineLevel = (style, styleById) => {
250
- const seen = /* @__PURE__ */ new Set();
251
- let current = style;
252
- while (current && !seen.has(current.styleId)) {
253
- seen.add(current.styleId);
254
- if (current.pPr?.outlineLevel !== void 0) return current.pPr.outlineLevel;
255
- current = current.basedOn ? styleById.get(current.basedOn) : void 0;
256
- }
257
- };
258
249
  /** The numbering a paragraph renders with: direct `numPr`, else the style chain's. */
259
250
  const effectiveNumPr = (paragraph, styleById) => {
260
251
  const direct = paragraph.formatting?.numPr;
@@ -703,6 +694,7 @@ const createParaIdMinter = (taken) => {
703
694
  */
704
695
  function readBilingualDocument(document, editableParagraphIds) {
705
696
  const styleById = new Map((document.package.styles?.styles ?? []).map((style) => [style.styleId, style]));
697
+ const builtInStyles = createBuiltInStyleIndex(styleById.values(), document.package.styles?.docDefaults);
706
698
  const rows = [];
707
699
  let missingHandleCount = 0;
708
700
  for (const block of flattenBlocks(document.package.document.content)) {
@@ -777,7 +769,7 @@ function readBilingualDocument(document, editableParagraphIds) {
777
769
  if (targetMissing) missingHandleCount += 1;
778
770
  if (sourceMissing || targetMissing) continue;
779
771
  rows.push({
780
- kind: classifyParagraph(source, styleById),
772
+ kind: classifyParagraph(source, styleById, builtInStyles),
781
773
  rowId: target.paraId,
782
774
  sourceParaId: source.paraId,
783
775
  targetParaId: target.paraId,
@@ -1,6 +1,6 @@
1
1
  import { escapeTableCell } from "../../markdown/escape.js";
2
2
  import { RELATIONSHIP_TYPES, parseRelationships } from "../relsParser.js";
3
- import { findAllDeep, findDeep, getAttribute, getAttributeByNamespaceUri, getLocalName, getNamespaceUri, getTextContent, parseXml } from "../xmlParser.js";
3
+ import { findAllDeep, findDeep, getAttribute, getAttributeByNamespaceUri, getLocalName, getNamespaceUri, getTextContent, parseOnOffValue, parseXml } from "../xmlParser.js";
4
4
  import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
5
5
  //#region src/docx/server/extractDocxText.ts
6
6
  const DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
@@ -68,7 +68,7 @@ const readRunMetrics = (paragraph) => {
68
68
  const properties = findWordChild(run, "rPr");
69
69
  const boldProperty = findWordChild(properties, "b");
70
70
  const boldValue = getWordAttribute(boldProperty, "val");
71
- const bold = boldProperty !== null && boldValue !== "0" && boldValue !== "false";
71
+ const bold = boldProperty !== null && (parseOnOffValue(boldValue) ?? true);
72
72
  const sizeProperty = findWordChild(properties, "sz");
73
73
  const sizeValue = getWordAttribute(sizeProperty, "val");
74
74
  const parsedSize = sizeValue === null ? NaN : Number.parseInt(sizeValue, 10);
@@ -270,8 +270,7 @@ const readTableCell = (cell, { depth, budget }) => {
270
270
  const declaresHeaderRow = (row) => {
271
271
  const header = findWordChild(findWordChild(row, "trPr"), "tblHeader");
272
272
  if (header === null) return false;
273
- const value = getWordAttribute(header, "val");
274
- return value !== "0" && value !== "false";
273
+ return parseOnOffValue(getWordAttribute(header, "val")) ?? true;
275
274
  };
276
275
  const readRowGridOffset = (row, localName) => {
277
276
  const properties = findWordChild(row, "trPr");
@@ -3,6 +3,7 @@ import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "../encryption/con
3
3
  import { DocxModelValidationError, validateFolioDocumentModel } from "../modelValidation.js";
4
4
  import { DocxParseError, parseDocx } from "../parser.js";
5
5
  import { findChildByLocalName, getAttributeAnyPrefix, getLocalName, getNamespacePrefix, parseXmlDocument } from "../xmlParser.js";
6
+ import { XmlResourceLimitError } from "../xmlResourceLimits.js";
6
7
  import { getDocxXmlSafetyIssue } from "../xmlSafety.js";
7
8
  import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
8
9
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
@@ -112,11 +113,31 @@ const XML_SAFETY_ISSUE = {
112
113
  message: "An XML package part is not well formed."
113
114
  }
114
115
  };
116
+ /**
117
+ * Read a part for inspection, including one the safe reader declines.
118
+ *
119
+ * `readEntryString` runs the XML structure preflight, and the preflight
120
+ * refuses to scan markup it cannot count — a document type declaration, an
121
+ * unterminated tag — which is exactly the markup this check exists to report.
122
+ * Such a part is read raw and classified below, so the report names why the
123
+ * package is invalid instead of saying it could not tell. A part that overran
124
+ * a real bound still refuses, and the caller reports that as indeterminate,
125
+ * because a package too large to scan is not a package known to be wrong.
126
+ */
127
+ const readPartForInspection = async (archive, part) => {
128
+ try {
129
+ return await archive.readEntryString(part);
130
+ } catch (error) {
131
+ if (!(error instanceof XmlResourceLimitError) || error.limit !== "syntax") throw error;
132
+ const bytes = await archive.readEntryUint8(part);
133
+ return bytes === null ? null : new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes);
134
+ }
135
+ };
115
136
  const validateXmlParts = async (archive, report) => {
116
137
  const xmlByPath = /* @__PURE__ */ new Map();
117
138
  let hasInvalidXml = false;
118
139
  for (const part of archive.entries.filter((path) => XML_PART_PATTERN.test(path)).toSorted()) {
119
- const xml = await archive.readEntryString(part);
140
+ const xml = await readPartForInspection(archive, part);
120
141
  if (xml === null) continue;
121
142
  xmlByPath.set(part, xml);
122
143
  const safetyIssue = getDocxXmlSafetyIssue(xml);
@@ -0,0 +1,6 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/shadingParser.d.ts
4
+ declare function parseShading(shd: XmlElement | null): document_d_exports.ShadingProperties | undefined;
5
+ //#endregion
6
+ export { parseShading };
@@ -0,0 +1,32 @@
1
+ import { isValidHexColor } from "../utils/colorResolver.js";
2
+ import { ShadingPatternSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
3
+ import { getAttribute } from "./xmlParser.js";
4
+ //#region src/docx/shadingParser.ts
5
+ /** `ST_HexColor`'s reserved "let the consumer decide" token. */
6
+ const AUTOMATIC_COLOR = "auto";
7
+ const parseHexColor = (value) => {
8
+ if (value === AUTOMATIC_COLOR) return { auto: true };
9
+ if (value && isValidHexColor(value)) return { rgb: value };
10
+ };
11
+ function parseShading(shd) {
12
+ if (!shd) return;
13
+ const props = {};
14
+ const color = parseHexColor(getAttribute(shd, "w", "color"));
15
+ if (color) props.color = color;
16
+ const fill = parseHexColor(getAttribute(shd, "w", "fill"));
17
+ if (fill) props.fill = fill;
18
+ const themeFill = narrowEnum(getAttribute(shd, "w", "themeFill"), ThemeColorSlotSchema);
19
+ if (themeFill) {
20
+ props.fill ??= {};
21
+ props.fill.themeColor = themeFill;
22
+ }
23
+ const themeFillTint = getAttribute(shd, "w", "themeFillTint");
24
+ if (themeFillTint && props.fill) props.fill.themeTint = themeFillTint;
25
+ const themeFillShade = getAttribute(shd, "w", "themeFillShade");
26
+ if (themeFillShade && props.fill) props.fill.themeShade = themeFillShade;
27
+ const pattern = narrowEnum(getAttribute(shd, "w", "val"), ShadingPatternSchema);
28
+ if (pattern) props.pattern = pattern;
29
+ return Object.keys(props).length > 0 ? props : void 0;
30
+ }
31
+ //#endregion
32
+ export { parseShading };