@stll/folio-core 0.42.0 → 0.44.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 (206) hide show
  1. package/dist/ai-edits/headless.js +6 -5
  2. package/dist/ai-edits/index.d.ts +2 -2
  3. package/dist/ai-edits/index.js +2 -2
  4. package/dist/ai-edits/snapshot.js +13 -9
  5. package/dist/compare/content-alignment.js +16 -1
  6. package/dist/compare/inline-atoms.js +1 -1
  7. package/dist/compare/style-resources.js +6 -0
  8. package/dist/compat/eigenpal.d.ts +2 -2
  9. package/dist/controller/layoutPipeline.d.ts +2 -1
  10. package/dist/controller/layoutPipeline.js +5 -2
  11. package/dist/controller/layoutSession.d.ts +2 -1
  12. package/dist/controller/layoutSession.js +1 -0
  13. package/dist/docx/appVersionNormalization.d.ts +0 -18
  14. package/dist/docx/blockContentParser.js +10 -1
  15. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  16. package/dist/docx/blockRangeMarkers.js +59 -0
  17. package/dist/docx/bookmarkParser.d.ts +2 -20
  18. package/dist/docx/bookmarkParser.js +6 -30
  19. package/dist/docx/borderParser.d.ts +13 -0
  20. package/dist/docx/borderParser.js +71 -0
  21. package/dist/docx/builtInStyles.d.ts +165 -0
  22. package/dist/docx/builtInStyles.js +239 -0
  23. package/dist/docx/commentIdNormalization.d.ts +10 -0
  24. package/dist/docx/commentIdNormalization.js +33 -0
  25. package/dist/docx/commentParser.d.ts +2 -1
  26. package/dist/docx/commentParser.js +34 -7
  27. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  28. package/dist/docx/commentReferenceNormalization.js +23 -14
  29. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  30. package/dist/docx/danglingRelationshipReferences.js +30 -0
  31. package/dist/docx/defaultParagraphStyle.d.ts +39 -0
  32. package/dist/docx/defaultParagraphStyle.js +54 -0
  33. package/dist/docx/documentParser.d.ts +2 -1
  34. package/dist/docx/documentParser.js +2 -2
  35. package/dist/docx/drawingUtils.d.ts +8 -1
  36. package/dist/docx/drawingUtils.js +12 -3
  37. package/dist/docx/fieldParser.js +3 -5
  38. package/dist/docx/footnoteParser.d.ts +3 -2
  39. package/dist/docx/footnoteParser.js +19 -2
  40. package/dist/docx/groupDrawingParser.js +1 -1
  41. package/dist/docx/headerFooterRefParser.d.ts +15 -3
  42. package/dist/docx/headerFooterRefParser.js +51 -14
  43. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  44. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  45. package/dist/docx/hyperlinkParser.js +11 -15
  46. package/dist/docx/imageParser.d.ts +1 -1
  47. package/dist/docx/imageParser.js +22 -18
  48. package/dist/docx/imageRawXml.js +5 -5
  49. package/dist/docx/markupRangeMarker.d.ts +15 -0
  50. package/dist/docx/markupRangeMarker.js +44 -0
  51. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  52. package/dist/docx/noteReferenceStyles.js +70 -0
  53. package/dist/docx/numberingParser.js +2 -1
  54. package/dist/docx/numberingReference.d.ts +21 -0
  55. package/dist/docx/numberingReference.js +21 -0
  56. package/dist/docx/numberingReferenceNormalization.d.ts +14 -2
  57. package/dist/docx/numberingReferenceNormalization.js +51 -9
  58. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  59. package/dist/docx/paragraphParser.js +69 -101
  60. package/dist/docx/paragraphPropertySource.js +1 -0
  61. package/dist/docx/paragraphTraversal.d.ts +37 -1
  62. package/dist/docx/paragraphTraversal.js +84 -1
  63. package/dist/docx/parseContext.d.ts +37 -0
  64. package/dist/docx/parseContext.js +67 -0
  65. package/dist/docx/parseWarningMessage.d.ts +6 -0
  66. package/dist/docx/parseWarningMessage.js +44 -0
  67. package/dist/docx/parser.js +86 -24
  68. package/dist/docx/relsParser.d.ts +28 -11
  69. package/dist/docx/relsParser.js +26 -13
  70. package/dist/docx/revisionIdNormalization.js +81 -7
  71. package/dist/docx/rezip.js +85 -27
  72. package/dist/docx/runConsolidator.js +1 -2
  73. package/dist/docx/runParser.d.ts +8 -1
  74. package/dist/docx/runParser.js +30 -48
  75. package/dist/docx/sectionParser.d.ts +2 -1
  76. package/dist/docx/sectionParser.js +21 -65
  77. package/dist/docx/serializer/borderSerializer.d.ts +1 -2
  78. package/dist/docx/serializer/commentSerializer.js +22 -9
  79. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  80. package/dist/docx/serializer/documentSerializer.js +6 -16
  81. package/dist/docx/serializer/headerFooterSerializer.js +5 -0
  82. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  83. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  84. package/dist/docx/serializer/noteSerializer.js +5 -0
  85. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  86. package/dist/docx/serializer/paragraphSerializer.js +29 -35
  87. package/dist/docx/serializer/runSerializer.js +13 -7
  88. package/dist/docx/serializer/tableSerializer.js +28 -13
  89. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  90. package/dist/docx/server/build.js +8 -1
  91. package/dist/docx/server/createBilingualDocument.js +15 -22
  92. package/dist/docx/server/extractDocxText.js +3 -4
  93. package/dist/docx/shadingParser.d.ts +6 -0
  94. package/dist/docx/shadingParser.js +32 -0
  95. package/dist/docx/shapeParser.js +3 -3
  96. package/dist/docx/styleParser.js +15 -89
  97. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  98. package/dist/docx/styleReferenceResolution.js +51 -0
  99. package/dist/docx/tableLook.d.ts +57 -0
  100. package/dist/docx/tableLook.js +63 -0
  101. package/dist/docx/tableParser.d.ts +7 -9
  102. package/dist/docx/tableParser.js +64 -110
  103. package/dist/docx/textBoxParser.js +4 -4
  104. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  105. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  106. package/dist/docx/transitionalSpelling.d.ts +13 -2
  107. package/dist/docx/transitionalSpelling.js +23 -1
  108. package/dist/docx/verbatimCapture.js +4 -11
  109. package/dist/docx/vmlImageParser.js +2 -2
  110. package/dist/docx/watermarkParser.js +2 -2
  111. package/dist/docx/xmlParser.d.ts +22 -32
  112. package/dist/docx/xmlParser.js +36 -21
  113. package/dist/index.d.ts +2 -2
  114. package/dist/internal/pageBreakRunSourceDescendantIndex.d.ts +2 -0
  115. package/dist/internal/pageBreakRunSourceDescendantIndex.js +9 -6
  116. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  117. package/dist/internal/paragraphFormattingSerialization.js +26 -6
  118. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  119. package/dist/layout-bridge/convert/templatePreviewFlow.d.ts +19 -11
  120. package/dist/layout-bridge/convert/templatePreviewFlow.js +103 -38
  121. package/dist/layout-bridge/convert/toFlowBlocks.js +12 -3
  122. package/dist/layout-engine/index.d.ts +2 -2
  123. package/dist/layout-engine/index.js +2 -2
  124. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  125. package/dist/layout-engine/types.d.ts +8 -2
  126. package/dist/layout-engine/types.js +35 -2
  127. package/dist/markdown/index.js +1 -1
  128. package/dist/markdown/internals.d.ts +6 -1
  129. package/dist/markdown/internals.js +14 -1
  130. package/dist/markdown/renderBlock.js +35 -21
  131. package/dist/markdown/renderParagraph.js +14 -5
  132. package/dist/markdown/renderRuns.js +4 -3
  133. package/dist/markdown/renderTable.js +4 -3
  134. package/dist/markdown/trailers.js +41 -7
  135. package/dist/markdown/types.d.ts +3 -7
  136. package/dist/prosemirror/attrs/index.js +2 -5
  137. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  138. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  139. package/dist/prosemirror/commands/index.d.ts +3 -3
  140. package/dist/prosemirror/commands/index.js +2 -2
  141. package/dist/prosemirror/commands/pageBreak.js +12 -1
  142. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  143. package/dist/prosemirror/commands/paragraph.js +2 -2
  144. package/dist/prosemirror/commentIdAllocator.js +2 -7
  145. package/dist/prosemirror/conversion/fromProseDoc.js +131 -41
  146. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  147. package/dist/prosemirror/conversion/toProseDoc.js +402 -328
  148. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  149. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  150. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  151. package/dist/prosemirror/extensions/features/ListExtension.js +42 -4
  152. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  153. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  154. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  155. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  156. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  157. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  158. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  159. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  160. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  161. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  162. package/dist/prosemirror/extensions/types.d.ts +2 -2
  163. package/dist/prosemirror/index.d.ts +3 -3
  164. package/dist/prosemirror/index.js +3 -3
  165. package/dist/prosemirror/insertOperations.d.ts +9 -2
  166. package/dist/prosemirror/insertOperations.js +9 -4
  167. package/dist/prosemirror/listMarker.js +2 -1
  168. package/dist/prosemirror/numberedRefFields.js +2 -1
  169. package/dist/prosemirror/pageBreakRunProjection.d.ts +11 -3
  170. package/dist/prosemirror/pageBreakRunProjection.js +16 -8
  171. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +159 -0
  172. package/dist/prosemirror/paragraphFormattingProvenance.js +106 -0
  173. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  174. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  175. package/dist/prosemirror/plugins/index.d.ts +2 -2
  176. package/dist/prosemirror/plugins/index.js +2 -2
  177. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  178. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  179. package/dist/prosemirror/plugins/templatePreviewValues.d.ts +42 -1
  180. package/dist/prosemirror/plugins/templatePreviewValues.js +217 -14
  181. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  182. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  183. package/dist/prosemirror/schema/nodes.d.ts +31 -0
  184. package/dist/prosemirror/styles/resolvedStyleAttrs.js +4 -1
  185. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  186. package/dist/prosemirror/styles/styleResolver.js +15 -6
  187. package/dist/prosemirror/utils/visualLineNavigation.d.ts +22 -2
  188. package/dist/prosemirror/utils/visualLineNavigation.js +80 -63
  189. package/dist/style-engine/styleEngine.d.ts +4 -1
  190. package/dist/style-engine/styleEngine.js +3 -0
  191. package/dist/style-sets/extract.js +35 -11
  192. package/dist/style-sets/stellaStyle.js +46 -39
  193. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  194. package/dist/style-sets/styleSetNormalization.js +99 -0
  195. package/dist/types/content.d.ts +2 -2
  196. package/dist/utils/createDocument.js +145 -20
  197. package/dist/utils/headingCollector.d.ts +8 -5
  198. package/dist/utils/headingCollector.js +23 -25
  199. package/dist/utils/tableOfContentsStyle.js +9 -2
  200. package/package.json +3 -2
  201. package/dist/docx/textWhitespace.d.ts +0 -4
  202. package/dist/docx/textWhitespace.js +0 -4
  203. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  204. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  205. package/dist/markdown/headings.d.ts +0 -13
  206. package/dist/markdown/headings.js +0 -20
@@ -1,21 +1,24 @@
1
- import { isValidHexColor } from "../utils/colorResolver.js";
2
1
  import { isValidHexId } from "../utils/hexId.js";
3
2
  import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
3
+ import { parseBorderSpec } from "./borderParser.js";
4
4
  import { parseFieldType } from "./fieldParser.js";
5
5
  import { parseHyperlink as parseHyperlink$1, parseHyperlinkChild } from "./hyperlinkParser.js";
6
+ import { parseMarkupRangeMarker, parseMoveBookmarkMarker } from "./markupRangeMarker.js";
6
7
  import { markerFormattingFromLevel, numberingLevelHasMarkerSlot } from "./numberingParser.js";
8
+ import { isNumberingReference } from "./numberingReference.js";
7
9
  import { paraIdInRange } from "./paraIdRangeNormalization.js";
8
10
  import { assignParagraphPropertySource } from "./paragraphPropertySource.js";
9
- import { BorderStyleSchema, FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, TabLeaderSchema, TabStopAlignmentSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
11
+ import { FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, TabLeaderSchema, TabStopAlignmentSchema, narrowEnum } from "./parserEnums.js";
10
12
  import { consolidateParagraphContent } from "./runConsolidator.js";
11
13
  import { parseRun, parseRunProperties } from "./runParser.js";
12
14
  import { parseSdtProperties } from "./sdtProperties.js";
13
15
  import { parseSectionProperties } from "./sectionParser.js";
16
+ import { parseShading } from "./shadingParser.js";
14
17
  import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
15
18
  import { captureVerbatimXml } from "./verbatimCapture.js";
16
- import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildByNamespaceUri, findChildren, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
19
+ import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildByNamespaceUri, findChildren, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffAttribute, selectAlternateContentBranch } from "./xmlParser.js";
17
20
  import { panic } from "better-result";
18
- import { PARAGRAPH_MARK_CHANGE_KINDS, REVIEW_CARRIERS } from "@stll/docx-core/model";
21
+ import { BIDI_CONTROLS, PARAGRAPH_MARK_CHANGE_KINDS, REVIEW_CARRIERS } from "@stll/docx-core/model";
19
22
  //#region src/docx/paragraphParser.ts
20
23
  const FOLIO_REVIEW_HISTORY_NAMESPACES = /* @__PURE__ */ new Set(["urn:stella:folio:review-history:1"]);
21
24
  /**
@@ -35,63 +38,6 @@ function extractMathText(el) {
35
38
  return text;
36
39
  }
37
40
  /**
38
- * Parse color value from attributes
39
- */
40
- function parseColorValue(rgb, themeColor, themeTint, themeShade) {
41
- const color = {};
42
- if (rgb && rgb !== "auto") color.rgb = rgb;
43
- else if (rgb === "auto") color.auto = true;
44
- const validatedThemeColor = narrowEnum(themeColor, ThemeColorSlotSchema);
45
- if (validatedThemeColor) color.themeColor = validatedThemeColor;
46
- if (themeTint) color.themeTint = themeTint;
47
- if (themeShade) color.themeShade = themeShade;
48
- return color;
49
- }
50
- /**
51
- * Parse shading properties (w:shd)
52
- */
53
- function parseShadingProperties(shd) {
54
- if (!shd) return;
55
- const props = {};
56
- const color = getAttribute(shd, "w", "color");
57
- if (color && color !== "auto" && isValidHexColor(color)) props.color = { rgb: color };
58
- const fill = getAttribute(shd, "w", "fill");
59
- if (fill && fill !== "auto" && isValidHexColor(fill)) props.fill = { rgb: fill };
60
- const validatedThemeFill = narrowEnum(getAttribute(shd, "w", "themeFill"), ThemeColorSlotSchema);
61
- if (validatedThemeFill) {
62
- props.fill = props.fill || {};
63
- props.fill.themeColor = validatedThemeFill;
64
- }
65
- const themeFillTint = getAttribute(shd, "w", "themeFillTint");
66
- if (themeFillTint && props.fill) props.fill.themeTint = themeFillTint;
67
- const themeFillShade = getAttribute(shd, "w", "themeFillShade");
68
- if (themeFillShade && props.fill) props.fill.themeShade = themeFillShade;
69
- const pattern = narrowEnum(getAttribute(shd, "w", "val"), ShadingPatternSchema);
70
- if (pattern) props.pattern = pattern;
71
- return Object.keys(props).length > 0 ? props : void 0;
72
- }
73
- /**
74
- * Parse border specification (w:top, w:bottom, w:left, w:right, etc.)
75
- */
76
- function parseBorderSpec(border) {
77
- if (!border) return;
78
- const rawStyle = getAttribute(border, "w", "val");
79
- if (!rawStyle) return;
80
- const spec = { style: narrowEnum(rawStyle, BorderStyleSchema) ?? rawStyle };
81
- const colorVal = getAttribute(border, "w", "color");
82
- const themeColor = getAttribute(border, "w", "themeColor");
83
- if (colorVal || themeColor) spec.color = parseColorValue(colorVal, themeColor, getAttribute(border, "w", "themeTint"), getAttribute(border, "w", "themeShade"));
84
- const sz = parseNumericAttribute(border, "w", "sz");
85
- if (sz !== void 0) spec.size = sz;
86
- const space = parseNumericAttribute(border, "w", "space");
87
- if (space !== void 0) spec.space = space;
88
- const shadowAttr = getAttribute(border, "w", "shadow");
89
- if (shadowAttr) spec.shadow = shadowAttr === "1" || shadowAttr === "true";
90
- const frame = getAttribute(border, "w", "frame");
91
- if (frame) spec.frame = frame === "1" || frame === "true";
92
- return spec;
93
- }
94
- /**
95
41
  * Parse tab stops (w:tabs)
96
42
  */
97
43
  function parseTabStops(tabs) {
@@ -272,10 +218,10 @@ function parseParagraphProperties(pPr, theme, styles) {
272
218
  if (spacingExplicit.before || spacingExplicit.after) formatting.spacingExplicit = spacingExplicit;
273
219
  const lineRule = narrowEnum(getAttribute(spacing, "w", "lineRule"), LineSpacingRuleSchema);
274
220
  if (lineRule) formatting.lineSpacingRule = lineRule;
275
- const beforeAuto = getAttribute(spacing, "w", "beforeAutospacing");
276
- if (beforeAuto) formatting.beforeAutospacing = beforeAuto === "1" || beforeAuto === "true";
277
- const afterAuto = getAttribute(spacing, "w", "afterAutospacing");
278
- if (afterAuto) formatting.afterAutospacing = afterAuto === "1" || afterAuto === "true";
221
+ const beforeAutospacing = parseOnOffAttribute(spacing, "w", "beforeAutospacing");
222
+ if (beforeAutospacing !== void 0) formatting.beforeAutospacing = beforeAutospacing;
223
+ const afterAutospacing = parseOnOffAttribute(spacing, "w", "afterAutospacing");
224
+ if (afterAutospacing !== void 0) formatting.afterAutospacing = afterAutospacing;
279
225
  }
280
226
  const ind = propertyChildren.ind;
281
227
  if (ind) {
@@ -314,7 +260,7 @@ function parseParagraphProperties(pPr, theme, styles) {
314
260
  }
315
261
  const shd = propertyChildren.shd;
316
262
  if (shd) {
317
- const shadingResult = parseShadingProperties(shd);
263
+ const shadingResult = parseShading(shd);
318
264
  if (shadingResult !== void 0) formatting.shading = shadingResult;
319
265
  }
320
266
  const tabs = propertyChildren.tabs;
@@ -347,6 +293,8 @@ function parseParagraphProperties(pPr, theme, styles) {
347
293
  if (val !== void 0) formatting.numPr.ilvl = val;
348
294
  }
349
295
  }
296
+ const numberingChange = findChild(numPr, "w", "numberingChange");
297
+ if (numberingChange) formatting.numberingChangeXml = captureVerbatimXml(numberingChange);
350
298
  }
351
299
  const outlineLvl = propertyChildren.outlineLvl;
352
300
  if (outlineLvl) {
@@ -652,6 +600,27 @@ const hyperlinkRevisionWrapperType = (node) => {
652
600
  default: return;
653
601
  }
654
602
  };
603
+ const OMML_NAMESPACE = "http://schemas.openxmlformats.org/officeDocument/2006/math";
604
+ /**
605
+ * A bare OMML element as an inline equation, or nothing when the child is not one.
606
+ *
607
+ * `m:oMath` and `m:oMathPara` have branches of their own. This is for the rest
608
+ * of `m:EG_OMathMathElements` — `m:f`, `m:acc`, `m:rad` and their siblings —
609
+ * which the schema admits wherever `m:oMath` is admitted. They carry no
610
+ * structure the editable model holds, so they travel as the markup they
611
+ * arrived as, exactly like the equations that do have a wrapper.
612
+ */
613
+ const mathContentOf = (child) => {
614
+ if (getNamespaceUri(child) !== OMML_NAMESPACE) return;
615
+ const equation = {
616
+ type: "mathEquation",
617
+ display: "inline",
618
+ ommlXml: captureVerbatimXml(child)
619
+ };
620
+ const plainText = extractMathText(child);
621
+ if (plainText) equation.plainText = plainText;
622
+ return equation;
623
+ };
655
624
  const isHyperlinkChildContent = (content) => content.type === "run" || content.type === "bookmarkStart" || content.type === "bookmarkEnd";
656
625
  /**
657
626
  * A `w:hyperlink` as paragraph content, with any revision wrapper it holds
@@ -741,10 +710,8 @@ function parseSimpleField(node, styles, theme, rels, media, rootXmlns = {}) {
741
710
  fieldType: parseFieldType(instruction),
742
711
  content: []
743
712
  };
744
- const fldLock = getAttribute(node, "w", "fldLock");
745
- if (fldLock === "1" || fldLock === "true") field.fldLock = true;
746
- const dirty = getAttribute(node, "w", "dirty");
747
- if (dirty === "1" || dirty === "true") field.dirty = true;
713
+ if (parseOnOffAttribute(node, "w", "fldLock") === true) field.fldLock = true;
714
+ if (parseOnOffAttribute(node, "w", "dirty") === true) field.dirty = true;
748
715
  const inScopeXmlns = mergeXmlnsDeclarations(rootXmlns, node);
749
716
  const children = getChildElements(node);
750
717
  for (const child of children) {
@@ -985,57 +952,53 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
985
952
  contents.push(...inner);
986
953
  break;
987
954
  }
988
- case "moveFromRangeStart": {
989
- const id = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
990
- const name = getAttribute(child, "w", "name") ?? "";
955
+ case "moveFromRangeStart":
991
956
  contents.push({
992
957
  type: "moveFromRangeStart",
993
- id,
994
- name
958
+ ...parseMoveBookmarkMarker(child)
995
959
  });
996
960
  break;
997
- }
998
- case "moveFromRangeEnd": {
999
- const id = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
961
+ case "moveFromRangeEnd":
1000
962
  contents.push({
1001
963
  type: "moveFromRangeEnd",
1002
- id
964
+ ...parseMarkupRangeMarker(child)
1003
965
  });
1004
966
  break;
1005
- }
1006
- case "moveToRangeStart": {
1007
- const id = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
1008
- const name = getAttribute(child, "w", "name") ?? "";
967
+ case "moveToRangeStart":
1009
968
  contents.push({
1010
969
  type: "moveToRangeStart",
1011
- id,
1012
- name
970
+ ...parseMoveBookmarkMarker(child)
1013
971
  });
1014
972
  break;
1015
- }
1016
- case "moveToRangeEnd": {
1017
- const id = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
973
+ case "moveToRangeEnd":
1018
974
  contents.push({
1019
975
  type: "moveToRangeEnd",
1020
- id
976
+ ...parseMarkupRangeMarker(child)
1021
977
  });
1022
978
  break;
1023
- }
1024
- case "commentRangeStart": {
1025
- const commentId = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
979
+ case "commentRangeStart":
1026
980
  contents.push({
1027
981
  type: "commentRangeStart",
1028
- id: commentId
982
+ ...parseMarkupRangeMarker(child)
1029
983
  });
1030
984
  break;
1031
- }
1032
- case "commentRangeEnd": {
1033
- const commentId = Number.parseInt(getAttribute(child, "w", "id") ?? "0", 10);
985
+ case "commentRangeEnd":
1034
986
  contents.push({
1035
987
  type: "commentRangeEnd",
1036
- id: commentId
988
+ ...parseMarkupRangeMarker(child)
1037
989
  });
1038
990
  break;
991
+ case "bdo":
992
+ case "dir": {
993
+ const direction = getAttribute(child, "w", "val");
994
+ const wrapper = {
995
+ type: "bidiWrapper",
996
+ control: localName === "bdo" ? BIDI_CONTROLS.override : BIDI_CONTROLS.embedding,
997
+ content: parseParagraphContents(child, styles, theme, null, rels, media, trackedContext, inScopeXmlns)
998
+ };
999
+ if (direction === "ltr" || direction === "rtl") wrapper.direction = direction;
1000
+ contents.push(wrapper);
1001
+ break;
1039
1002
  }
1040
1003
  case "oMath":
1041
1004
  case "oMathPara": {
@@ -1051,7 +1014,11 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
1051
1014
  contents.push(mathEq);
1052
1015
  break;
1053
1016
  }
1054
- default: break;
1017
+ default: {
1018
+ const mathElement = mathContentOf(child);
1019
+ if (mathElement !== void 0) contents.push(mathElement);
1020
+ break;
1021
+ }
1055
1022
  }
1056
1023
  }
1057
1024
  if (inComplexField && afterSeparator) contents.push(...complexFieldResultRuns);
@@ -1114,7 +1081,7 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
1114
1081
  }
1115
1082
  if (effectiveNumPr && numbering) {
1116
1083
  const { numId, ilvl = 0 } = effectiveNumPr;
1117
- if (numId !== void 0 && numId !== 0) {
1084
+ if (isNumberingReference(numId)) {
1118
1085
  const level = numbering.getLevel(numId, ilvl);
1119
1086
  if (level) {
1120
1087
  const levelNumFmts = [];
@@ -1269,7 +1236,8 @@ const getParagraphContentText = (content) => {
1269
1236
  case "complexField": return content.fieldResult.map(getRunText).join("");
1270
1237
  case "inlineSdt": return content.content.map(getParagraphContentText).join("");
1271
1238
  case "insertion":
1272
- case "moveTo": return content.content.map(getParagraphContentText).join("");
1239
+ case "moveTo":
1240
+ case "bidiWrapper": return content.content.map(getParagraphContentText).join("");
1273
1241
  case "deletion":
1274
1242
  case "moveFrom": return "";
1275
1243
  case "mathEquation": return content.plainText ?? "";
@@ -1310,7 +1278,7 @@ function isEmptyParagraph(paragraph) {
1310
1278
  * @returns true if paragraph has numbering properties
1311
1279
  */
1312
1280
  function isListItem(paragraph) {
1313
- return paragraph.formatting?.numPr !== void 0 && paragraph.formatting.numPr.numId !== void 0 && paragraph.formatting.numPr.numId !== 0;
1281
+ return isNumberingReference(paragraph.formatting?.numPr?.numId);
1314
1282
  }
1315
1283
  /**
1316
1284
  * Get the list level of a paragraph (0-8)
@@ -265,6 +265,7 @@ const tableCellBlockTraversalByType = {
265
265
  table: "table"
266
266
  };
267
267
  const tableCellParagraphContentTraversalByType = {
268
+ bidiWrapper: "content",
268
269
  bookmarkEnd: "leaf",
269
270
  bookmarkStart: "leaf",
270
271
  commentRangeEnd: "leaf",
@@ -9,6 +9,42 @@ type DocxParagraphSurfaces = {
9
9
  };
10
10
  /** Visit every run directly owned by a paragraph's inline-content tree. */
11
11
  declare const visitParagraphRuns: (paragraph: document_d_exports.Paragraph, visit: (run: document_d_exports.Run) => void) => void;
12
+ /**
13
+ * One position in an inline-content array: the array itself, the index, and
14
+ * what sits there. A normaliser that drops or rewrites a marker needs the
15
+ * array and the index, not only the value.
16
+ */
17
+ type InlineContentSlot = {
18
+ content: document_d_exports.ParagraphContent[];
19
+ index: number;
20
+ item: document_d_exports.ParagraphContent;
21
+ };
22
+ /**
23
+ * Visit every inline-content position a paragraph owns, in document order,
24
+ * descending through every wrapper that is transparent to a range marker.
25
+ *
26
+ * The model validator walks the whole inline tree; a normaliser that walks
27
+ * only `paragraph.content` sees a different document from the one the
28
+ * validator judges, and a marker inside `w:ins`, `w:hyperlink`, `w:sdt`,
29
+ * `w:bdo` or `w:dir` then reaches the validator unnormalised. Both sides read
30
+ * the tree through this one traversal so they cannot disagree again.
31
+ *
32
+ * A complex field's runs are skipped: `fieldCode` and `fieldResult` hold runs
33
+ * only, and a run is not a marker position.
34
+ */
35
+ declare const visitInlineContentSlots: (paragraph: document_d_exports.Paragraph, visit: (slot: InlineContentSlot) => void) => void;
36
+ /**
37
+ * Positions marked for removal, per inline-content array.
38
+ *
39
+ * Removal shifts every later index in that array, so a normaliser records the
40
+ * positions while it reads and drops them once, after it has finished reading.
41
+ */
42
+ declare class InlineContentRemovals {
43
+ #private;
44
+ mark({ content, index }: Pick<InlineContentSlot, "content" | "index">): void;
45
+ /** Applies every marked removal and answers how many items were dropped. */
46
+ apply(): number;
47
+ }
12
48
  declare const visitDocxParagraphs: ({ documentBody, headers, footers, footnotes, endnotes }: DocxParagraphSurfaces, visit: (paragraph: document_d_exports.Paragraph) => void) => void;
13
49
  //#endregion
14
- export { DocxParagraphSurfaces, visitDocxParagraphs, visitParagraphRuns };
50
+ export { DocxParagraphSurfaces, InlineContentRemovals, InlineContentSlot, visitDocxParagraphs, visitInlineContentSlots, visitParagraphRuns };
@@ -1,3 +1,4 @@
1
+ import { panic } from "better-result";
1
2
  //#region src/docx/paragraphTraversal.ts
2
3
  /** Visit every run directly owned by a paragraph's inline-content tree. */
3
4
  const visitParagraphRuns = (paragraph, visit) => {
@@ -15,6 +16,7 @@ const visitParagraphRuns = (paragraph, visit) => {
15
16
  case "moveFrom":
16
17
  case "moveTo":
17
18
  case "inlineSdt":
19
+ case "bidiWrapper":
18
20
  for (const child of content.content) visitParagraphContent(child);
19
21
  return;
20
22
  case "complexField":
@@ -36,6 +38,87 @@ const visitParagraphRuns = (paragraph, visit) => {
36
38
  };
37
39
  for (const content of paragraph.content) visitParagraphContent(content);
38
40
  };
41
+ /**
42
+ * Visit every inline-content position a paragraph owns, in document order,
43
+ * descending through every wrapper that is transparent to a range marker.
44
+ *
45
+ * The model validator walks the whole inline tree; a normaliser that walks
46
+ * only `paragraph.content` sees a different document from the one the
47
+ * validator judges, and a marker inside `w:ins`, `w:hyperlink`, `w:sdt`,
48
+ * `w:bdo` or `w:dir` then reaches the validator unnormalised. Both sides read
49
+ * the tree through this one traversal so they cannot disagree again.
50
+ *
51
+ * A complex field's runs are skipped: `fieldCode` and `fieldResult` hold runs
52
+ * only, and a run is not a marker position.
53
+ */
54
+ const visitInlineContentSlots = (paragraph, visit) => {
55
+ const visitContent = (content) => {
56
+ for (const [index, item] of content.entries()) {
57
+ visit({
58
+ content,
59
+ index,
60
+ item
61
+ });
62
+ switch (item.type) {
63
+ case "hyperlink":
64
+ visitContent(item.children);
65
+ break;
66
+ case "simpleField":
67
+ case "inlineSdt":
68
+ case "insertion":
69
+ case "deletion":
70
+ case "moveFrom":
71
+ case "moveTo":
72
+ case "bidiWrapper":
73
+ visitContent(item.content);
74
+ break;
75
+ case "run":
76
+ case "complexField":
77
+ case "bookmarkStart":
78
+ case "bookmarkEnd":
79
+ case "commentRangeStart":
80
+ case "commentRangeEnd":
81
+ case "commentReference":
82
+ case "moveFromRangeStart":
83
+ case "moveFromRangeEnd":
84
+ case "moveToRangeStart":
85
+ case "moveToRangeEnd":
86
+ case "mathEquation": break;
87
+ default: panic(`Unsupported paragraph content: ${JSON.stringify(item)}`);
88
+ }
89
+ }
90
+ };
91
+ visitContent(paragraph.content);
92
+ };
93
+ /**
94
+ * Positions marked for removal, per inline-content array.
95
+ *
96
+ * Removal shifts every later index in that array, so a normaliser records the
97
+ * positions while it reads and drops them once, after it has finished reading.
98
+ */
99
+ var InlineContentRemovals = class {
100
+ #byContent = /* @__PURE__ */ new Map();
101
+ mark({ content, index }) {
102
+ const indexes = this.#byContent.get(content);
103
+ if (indexes) {
104
+ indexes.add(index);
105
+ return;
106
+ }
107
+ this.#byContent.set(content, /* @__PURE__ */ new Set([index]));
108
+ }
109
+ /** Applies every marked removal and answers how many items were dropped. */
110
+ apply() {
111
+ let removed = 0;
112
+ for (const [content, indexes] of this.#byContent) {
113
+ if (indexes.size === 0) continue;
114
+ const next = content.filter((_, index) => !indexes.has(index));
115
+ removed += content.length - next.length;
116
+ content.length = 0;
117
+ content.push(...next);
118
+ }
119
+ return removed;
120
+ }
121
+ };
39
122
  const visitDocxParagraphs = ({ documentBody, headers, footers, footnotes, endnotes }, visit) => {
40
123
  const seenParagraphs = /* @__PURE__ */ new WeakSet();
41
124
  const visitParagraph = (paragraph) => {
@@ -76,4 +159,4 @@ const visitDocxParagraphs = ({ documentBody, headers, footers, footnotes, endnot
76
159
  for (const comment of documentBody.comments ?? []) for (const paragraph of comment.content) visitParagraph(paragraph);
77
160
  };
78
161
  //#endregion
79
- export { visitDocxParagraphs, visitParagraphRuns };
162
+ export { InlineContentRemovals, visitDocxParagraphs, visitInlineContentSlots, visitParagraphRuns };
@@ -0,0 +1,37 @@
1
+ import { ParseWarning, ParseWarningCode } from "@stll/docx-core/model";
2
+ //#region src/docx/parseContext.d.ts
3
+ /** What a call site states; the collector supplies the rest. */
4
+ type ParseWarningReport = {
5
+ code: ParseWarningCode;
6
+ /** The element as written, prefix included. */
7
+ element?: string;
8
+ /** The best position this part can name, e.g. `style "Heading1"`. */
9
+ at?: string;
10
+ /** The value folio declined to read, as written. */
11
+ value?: string;
12
+ /** Text passed through from another owner, for the pass-through codes. */
13
+ detail?: string;
14
+ /** How many occurrences this one report stands for; defaults to 1. */
15
+ count?: number;
16
+ };
17
+ type ParseContext = {
18
+ /** Record one normalisation applied to input folio accepted. */
19
+ warn: (report: ParseWarningReport) => void;
20
+ /** The same collector, reporting against another part or position. */
21
+ scoped: (location: {
22
+ part?: string;
23
+ at?: string;
24
+ }) => ParseContext;
25
+ };
26
+ type ParseWarningCollector = {
27
+ context: ParseContext;
28
+ /**
29
+ * Everything recorded, in the order it was recorded, followed by one entry
30
+ * per code whose occurrences ran past the cap. Deterministic: the same
31
+ * document yields the same list.
32
+ */
33
+ warnings: () => ParseWarning[];
34
+ };
35
+ declare const createParseWarningCollector: (part?: string) => ParseWarningCollector;
36
+ //#endregion
37
+ export { ParseContext, ParseWarningCollector, ParseWarningReport, createParseWarningCollector };
@@ -0,0 +1,67 @@
1
+ import { MAX_RETAINED_PARSE_WARNINGS_PER_CODE } from "@stll/docx-core/model";
2
+ //#region src/docx/parseContext.ts
3
+ /**
4
+ * The channel a parser reports a normalisation through.
5
+ *
6
+ * Folio accepts input Word accepts, which means normalising at the parse
7
+ * boundary rather than refusing. Every such decision has to be visible, and
8
+ * before this the only place that could say so was `parseDocx` itself: the
9
+ * leaf readers are pure functions with no way to report, so a value outside
10
+ * `ST_OnOff` or a `w:type` outside `ST_HdrFtr` was normalised in silence.
11
+ *
12
+ * The context is an explicit parameter, never a module-level accumulator and
13
+ * never async-local storage. Parsers stay re-entrant, a test can hand one in
14
+ * and read what a single reader reported, and two documents parsed at once
15
+ * cannot write into each other's list. The cost is a threaded argument, which
16
+ * is also the thing that makes the reporting greppable.
17
+ */
18
+ const PACKAGE_PART = "package";
19
+ const createParseWarningCollector = (part = PACKAGE_PART) => {
20
+ const retained = [];
21
+ const retainedByCode = /* @__PURE__ */ new Map();
22
+ const suppressedByCode = /* @__PURE__ */ new Map();
23
+ const record = (location, report) => {
24
+ const count = report.count ?? 1;
25
+ const kept = retainedByCode.get(report.code) ?? 0;
26
+ if (kept >= MAX_RETAINED_PARSE_WARNINGS_PER_CODE) {
27
+ suppressedByCode.set(report.code, (suppressedByCode.get(report.code) ?? 0) + count);
28
+ return;
29
+ }
30
+ retainedByCode.set(report.code, kept + 1);
31
+ retained.push({
32
+ code: report.code,
33
+ location: {
34
+ ...location,
35
+ ...report.element === void 0 ? {} : { element: report.element },
36
+ ...report.at === void 0 ? {} : { at: report.at }
37
+ },
38
+ ...report.value === void 0 ? {} : { value: report.value },
39
+ ...report.detail === void 0 ? {} : { detail: report.detail },
40
+ count
41
+ });
42
+ };
43
+ const contextAt = (location) => ({
44
+ warn: (report) => {
45
+ record(location, report);
46
+ },
47
+ scoped: (next) => {
48
+ const at = next.at ?? location.at;
49
+ return contextAt({
50
+ part: next.part ?? location.part,
51
+ ...location.element === void 0 ? {} : { element: location.element },
52
+ ...at === void 0 ? {} : { at }
53
+ });
54
+ }
55
+ });
56
+ return {
57
+ context: contextAt({ part }),
58
+ warnings: () => [...retained, ...[...suppressedByCode.entries()].sort(([left], [right]) => left < right ? -1 : 1).map(([code, count]) => ({
59
+ code,
60
+ location: { part },
61
+ count,
62
+ detail: `${String(count)} further occurrence(s) were counted but not retained.`
63
+ }))]
64
+ };
65
+ };
66
+ //#endregion
67
+ export { createParseWarningCollector };
@@ -0,0 +1,6 @@
1
+ import { ParseWarning } from "@stll/docx-core/model";
2
+ //#region src/docx/parseWarningMessage.d.ts
3
+ declare const formatParseWarning: (warning: ParseWarning) => string;
4
+ declare const formatParseWarnings: (warnings: readonly ParseWarning[]) => string[];
5
+ //#endregion
6
+ export { formatParseWarning, formatParseWarnings };
@@ -0,0 +1,44 @@
1
+ import { PARSE_WARNING_CODES } from "@stll/docx-core/model";
2
+ //#region src/docx/parseWarningMessage.ts
3
+ /**
4
+ * The one place a parse warning becomes a sentence.
5
+ *
6
+ * `Document.warnings` is the string list hosts have always read, and it is now
7
+ * rendered from `Document.parseWarnings` rather than written at the call site,
8
+ * so a warning cannot say one thing in the structured list and another in the
9
+ * prose. The map is total over the code union: a new code does not compile
10
+ * until it has a message.
11
+ */
12
+ const plural = (count, singular, pluralForm = `${singular}s`) => `${String(count)} ${count === 1 ? singular : pluralForm}`;
13
+ /** The location suffix, omitted when the part is all we know. */
14
+ const where = ({ location }) => {
15
+ if (location.at === void 0) return "";
16
+ return ` at ${location.at} in ${location.part}`;
17
+ };
18
+ const quoted = (value) => value === void 0 ? "" : ` "${value}"`;
19
+ const PARSE_WARNING_MESSAGES = {
20
+ [PARSE_WARNING_CODES.packageDecrypted]: () => "Document was opened from password-protected storage; saving writes an unencrypted .docx file.",
21
+ [PARSE_WARNING_CODES.packageArchive]: (warning) => warning.detail ?? "The archive reader reported an issue.",
22
+ [PARSE_WARNING_CODES.documentPartMissing]: () => "No document.xml found in DOCX",
23
+ [PARSE_WARNING_CODES.documentModelIssue]: (warning) => warning.detail ?? "The document model reported an issue.",
24
+ [PARSE_WARNING_CODES.duplicateCommentId]: (warning) => `Dropped ${plural(warning.count, "comment")} repeating a w:id another comment already defines.`,
25
+ [PARSE_WARNING_CODES.missingCommentId]: (warning) => `Dropped ${plural(warning.count, "comment")} with no readable w:id.`,
26
+ [PARSE_WARNING_CODES.duplicateNoteId]: (warning) => `Dropped ${plural(warning.count, "note")} repeating a w:id another note already defines.`,
27
+ [PARSE_WARNING_CODES.danglingCommentReference]: (warning) => `Removed ${plural(warning.count, "dangling comment reference marker")} whose comments.xml entries are missing.`,
28
+ [PARSE_WARNING_CODES.unbalancedCommentRange]: (warning) => `Re-anchored ${plural(warning.count, "unbalanced comment range marker")} as point comments.`,
29
+ [PARSE_WARNING_CODES.danglingHeaderReference]: (warning) => `Removed ${plural(warning.count, "dangling header reference")} whose header parts are missing.`,
30
+ [PARSE_WARNING_CODES.danglingFooterReference]: (warning) => `Removed ${plural(warning.count, "dangling footer reference")} whose footer parts are missing.`,
31
+ [PARSE_WARNING_CODES.danglingRelationshipId]: (warning) => `Left relationship id${quoted(warning.value)} unresolved${where(warning)}; the part defines no such relationship.`,
32
+ [PARSE_WARNING_CODES.unnumberedParagraph]: (warning) => `Unnumbered ${plural(warning.count, "paragraph")} whose numbering definitions are missing.`,
33
+ [PARSE_WARNING_CODES.unnumberedStyle]: (warning) => `Unnumbered style${quoted(warning.value)} whose numbering definition is missing.`,
34
+ [PARSE_WARNING_CODES.unbalancedMoveRange]: (warning) => `Removed ${plural(warning.count, "unbalanced tracked move range marker")}.`,
35
+ [PARSE_WARNING_CODES.headerFooterTypeOutsideEnum]: (warning) => `Read header/footer type${quoted(warning.value)} as "default"${where(warning)}; ST_HdrFtr is even, default or first.`,
36
+ [PARSE_WARNING_CODES.unrecognisedOnOffValue]: (warning) => `Ignored on/off value${quoted(warning.value)}${where(warning)}; ST_OnOff is 1, 0, true, false, on or off.`,
37
+ [PARSE_WARNING_CODES.borderWithoutValue]: (warning) => `Read a border with no w:val${where(warning)} as having no border style.`,
38
+ [PARSE_WARNING_CODES.styleSetDuplicateStyleId]: (warning) => `Dropped a style repeating the id${quoted(warning.value)} another style in the set already defines.`,
39
+ [PARSE_WARNING_CODES.styleSetInitialStyleMissing]: (warning) => `The style set names initial paragraph style${quoted(warning.value)}, which it does not contain; used the set's default instead${where(warning)}.`
40
+ };
41
+ const formatParseWarning = (warning) => PARSE_WARNING_MESSAGES[warning.code](warning);
42
+ const formatParseWarnings = (warnings) => warnings.map(formatParseWarning);
43
+ //#endregion
44
+ export { formatParseWarning, formatParseWarnings };