@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,11 +1,16 @@
1
+ import { attachPendingRangeMarkers, attachTrailingRangeMarkers, isBlockRangeMarker } from "./blockRangeMarkers.js";
1
2
  import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
2
3
  import { appendBookmarkMarkerToLastParagraphInBlocks, appendBookmarkMarkerToLastParagraphInCells, prependBookmarkMarkersToFirstParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInCell } from "./bookmarkPlacement.js";
4
+ import { parseBorderSpec } from "./borderParser.js";
3
5
  import { parseParagraph } from "./paragraphParser.js";
4
6
  import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
5
- import { BorderStyleSchema, FloatingTableXSpecSchema, FloatingTableYSpecSchema, ShadingPatternSchema, TableCellTextDirectionSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
7
+ import { FloatingTableXSpecSchema, FloatingTableYSpecSchema, TableCellTextDirectionSchema, narrowEnum } from "./parserEnums.js";
8
+ import { parseShading } from "./shadingParser.js";
9
+ import { TABLE_LOOK_FLAGS } from "./tableLook.js";
6
10
  import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
11
+ import { percentageSpelling, transitionalSlotEncoding } from "./transitionalSpelling.js";
7
12
  import { captureVerbatimXml } from "./verbatimCapture.js";
8
- import { cloneElement, findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseTableMeasurementValue, selectAlternateContentBranch } from "./xmlParser.js";
13
+ import { cloneElement, findChild, findChildByLocalName, findChildren, getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, parseTableMeasurementValue, selectAlternateContentBranch } from "./xmlParser.js";
9
14
  //#region src/docx/tableParser.ts
10
15
  /**
11
16
  * Sanity cap on `w:gridSpan` (and the derived table column count). Word's
@@ -22,15 +27,32 @@ const MAX_TABLE_COLUMNS = 63;
22
27
  */
23
28
  function parseTableMeasurement(element) {
24
29
  if (!element) return;
25
- const typeStr = getAttribute(element, "w", "type") ?? "dxa";
26
- let type = "dxa";
27
- if (typeStr === "auto" || typeStr === "dxa" || typeStr === "nil" || typeStr === "pct") type = typeStr;
30
+ const declared = getAttribute(element, "w", "type");
31
+ const type = tableWidthType(element, declared === "auto" || declared === "dxa" || declared === "nil" || declared === "pct" ? declared : void 0);
28
32
  return {
29
33
  value: parseTableMeasurementValue(element, type) ?? 0,
30
34
  type
31
35
  };
32
36
  }
33
37
  /**
38
+ * What unit `w:w` counts in.
39
+ *
40
+ * `w:type` is optional on `CT_TblWidth` and the schema gives it no default, so
41
+ * reading an absent one as `dxa` turned `w:w="50%"` into 50 twips: a
42
+ * full-width table became a hairline. `w:w` is `ST_MeasurementOrPercent`, so a
43
+ * value spelled with a `%` is a percentage whatever `w:type` says — the
44
+ * spelling carries its own unit, and no number of twips is ever written that
45
+ * way. `auto` and `nil` are left alone: neither reads `w:w` as a width at all.
46
+ */
47
+ const tableWidthType = (element, declared) => {
48
+ if (declared === "auto" || declared === "nil") return declared;
49
+ const raw = getAttribute(element, "w", "w");
50
+ const spelledAsPercent = raw !== null && percentageSpelling(raw) !== void 0;
51
+ const slotTakesPercent = transitionalSlotEncoding(element.namespaceUri, getLocalName(element.name), "w")?.percent !== void 0;
52
+ if (spelledAsPercent && slotTakesPercent) return "pct";
53
+ return declared ?? "dxa";
54
+ };
55
+ /**
34
56
  * Parse width from an element (shorthand for common case)
35
57
  */
36
58
  function parseWidth(element) {
@@ -42,34 +64,6 @@ function parseWidth(element) {
42
64
  * @param element - Border element (w:top, w:bottom, etc.)
43
65
  * @returns Parsed border or undefined
44
66
  */
45
- function parseBorderSpec(element) {
46
- if (!element) return;
47
- const rawStyle = getAttribute(element, "w", "val") ?? "none";
48
- const border = { style: narrowEnum(rawStyle, BorderStyleSchema) ?? rawStyle };
49
- const sz = parseNumericAttribute(element, "w", "sz");
50
- if (sz !== void 0) border.size = sz;
51
- const space = parseNumericAttribute(element, "w", "space");
52
- if (space !== void 0) border.space = space;
53
- const color = getAttribute(element, "w", "color");
54
- const themeColor = getAttribute(element, "w", "themeColor");
55
- const themeTint = getAttribute(element, "w", "themeTint");
56
- const themeShade = getAttribute(element, "w", "themeShade");
57
- if (color || themeColor || themeTint || themeShade) {
58
- const colorVal = {};
59
- if (color === "auto") colorVal.auto = true;
60
- else if (color !== null) colorVal.rgb = color;
61
- const validatedThemeColor = narrowEnum(themeColor, ThemeColorSlotSchema);
62
- if (validatedThemeColor) colorVal.themeColor = validatedThemeColor;
63
- if (themeTint !== null) colorVal.themeTint = themeTint;
64
- if (themeShade !== null) colorVal.themeShade = themeShade;
65
- border.color = colorVal;
66
- }
67
- const shadow = getAttribute(element, "w", "shadow");
68
- if (shadow === "1" || shadow === "true") border.shadow = true;
69
- const frame = getAttribute(element, "w", "frame");
70
- if (frame === "1" || frame === "true") border.frame = true;
71
- return border;
72
- }
73
67
  /**
74
68
  * Parse table borders (w:tblBorders or w:tcBorders)
75
69
  *
@@ -125,33 +119,13 @@ function parseCellMargins(marginsElement) {
125
119
  return margins;
126
120
  }
127
121
  /**
128
- * Parse shading properties (w:shd)
122
+ * Read a `w:tblLook` (the only reader; `styleParser` calls this one).
129
123
  *
130
- * @param shdElement - The w:shd element
131
- * @returns Parsed shading or undefined
132
- */
133
- function parseShading(shdElement) {
134
- if (!shdElement) return;
135
- const shading = {};
136
- const fillStr = getAttribute(shdElement, "w", "fill");
137
- if (fillStr && fillStr !== "auto") shading.fill = { rgb: fillStr };
138
- const themeFill = narrowEnum(getAttribute(shdElement, "w", "themeFill"), ThemeColorSlotSchema);
139
- if (themeFill) {
140
- shading.fill = { themeColor: themeFill };
141
- const themeFillTint = getAttribute(shdElement, "w", "themeFillTint");
142
- if (themeFillTint) shading.fill.themeTint = themeFillTint;
143
- const themeFillShade = getAttribute(shdElement, "w", "themeFillShade");
144
- if (themeFillShade) shading.fill.themeShade = themeFillShade;
145
- }
146
- const colorStr = getAttribute(shdElement, "w", "color");
147
- if (colorStr && colorStr !== "auto") shading.color = { rgb: colorStr };
148
- const pattern = narrowEnum(getAttribute(shdElement, "w", "val"), ShadingPatternSchema);
149
- if (pattern) shading.pattern = pattern;
150
- if (Object.keys(shading).length === 0) return;
151
- return shading;
152
- }
153
- /**
154
- * Parse table look flags (w:tblLook)
124
+ * What the author wrote, and nothing more: `w:val` verbatim and each flag as
125
+ * stated, absent, `false` or `true`. Folding `w:val`'s bits into the flags here
126
+ * would forget which of the two the document said, and writing the result back
127
+ * would invent attributes the author never had. `resolveTableLook` owns the
128
+ * other direction.
155
129
  *
156
130
  * @param lookElement - The w:tblLook element
157
131
  * @returns Parsed table look or undefined
@@ -159,29 +133,11 @@ function parseShading(shdElement) {
159
133
  function parseTableLook(lookElement) {
160
134
  if (!lookElement) return;
161
135
  const look = {};
162
- const firstRow = getAttribute(lookElement, "w", "firstRow");
163
- if (firstRow === "1" || firstRow === "true") look.firstRow = true;
164
- const lastRow = getAttribute(lookElement, "w", "lastRow");
165
- if (lastRow === "1" || lastRow === "true") look.lastRow = true;
166
- const firstColumn = getAttribute(lookElement, "w", "firstColumn");
167
- if (firstColumn === "1" || firstColumn === "true") look.firstColumn = true;
168
- const lastColumn = getAttribute(lookElement, "w", "lastColumn");
169
- if (lastColumn === "1" || lastColumn === "true") look.lastColumn = true;
170
- const noHBand = getAttribute(lookElement, "w", "noHBand");
171
- if (noHBand === "1" || noHBand === "true") look.noHBand = true;
172
- const noVBand = getAttribute(lookElement, "w", "noVBand");
173
- if (noVBand === "1" || noVBand === "true") look.noVBand = true;
174
136
  const val = getAttribute(lookElement, "w", "val");
175
- if (val) {
176
- const flags = Number.parseInt(val, 16);
177
- if (!Number.isNaN(flags)) {
178
- if (flags & 32) look.firstRow = true;
179
- if (flags & 64) look.lastRow = true;
180
- if (flags & 128) look.firstColumn = true;
181
- if (flags & 256) look.lastColumn = true;
182
- if (flags & 512) look.noHBand = true;
183
- if (flags & 1024) look.noVBand = true;
184
- }
137
+ if (val !== null && val !== "") look.val = val;
138
+ for (const flag of TABLE_LOOK_FLAGS) {
139
+ const stated = parseOnOffAttribute(lookElement, "w", flag);
140
+ if (stated !== void 0) look[flag] = stated;
185
141
  }
186
142
  if (Object.keys(look).length === 0) return;
187
143
  return look;
@@ -430,30 +386,18 @@ function parseTableRowProperties(trPrElement) {
430
386
  function parseConditionalFormatStyle(cnfElement) {
431
387
  if (!cnfElement) return;
432
388
  const style = {};
433
- const firstRow = getAttribute(cnfElement, "w", "firstRow");
434
- if (firstRow === "1" || firstRow === "true") style.firstRow = true;
435
- const lastRow = getAttribute(cnfElement, "w", "lastRow");
436
- if (lastRow === "1" || lastRow === "true") style.lastRow = true;
437
- const firstColumn = getAttribute(cnfElement, "w", "firstColumn");
438
- if (firstColumn === "1" || firstColumn === "true") style.firstColumn = true;
439
- const lastColumn = getAttribute(cnfElement, "w", "lastColumn");
440
- if (lastColumn === "1" || lastColumn === "true") style.lastColumn = true;
441
- const oddHBand = getAttribute(cnfElement, "w", "oddHBand");
442
- if (oddHBand === "1" || oddHBand === "true") style.oddHBand = true;
443
- const evenHBand = getAttribute(cnfElement, "w", "evenHBand");
444
- if (evenHBand === "1" || evenHBand === "true") style.evenHBand = true;
445
- const oddVBand = getAttribute(cnfElement, "w", "oddVBand");
446
- if (oddVBand === "1" || oddVBand === "true") style.oddVBand = true;
447
- const evenVBand = getAttribute(cnfElement, "w", "evenVBand");
448
- if (evenVBand === "1" || evenVBand === "true") style.evenVBand = true;
449
- const nwCell = getAttribute(cnfElement, "w", "firstRowFirstColumn");
450
- if (nwCell === "1" || nwCell === "true") style.nwCell = true;
451
- const neCell = getAttribute(cnfElement, "w", "firstRowLastColumn");
452
- if (neCell === "1" || neCell === "true") style.neCell = true;
453
- const swCell = getAttribute(cnfElement, "w", "lastRowFirstColumn");
454
- if (swCell === "1" || swCell === "true") style.swCell = true;
455
- const seCell = getAttribute(cnfElement, "w", "lastRowLastColumn");
456
- if (seCell === "1" || seCell === "true") style.seCell = true;
389
+ if (parseOnOffAttribute(cnfElement, "w", "firstRow") === true) style.firstRow = true;
390
+ if (parseOnOffAttribute(cnfElement, "w", "lastRow") === true) style.lastRow = true;
391
+ if (parseOnOffAttribute(cnfElement, "w", "firstColumn") === true) style.firstColumn = true;
392
+ if (parseOnOffAttribute(cnfElement, "w", "lastColumn") === true) style.lastColumn = true;
393
+ if (parseOnOffAttribute(cnfElement, "w", "oddHBand") === true) style.oddHBand = true;
394
+ if (parseOnOffAttribute(cnfElement, "w", "evenHBand") === true) style.evenHBand = true;
395
+ if (parseOnOffAttribute(cnfElement, "w", "oddVBand") === true) style.oddVBand = true;
396
+ if (parseOnOffAttribute(cnfElement, "w", "evenVBand") === true) style.evenVBand = true;
397
+ if (parseOnOffAttribute(cnfElement, "w", "firstRowFirstColumn") === true) style.nwCell = true;
398
+ if (parseOnOffAttribute(cnfElement, "w", "firstRowLastColumn") === true) style.neCell = true;
399
+ if (parseOnOffAttribute(cnfElement, "w", "lastRowFirstColumn") === true) style.swCell = true;
400
+ if (parseOnOffAttribute(cnfElement, "w", "lastRowLastColumn") === true) style.seCell = true;
457
401
  const val = getAttribute(cnfElement, "w", "val");
458
402
  if (val && val.length === 12) {
459
403
  if (val[0] === "1") style.firstRow = true;
@@ -530,6 +474,7 @@ function withContainerXmlns(options, element) {
530
474
  function parseCellContent(tcElement, styles, theme, numbering, rels, media, options) {
531
475
  const content = [];
532
476
  const pendingBookmarkMarkers = [];
477
+ const pendingRangeMarkers = [];
533
478
  const elements = getChildElements(tcElement);
534
479
  const parseCellChild = (child, childOptions = options) => {
535
480
  if (!child.name) return;
@@ -538,6 +483,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
538
483
  const para = parseParagraph(child, styles, theme, numbering, rels, media, childOptions);
539
484
  enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
540
485
  prependPendingBookmarkMarkers(para, pendingBookmarkMarkers);
486
+ attachPendingRangeMarkers(para, pendingRangeMarkers);
541
487
  content.push(para);
542
488
  return;
543
489
  }
@@ -545,6 +491,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
545
491
  const table = parseTable(child, styles, theme, numbering, rels, media, childOptions);
546
492
  if (!table) return;
547
493
  if (prependBookmarkMarkersToFirstParagraphInBlocks([table], pendingBookmarkMarkers)) pendingBookmarkMarkers.length = 0;
494
+ attachPendingRangeMarkers(table, pendingRangeMarkers);
548
495
  content.push(table);
549
496
  return;
550
497
  }
@@ -565,7 +512,9 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
565
512
  if (localName === "bookmarkStart" || localName === "bookmarkEnd") {
566
513
  const marker = parseBookmarkMarker(child, localName);
567
514
  if (!appendBookmarkMarkerToLastParagraphInBlocks(content, marker)) pendingBookmarkMarkers.push(marker);
515
+ return;
568
516
  }
517
+ if (isBlockRangeMarker(localName)) pendingRangeMarkers.push(captureVerbatimXml(child));
569
518
  };
570
519
  for (const child of elements) parseCellChild(child);
571
520
  if (content.length === 0) content.push({
@@ -573,6 +522,7 @@ function parseCellContent(tcElement, styles, theme, numbering, rels, media, opti
573
522
  content: [...pendingBookmarkMarkers]
574
523
  });
575
524
  else if (pendingBookmarkMarkers.length > 0) appendBookmarkMarkersToLastParagraphInBlocks(content, pendingBookmarkMarkers);
525
+ attachTrailingRangeMarkers(content, pendingRangeMarkers);
576
526
  return content;
577
527
  }
578
528
  /**
@@ -747,10 +697,14 @@ function parseTable(tblElement, styles, theme, numbering, rels, media, options)
747
697
  const gridElement = findChild(tblElement, "w", "tblGrid");
748
698
  const columnWidths = parseTableGrid(gridElement);
749
699
  if (columnWidths) table.columnWidths = columnWidths;
750
- if (gridElement) table.formatting = {
751
- ...table.formatting,
752
- gridSourceXml: captureVerbatimXml(gridElement)
753
- };
700
+ if (gridElement) {
701
+ const gridChange = findChild(gridElement, "w", "tblGridChange");
702
+ table.formatting = {
703
+ ...table.formatting,
704
+ gridSourceXml: captureVerbatimXml(gridElement),
705
+ ...gridChange ? { gridChangeXml: captureVerbatimXml(gridChange) } : {}
706
+ };
707
+ }
754
708
  const tableOptions = withContainerXmlns(options, tblElement);
755
709
  const rowsWithGridOffsets = /* @__PURE__ */ new Set();
756
710
  const parseTableChild = (child, childOptions = tableOptions) => {
@@ -878,4 +832,4 @@ function isFloatingTable(table) {
878
832
  return table.formatting?.floating !== void 0;
879
833
  }
880
834
  //#endregion
881
- export { MAX_TABLE_COLUMNS, getHeaderRows, getTableColumnCount, getTableRowCount, getTableText, hasHeaderRow, isCellHorizontallyMerged, isCellMergeContinuation, isCellMergeStart, isFloatingTable, parseBorderSpec, parseCellMargins, parseConditionalFormatStyle, parseFloatingTableProperties, parseShading, parseTable, parseTableBorders, parseTableCell, parseTableCellProperties, parseTableGrid, parseTableLook, parseTableMeasurement, parseTableProperties, parseTableRow, parseTableRowProperties };
835
+ export { MAX_TABLE_COLUMNS, getHeaderRows, getTableColumnCount, getTableRowCount, getTableText, hasHeaderRow, isCellHorizontallyMerged, isCellMergeContinuation, isCellMergeStart, isFloatingTable, parseCellMargins, parseConditionalFormatStyle, parseFloatingTableProperties, parseTable, parseTableBorders, parseTableCell, parseTableCellProperties, parseTableGrid, parseTableLook, parseTableMeasurement, parseTableProperties, parseTableRow, parseTableRowProperties };
@@ -1,6 +1,7 @@
1
1
  import { emuToPixels } from "../utils/units.js";
2
2
  import { parseAnchorPosition, parseAnchorWrap, parseFill, parseOutline, resolveColorValueToHex } from "./drawingUtils.js";
3
- import { findByFullName, findChildByLocalName, findChildByNamespaceUri, findChildrenByLocalName, findChildrenByNamespaceUri, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
3
+ import { parseNonVisualDrawingNames } from "./nonVisualDrawingProps.js";
4
+ import { findByFullName, findChildByLocalName, findChildByNamespaceUri, findChildrenByLocalName, findChildrenByNamespaceUri, getAttribute, getChildElements, parseNumericAttribute, parseOnOffAttribute } from "./xmlParser.js";
4
5
  //#region src/docx/textBoxParser.ts
5
6
  const DRAWINGML_MAIN_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.openxmlformats.org/drawingml/2006/main", "http://purl.oclc.org/ooxml/drawingml/main"]);
6
7
  const WORDPROCESSING_SHAPE_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.microsoft.com/office/word/2010/wordprocessingShape"]);
@@ -30,9 +31,9 @@ function parseBodyProperties(bodyPr) {
30
31
  else if (findChildByLocalName(bodyPr, "noAutofit")) result.autoFit = "none";
31
32
  const verticalAlign = parseTextBoxVerticalAlign(getAttribute(bodyPr, null, "anchor"));
32
33
  if (verticalAlign) result.verticalAlign = verticalAlign;
33
- const fromWordArt = getAttribute(bodyPr, null, "fromWordArt");
34
+ const fromWordArt = parseOnOffAttribute(bodyPr, null, "fromWordArt");
34
35
  const warp = findChildByNamespaceUri(bodyPr, DRAWINGML_MAIN_NAMESPACE_URIS, "prstTxWarp");
35
- if (fromWordArt !== null || warp) {
36
+ if (fromWordArt !== void 0 || warp) {
36
37
  const adjustments = warp ? findChildrenByNamespaceUri(findChildByNamespaceUri(warp, DRAWINGML_MAIN_NAMESPACE_URIS, "avLst"), DRAWINGML_MAIN_NAMESPACE_URIS, "gd").map((adjustment) => {
37
38
  const name = getAttribute(adjustment, null, "name");
38
39
  const formula = getAttribute(adjustment, null, "fmla");
@@ -43,7 +44,7 @@ function parseBodyProperties(bodyPr) {
43
44
  }).filter((adjustment) => adjustment !== void 0) : [];
44
45
  const preset = warp ? getAttribute(warp, null, "prst") : null;
45
46
  result.wordArt = {
46
- ...fromWordArt !== null ? { fromWordArt: fromWordArt === "1" || fromWordArt === "true" || fromWordArt === "on" } : {},
47
+ ...fromWordArt !== void 0 ? { fromWordArt } : {},
47
48
  ...preset !== null ? { preset } : {},
48
49
  ...adjustments.length > 0 ? { adjustments } : {}
49
50
  };
@@ -148,13 +149,15 @@ function parseTextBox(drawingEl) {
148
149
  };
149
150
  const docPr = findByFullName(container, "wp:docPr");
150
151
  const id = docPr ? getAttribute(docPr, null, "id") ?? void 0 : void 0;
152
+ const names = parseNonVisualDrawingNames(docPr);
151
153
  const fill = parseFill(spPr ?? null);
152
154
  const outline = parseOutline(spPr ?? null);
153
155
  const bodyProps = parseBodyProperties(bodyPr ?? null);
154
156
  const textBox = {
155
157
  type: "textBox",
156
158
  size,
157
- content: []
159
+ content: [],
160
+ ...names
158
161
  };
159
162
  if (id) textBox.id = id;
160
163
  if (fill) textBox.fill = fill;
@@ -193,13 +196,15 @@ function parseTextBoxFromShape(wsp, size, position, wrap) {
193
196
  const bodyPr = findChildByNamespaceUri(wsp, WORDPROCESSING_SHAPE_NAMESPACE_URIS, "bodyPr");
194
197
  const cNvPr = wspChildren.find((el) => el.name === "wps:cNvPr");
195
198
  const id = cNvPr ? getAttribute(cNvPr, null, "id") ?? void 0 : void 0;
199
+ const names = parseNonVisualDrawingNames(cNvPr);
196
200
  const fill = parseFill(spPr ?? null);
197
201
  const outline = parseOutline(spPr ?? null);
198
202
  const bodyProps = parseBodyProperties(bodyPr ?? null);
199
203
  const textBox = {
200
204
  type: "textBox",
201
205
  size,
202
- content: []
206
+ content: [],
207
+ ...names
203
208
  };
204
209
  if (id) textBox.id = id;
205
210
  if (fill) textBox.fill = fill;
@@ -1,5 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  //#region src/docx/trackedMoveRangeNormalization.d.ts
3
+ /** The code this normalisation is reported under, owned here, not at the caller. */
4
+ declare const UNBALANCED_MOVE_RANGE_WARNING: "unbalanced-move-range";
3
5
  type NormalizeTrackedMoveRangesInput = {
4
6
  documentBody: document_d_exports.DocumentBody;
5
7
  headers?: Map<string, document_d_exports.HeaderFooter>;
@@ -12,4 +14,4 @@ type NormalizeTrackedMoveRangesResult = {
12
14
  };
13
15
  declare const normalizeTrackedMoveRanges: ({ documentBody, headers, footers, footnotes, endnotes }: NormalizeTrackedMoveRangesInput) => NormalizeTrackedMoveRangesResult;
14
16
  //#endregion
15
- export { normalizeTrackedMoveRanges };
17
+ export { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges };
@@ -1,5 +1,8 @@
1
- import { visitDocxParagraphs } from "./paragraphTraversal.js";
1
+ import { InlineContentRemovals, visitDocxParagraphs, visitInlineContentSlots } from "./paragraphTraversal.js";
2
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
2
3
  //#region src/docx/trackedMoveRangeNormalization.ts
4
+ /** The code this normalisation is reported under, owned here, not at the caller. */
5
+ const UNBALANCED_MOVE_RANGE_WARNING = PARSE_WARNING_CODES.unbalancedMoveRange;
3
6
  const normalizeTrackedMoveRanges = ({ documentBody, headers, footers, footnotes, endnotes }) => {
4
7
  const rangeMarkers = [];
5
8
  visitDocxParagraphs({
@@ -9,10 +12,10 @@ const normalizeTrackedMoveRanges = ({ documentBody, headers, footers, footnotes,
9
12
  footnotes,
10
13
  endnotes
11
14
  }, (paragraph) => {
12
- for (const [index, content] of paragraph.content.entries()) {
13
- const marker = toMoveRangeMarkerRef(paragraph.content, index, content);
15
+ visitInlineContentSlots(paragraph, ({ content, index, item }) => {
16
+ const marker = toMoveRangeMarkerRef(content, index, item);
14
17
  if (marker) rangeMarkers.push(marker);
15
- }
18
+ });
16
19
  });
17
20
  return { removedUnbalancedMoveRangeMarkers: removeUnbalancedMoveRangeMarkers(rangeMarkers) };
18
21
  };
@@ -27,14 +30,9 @@ const removeUnbalancedMoveRangeMarkers = (rangeMarkers) => {
27
30
  }
28
31
  byRange.set(key, [marker]);
29
32
  }
30
- const removals = /* @__PURE__ */ new Map();
33
+ const removals = new InlineContentRemovals();
31
34
  const markForRemoval = (marker) => {
32
- const indexes = removals.get(marker.content);
33
- if (indexes) {
34
- indexes.add(marker.index);
35
- return;
36
- }
37
- removals.set(marker.content, /* @__PURE__ */ new Set([marker.index]));
35
+ removals.mark(marker);
38
36
  };
39
37
  for (const markers of byRange.values()) {
40
38
  let openStart = null;
@@ -55,15 +53,7 @@ const removeUnbalancedMoveRangeMarkers = (rangeMarkers) => {
55
53
  }
56
54
  if (openStart) markForRemoval(openStart);
57
55
  }
58
- let removedCount = 0;
59
- for (const [content, indexes] of removals.entries()) {
60
- if (indexes.size === 0) continue;
61
- const nextContent = content.filter((_, index) => !indexes.has(index));
62
- removedCount += content.length - nextContent.length;
63
- content.length = 0;
64
- content.push(...nextContent);
65
- }
66
- return removedCount;
56
+ return removals.apply();
67
57
  };
68
58
  const toMoveRangeMarkerRef = (content, index, marker) => {
69
59
  if (marker.type === "moveFromRangeStart") return {
@@ -97,4 +87,4 @@ const toMoveRangeMarkerRef = (content, index, marker) => {
97
87
  return null;
98
88
  };
99
89
  //#endregion
100
- export { normalizeTrackedMoveRanges };
90
+ export { UNBALANCED_MOVE_RANGE_WARNING, normalizeTrackedMoveRanges };
@@ -1,4 +1,4 @@
1
- import { SlotEncoding } from "./strictValueEncodings.gen.js";
1
+ import { PercentUnit, SlotEncoding } from "./strictValueEncodings.gen.js";
2
2
  //#region src/docx/transitionalSpelling.d.ts
3
3
  /**
4
4
  * The Transitional URI a Strict one stands for, or the URI unchanged.
@@ -12,6 +12,17 @@ declare const toTransitionalNamespaceUri: (uri: string) => string;
12
12
  declare const isStrictNamespaceUri: (uri: string) => boolean;
13
13
  /** Namespaces ECMA-376 Part 4 republished; nothing else starts with this. */
14
14
  declare const STRICT_URI_PREFIX = "http://purl.oclc.org/ooxml/";
15
+ /** How many of a unit's numbers make one whole percent. */
16
+ declare const NUMBERS_PER_PERCENT: Readonly<Record<PercentUnit, number>>;
17
+ /**
18
+ * The percent a value spells out, or undefined when it is not spelled as one.
19
+ *
20
+ * A value written `50%` says what it is; a value written `2500` needs its
21
+ * slot's unit to be read. Callers that must tell the two apart — the
22
+ * verbatim-capture conversion, and `CT_TblWidth`, whose `w:type` is optional —
23
+ * ask here rather than testing for a `%` themselves.
24
+ */
25
+ declare const percentageSpelling: (value: string) => number | undefined;
15
26
  /**
16
27
  * How one slot's Transitional type spells its value as a number.
17
28
  *
@@ -20,4 +31,4 @@ declare const STRICT_URI_PREFIX = "http://purl.oclc.org/ooxml/";
20
31
  */
21
32
  declare const transitionalSlotEncoding: (namespaceUri: string | undefined, elementLocalName: string, attributeLocalName?: string) => SlotEncoding | undefined;
22
33
  //#endregion
23
- export { STRICT_URI_PREFIX, isStrictNamespaceUri, toTransitionalNamespaceUri, transitionalSlotEncoding };
34
+ export { NUMBERS_PER_PERCENT, STRICT_URI_PREFIX, isStrictNamespaceUri, percentageSpelling, toTransitionalNamespaceUri, transitionalSlotEncoding };
@@ -21,6 +21,28 @@ const toTransitionalNamespaceUri = (uri) => TRANSITIONAL_NAMESPACE_BY_STRICT_URI
21
21
  const isStrictNamespaceUri = (uri) => TRANSITIONAL_NAMESPACE_BY_STRICT_URI.has(uri);
22
22
  /** Namespaces ECMA-376 Part 4 republished; nothing else starts with this. */
23
23
  const STRICT_URI_PREFIX = "http://purl.oclc.org/ooxml/";
24
+ /** `-?12.5%`: the one shape ECMA-376 gives a percentage that carries its unit. */
25
+ const PERCENTAGE = /^(-?[0-9]+(?:\.[0-9]+)?)%$/u;
26
+ /** How many of a unit's numbers make one whole percent. */
27
+ const NUMBERS_PER_PERCENT = {
28
+ fiftiethPercent: 50,
29
+ thousandthPercent: 1e3,
30
+ wholePercent: 1
31
+ };
32
+ /**
33
+ * The percent a value spells out, or undefined when it is not spelled as one.
34
+ *
35
+ * A value written `50%` says what it is; a value written `2500` needs its
36
+ * slot's unit to be read. Callers that must tell the two apart — the
37
+ * verbatim-capture conversion, and `CT_TblWidth`, whose `w:type` is optional —
38
+ * ask here rather than testing for a `%` themselves.
39
+ */
40
+ const percentageSpelling = (value) => {
41
+ const match = PERCENTAGE.exec(value.trim());
42
+ if (match === null) return;
43
+ const percent = Number(match[1]);
44
+ return Number.isNaN(percent) ? void 0 : percent;
45
+ };
24
46
  /**
25
47
  * How one slot's Transitional type spells its value as a number.
26
48
  *
@@ -33,4 +55,4 @@ const transitionalSlotEncoding = (namespaceUri, elementLocalName, attributeLocal
33
55
  return TRANSITIONAL_SLOT_ENCODINGS.get(attributeLocalName === void 0 ? slot : `${slot} @${attributeLocalName}`);
34
56
  };
35
57
  //#endregion
36
- export { STRICT_URI_PREFIX, isStrictNamespaceUri, toTransitionalNamespaceUri, transitionalSlotEncoding };
58
+ export { NUMBERS_PER_PERCENT, STRICT_URI_PREFIX, isStrictNamespaceUri, percentageSpelling, toTransitionalNamespaceUri, transitionalSlotEncoding };
@@ -6,10 +6,33 @@ declare class DocxSecurityError extends Error {
6
6
  type DocxUnzipLimits = {
7
7
  maxInputBytes: number;
8
8
  maxFiles: number;
9
+ /**
10
+ * Inflated bytes allowed in one XML part.
11
+ *
12
+ * A byte ceiling bounds the markup, not what parsing it allocates: a parsed
13
+ * tree costs 3x to 15x the part's bytes, and the cheapest element to write
14
+ * is the most expensive per byte. Use `maxXmlElementsPerPart` and the
15
+ * package bounds to cap memory; this one caps text-dominated parts, where
16
+ * bytes and cost do track each other.
17
+ */
9
18
  maxXmlBytes: number;
10
19
  maxMediaBytes: number;
11
20
  maxFontBytes: number;
21
+ /**
22
+ * Inflated bytes allowed across every entry in the package.
23
+ *
24
+ * Same caveat as `maxXmlBytes`: this is a ceiling on what passes through
25
+ * memory as bytes, not on what the parsed structure retains.
26
+ */
12
27
  maxTotalUncompressedBytes: number;
28
+ /** Elements allowed in one XML part, counted before any tree is built. */
29
+ maxXmlElementsPerPart: number;
30
+ /** Attributes allowed in one XML part, counted before any tree is built. */
31
+ maxXmlAttributesPerPart: number;
32
+ /** Elements allowed across every XML part in the package. */
33
+ maxXmlElementsPerPackage: number;
34
+ /** Attributes allowed across every XML part in the package. */
35
+ maxXmlAttributesPerPackage: number;
13
36
  allowedMediaMimeTypes: ReadonlySet<string>;
14
37
  };
15
38
  type DocxUnzipOptions = Partial<Omit<DocxUnzipLimits, "allowedMediaMimeTypes">> & {
@@ -1,6 +1,7 @@
1
+ import { bytesToDataUrl } from "../utils/base64.js";
1
2
  import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "./encryption/containerFormat.js";
2
3
  import { openDocxBuffer } from "./encryption/openEncryptedDocx.js";
3
- import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits } from "./xmlResourceLimits.js";
4
+ import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits, createXmlPackageBudget } from "./xmlResourceLimits.js";
4
5
  import JSZip from "jszip";
5
6
  //#region src/docx/unzip.ts
6
7
  /**
@@ -58,8 +59,21 @@ const DEFAULT_UNZIP_LIMITS = {
58
59
  maxMediaBytes: 25 * MEBIBYTE,
59
60
  maxFontBytes: 10 * MEBIBYTE,
60
61
  maxTotalUncompressedBytes: 250 * MEBIBYTE,
62
+ maxXmlElementsPerPart: FOLIO_XML_RESOURCE_LIMITS.maxElementsPerPart,
63
+ maxXmlAttributesPerPart: FOLIO_XML_RESOURCE_LIMITS.maxAttributesPerPart,
64
+ maxXmlElementsPerPackage: FOLIO_XML_RESOURCE_LIMITS.maxElementsPerPackage,
65
+ maxXmlAttributesPerPackage: FOLIO_XML_RESOURCE_LIMITS.maxAttributesPerPackage,
61
66
  allowedMediaMimeTypes: DEFAULT_ALLOWED_MEDIA_MIME_TYPES
62
67
  };
68
+ /** The XML bounds an unzip enforces, in the shape the preflight takes. */
69
+ const xmlResourceLimitsFor = (limits) => ({
70
+ maxBytes: limits.maxXmlBytes,
71
+ maxDepth: FOLIO_XML_RESOURCE_LIMITS.maxDepth,
72
+ maxElementsPerPart: limits.maxXmlElementsPerPart,
73
+ maxAttributesPerPart: limits.maxXmlAttributesPerPart,
74
+ maxElementsPerPackage: limits.maxXmlElementsPerPackage,
75
+ maxAttributesPerPackage: limits.maxXmlAttributesPerPackage
76
+ });
63
77
  const PARSED_XML_PARTS = /* @__PURE__ */ new Set([
64
78
  "[content_types].xml",
65
79
  "_rels/.rels",
@@ -188,10 +202,11 @@ async function unzipDocx(buffer, options = {}) {
188
202
  }));
189
203
  }
190
204
  }
205
+ const xmlBudget = createXmlPackageBudget();
191
206
  for (const extracted of await Promise.all(extractionTasks.map((extract) => extract()))) {
192
207
  if (!extracted) continue;
193
208
  if (extracted.type === "xml") {
194
- assignXmlContent(content, extracted, limits);
209
+ assignXmlContent(content, extracted, limits, xmlBudget);
195
210
  continue;
196
211
  }
197
212
  if (extracted.type === "media") {
@@ -203,19 +218,21 @@ async function unzipDocx(buffer, options = {}) {
203
218
  return content;
204
219
  }
205
220
  /**
206
- * Parts that every consumer expands into an object tree. Preflighting them once
207
- * here puts the bound on the unzip, so `parseDocx`, the selective save and the
208
- * repack path all share it instead of each entry point carrying its own.
221
+ * Preflight every XML part the unzip retains, not a named few.
222
+ *
223
+ * Naming the parts to bound is the bug: `word/document.xml`, `word/styles.xml`
224
+ * and `word/numbering.xml` were counted, and headers, footers, footnotes,
225
+ * endnotes, comments and every other `word/*.xml` part were parsed into trees
226
+ * unbounded. Bounding the reader instead of the part list means a part added
227
+ * later is bounded by construction, and the package budget makes the ceiling
228
+ * the package's rather than each part's.
209
229
  */
210
- const PREFLIGHT_XML_PARTS = /* @__PURE__ */ new Set([
211
- "word/document.xml",
212
- "word/styles.xml",
213
- "word/numbering.xml"
214
- ]);
215
- function assignXmlContent(content, { path, lowerPath, content: xmlContent }, limits) {
216
- if (PREFLIGHT_XML_PARTS.has(lowerPath)) assertXmlResourceLimits(xmlContent, {
217
- ...FOLIO_XML_RESOURCE_LIMITS,
218
- maxBytes: limits.maxXmlBytes
230
+ function assignXmlContent(content, { path, lowerPath, content: xmlContent }, limits, budget) {
231
+ assertXmlResourceLimits({
232
+ xml: xmlContent,
233
+ limits: xmlResourceLimitsFor(limits),
234
+ partPath: path,
235
+ budget
219
236
  });
220
237
  content.allXml.set(path, xmlContent);
221
238
  if (lowerPath === "word/document.xml") content.documentXml = xmlContent;
@@ -426,14 +443,7 @@ function getMediaMimeType(path) {
426
443
  * @returns Data URL string
427
444
  */
428
445
  function mediaToDataUrl(data, mimeType) {
429
- const bytes = new Uint8Array(data);
430
- const chunks = [];
431
- const chunkSize = 32768;
432
- for (let offset = 0; offset < bytes.length; offset += chunkSize) {
433
- const chunk = bytes.subarray(offset, offset + chunkSize);
434
- chunks.push(String.fromCodePoint(...chunk));
435
- }
436
- return `data:${mimeType};base64,${btoa(chunks.join(""))}`;
446
+ return bytesToDataUrl(new Uint8Array(data), mimeType);
437
447
  }
438
448
  /**
439
449
  * Extract a specific file from the original ZIP
@@ -1,5 +1,5 @@
1
1
  import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
2
- import { isStrictNamespaceUri, toTransitionalNamespaceUri, transitionalSlotEncoding } from "./transitionalSpelling.js";
2
+ import { NUMBERS_PER_PERCENT, isStrictNamespaceUri, percentageSpelling, toTransitionalNamespaceUri, transitionalSlotEncoding } from "./transitionalSpelling.js";
3
3
  import { roundHalfAwayFromZero, universalMeasureAs } from "./universalMeasure.js";
4
4
  import { NAMESPACES, OOXML_NAMESPACE_SCOPE, cloneElement, elementToXml, getChildElements, getLocalName, getNamespacePrefix, parseXml } from "./xmlParser.js";
5
5
  import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
@@ -52,13 +52,6 @@ const DRAWING_EXTENSION_MARKERS = [
52
52
  "/wordprocessingShape"
53
53
  ];
54
54
  const isUnschemadDrawingNamespace = (uri) => !TRANSITIONAL_URIS.has(uri) && DRAWING_EXTENSION_MARKERS.some((marker) => uri.includes(marker));
55
- /** `-?12.5%`: the one shape ECMA-376 gives a percentage that carries its unit. */
56
- const PERCENTAGE = /^(-?[0-9]+(?:\.[0-9]+)?)%$/u;
57
- const NUMBERS_PER_PERCENT = {
58
- fiftiethPercent: 50,
59
- thousandthPercent: 1e3,
60
- wholePercent: 1
61
- };
62
55
  /** The Transitional spelling of one Strict-produced value, or the value unchanged. */
63
56
  const transitionalValue = (value, namespaceUri, encoding) => {
64
57
  const measureUnit = encoding?.measure;
@@ -66,11 +59,11 @@ const transitionalValue = (value, namespaceUri, encoding) => {
66
59
  const measure = universalMeasureAs(value, measureUnit);
67
60
  if (measure !== void 0) return String(measure);
68
61
  }
69
- const percentage = PERCENTAGE.exec(value);
70
- if (percentage === null) return value;
62
+ const percentage = percentageSpelling(value);
63
+ if (percentage === void 0) return value;
71
64
  const percentUnit = encoding?.percent ?? (isUnschemadDrawingNamespace(namespaceUri) ? "thousandthPercent" : void 0);
72
65
  if (percentUnit === void 0) return value;
73
- return String(roundHalfAwayFromZero(Number(percentage[1]) * NUMBERS_PER_PERCENT[percentUnit]));
66
+ return String(roundHalfAwayFromZero(percentage * NUMBERS_PER_PERCENT[percentUnit]));
74
67
  };
75
68
  const originOf = (element, inherited) => {
76
69
  const uri = element.namespaceUri;
@@ -277,7 +270,7 @@ const parsedReplayEnvelope = (xml, inheritedNamespaceScope) => {
277
270
  };
278
271
  const withinXmlResourceLimits = (xml) => {
279
272
  try {
280
- assertXmlResourceLimits(xml);
273
+ assertXmlResourceLimits({ xml });
281
274
  return true;
282
275
  } catch {
283
276
  return false;