@stll/folio-core 0.47.7 → 0.49.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 (213) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  3. package/dist/ai-edits/minimal-replacement.js +282 -0
  4. package/dist/controller/fontReadiness.js +3 -3
  5. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  6. package/dist/controller/hyphenationReadiness.js +47 -0
  7. package/dist/controller/layoutPipeline.d.ts +3 -0
  8. package/dist/controller/layoutPipeline.js +70 -18
  9. package/dist/display-list/build/buildDisplayList.js +39 -12
  10. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  11. package/dist/display-list/build/pageFurniture.js +21 -8
  12. package/dist/display-list/build/paragraphPrimitives.js +68 -11
  13. package/dist/display-list/build/tablePrimitives.js +10 -6
  14. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  15. package/dist/display-list/build/unsupported.d.ts +2 -2
  16. package/dist/display-list/build/unsupported.js +0 -0
  17. package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
  18. package/dist/docx/attributeRemainder.js +10 -2
  19. package/dist/docx/blockContentParser.d.ts +2 -0
  20. package/dist/docx/blockContentParser.js +79 -64
  21. package/dist/docx/bulletMarkers.d.ts +18 -2
  22. package/dist/docx/bulletMarkers.js +27 -2
  23. package/dist/docx/commentParser.d.ts +2 -1
  24. package/dist/docx/commentParser.js +57 -45
  25. package/dist/docx/containerChildren.d.ts +21 -8
  26. package/dist/docx/containerChildren.js +39 -9
  27. package/dist/docx/documentParser.d.ts +5 -1
  28. package/dist/docx/documentParser.js +8 -5
  29. package/dist/docx/documentSectionFacts.d.ts +22 -0
  30. package/dist/docx/documentSectionFacts.js +43 -0
  31. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  32. package/dist/docx/drawingGroupChildren.js +176 -0
  33. package/dist/docx/drawingUtils.js +3 -0
  34. package/dist/docx/fieldParser.d.ts +8 -1
  35. package/dist/docx/fieldParser.js +47 -3
  36. package/dist/docx/fontTableParser.js +34 -27
  37. package/dist/docx/footnoteParser.d.ts +3 -2
  38. package/dist/docx/footnoteParser.js +15 -12
  39. package/dist/docx/groupDrawingParser.d.ts +9 -2
  40. package/dist/docx/groupDrawingParser.js +125 -37
  41. package/dist/docx/headerFooterParser.d.ts +7 -2
  42. package/dist/docx/headerFooterParser.js +15 -5
  43. package/dist/docx/hyperlinkParser.d.ts +48 -36
  44. package/dist/docx/hyperlinkParser.js +97 -82
  45. package/dist/docx/numberingParser.js +108 -84
  46. package/dist/docx/packageParts.d.ts +45 -1
  47. package/dist/docx/packageParts.js +93 -2
  48. package/dist/docx/paraIdAttribute.d.ts +3 -2
  49. package/dist/docx/paragraphParser.d.ts +5 -1
  50. package/dist/docx/paragraphParser.js +468 -365
  51. package/dist/docx/paragraphProperties.js +123 -109
  52. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  53. package/dist/docx/paragraphPropertySource.js +9 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  55. package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
  56. package/dist/docx/parser.d.ts +7 -1
  57. package/dist/docx/parser.js +33 -16
  58. package/dist/docx/previewBudget.d.ts +55 -33
  59. package/dist/docx/previewBudget.js +59 -65
  60. package/dist/docx/rezip.js +41 -58
  61. package/dist/docx/runParser.d.ts +4 -1
  62. package/dist/docx/runParser.js +182 -184
  63. package/dist/docx/sdtProperties.js +87 -84
  64. package/dist/docx/sectionParser.js +169 -160
  65. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  66. package/dist/docx/selectiveSaveFlags.js +1 -1
  67. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  68. package/dist/docx/selectiveXmlPatch.js +222 -115
  69. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  70. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  71. package/dist/docx/serializer/paragraphSerializer.js +47 -42
  72. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  73. package/dist/docx/serializer/runSerializer.js +4 -3
  74. package/dist/docx/serializer/settingsSerializer.js +19 -1
  75. package/dist/docx/settingsParser.js +8 -0
  76. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  77. package/dist/docx/shapeAlternateContent.js +52 -0
  78. package/dist/docx/streamingXmlParser.d.ts +6 -2
  79. package/dist/docx/streamingXmlParser.js +12 -7
  80. package/dist/docx/styleParser.js +34 -8
  81. package/dist/docx/tableParser.d.ts +21 -12
  82. package/dist/docx/tableParser.js +518 -441
  83. package/dist/docx/textBoxParser.d.ts +18 -5
  84. package/dist/docx/textBoxParser.js +12 -6
  85. package/dist/docx/verbatimCapture.d.ts +10 -8
  86. package/dist/docx/verbatimCapture.js +45 -2
  87. package/dist/docx/vmlImageParser.d.ts +15 -13
  88. package/dist/docx/vmlImageParser.js +75 -60
  89. package/dist/docx/vmlPreview.d.ts +3 -3
  90. package/dist/docx/vmlPreview.js +3 -4
  91. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  92. package/dist/docx/xmlNamespaceContext.js +54 -0
  93. package/dist/docx/xmlParser.d.ts +6 -3
  94. package/dist/docx/xmlParser.js +11 -41
  95. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  96. package/dist/docx/xmlResourceLimits.js +4 -1
  97. package/dist/fonts/headlessMeasure.js +0 -0
  98. package/dist/headless-layout.d.ts +11 -2
  99. package/dist/headless-layout.js +153 -107
  100. package/dist/internal/headlessRevisionResolution.js +50 -13
  101. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  102. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  103. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  104. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  105. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  106. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  107. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  108. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  109. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  110. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  111. package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
  112. package/dist/layout-bridge/engine/hitTest.js +5 -3
  113. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  114. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  115. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  116. package/dist/layout-engine/index.d.ts +2 -2
  117. package/dist/layout-engine/index.js +102 -14
  118. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  119. package/dist/layout-engine/justificationCompatibility.js +9 -5
  120. package/dist/layout-engine/keep-together.d.ts +15 -6
  121. package/dist/layout-engine/keep-together.js +43 -16
  122. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  123. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  124. package/dist/layout-engine/measure/advanceComposition.js +50 -7
  125. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  126. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  127. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  128. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  129. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  130. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  131. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  132. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  133. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  134. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  135. package/dist/layout-engine/measure/measureBlocks.js +46 -10
  136. package/dist/layout-engine/measure/measureContainer.js +51 -8
  137. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  138. package/dist/layout-engine/measure/measureHelpers.js +34 -8
  139. package/dist/layout-engine/measure/measureParagraph.js +143 -101
  140. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  141. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  142. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  143. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  144. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  145. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  146. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  147. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  148. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  149. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  150. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  151. package/dist/layout-engine/noteAreaFlow.js +83 -0
  152. package/dist/layout-engine/paginator.d.ts +12 -1
  153. package/dist/layout-engine/paginator.js +18 -2
  154. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  155. package/dist/layout-engine/paragraphSpacing.js +2 -1
  156. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  157. package/dist/layout-engine/tableRowBreak.js +25 -2
  158. package/dist/layout-engine/types.d.ts +108 -3
  159. package/dist/layout-engine/types.js +96 -1
  160. package/dist/layout-painter/index.d.ts +3 -0
  161. package/dist/layout-painter/renderPage.js +32 -83
  162. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  163. package/dist/layout-painter/renderParagraph.js +75 -26
  164. package/dist/layout-painter/renderTable.js +26 -13
  165. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  166. package/dist/layout-painter/renderTextBox.js +26 -2
  167. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  168. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  169. package/dist/prosemirror/attrs/index.d.ts +9 -9
  170. package/dist/prosemirror/attrs/index.js +133 -11
  171. package/dist/prosemirror/commands/comments.js +51 -41
  172. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  173. package/dist/prosemirror/contentControlRevisions.js +84 -0
  174. package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
  175. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  176. package/dist/prosemirror/conversion/markInterner.js +90 -0
  177. package/dist/prosemirror/conversion/toProseDoc.js +117 -58
  178. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  180. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  181. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  182. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  183. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  184. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  185. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  186. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  187. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  188. package/dist/prosemirror/listMarker.d.ts +1 -0
  189. package/dist/prosemirror/listMarker.js +4 -2
  190. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  191. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  192. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  193. package/dist/prosemirror/paragraphDirection.js +21 -2
  194. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  195. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  196. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  197. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  198. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  199. package/dist/prosemirror/schema/nodes.d.ts +50 -6
  200. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  201. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  202. package/dist/prosemirror/validation.js +91 -80
  203. package/dist/types/content.d.ts +2 -2
  204. package/dist/utils/createDocument.js +10 -1
  205. package/dist/utils/scriptSegments.d.ts +33 -4
  206. package/dist/utils/scriptSegments.js +39 -6
  207. package/dist/utils/textFormattingMerge.d.ts +17 -3
  208. package/dist/utils/textFormattingMerge.js +18 -12
  209. package/dist/utils/trailingText.d.ts +22 -0
  210. package/dist/utils/trailingText.js +32 -0
  211. package/package.json +2 -2
  212. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  213. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -2,6 +2,7 @@ import { document_d_exports } from "../types/document.js";
2
2
  import { NumberingMap } from "./numberingParser.js";
3
3
  import { ParseContext } from "./parseContext.js";
4
4
  import { XmlElement } from "./xmlParser.js";
5
+ import { PreviewLedger } from "./previewBudget.js";
5
6
  import { StyleMap } from "./styleParser.js";
6
7
  //#region src/docx/textBoxParser.d.ts
7
8
  /**
@@ -15,16 +16,20 @@ declare function extractTextBoxContentElements(txbxContent: XmlElement | null):
15
16
  /**
16
17
  * Type for the paragraph parser function to avoid circular imports
17
18
  */
18
- type ParagraphParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => document_d_exports.Paragraph;
19
+ type ParagraphParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, options: {
20
+ previews: PreviewLedger;
21
+ }) => document_d_exports.Paragraph;
19
22
  /**
20
23
  * Type for the table parser function to avoid circular imports
21
24
  */
22
- type TableParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => document_d_exports.Table | undefined;
25
+ type TableParserFn = (node: XmlElement, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, options: {
26
+ previews: PreviewLedger;
27
+ }) => document_d_exports.Table | undefined;
23
28
  /**
24
29
  * Parse text box content with provided parser functions
25
30
  * This avoids circular dependencies by accepting parser functions as parameters
26
31
  */
27
- declare function parseTextBoxContent(txbxContent: XmlElement | null, parseParagraph: ParagraphParserFn, parseTable: TableParserFn | null, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null): (document_d_exports.Paragraph | document_d_exports.Table)[];
32
+ declare function parseTextBoxContent(txbxContent: XmlElement | null, parseParagraph: ParagraphParserFn, parseTable: TableParserFn | null, styles: StyleMap | null, theme: document_d_exports.Theme | null, numbering: NumberingMap | null, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, previews: PreviewLedger): (document_d_exports.Paragraph | document_d_exports.Table)[];
28
33
  /**
29
34
  * Check if a drawing element contains a text box
30
35
  * Text boxes are shapes with wps:txbx content
@@ -114,8 +119,16 @@ declare function resolveTextBoxOutlineColor(textBox: document_d_exports.TextBox)
114
119
  * Get outline width in pixels
115
120
  */
116
121
  declare function getTextBoxOutlineWidthPx(textBox: document_d_exports.TextBox): number;
122
+ /** A text-box `w:drawing`, with the `mc:AlternateContent` branch it sits in, if any. */
123
+ type TextBoxDrawing = {
124
+ drawing: XmlElement;
125
+ alternateContent?: {
126
+ element: XmlElement;
127
+ branch: XmlElement;
128
+ };
129
+ };
117
130
  type TextBoxRunScan = {
118
- textBoxDrawings: XmlElement[];
131
+ textBoxDrawings: TextBoxDrawing[];
119
132
  vmlTextBoxes: XmlElement[];
120
133
  hasNonTextBoxContent: boolean;
121
134
  };
@@ -138,4 +151,4 @@ type ScanRunForTextBoxDrawingsOptions = {
138
151
  };
139
152
  declare const scanRunForTextBoxDrawings: ({ xmlRun, claimedByRunParser }: ScanRunForTextBoxDrawingsOptions) => TextBoxRunScan;
140
153
  //#endregion
141
- export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxText, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
154
+ export { ParagraphParserFn, ScanRunForTextBoxDrawingsOptions, TableParserFn, TextBoxDrawing, TextBoxRunScan, extractTextBoxContentElements, getTextBoxContentElement, getTextBoxDimensionsPx, getTextBoxHeightPx, getTextBoxMarginsPx, getTextBoxOutlineWidthPx, getTextBoxText, getTextBoxWidthPx, hasTextBoxContent, hasTextBoxFill, hasTextBoxOutline, isFloatingTextBox, isShapeTextBox, isTextBoxDrawing, parseTextBox, parseTextBoxContent, parseTextBoxFromShape, resolveTextBoxFillColor, resolveTextBoxOutlineColor, scanRunForTextBoxDrawings };
@@ -80,7 +80,7 @@ function extractTextBoxContentElements(txbxContent) {
80
80
  * Parse text box content with provided parser functions
81
81
  * This avoids circular dependencies by accepting parser functions as parameters
82
82
  */
83
- function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, theme, numbering, rels, media) {
83
+ function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, theme, numbering, rels, media, previews) {
84
84
  if (!txbxContent) return [];
85
85
  const blocks = [];
86
86
  const children = getChildElements(txbxContent);
@@ -89,10 +89,10 @@ function parseTextBoxContent(txbxContent, parseParagraph, parseTable, styles, th
89
89
  const colonIdx = name.indexOf(":");
90
90
  const localName = colonIdx !== -1 ? name.slice(colonIdx + 1) : name;
91
91
  if (localName === "p") {
92
- const paragraph = parseParagraph(child, styles, theme, numbering, rels, media);
92
+ const paragraph = parseParagraph(child, styles, theme, numbering, rels, media, { previews });
93
93
  blocks.push(paragraph);
94
94
  } else if (localName === "tbl" && parseTable) {
95
- const table = parseTable(child, styles, theme, numbering, rels, media);
95
+ const table = parseTable(child, styles, theme, numbering, rels, media, { previews });
96
96
  if (table) blocks.push(table);
97
97
  }
98
98
  }
@@ -328,9 +328,12 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
328
328
  const textBoxDrawings = [];
329
329
  const vmlTextBoxes = [];
330
330
  let hasNonTextBoxContent = false;
331
- const visitDrawing = (drawingEl) => {
331
+ const visitDrawing = (drawingEl, alternateContent) => {
332
332
  if (isTextBoxDrawing(drawingEl)) {
333
- textBoxDrawings.push(drawingEl);
333
+ textBoxDrawings.push({
334
+ drawing: drawingEl,
335
+ ...alternateContent === void 0 ? {} : { alternateContent }
336
+ });
334
337
  return;
335
338
  }
336
339
  hasNonTextBoxContent = true;
@@ -355,7 +358,10 @@ const scanRunForTextBoxDrawings = ({ xmlRun, claimedByRunParser }) => {
355
358
  if (!branch) return false;
356
359
  let found = false;
357
360
  for (const innerEl of getChildElements(branch)) if (getLocalName(innerEl.name ?? "") === "drawing") {
358
- visitDrawing(innerEl);
361
+ visitDrawing(innerEl, {
362
+ element: el,
363
+ branch
364
+ });
359
365
  found = true;
360
366
  }
361
367
  return found;
@@ -18,14 +18,16 @@ type SanitizeCapturedXmlOptions = {
18
18
  /** Validate a complete XML part immediately before package output. */
19
19
  declare const isSafeCapturedXmlDocument: (xml: string) => boolean;
20
20
  /**
21
- * Re-parse and serialize one captured element before replaying it.
21
+ * {@link sanitizeCapturedXmlElement} over fixed options, memoized by source
22
+ * string.
22
23
  *
23
- * The parser deliberately ignores declarations, processing instructions,
24
- * comments, and document types. Returning the caller's source string after a
25
- * successful parse would therefore replay tokens that were never validated.
26
- * This function returns only the parsed root serialized through the capture
27
- * owner, and rejects siblings, unexpected roots, and namespace shadowing.
24
+ * Sanitizing is a pure function of the string and the options, and a capture
25
+ * is fixed when it is parsed, so every save after the first would otherwise
26
+ * re-parse and re-validate the same markup. The key is the string rather than
27
+ * the object holding it: editor round-trips copy capture holders, but the
28
+ * string they carry is the one the parser produced. `transform` and `validate`
29
+ * must therefore depend on nothing but the element they are given.
28
30
  */
29
- declare const sanitizeCapturedXmlElement: (xml: string | undefined, options: SanitizeCapturedXmlOptions) => string | null;
31
+ declare const createCapturedXmlSanitizer: (options: SanitizeCapturedXmlOptions) => ((xml: string | undefined) => string | null);
30
32
  //#endregion
31
- export { UntranslatableStrictNamespaceError, captureVerbatimXml, isSafeCapturedXmlDocument, sanitizeCapturedXmlElement };
33
+ export { UntranslatableStrictNamespaceError, captureVerbatimXml, createCapturedXmlSanitizer, isSafeCapturedXmlDocument };
@@ -313,12 +313,55 @@ const captureAllowedElement = (element, { allowedLocalNames, allowedNamespaceUri
313
313
  * owner, and rejects siblings, unexpected roots, and namespace shadowing.
314
314
  */
315
315
  const sanitizeCapturedXmlElement = (xml, options) => {
316
- if (xml === void 0) return null;
317
316
  const trimmed = xml.trim();
318
317
  if (!trimmed) return null;
319
318
  if (!withinXmlResourceLimits(xml) || getDocxXmlSafetyIssue(xml) !== null) return null;
320
319
  const root = parsedReplayEnvelope(trimmed, options.inheritedNamespaceScope ?? OOXML_NAMESPACE_SCOPE);
321
320
  return root ? captureAllowedElement(root, options) : null;
322
321
  };
322
+ /**
323
+ * Characters of source and sanitized XML one sanitizer retains. A paragraph
324
+ * capture is a few hundred characters, so this holds every distinct capture of
325
+ * a large document while bounding what outlives the documents it came from.
326
+ */
327
+ const CAPTURED_XML_SANITIZER_CACHE_CHARACTERS = 2 * 1024 * 1024;
328
+ /**
329
+ * What one cache entry costs beyond its strings, in characters. Charging it
330
+ * keeps many short keys from holding far more memory than their text, and
331
+ * caps the entry count at the budget divided by this.
332
+ */
333
+ const CAPTURED_XML_SANITIZER_ENTRY_OVERHEAD_CHARACTERS = 64;
334
+ const capturedXmlCacheCharacters = (xml, sanitized) => CAPTURED_XML_SANITIZER_ENTRY_OVERHEAD_CHARACTERS + xml.length + (sanitized?.length ?? 0);
335
+ /**
336
+ * {@link sanitizeCapturedXmlElement} over fixed options, memoized by source
337
+ * string.
338
+ *
339
+ * Sanitizing is a pure function of the string and the options, and a capture
340
+ * is fixed when it is parsed, so every save after the first would otherwise
341
+ * re-parse and re-validate the same markup. The key is the string rather than
342
+ * the object holding it: editor round-trips copy capture holders, but the
343
+ * string they carry is the one the parser produced. `transform` and `validate`
344
+ * must therefore depend on nothing but the element they are given.
345
+ */
346
+ const createCapturedXmlSanitizer = (options) => {
347
+ const cache = /* @__PURE__ */ new Map();
348
+ let cachedCharacters = 0;
349
+ return (xml) => {
350
+ if (xml === void 0) return null;
351
+ const cached = cache.get(xml);
352
+ if (cached !== void 0) return cached;
353
+ const sanitized = sanitizeCapturedXmlElement(xml, options);
354
+ const characters = capturedXmlCacheCharacters(xml, sanitized);
355
+ if (characters > CAPTURED_XML_SANITIZER_CACHE_CHARACTERS) return sanitized;
356
+ for (const [oldestXml, oldestSanitized] of cache) {
357
+ if (cachedCharacters + characters <= CAPTURED_XML_SANITIZER_CACHE_CHARACTERS) break;
358
+ cache.delete(oldestXml);
359
+ cachedCharacters -= capturedXmlCacheCharacters(oldestXml, oldestSanitized);
360
+ }
361
+ cache.set(xml, sanitized);
362
+ cachedCharacters += characters;
363
+ return sanitized;
364
+ };
365
+ };
323
366
  //#endregion
324
- export { UntranslatableStrictNamespaceError, captureVerbatimXml, isSafeCapturedXmlDocument, sanitizeCapturedXmlElement };
367
+ export { UntranslatableStrictNamespaceError, captureVerbatimXml, createCapturedXmlSanitizer, isSafeCapturedXmlDocument };
@@ -1,5 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { XmlElement } from "./xmlParser.js";
3
+ import { PreviewLedger } from "./previewBudget.js";
3
4
  //#region src/docx/vmlImageParser.d.ts
4
5
  /**
5
6
  * Whether a `w:pict` that resolved to no image must still be kept verbatim.
@@ -20,6 +21,16 @@ import { XmlElement } from "./xmlParser.js";
20
21
  * the paragraph it detached, so this predicate need not guess who claimed what.
21
22
  */
22
23
  declare function shouldPreserveRawVmlPict(pictElement: XmlElement): boolean;
24
+ /**
25
+ * Parse a `w:pict` element into an inline image, or null when it carries no
26
+ * ordinary VML picture (no resolvable `<v:imagedata>`, or a watermark shape).
27
+ *
28
+ * `rootXmlns` carries the source document/header namespace declarations. They
29
+ * are injected onto the captured VML so the raw replay stays self-contained
30
+ * when the producer bound WordprocessingML / relationship / VML namespaces to
31
+ * non-canonical prefixes the serializer's root does not declare.
32
+ */
33
+ declare function parseVmlImageContent(pictElement: XmlElement, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, previews: PreviewLedger, rootXmlns?: Record<string, string>): document_d_exports.DrawingContent | null;
23
34
  /**
24
35
  * Whether the run parser turns this `w:pict` into run content of its own.
25
36
  *
@@ -31,20 +42,11 @@ declare function shouldPreserveRawVmlPict(pictElement: XmlElement): boolean;
31
42
  *
32
43
  * The enrichment asks this rather than re-deriving the answer from the markup:
33
44
  * its own reading (`v:imagedata` present) named only the picture path, so a
34
- * `v:group` claimed by the preview path was represented by both owners. The
35
- * result is a pure function of the element, so asking it a second time costs
36
- * only the work the run parser already did.
45
+ * `v:group` claimed by the preview path was represented by both owners. It
46
+ * reads the two decisions {@link parseVmlImageContent} makes without building
47
+ * either result: a preview built here would be registered against the package
48
+ * budget for a drawing the model never holds.
37
49
  */
38
50
  declare const isVmlPictParsedByRunParser: (pictElement: XmlElement, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null) => boolean;
39
- /**
40
- * Parse a `w:pict` element into an inline image, or null when it carries no
41
- * ordinary VML picture (no resolvable `<v:imagedata>`, or a watermark shape).
42
- *
43
- * `rootXmlns` carries the source document/header namespace declarations. They
44
- * are injected onto the captured VML so the raw replay stays self-contained
45
- * when the producer bound WordprocessingML / relationship / VML namespaces to
46
- * non-canonical prefixes the serializer's root does not declare.
47
- */
48
- declare function parseVmlImageContent(pictElement: XmlElement, rels: document_d_exports.RelationshipMap | null, media: Map<string, document_d_exports.MediaFile> | null, rootXmlns?: Record<string, string>): document_d_exports.DrawingContent | null;
49
51
  //#endregion
50
52
  export { isVmlPictParsedByRunParser, parseVmlImageContent, shouldPreserveRawVmlPict };
@@ -2,9 +2,8 @@ import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
2
2
  import { pixelsToEmu } from "../utils/units.js";
3
3
  import { resolveImageData } from "./imageParser.js";
4
4
  import { imageRawXmlFingerprint } from "./imageRawXml.js";
5
- import { PREVIEW_KINDS } from "./previewBudget.js";
6
5
  import { captureVerbatimXml } from "./verbatimCapture.js";
7
- import { isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx, vmlSvgDataUrl } from "./vmlPreview.js";
6
+ import { fitsVmlSvgCap, isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx } from "./vmlPreview.js";
8
7
  import { isWatermarkShape } from "./watermarkParser.js";
9
8
  import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findDeep, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
10
9
  import { DRAWING_RAW_XML_MODES, relationshipIdOf } from "@stll/docx-core/model";
@@ -93,19 +92,44 @@ const vmlImageLayout = (style) => {
93
92
  }
94
93
  };
95
94
  };
96
- const previewImage = (pictElement, svg, widthPx, heightPx, style, rootXmlns) => {
97
- const src = vmlSvgDataUrl(svg);
98
- if (!src) return null;
95
+ /** A render the model may retain: one that painted, within the SVG output cap. */
96
+ const retainableRender = (preview) => {
97
+ switch (preview.type) {
98
+ case "rendered": return fitsVmlSvgCap(preview.svg) ? preview : null;
99
+ case "empty":
100
+ case "invalid": return null;
101
+ default: return preview;
102
+ }
103
+ };
104
+ /**
105
+ * The render folio draws a `w:pict` as when it holds no picture: its first
106
+ * group or standalone shape that renders within the cap, or null.
107
+ *
108
+ * Deciding this builds nothing, so the text-box pass can ask whether the run
109
+ * parser claims a pict without making a second preview of it.
110
+ */
111
+ const vmlPictPreview = (pictElement) => {
112
+ for (const child of getChildElements(pictElement)) {
113
+ const localName = getLocalName(child.name ?? "");
114
+ if (localName === "group") {
115
+ const preview = retainableRender(renderVmlGroupPreview(child));
116
+ if (preview) return preview;
117
+ continue;
118
+ }
119
+ if (localName === "rect" || localName === "roundrect" || localName === "oval") {
120
+ const preview = retainableRender(renderStandaloneVmlPreview(child));
121
+ if (preview) return preview;
122
+ }
123
+ }
124
+ return null;
125
+ };
126
+ const previewDrawing = (pictElement, { svg, widthPx, heightPx, style }, rootXmlns, previews) => {
99
127
  const leftPx = vmlCssLengthToPx(style["margin-left"] ?? style["left"]) ?? 0;
100
128
  const topPx = vmlCssLengthToPx(style["margin-top"] ?? style["top"]) ?? 0;
101
129
  const horizontalRelative = style["mso-position-horizontal-relative"] === "page";
102
130
  const verticalRelative = style["mso-position-vertical-relative"] === "page";
103
131
  const zIndex = parseVmlNumber(style["z-index"]);
104
- const image = {
105
- type: "image",
106
- src,
107
- mimeType: PREVIEW_KINDS.vmlShape.mimeType,
108
- filename: PREVIEW_KINDS.vmlShape.filename,
132
+ const image = previews.svgImage("vmlShape", svg, {
109
133
  size: {
110
134
  width: pixelsToEmu(widthPx),
111
135
  height: pixelsToEmu(heightPx)
@@ -121,7 +145,7 @@ const previewImage = (pictElement, svg, widthPx, heightPx, style, rootXmlns) =>
121
145
  posOffset: pixelsToSafeEmu(topPx) ?? 0
122
146
  }
123
147
  }
124
- };
148
+ });
125
149
  return {
126
150
  type: "drawing",
127
151
  image,
@@ -130,14 +154,6 @@ const previewImage = (pictElement, svg, widthPx, heightPx, style, rootXmlns) =>
130
154
  rawXmlMode: DRAWING_RAW_XML_MODES.PREVIEW_ONLY
131
155
  };
132
156
  };
133
- const previewDrawing = (pictElement, preview, rootXmlns) => {
134
- switch (preview.type) {
135
- case "rendered": return previewImage(pictElement, preview.svg, preview.widthPx, preview.heightPx, preview.style, rootXmlns);
136
- case "empty":
137
- case "invalid": return null;
138
- default: return preview;
139
- }
140
- };
141
157
  /** VML elements that paint something, i.e. content a save must not lose. */
142
158
  const VML_DRAWABLE_SHAPES = [
143
159
  "shape",
@@ -174,22 +190,6 @@ function shouldPreserveRawVmlPict(pictElement) {
174
190
  return VML_DRAWABLE_SHAPES.some((localName) => findDeep(pictElement, "v", localName) !== null);
175
191
  }
176
192
  /**
177
- * Whether the run parser turns this `w:pict` into run content of its own.
178
- *
179
- * Exactly one owner may represent a `w:pict`: the run parser, whose drawing
180
- * carries the whole element as captured XML, or the text-box enrichment pass,
181
- * which rebuilds one `v:textbox` as an editable shape. Two owners write the
182
- * same artwork twice, and when the pict holds text, the saved document says it
183
- * twice.
184
- *
185
- * The enrichment asks this rather than re-deriving the answer from the markup:
186
- * its own reading (`v:imagedata` present) named only the picture path, so a
187
- * `v:group` claimed by the preview path was represented by both owners. The
188
- * result is a pure function of the element, so asking it a second time costs
189
- * only the work the run parser already did.
190
- */
191
- const isVmlPictParsedByRunParser = (pictElement, rels, media) => parseVmlImageContent(pictElement, rels, media) !== null || shouldPreserveRawVmlPict(pictElement);
192
- /**
193
193
  * Read the relationship id off a `v:imagedata` element. Word writes `r:id`;
194
194
  * some legacy / third-party generators use `r:embed` or the office-namespace
195
195
  * `o:relid` instead, so fall back through those before the bare `id`.
@@ -198,15 +198,10 @@ function readImageDataRId(imagedata) {
198
198
  return getAttribute(imagedata, "r", "id") ?? getAttribute(imagedata, "r", "embed") ?? getAttribute(imagedata, "o", "relid") ?? getAttribute(imagedata, null, "id") ?? void 0;
199
199
  }
200
200
  /**
201
- * Parse a `w:pict` element into an inline image, or null when it carries no
202
- * ordinary VML picture (no resolvable `<v:imagedata>`, or a watermark shape).
203
- *
204
- * `rootXmlns` carries the source document/header namespace declarations. They
205
- * are injected onto the captured VML so the raw replay stays self-contained
206
- * when the producer bound WordprocessingML / relationship / VML namespaces to
207
- * non-canonical prefixes the serializer's root does not declare.
201
+ * The picture a `w:pict` carries: the first shape whose `<v:imagedata>`
202
+ * names a relationship, skipping watermarks, or null.
208
203
  */
209
- function parseVmlImageContent(pictElement, rels, media, rootXmlns = {}) {
204
+ const vmlPictPicture = (pictElement, rels, media) => {
210
205
  const shapes = [
211
206
  ...findAllDeep(pictElement, "v", "shape"),
212
207
  ...findAllDeep(pictElement, "v", "rect"),
@@ -239,25 +234,45 @@ function parseVmlImageContent(pictElement, rels, media, rootXmlns = {}) {
239
234
  if (filename) image.filename = filename;
240
235
  const title = getAttribute(imagedata, "o", "title");
241
236
  if (title) image.title = title;
242
- return {
243
- type: "drawing",
244
- image,
245
- rawXml: captureVerbatimXml(cloneWithXmlnsDeclarations(pictElement, rootXmlns))
246
- };
247
- }
248
- for (const child of getChildElements(pictElement)) {
249
- const localName = getLocalName(child.name ?? "");
250
- if (localName === "group") {
251
- const preview = previewDrawing(pictElement, renderVmlGroupPreview(child), rootXmlns);
252
- if (preview) return preview;
253
- continue;
254
- }
255
- if (localName === "rect" || localName === "roundrect" || localName === "oval") {
256
- const preview = previewDrawing(pictElement, renderStandaloneVmlPreview(child), rootXmlns);
257
- if (preview) return preview;
258
- }
237
+ return image;
259
238
  }
260
239
  return null;
240
+ };
241
+ /**
242
+ * Parse a `w:pict` element into an inline image, or null when it carries no
243
+ * ordinary VML picture (no resolvable `<v:imagedata>`, or a watermark shape).
244
+ *
245
+ * `rootXmlns` carries the source document/header namespace declarations. They
246
+ * are injected onto the captured VML so the raw replay stays self-contained
247
+ * when the producer bound WordprocessingML / relationship / VML namespaces to
248
+ * non-canonical prefixes the serializer's root does not declare.
249
+ */
250
+ function parseVmlImageContent(pictElement, rels, media, previews, rootXmlns = {}) {
251
+ const image = vmlPictPicture(pictElement, rels, media);
252
+ if (image) return {
253
+ type: "drawing",
254
+ image,
255
+ rawXml: captureVerbatimXml(cloneWithXmlnsDeclarations(pictElement, rootXmlns))
256
+ };
257
+ const preview = vmlPictPreview(pictElement);
258
+ return preview ? previewDrawing(pictElement, preview, rootXmlns, previews) : null;
261
259
  }
260
+ /**
261
+ * Whether the run parser turns this `w:pict` into run content of its own.
262
+ *
263
+ * Exactly one owner may represent a `w:pict`: the run parser, whose drawing
264
+ * carries the whole element as captured XML, or the text-box enrichment pass,
265
+ * which rebuilds one `v:textbox` as an editable shape. Two owners write the
266
+ * same artwork twice, and when the pict holds text, the saved document says it
267
+ * twice.
268
+ *
269
+ * The enrichment asks this rather than re-deriving the answer from the markup:
270
+ * its own reading (`v:imagedata` present) named only the picture path, so a
271
+ * `v:group` claimed by the preview path was represented by both owners. It
272
+ * reads the two decisions {@link parseVmlImageContent} makes without building
273
+ * either result: a preview built here would be registered against the package
274
+ * budget for a drawing the model never holds.
275
+ */
276
+ const isVmlPictParsedByRunParser = (pictElement, rels, media) => vmlPictPicture(pictElement, rels, media) !== null || vmlPictPreview(pictElement) !== null || shouldPreserveRawVmlPict(pictElement);
262
277
  //#endregion
263
278
  export { isVmlPictParsedByRunParser, parseVmlImageContent, shouldPreserveRawVmlPict };
@@ -19,11 +19,11 @@ declare const parseVmlStyle: (style: string | null) => Record<string, string>;
19
19
  declare const parseVmlNumber: (raw: string | null | undefined) => number | undefined;
20
20
  /** Check a physical preview dimension against the retained SVG layout bound. */
21
21
  declare const isValidVmlPreviewDimension: (value: number | undefined) => value is number;
22
- /** Encode a generated, already-sanitized SVG without exceeding its output cap. */
23
- declare const vmlSvgDataUrl: (svg: string) => string | undefined;
22
+ /** Whether a generated, already-sanitized SVG is within the output cap a preview may retain. */
23
+ declare const fitsVmlSvgCap: (svg: string) => boolean;
24
24
  /** Render a bounded standalone VML rectangle or oval preview. */
25
25
  declare const renderStandaloneVmlPreview: (shape: XmlElement) => VmlPreviewResult;
26
26
  /** Render a VML group through bounded, non-clipping local-coordinate transforms. */
27
27
  declare const renderVmlGroupPreview: (group: XmlElement) => VmlPreviewResult;
28
28
  //#endregion
29
- export { VmlPreviewResult, isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx, vmlSvgDataUrl };
29
+ export { VmlPreviewResult, fitsVmlSvgCap, isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx };
@@ -1,4 +1,3 @@
1
- import { PREVIEW_KINDS } from "./previewBudget.js";
2
1
  import { findChild, findDeep, getAttribute, getChildElements, getLocalName } from "./xmlParser.js";
3
2
  //#region src/docx/vmlPreview.ts
4
3
  const MAX_VML_PREVIEW_DEPTH = 16;
@@ -83,8 +82,8 @@ const parseVmlNumber = (raw) => {
83
82
  };
84
83
  /** Check a physical preview dimension against the retained SVG layout bound. */
85
84
  const isValidVmlPreviewDimension = (value) => value !== void 0 && value > 0 && value <= MAX_VML_PREVIEW_DIMENSION_PX;
86
- /** Encode a generated, already-sanitized SVG without exceeding its output cap. */
87
- const vmlSvgDataUrl = (svg) => svg.length <= MAX_VML_SVG_CHARACTERS ? `${PREVIEW_KINDS.vmlShape.srcPrefix}${encodeURIComponent(svg)}` : void 0;
85
+ /** Whether a generated, already-sanitized SVG is within the output cap a preview may retain. */
86
+ const fitsVmlSvgCap = (svg) => svg.length <= MAX_VML_SVG_CHARACTERS;
88
87
  const validCoordinate = (value) => value !== void 0 && Math.abs(value) <= MAX_VML_PREVIEW_COORDINATE;
89
88
  const validExtent = (value) => value !== void 0 && value > 0 && value <= MAX_VML_PREVIEW_COORDINATE;
90
89
  const coordinatePair = (raw) => {
@@ -504,4 +503,4 @@ const renderVmlGroupPreview = (group) => {
504
503
  };
505
504
  };
506
505
  //#endregion
507
- export { isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx, vmlSvgDataUrl };
506
+ export { fitsVmlSvgCap, isValidVmlPreviewDimension, parseVmlNumber, parseVmlStyle, renderStandaloneVmlPreview, renderVmlGroupPreview, vmlCssLengthToPx };
@@ -0,0 +1,9 @@
1
+ import { XmlElement, XmlNamespaceScope } from "./xmlParser.js";
2
+ //#region src/docx/xmlNamespaceContext.d.ts
3
+ declare const EMPTY_NAMESPACE_SCOPE: XmlNamespaceScope;
4
+ declare const XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
5
+ declare const resolveNamespaceUri: (scope: XmlNamespaceScope | undefined, prefix: string) => string | undefined;
6
+ /** Attach the element's resolved namespace metadata from its in-scope declarations. */
7
+ declare const attachXmlNamespaceContext: (element: XmlElement, inheritedNamespaceScope?: XmlNamespaceScope) => XmlNamespaceScope;
8
+ //#endregion
9
+ export { EMPTY_NAMESPACE_SCOPE, XML_NAMESPACE_URI, attachXmlNamespaceContext, resolveNamespaceUri };
@@ -0,0 +1,54 @@
1
+ //#region src/docx/xmlNamespaceContext.ts
2
+ const EMPTY_NAMESPACE_SCOPE = { bindings: /* @__PURE__ */ new Map() };
3
+ /**
4
+ * Reserved by the XML Namespaces recommendation (§3): every document binds
5
+ * this prefix to this URI whether or not it declares `xmlns:xml`, and no
6
+ * document may bind it to anything else. A source that omits the declaration
7
+ * — the overwhelming majority — must still resolve `xml:space`, `xml:lang`
8
+ * and `xml:id` to it, or a reader mistakes the attribute for an unprefixed
9
+ * one and a save drops its prefix.
10
+ */
11
+ const XML_RESERVED_PREFIX = "xml";
12
+ const XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace";
13
+ const resolveNamespaceUri = (scope, prefix) => {
14
+ let current = scope;
15
+ while (current) {
16
+ const value = current.bindings.get(prefix);
17
+ if (value !== void 0) return value;
18
+ current = current.parent;
19
+ }
20
+ return prefix === XML_RESERVED_PREFIX ? XML_NAMESPACE_URI : void 0;
21
+ };
22
+ /** Attach the element's resolved namespace metadata from its in-scope declarations. */
23
+ const attachXmlNamespaceContext = (element, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
24
+ let localBindings = null;
25
+ if (element.attributes) for (const [attribute, value] of Object.entries(element.attributes)) {
26
+ if (typeof value !== "string" || attribute !== "xmlns" && !attribute.startsWith("xmlns:")) continue;
27
+ localBindings ??= /* @__PURE__ */ new Map();
28
+ const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
29
+ localBindings.set(prefix, value);
30
+ }
31
+ const namespaceScope = localBindings === null ? inheritedNamespaceScope : {
32
+ bindings: localBindings,
33
+ parent: inheritedNamespaceScope
34
+ };
35
+ Object.defineProperty(element, "namespaceScope", {
36
+ configurable: false,
37
+ enumerable: false,
38
+ value: namespaceScope,
39
+ writable: false
40
+ });
41
+ const name = element.name ?? "";
42
+ const colonIndex = name.indexOf(":");
43
+ const prefix = colonIndex === -1 ? "" : name.slice(0, colonIndex);
44
+ const namespaceUri = resolveNamespaceUri(namespaceScope, prefix);
45
+ if (namespaceUri !== void 0) Object.defineProperty(element, "namespaceUri", {
46
+ configurable: false,
47
+ enumerable: false,
48
+ value: namespaceUri,
49
+ writable: false
50
+ });
51
+ return namespaceScope;
52
+ };
53
+ //#endregion
54
+ export { EMPTY_NAMESPACE_SCOPE, XML_NAMESPACE_URI, attachXmlNamespaceContext, resolveNamespaceUri };
@@ -28,8 +28,6 @@ type XmlNamespaceScope = {
28
28
  bindings: ReadonlyMap<string, string>;
29
29
  parent?: XmlNamespaceScope;
30
30
  };
31
- /** Attach the element's resolved namespace metadata from its in-scope declarations. */
32
- declare const attachXmlNamespaceContext: (element: XmlElement, inheritedNamespaceScope?: XmlNamespaceScope) => XmlNamespaceScope;
33
31
  /**
34
32
  * Common OOXML namespace URIs — re-exported from @stll/docx-utils.
35
33
  */
@@ -70,6 +68,11 @@ declare const NAMESPACES: {
70
68
  */
71
69
  declare const OOXML_NAMESPACE_SCOPE: XmlNamespaceScope;
72
70
  declare function parseXml(xml: string, inheritedNamespaceScope?: XmlNamespaceScope): XmlElement;
71
+ /**
72
+ * The general-purpose reader {@link parseXml} falls back to, and the reference
73
+ * the streaming reader's equivalence tests compare against.
74
+ */
75
+ declare function parseXmlWithFastXmlParser(xml: string, inheritedNamespaceScope?: XmlNamespaceScope): XmlElement;
73
76
  /**
74
77
  * Serialize an XmlElement back to an XML string.
75
78
  *
@@ -405,4 +408,4 @@ declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Rec
405
408
  */
406
409
  declare function cloneElement(element: XmlElement, overrides: Partial<XmlElement>): XmlElement;
407
410
  //#endregion
408
- export { NAMESPACES, OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, attachXmlNamespaceContext, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findChildrenByNamespaceUri, findDeep, findWordprocessingChild, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffAttribute, parseOnOffChild, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument, resolveAttributeNamespaceUri, selectAlternateContentBranch };
411
+ export { NAMESPACES, OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findChildrenByNamespaceUri, findDeep, findWordprocessingChild, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffAttribute, parseOnOffChild, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument, parseXmlWithFastXmlParser, resolveAttributeNamespaceUri, selectAlternateContentBranch };
@@ -1,5 +1,7 @@
1
+ import { parseStreamingXml } from "./streamingXmlParser.js";
1
2
  import { NUMBERS_PER_PERCENT, percentageSpelling, transitionalSlotEncoding } from "./transitionalSpelling.js";
2
3
  import { universalMeasureAs } from "./universalMeasure.js";
4
+ import { EMPTY_NAMESPACE_SCOPE, attachXmlNamespaceContext, resolveNamespaceUri } from "./xmlNamespaceContext.js";
3
5
  import { escapeXmlAttribute, escapeXmlText } from "@stll/docx-core";
4
6
  import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
5
7
  import { XMLParser } from "fast-xml-parser";
@@ -49,46 +51,6 @@ const fxpParserWithStopNodes = new XMLParser(fxpParserOptionsWithStopNodes);
49
51
  const TEXT_KEY = "#text";
50
52
  /** Attribute group key used by fast-xml-parser in preserveOrder mode. */
51
53
  const ATTR_KEY = ":@";
52
- const EMPTY_NAMESPACE_SCOPE = { bindings: /* @__PURE__ */ new Map() };
53
- const resolveNamespaceUri = (scope, prefix) => {
54
- let current = scope;
55
- while (current) {
56
- const value = current.bindings.get(prefix);
57
- if (value !== void 0) return value;
58
- current = current.parent;
59
- }
60
- };
61
- /** Attach the element's resolved namespace metadata from its in-scope declarations. */
62
- const attachXmlNamespaceContext = (element, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
63
- let localBindings = null;
64
- if (element.attributes) for (const [attribute, value] of Object.entries(element.attributes)) {
65
- if (typeof value !== "string" || attribute !== "xmlns" && !attribute.startsWith("xmlns:")) continue;
66
- localBindings ??= /* @__PURE__ */ new Map();
67
- const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
68
- localBindings.set(prefix, value);
69
- }
70
- const namespaceScope = localBindings === null ? inheritedNamespaceScope : {
71
- bindings: localBindings,
72
- parent: inheritedNamespaceScope
73
- };
74
- Object.defineProperty(element, "namespaceScope", {
75
- configurable: false,
76
- enumerable: false,
77
- value: namespaceScope,
78
- writable: false
79
- });
80
- const name = element.name ?? "";
81
- const colonIndex = name.indexOf(":");
82
- const prefix = colonIndex === -1 ? "" : name.slice(0, colonIndex);
83
- const namespaceUri = resolveNamespaceUri(namespaceScope, prefix);
84
- if (namespaceUri !== void 0) Object.defineProperty(element, "namespaceUri", {
85
- configurable: false,
86
- enumerable: false,
87
- value: namespaceUri,
88
- writable: false
89
- });
90
- return namespaceScope;
91
- };
92
54
  /**
93
55
  * Convert a fast-xml-parser preserveOrder node into an XmlElement.
94
56
  *
@@ -150,6 +112,14 @@ const NAMESPACES = OOXML_NS;
150
112
  */
151
113
  const OOXML_NAMESPACE_SCOPE = { bindings: new Map(Object.entries(OOXML_NS)) };
152
114
  function parseXml(xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) {
115
+ const streamed = parseStreamingXml(xml, inheritedNamespaceScope);
116
+ return streamed.status === "parsed" ? streamed.value : parseXmlWithFastXmlParser(xml, inheritedNamespaceScope);
117
+ }
118
+ /**
119
+ * The general-purpose reader {@link parseXml} falls back to, and the reference
120
+ * the streaming reader's equivalence tests compare against.
121
+ */
122
+ function parseXmlWithFastXmlParser(xml, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) {
153
123
  return fxpToRootElement((xml.includes("binData") ? fxpParserWithStopNodes : fxpParser).parse(xml), inheritedNamespaceScope);
154
124
  }
155
125
  /**
@@ -884,4 +854,4 @@ function cloneElement(element, overrides) {
884
854
  return clone;
885
855
  }
886
856
  //#endregion
887
- export { NAMESPACES, OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, attachXmlNamespaceContext, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findChildrenByNamespaceUri, findDeep, findWordprocessingChild, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffAttribute, parseOnOffChild, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument, resolveAttributeNamespaceUri, selectAlternateContentBranch };
857
+ export { NAMESPACES, OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findChildrenByNamespaceUri, findDeep, findWordprocessingChild, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffAttribute, parseOnOffChild, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument, parseXmlWithFastXmlParser, resolveAttributeNamespaceUri, selectAlternateContentBranch };