@stll/folio-core 0.43.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +7 -5
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/ai-edits/snapshot.js +13 -9
  6. package/dist/compare/content-alignment.js +94 -54
  7. package/dist/compare/inline-atoms.js +34 -20
  8. package/dist/compare/style-resources.js +6 -0
  9. package/dist/content-controls/mutateContentControls.js +4 -2
  10. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  11. package/dist/document-operations.js +14 -3
  12. package/dist/docx/appVersionNormalization.d.ts +0 -18
  13. package/dist/docx/blockContentParser.js +8 -0
  14. package/dist/docx/blockRangeMarkers.d.ts +36 -0
  15. package/dist/docx/blockRangeMarkers.js +59 -0
  16. package/dist/docx/bookmarkParser.d.ts +2 -20
  17. package/dist/docx/bookmarkParser.js +6 -30
  18. package/dist/docx/borderParser.d.ts +13 -0
  19. package/dist/docx/borderParser.js +71 -0
  20. package/dist/docx/builtInStyles.d.ts +165 -0
  21. package/dist/docx/builtInStyles.js +239 -0
  22. package/dist/docx/commentIdNormalization.d.ts +3 -1
  23. package/dist/docx/commentIdNormalization.js +18 -1
  24. package/dist/docx/commentParser.d.ts +2 -1
  25. package/dist/docx/commentParser.js +80 -42
  26. package/dist/docx/commentReferenceNormalization.d.ts +4 -1
  27. package/dist/docx/commentReferenceNormalization.js +23 -14
  28. package/dist/docx/commentThreadKey.d.ts +18 -0
  29. package/dist/docx/commentThreadKey.js +22 -0
  30. package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
  31. package/dist/docx/danglingRelationshipReferences.js +30 -0
  32. package/dist/docx/defaultParagraphStyle.d.ts +18 -1
  33. package/dist/docx/defaultParagraphStyle.js +23 -1
  34. package/dist/docx/diagramPreview.js +87 -27
  35. package/dist/docx/documentParser.d.ts +2 -1
  36. package/dist/docx/documentParser.js +2 -2
  37. package/dist/docx/drawingUtils.d.ts +8 -1
  38. package/dist/docx/drawingUtils.js +12 -3
  39. package/dist/docx/fieldParser.js +3 -5
  40. package/dist/docx/footnoteParser.d.ts +3 -2
  41. package/dist/docx/footnoteParser.js +19 -4
  42. package/dist/docx/groupDrawingParser.js +4 -4
  43. package/dist/docx/headerFooterRefParser.d.ts +4 -3
  44. package/dist/docx/headerFooterRefParser.js +42 -12
  45. package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
  46. package/dist/docx/headerFooterReferenceNormalization.js +5 -1
  47. package/dist/docx/hyperlinkParser.js +13 -17
  48. package/dist/docx/imageParser.d.ts +10 -2
  49. package/dist/docx/imageParser.js +80 -30
  50. package/dist/docx/imageRawXml.d.ts +14 -1
  51. package/dist/docx/imageRawXml.js +35 -11
  52. package/dist/docx/markupRangeMarker.d.ts +15 -0
  53. package/dist/docx/markupRangeMarker.js +44 -0
  54. package/dist/docx/mathToMathml.js +12 -14
  55. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  56. package/dist/docx/nonVisualDrawingProps.js +46 -0
  57. package/dist/docx/noteReferenceStyles.d.ts +29 -0
  58. package/dist/docx/noteReferenceStyles.js +70 -0
  59. package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
  60. package/dist/docx/numberingReferenceNormalization.js +20 -1
  61. package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
  62. package/dist/docx/paragraphParser.js +66 -99
  63. package/dist/docx/paragraphPropertySource.js +1 -0
  64. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  65. package/dist/docx/paragraphTraversal.d.ts +37 -1
  66. package/dist/docx/paragraphTraversal.js +84 -1
  67. package/dist/docx/parseContext.d.ts +37 -0
  68. package/dist/docx/parseContext.js +67 -0
  69. package/dist/docx/parseWarningMessage.d.ts +6 -0
  70. package/dist/docx/parseWarningMessage.js +44 -0
  71. package/dist/docx/parser.js +83 -29
  72. package/dist/docx/previewBudget.d.ts +64 -0
  73. package/dist/docx/previewBudget.js +88 -0
  74. package/dist/docx/relsParser.d.ts +28 -11
  75. package/dist/docx/relsParser.js +26 -13
  76. package/dist/docx/revisionIdNormalization.js +96 -10
  77. package/dist/docx/rezip.js +80 -40
  78. package/dist/docx/runConsolidator.js +1 -2
  79. package/dist/docx/runParser.d.ts +8 -1
  80. package/dist/docx/runParser.js +30 -48
  81. package/dist/docx/sdtPropertiesPatch.js +24 -18
  82. package/dist/docx/sectionParser.d.ts +2 -1
  83. package/dist/docx/sectionParser.js +21 -65
  84. package/dist/docx/sectionReferenceHistory.js +2 -2
  85. package/dist/docx/selectiveSave.js +6 -6
  86. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  87. package/dist/docx/serializer/borderSerializer.d.ts +2 -3
  88. package/dist/docx/serializer/borderSerializer.js +13 -12
  89. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  90. package/dist/docx/serializer/commentSerializer.js +82 -72
  91. package/dist/docx/serializer/documentSerializer.d.ts +1 -5
  92. package/dist/docx/serializer/documentSerializer.js +6 -16
  93. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  94. package/dist/docx/serializer/headerFooterSerializer.js +10 -5
  95. package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
  96. package/dist/docx/serializer/markupRangeAttributes.js +24 -0
  97. package/dist/docx/serializer/noteSerializer.js +5 -0
  98. package/dist/docx/serializer/numberingSerializer.js +7 -6
  99. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
  100. package/dist/docx/serializer/paragraphSerializer.js +47 -52
  101. package/dist/docx/serializer/partNamespaces.js +2 -2
  102. package/dist/docx/serializer/runSerializer.js +57 -31
  103. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  104. package/dist/docx/serializer/settingsSerializer.js +4 -3
  105. package/dist/docx/serializer/stylesSerializer.js +6 -6
  106. package/dist/docx/serializer/tableSerializer.js +37 -21
  107. package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
  108. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  109. package/dist/docx/serializer/themeSerializer.js +6 -6
  110. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  111. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  112. package/dist/docx/serializer/xmlUtils.js +1 -13
  113. package/dist/docx/server/boundedArchive.d.ts +12 -0
  114. package/dist/docx/server/boundedArchive.js +20 -1
  115. package/dist/docx/server/build.js +8 -1
  116. package/dist/docx/server/createBilingualDocument.js +10 -18
  117. package/dist/docx/server/extractDocxText.js +3 -4
  118. package/dist/docx/server/validateDocxConformance.js +22 -1
  119. package/dist/docx/shadingParser.d.ts +6 -0
  120. package/dist/docx/shadingParser.js +32 -0
  121. package/dist/docx/shapeParser.js +10 -8
  122. package/dist/docx/styleParser.js +13 -87
  123. package/dist/docx/styleReferenceResolution.d.ts +36 -0
  124. package/dist/docx/styleReferenceResolution.js +51 -0
  125. package/dist/docx/tableLook.d.ts +57 -0
  126. package/dist/docx/tableLook.js +63 -0
  127. package/dist/docx/tableParser.d.ts +7 -9
  128. package/dist/docx/tableParser.js +64 -110
  129. package/dist/docx/textBoxParser.js +11 -6
  130. package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
  131. package/dist/docx/trackedMoveRangeNormalization.js +11 -21
  132. package/dist/docx/transitionalSpelling.d.ts +13 -2
  133. package/dist/docx/transitionalSpelling.js +23 -1
  134. package/dist/docx/unzip.d.ts +23 -0
  135. package/dist/docx/unzip.js +32 -22
  136. package/dist/docx/verbatimCapture.js +5 -12
  137. package/dist/docx/vmlImageParser.js +5 -4
  138. package/dist/docx/vmlPreview.d.ts +1 -3
  139. package/dist/docx/vmlPreview.js +2 -30
  140. package/dist/docx/watermarkParser.js +2 -2
  141. package/dist/docx/xmlParser.d.ts +38 -33
  142. package/dist/docx/xmlParser.js +92 -47
  143. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  144. package/dist/docx/xmlResourceLimits.js +105 -24
  145. package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
  146. package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
  147. package/dist/internal/paragraphFormattingSerialization.js +29 -8
  148. package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
  149. package/dist/layout-engine/index.d.ts +2 -2
  150. package/dist/layout-engine/index.js +2 -2
  151. package/dist/layout-engine/measure/measureBlocks.js +1 -6
  152. package/dist/layout-engine/types.d.ts +8 -2
  153. package/dist/layout-engine/types.js +35 -2
  154. package/dist/layout-painter/renderImage.js +4 -3
  155. package/dist/layout-painter/renderParagraph.js +4 -3
  156. package/dist/managers/autoSaveCodec.js +2 -8
  157. package/dist/markdown/images.js +1 -4
  158. package/dist/markdown/index.js +1 -1
  159. package/dist/markdown/internals.d.ts +6 -1
  160. package/dist/markdown/internals.js +14 -1
  161. package/dist/markdown/renderBlock.js +35 -21
  162. package/dist/markdown/renderParagraph.js +14 -5
  163. package/dist/markdown/renderRuns.js +4 -3
  164. package/dist/markdown/renderTable.js +4 -3
  165. package/dist/markdown/trailers.js +41 -7
  166. package/dist/markdown/types.d.ts +3 -7
  167. package/dist/prosemirror/attrs/index.js +71 -5
  168. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
  169. package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
  170. package/dist/prosemirror/commands/image.js +1 -0
  171. package/dist/prosemirror/commands/index.d.ts +3 -3
  172. package/dist/prosemirror/commands/index.js +2 -2
  173. package/dist/prosemirror/commands/paragraph.d.ts +3 -3
  174. package/dist/prosemirror/commands/paragraph.js +2 -2
  175. package/dist/prosemirror/commentIdAllocator.js +2 -7
  176. package/dist/prosemirror/conversion/fromProseDoc.js +197 -68
  177. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
  178. package/dist/prosemirror/conversion/toProseDoc.js +458 -335
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
  180. package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
  181. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
  182. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  183. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
  184. package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
  185. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
  186. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  187. package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
  188. package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
  189. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
  190. package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -1
  191. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -2
  192. package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
  193. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  194. package/dist/prosemirror/extensions/types.d.ts +2 -2
  195. package/dist/prosemirror/index.d.ts +3 -3
  196. package/dist/prosemirror/index.js +3 -3
  197. package/dist/prosemirror/insertOperations.d.ts +9 -2
  198. package/dist/prosemirror/insertOperations.js +9 -4
  199. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +162 -0
  200. package/dist/prosemirror/paragraphFormattingProvenance.js +115 -0
  201. package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
  202. package/dist/prosemirror/plugins/documentStyles.js +11 -1
  203. package/dist/prosemirror/plugins/index.d.ts +2 -2
  204. package/dist/prosemirror/plugins/index.js +2 -2
  205. package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
  206. package/dist/prosemirror/plugins/revisionIds.js +21 -6
  207. package/dist/prosemirror/runFormattingReconciliation.js +3 -2
  208. package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
  209. package/dist/prosemirror/schema/nodes.d.ts +81 -1
  210. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  211. package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
  212. package/dist/prosemirror/styles/styleResolver.js +12 -0
  213. package/dist/style-engine/styleEngine.d.ts +3 -0
  214. package/dist/style-engine/styleEngine.js +3 -0
  215. package/dist/style-sets/extract.js +1 -23
  216. package/dist/style-sets/stellaStyle.js +46 -39
  217. package/dist/style-sets/styleSetNormalization.d.ts +19 -0
  218. package/dist/style-sets/styleSetNormalization.js +99 -0
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/base64.d.ts +36 -0
  221. package/dist/utils/base64.js +40 -0
  222. package/dist/utils/clipboard.js +2 -1
  223. package/dist/utils/createDocument.js +145 -20
  224. package/dist/utils/headingCollector.d.ts +8 -5
  225. package/dist/utils/headingCollector.js +23 -25
  226. package/dist/utils/tableOfContentsStyle.js +9 -2
  227. package/dist/utils/units.d.ts +10 -1
  228. package/dist/utils/units.js +12 -1
  229. package/dist/utils/urlSecurity.d.ts +8 -2
  230. package/dist/utils/urlSecurity.js +21 -3
  231. package/package.json +2 -2
  232. package/dist/docx/textWhitespace.d.ts +0 -4
  233. package/dist/docx/textWhitespace.js +0 -4
  234. package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
  235. package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
  236. package/dist/markdown/headings.d.ts +0 -13
  237. package/dist/markdown/headings.js +0 -20
@@ -1,6 +1,7 @@
1
- import { sanitizeExternalUrl, sanitizeLinkTarget } from "../utils/urlSecurity.js";
1
+ import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
2
+ import { RELATIONSHIP_TYPES, resolveRelationshipIdOfType } from "./relsParser.js";
2
3
  import { parseRun } from "./runParser.js";
3
- import { getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseNumericAttribute } from "./xmlParser.js";
4
+ import { getAttribute, getChildElements, getLocalName, mergeXmlnsDeclarations, parseNumericAttribute, parseOnOffAttribute } from "./xmlParser.js";
4
5
  //#region src/docx/hyperlinkParser.ts
5
6
  /**
6
7
  * Parse bookmark start (w:bookmarkStart)
@@ -49,11 +50,9 @@ function parseHyperlink(node, rels, styles = null, theme = null, media = null, r
49
50
  const rId = getAttribute(node, "r", "id");
50
51
  if (rId) {
51
52
  hyperlink.rId = rId;
52
- if (rels) {
53
- const rel = rels.get(rId);
54
- if (rel) {
55
- if (sanitizeExternalUrl(rel.target)) hyperlink.href = rel.target;
56
- }
53
+ const resolved = resolveRelationshipIdOfType(rels, rId, RELATIONSHIP_TYPES.hyperlink);
54
+ if (resolved.status === "resolved") {
55
+ if (sanitizeExternalUrl(resolved.relationship.target)) hyperlink.href = resolved.relationship.target;
57
56
  }
58
57
  }
59
58
  const anchor = getAttribute(node, "w", "anchor");
@@ -64,9 +63,8 @@ function parseHyperlink(node, rels, styles = null, theme = null, media = null, r
64
63
  const tooltip = getAttribute(node, "w", "tooltip");
65
64
  if (tooltip) hyperlink.tooltip = tooltip;
66
65
  const tgtFrame = getAttribute(node, "w", "tgtFrame");
67
- if (tgtFrame) hyperlink.target = sanitizeLinkTarget(tgtFrame);
68
- const history = getAttribute(node, "w", "history");
69
- if (history === "1" || history === "true") hyperlink.history = true;
66
+ if (tgtFrame) hyperlink.target = tgtFrame;
67
+ if (parseOnOffAttribute(node, "w", "history") === true) hyperlink.history = true;
70
68
  const docLocation = getAttribute(node, "w", "docLocation");
71
69
  if (docLocation) hyperlink.docLocation = docLocation;
72
70
  const inScopeXmlns = mergeXmlnsDeclarations(rootXmlns, node);
@@ -168,13 +166,11 @@ function getHyperlinkRuns(hyperlink) {
168
166
  * @returns The resolved URL or undefined
169
167
  */
170
168
  function resolveHyperlinkUrl(hyperlink, rels) {
171
- if (hyperlink.rId) {
172
- const rel = rels.get(hyperlink.rId);
173
- if (rel) {
174
- if (sanitizeExternalUrl(rel.target)) {
175
- hyperlink.href = rel.target;
176
- return hyperlink.href;
177
- }
169
+ const resolved = resolveRelationshipIdOfType(rels, hyperlink.rId, RELATIONSHIP_TYPES.hyperlink);
170
+ if (resolved.status === "resolved") {
171
+ if (sanitizeExternalUrl(resolved.relationship.target)) {
172
+ hyperlink.href = resolved.relationship.target;
173
+ return hyperlink.href;
178
174
  }
179
175
  }
180
176
  if (hyperlink.anchor && !hyperlink.href) {
@@ -1,6 +1,14 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { XmlElement } from "./xmlParser.js";
3
3
  //#region src/docx/imageParser.d.ts
4
+ /**
5
+ * The `a:ext` uri under which Word records that a drawing is decorative. The
6
+ * extension holds `<adec:decorative val="…"/>`; `CT_NonVisualDrawingProps` has
7
+ * no `@decorative` attribute for it to be written as.
8
+ */
9
+ declare const DECORATIVE_EXTENSION_URI = "{C183D7F6-B498-43B3-948B-1728B52AA6E4}";
10
+ /** The namespace the decorative extension's element is bound to. */
11
+ declare const DECORATIVE_NAMESPACE = "http://schemas.microsoft.com/office/drawing/2017/decorative";
4
12
  /**
5
13
  * Resolve image data from relationships and media map
6
14
  *
@@ -9,7 +17,7 @@ import { XmlElement } from "./xmlParser.js";
9
17
  * @param media - Media files map
10
18
  * @returns Object with src (data URL or blob), mimeType, and filename
11
19
  */
12
- declare function resolveImageData(rId: string, rels: document_d_exports.RelationshipMap | undefined, media: Map<string, document_d_exports.MediaFile> | undefined): {
20
+ declare function resolveImageData(rId: string | undefined, rels: document_d_exports.RelationshipMap | undefined, media: Map<string, document_d_exports.MediaFile> | undefined): {
13
21
  src?: string;
14
22
  mimeType?: string;
15
23
  filename?: string;
@@ -89,4 +97,4 @@ declare function getWrapDistancesPx(image: document_d_exports.Image): {
89
97
  */
90
98
  declare function needsTextWrapping(image: document_d_exports.Image): boolean;
91
99
  //#endregion
92
- export { getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
100
+ export { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE, getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
@@ -1,11 +1,13 @@
1
1
  import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
2
2
  import { emuToPixels } from "../utils/units.js";
3
3
  import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
4
- import { WRAP_ELEMENT_NAMES, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
4
+ import { WRAP_ELEMENT_NAMES, parseAnchorBehindDoc, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
5
5
  import { parseGraphicFrameLocks } from "./graphicFrameLocks.js";
6
- import { resolveTarget } from "./relsParser.js";
6
+ import { parseNonVisualDrawingNames } from "./nonVisualDrawingProps.js";
7
+ import { RELATIONSHIP_TYPES, resolveRelationshipIdOfType } from "./relsParser.js";
7
8
  import { isTextBoxDrawing } from "./textBoxParser.js";
8
- import { findByFullName, findChild, getAttribute, getChildElements, parseNumericAttribute, parseOnOffValue } from "./xmlParser.js";
9
+ import { captureVerbatimXml } from "./verbatimCapture.js";
10
+ import { findByFullName, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getChildElements, getLocalName, getNamespaceUri, parseNumericAttribute, parseOnOffAttribute, parseOnOffValue } from "./xmlParser.js";
9
11
  //#region src/docx/imageParser.ts
10
12
  /**
11
13
  * Convert rotation value (1/60000 of a degree) to degrees
@@ -64,6 +66,47 @@ function parseEffectExtent(effectExtent) {
64
66
  };
65
67
  }
66
68
  /**
69
+ * The `a:ext` uri under which Word records that a drawing is decorative. The
70
+ * extension holds `<adec:decorative val="…"/>`; `CT_NonVisualDrawingProps` has
71
+ * no `@decorative` attribute for it to be written as.
72
+ */
73
+ const DECORATIVE_EXTENSION_URI = "{C183D7F6-B498-43B3-948B-1728B52AA6E4}";
74
+ /** The namespace the decorative extension's element is bound to. */
75
+ const DECORATIVE_NAMESPACE = "http://schemas.microsoft.com/office/drawing/2017/decorative";
76
+ /**
77
+ * `wp:docPr`'s extension list is DrawingML's (`a:extLst` holding `a:ext`), in
78
+ * the Transitional or the Strict namespace. Matching on the local name alone
79
+ * would take an `extLst` some other namespace owns and replay its children
80
+ * inside an `a:extLst`, which is a different container than the source wrote.
81
+ */
82
+ const DRAWINGML_NAMESPACE_URIS = /* @__PURE__ */ new Set(["http://schemas.openxmlformats.org/drawingml/2006/main", "http://purl.oclc.org/ooxml/drawingml/main"]);
83
+ /**
84
+ * Read the `wp:docPr` extension list.
85
+ *
86
+ * Only the decorative extension is modeled. The rest — a creation id, a
87
+ * local-DPI hint, whatever a later Word writes — are captured as they were
88
+ * written so the save path can put them back: an extension folio drops is a
89
+ * fact the document had and no longer does.
90
+ */
91
+ const parseDocPropsExtensions = (docPr) => {
92
+ const extLst = findChildByNamespaceUri(docPr, DRAWINGML_NAMESPACE_URIS, "extLst");
93
+ if (!extLst) return { other: [] };
94
+ const result = { other: [] };
95
+ for (const ext of findChildrenByNamespaceUri(extLst, DRAWINGML_NAMESPACE_URIS, "ext")) {
96
+ if (getAttribute(ext, null, "uri")?.toUpperCase() !== "{C183D7F6-B498-43B3-948B-1728B52AA6E4}") {
97
+ result.other.push(captureVerbatimXml(ext));
98
+ continue;
99
+ }
100
+ const flag = getChildElements(ext).find((child) => getLocalName(child.name) === "decorative" && getNamespaceUri(child) === "http://schemas.microsoft.com/office/drawing/2017/decorative");
101
+ if (!flag) {
102
+ result.other.push(captureVerbatimXml(ext));
103
+ continue;
104
+ }
105
+ result.decorative = parseOnOffValue(getAttribute(flag, null, "val")) ?? true;
106
+ }
107
+ return result;
108
+ };
109
+ /**
67
110
  * Parse document properties (wp:docPr)
68
111
  *
69
112
  * @param docPr - wp:docPr element
@@ -72,18 +115,17 @@ function parseEffectExtent(effectExtent) {
72
115
  function parseDocProps(docPr) {
73
116
  if (!docPr) return {};
74
117
  const id = getAttribute(docPr, null, "id");
75
- const name = getAttribute(docPr, null, "name");
76
- const descr = getAttribute(docPr, null, "descr");
77
- const title = getAttribute(docPr, null, "title");
78
- const decorative = parseOnOffValue(getAttribute(docPr, null, "decorative")) === true;
118
+ const names = parseNonVisualDrawingNames(docPr);
119
+ const hidden = parseOnOffAttribute(docPr, null, "hidden");
120
+ const { decorative, other: docPrExtensions } = parseDocPropsExtensions(docPr);
79
121
  const hlinkClickEl = findChild(docPr, "a", "hlinkClick");
80
122
  const hlinkRId = hlinkClickEl ? getAttribute(hlinkClickEl, "r", "id") : null;
81
123
  return {
82
124
  ...id != null ? { id } : {},
83
- ...name != null ? { name } : {},
84
- ...descr != null ? { alt: descr } : {},
85
- ...title != null ? { title } : {},
86
- ...decorative ? { decorative } : {},
125
+ ...names,
126
+ ...decorative === void 0 ? {} : { decorative },
127
+ ...hidden === void 0 ? {} : { hidden },
128
+ ...docPrExtensions.length > 0 ? { docPrExtensions } : {},
87
129
  ...hlinkRId != null ? { hlinkRId } : {}
88
130
  };
89
131
  }
@@ -178,17 +220,20 @@ function parseImageOpacity(blip) {
178
220
  return Math.max(0, amt / 1e5);
179
221
  }
180
222
  /**
181
- * Extract rId from a:blip element
223
+ * Extract rId from a:blip element.
224
+ *
225
+ * Undefined when the drawing has no blip to read one from: a chart, a diagram
226
+ * or an OLE frame carries an `a:graphic` that is not a picture, and a
227
+ * `wp:inline` may carry no graphic at all.
182
228
  */
183
229
  function extractBlipRId(blip) {
184
- if (!blip) return "";
230
+ if (!blip) return;
185
231
  const rEmbed = getAttribute(blip, "r", "embed");
186
232
  if (rEmbed) return rEmbed;
187
233
  const embed = getAttribute(blip, null, "embed");
188
234
  if (embed) return embed;
189
235
  const rLink = getAttribute(blip, "r", "link");
190
236
  if (rLink) return rLink;
191
- return "";
192
237
  }
193
238
  /**
194
239
  * Find transform (a:xfrm) from picture shape properties
@@ -242,10 +287,9 @@ function getMimeType(path) {
242
287
  * @returns Object with src (data URL or blob), mimeType, and filename
243
288
  */
244
289
  function resolveImageData(rId, rels, media) {
245
- if (!rId || !rels) return {};
246
- const rel = rels.get(rId);
247
- if (!rel) return {};
248
- const targetPath = rel.target;
290
+ const resolved = resolveRelationshipIdOfType(rels, rId, RELATIONSHIP_TYPES.image);
291
+ if (resolved.status !== "resolved") return {};
292
+ const targetPath = resolved.relationship.target;
249
293
  if (!targetPath) return {};
250
294
  const normalizedPath = normalizeMediaPath(targetPath);
251
295
  const filename = targetPath.split("/").pop();
@@ -319,7 +363,7 @@ function parseInline(inlineEl, rels, media) {
319
363
  if (distR !== void 0) wrap.distR = distR;
320
364
  const image = {
321
365
  type: "image",
322
- rId,
366
+ ...rId === void 0 ? {} : { rId },
323
367
  size,
324
368
  wrap
325
369
  };
@@ -327,7 +371,9 @@ function parseInline(inlineEl, rels, media) {
327
371
  if (props.name !== void 0) image.docPrName = props.name;
328
372
  if (props.alt !== void 0) image.alt = props.alt;
329
373
  if (props.title !== void 0) image.title = props.title;
330
- if (props.decorative) image.decorative = true;
374
+ if (props.decorative !== void 0) image.decorative = props.decorative;
375
+ if (props.hidden !== void 0) image.hidden = props.hidden;
376
+ if (props.docPrExtensions !== void 0) image.docPrExtensions = props.docPrExtensions;
331
377
  const safeSrc = sanitizeImageSrc(imageData.src);
332
378
  if (safeSrc) image.src = safeSrc;
333
379
  if (imageData.mimeType) image.mimeType = imageData.mimeType;
@@ -337,11 +383,12 @@ function parseInline(inlineEl, rels, media) {
337
383
  if (crop) image.crop = crop;
338
384
  if (opacity !== void 0) image.opacity = opacity;
339
385
  if (frameLocks) image.frameLocks = frameLocks;
340
- if (props.hlinkRId && rels) {
341
- const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
386
+ const hlink = resolveRelationshipIdOfType(rels, props.hlinkRId, RELATIONSHIP_TYPES.hyperlink);
387
+ if (hlink.status === "resolved") {
388
+ const safeHref = sanitizeExternalUrl(hlink.relationship.target);
342
389
  if (safeHref) {
343
390
  image.hlinkHref = safeHref;
344
- image.hlinkRId = props.hlinkRId;
391
+ image.hlinkRId = hlink.relationship.id;
345
392
  }
346
393
  }
347
394
  return image;
@@ -359,7 +406,7 @@ function parseAnchor(anchorEl, rels, media) {
359
406
  const padding = parseEffectExtent(findByFullName(anchorEl, "wp:effectExtent"));
360
407
  const props = parseDocProps(findByFullName(anchorEl, "wp:docPr"));
361
408
  const frameLocks = parseGraphicFrameLocks(anchorEl);
362
- const behindDoc = parseOnOffValue(getAttribute(anchorEl, null, "behindDoc")) === true;
409
+ const behindDoc = parseAnchorBehindDoc(anchorEl);
363
410
  const layoutInCell = parseOnOffAttr(anchorEl, "layoutInCell");
364
411
  const allowOverlap = parseOnOffAttr(anchorEl, "allowOverlap");
365
412
  const anchorDistT = parseNumericAttribute(anchorEl, null, "distT");
@@ -391,7 +438,7 @@ function parseAnchor(anchorEl, rels, media) {
391
438
  const transform = parseTransform(findPictureTransform(anchorEl));
392
439
  const image = {
393
440
  type: "image",
394
- rId,
441
+ ...rId === void 0 ? {} : { rId },
395
442
  size,
396
443
  wrap
397
444
  };
@@ -399,7 +446,9 @@ function parseAnchor(anchorEl, rels, media) {
399
446
  if (props.name !== void 0) image.docPrName = props.name;
400
447
  if (props.alt !== void 0) image.alt = props.alt;
401
448
  if (props.title !== void 0) image.title = props.title;
402
- if (props.decorative) image.decorative = true;
449
+ if (props.decorative !== void 0) image.decorative = props.decorative;
450
+ if (props.hidden !== void 0) image.hidden = props.hidden;
451
+ if (props.docPrExtensions !== void 0) image.docPrExtensions = props.docPrExtensions;
403
452
  const safeSrc = sanitizeImageSrc(imageData.src);
404
453
  if (safeSrc) image.src = safeSrc;
405
454
  if (imageData.mimeType) image.mimeType = imageData.mimeType;
@@ -412,11 +461,12 @@ function parseAnchor(anchorEl, rels, media) {
412
461
  if (frameLocks) image.frameLocks = frameLocks;
413
462
  if (layoutInCell !== void 0) image.layoutInCell = layoutInCell;
414
463
  if (allowOverlap !== void 0) image.allowOverlap = allowOverlap;
415
- if (props.hlinkRId && rels) {
416
- const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
464
+ const hlink = resolveRelationshipIdOfType(rels, props.hlinkRId, RELATIONSHIP_TYPES.hyperlink);
465
+ if (hlink.status === "resolved") {
466
+ const safeHref = sanitizeExternalUrl(hlink.relationship.target);
417
467
  if (safeHref) {
418
468
  image.hlinkHref = safeHref;
419
- image.hlinkRId = props.hlinkRId;
469
+ image.hlinkRId = hlink.relationship.id;
420
470
  }
421
471
  }
422
472
  return image;
@@ -533,4 +583,4 @@ function needsTextWrapping(image) {
533
583
  ].includes(image.wrap.type);
534
584
  }
535
585
  //#endregion
536
- export { getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
586
+ export { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE, getImageDimensionsPx, getImageHeightPx, getImageWidthPx, getWrapDistancesPx, hasAltText, isBehindText, isDecorativeImage, isFloatingImage, isInFrontOfText, isInlineImage, needsTextWrapping, parseDrawing, parseImage, resolveImageData };
@@ -12,7 +12,20 @@ declare const isDrawingRawXmlMode: (value: unknown) => value is document_d_expor
12
12
  declare const EDITED_PREVIEW_FINGERPRINT = "editedPreview";
13
13
  /** Fingerprints modeled image fields which make raw DrawingML stale when edited. */
14
14
  declare const imageRawXmlFingerprint: (image: document_d_exports.Image) => string;
15
- /** Editable raw drawing XML can replay only while its modeled projection is unchanged. */
15
+ /**
16
+ * Whether the serializer writes the captured raw XML back.
17
+ *
18
+ * For an unclassified drawing the capture is a cache of the model, so it
19
+ * replays only while the two agree. For a classified one the capture *is* the
20
+ * drawing: a preserve-only drawing has a placeholder image, and a preview-only
21
+ * group has a raster render of shapes the model cannot hold. Regenerating
22
+ * either writes something the source never contained — for the group, at best
23
+ * the one child picture the rasterizer saw first under that child's
24
+ * relationship, in place of the whole group. So a classified capture is always
25
+ * written back; a stale fingerprint means the edit that made it stale is lost,
26
+ * which {@link classifyDrawingSafety} reports as `opaque`, and never that
27
+ * folio may build a replacement.
28
+ */
16
29
  declare const canReplayEditableImageRawXml: (drawing: document_d_exports.DrawingContent) => boolean;
17
30
  /**
18
31
  * What a drawing costs the document when Folio writes it back.
@@ -26,10 +26,29 @@ const EDITED_PREVIEW_FINGERPRINT = "editedPreview";
26
26
  const editableImageProjection = ({ id: _id, rId: _rId, src: _src, mimeType: _mimeType, filename: _filename, ...image }) => image;
27
27
  /** Fingerprints modeled image fields which make raw DrawingML stale when edited. */
28
28
  const imageRawXmlFingerprint = (image) => canonicalJson(editableImageProjection(image));
29
- /** Editable raw drawing XML can replay only while its modeled projection is unchanged. */
29
+ /** Whether the modeled image still says what the captured raw drawing says. */
30
+ const modelAgreesWithRawXml = (drawing) => drawing.rawImageFingerprint === void 0 || drawing.rawImageFingerprint === imageRawXmlFingerprint(drawing.image);
31
+ /**
32
+ * Whether the serializer writes the captured raw XML back.
33
+ *
34
+ * For an unclassified drawing the capture is a cache of the model, so it
35
+ * replays only while the two agree. For a classified one the capture *is* the
36
+ * drawing: a preserve-only drawing has a placeholder image, and a preview-only
37
+ * group has a raster render of shapes the model cannot hold. Regenerating
38
+ * either writes something the source never contained — for the group, at best
39
+ * the one child picture the rasterizer saw first under that child's
40
+ * relationship, in place of the whole group. So a classified capture is always
41
+ * written back; a stale fingerprint means the edit that made it stale is lost,
42
+ * which {@link classifyDrawingSafety} reports as `opaque`, and never that
43
+ * folio may build a replacement.
44
+ */
30
45
  const canReplayEditableImageRawXml = (drawing) => {
31
- if (drawing.rawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY) return true;
32
- return drawing.rawImageFingerprint === void 0 || drawing.rawImageFingerprint === imageRawXmlFingerprint(drawing.image);
46
+ switch (drawing.rawXmlMode) {
47
+ case DRAWING_RAW_XML_MODES.PRESERVE_ONLY:
48
+ case DRAWING_RAW_XML_MODES.PREVIEW_ONLY: return true;
49
+ case void 0: return modelAgreesWithRawXml(drawing);
50
+ default: return drawing;
51
+ }
33
52
  };
34
53
  /**
35
54
  * What a drawing costs the document when Folio writes it back.
@@ -46,12 +65,12 @@ const DRAWING_SAFETY_CLASSES = {
46
65
  OPAQUE: "opaque"
47
66
  };
48
67
  /**
49
- * Regenerated DrawingML points at `image.rId`, and `serializePicGraphic` falls
50
- * back to `"rId1"` when that id is empty, which rebinds the picture to whichever
51
- * relationship happens to be first. A drawing with no relationship id therefore
52
- * has no faithful regeneration.
68
+ * A drawing with no picture relationship has no picture to regenerate. The
69
+ * serializer writes the anchor back without a graphic, which is faithful for an
70
+ * anchor that never had one and lossy for a chart or an OLE frame, so the
71
+ * drawing counts as opaque and an edit must block the save.
53
72
  */
54
- const canRegenerateDrawing = (drawing) => drawing.image.rId !== "";
73
+ const canRegenerateDrawing = (drawing) => drawing.image.rId !== void 0;
55
74
  /**
56
75
  * Classify a drawing by what the run serializer will do with it.
57
76
  *
@@ -60,9 +79,14 @@ const canRegenerateDrawing = (drawing) => drawing.image.rId !== "";
60
79
  */
61
80
  const classifyDrawingSafety = (drawing) => {
62
81
  if (drawing.rawXml === void 0) return DRAWING_SAFETY_CLASSES.NATIVE;
63
- if (canReplayEditableImageRawXml(drawing)) return DRAWING_SAFETY_CLASSES.REPLAYABLE;
64
- if (drawing.rawXmlMode === DRAWING_RAW_XML_MODES.PREVIEW_ONLY) return DRAWING_SAFETY_CLASSES.OPAQUE;
65
- return canRegenerateDrawing(drawing) ? DRAWING_SAFETY_CLASSES.NATIVE : DRAWING_SAFETY_CLASSES.OPAQUE;
82
+ switch (drawing.rawXmlMode) {
83
+ case DRAWING_RAW_XML_MODES.PRESERVE_ONLY: return DRAWING_SAFETY_CLASSES.REPLAYABLE;
84
+ case DRAWING_RAW_XML_MODES.PREVIEW_ONLY: return modelAgreesWithRawXml(drawing) ? DRAWING_SAFETY_CLASSES.REPLAYABLE : DRAWING_SAFETY_CLASSES.OPAQUE;
85
+ case void 0:
86
+ if (modelAgreesWithRawXml(drawing)) return DRAWING_SAFETY_CLASSES.REPLAYABLE;
87
+ return canRegenerateDrawing(drawing) ? DRAWING_SAFETY_CLASSES.NATIVE : DRAWING_SAFETY_CLASSES.OPAQUE;
88
+ default: return drawing;
89
+ }
66
90
  };
67
91
  //#endregion
68
92
  export { DRAWING_SAFETY_CLASSES, EDITED_PREVIEW_FINGERPRINT, allowsDirectDrawingEdit, canReplayEditableImageRawXml, classifyDrawingSafety, imageRawXmlFingerprint, isDrawingRawXmlMode };
@@ -0,0 +1,15 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ import { XmlElement } from "./xmlParser.js";
3
+ //#region src/docx/markupRangeMarker.d.ts
4
+ declare const parseMarkupRangeMarker: (node: XmlElement) => document_d_exports.MarkupRangeMarker;
5
+ declare const parseBookmarkRangeMarker: (node: XmlElement) => document_d_exports.BookmarkRangeMarker;
6
+ /**
7
+ * `w:author` is required on `CT_MoveBookmark`, so an absent one takes the same
8
+ * `Unknown` fallback a tracked change takes rather than leaving the saved
9
+ * package short of an attribute the schema demands. `w:date` is required too,
10
+ * but inventing a timestamp would state a fact about the document that is not
11
+ * true, so an absent date stays absent.
12
+ */
13
+ declare const parseMoveBookmarkMarker: (node: XmlElement) => document_d_exports.MoveBookmarkMarker;
14
+ //#endregion
15
+ export { parseBookmarkRangeMarker, parseMarkupRangeMarker, parseMoveBookmarkMarker };
@@ -0,0 +1,44 @@
1
+ import { getAttribute, parseNumericAttribute } from "./xmlParser.js";
2
+ //#region src/docx/markupRangeMarker.ts
3
+ /** ST_DisplacedByCustomXml. A value outside it is not a placement folio can honour. */
4
+ const DISPLACED_BY_CUSTOM_XML = /* @__PURE__ */ new Set(["next", "prev"]);
5
+ const parseDisplacedByCustomXml = (node) => {
6
+ const value = getAttribute(node, "w", "displacedByCustomXml") ?? "";
7
+ return DISPLACED_BY_CUSTOM_XML.has(value) ? value : void 0;
8
+ };
9
+ const parseMarkupRangeMarker = (node) => {
10
+ const marker = { id: parseNumericAttribute(node, "w", "id") ?? 0 };
11
+ const displacedByCustomXml = parseDisplacedByCustomXml(node);
12
+ if (displacedByCustomXml !== void 0) marker.displacedByCustomXml = displacedByCustomXml;
13
+ return marker;
14
+ };
15
+ const parseBookmarkRangeMarker = (node) => {
16
+ const marker = {
17
+ ...parseMarkupRangeMarker(node),
18
+ name: getAttribute(node, "w", "name") ?? ""
19
+ };
20
+ const colFirst = parseNumericAttribute(node, "w", "colFirst");
21
+ if (colFirst !== void 0) marker.colFirst = colFirst;
22
+ const colLast = parseNumericAttribute(node, "w", "colLast");
23
+ if (colLast !== void 0) marker.colLast = colLast;
24
+ return marker;
25
+ };
26
+ /**
27
+ * `w:author` is required on `CT_MoveBookmark`, so an absent one takes the same
28
+ * `Unknown` fallback a tracked change takes rather than leaving the saved
29
+ * package short of an attribute the schema demands. `w:date` is required too,
30
+ * but inventing a timestamp would state a fact about the document that is not
31
+ * true, so an absent date stays absent.
32
+ */
33
+ const parseMoveBookmarkMarker = (node) => {
34
+ const author = (getAttribute(node, "w", "author") ?? "").trim();
35
+ const marker = {
36
+ ...parseBookmarkRangeMarker(node),
37
+ author: author.length > 0 ? author : "Unknown"
38
+ };
39
+ const date = (getAttribute(node, "w", "date") ?? "").trim();
40
+ if (date.length > 0) marker.date = date;
41
+ return marker;
42
+ };
43
+ //#endregion
44
+ export { parseBookmarkRangeMarker, parseMarkupRangeMarker, parseMoveBookmarkMarker };
@@ -1,4 +1,5 @@
1
1
  import { findChildByLocalName, getLocalName } from "./xmlParser.js";
2
+ import { escapeXmlText } from "@stll/docx-core";
2
3
  //#region src/docx/mathToMathml.ts
3
4
  /**
4
5
  * OMML → MathML conversion.
@@ -95,13 +96,13 @@ const MATH_OPERATOR_HINT = /* @__PURE__ */ new Set([
95
96
  "…"
96
97
  ]);
97
98
  function renderElement(el) {
98
- if (el.type === "text") return escapeXml(textValue(el));
99
+ if (el.type === "text") return escapeXmlText(textValue(el));
99
100
  if (el.type !== "element") return "";
100
101
  switch (el.name ? getLocalName(el.name) : "") {
101
102
  case "oMath":
102
103
  case "oMathPara": return renderChildren(el);
103
104
  case "r": return renderRun(el);
104
- case "t": return escapeXml(extractText(el));
105
+ case "t": return escapeXmlText(extractText(el));
105
106
  case "f": return renderFraction(el);
106
107
  case "num":
107
108
  case "den": return wrapMrow(renderChildren(el));
@@ -179,8 +180,8 @@ function tokenizeMathText(text) {
179
180
  let bufKind = null;
180
181
  const flush = () => {
181
182
  if (!buf) return;
182
- if (bufKind === "digit") out += `<mn>${escapeXml(buf)}</mn>`;
183
- else out += `<mi>${escapeXml(buf)}</mi>`;
183
+ if (bufKind === "digit") out += `<mn>${escapeXmlText(buf)}</mn>`;
184
+ else out += `<mi>${escapeXmlText(buf)}</mi>`;
184
185
  buf = "";
185
186
  bufKind = null;
186
187
  };
@@ -193,7 +194,7 @@ function tokenizeMathText(text) {
193
194
  const kind = classifyChar(ch);
194
195
  if (kind === "operator") {
195
196
  flush();
196
- out += `<mo>${escapeXml(ch)}</mo>`;
197
+ out += `<mo>${escapeXmlText(ch)}</mo>`;
197
198
  continue;
198
199
  }
199
200
  if (kind === "digit") {
@@ -202,7 +203,7 @@ function tokenizeMathText(text) {
202
203
  continue;
203
204
  }
204
205
  flush();
205
- out += `<mi>${escapeXml(ch)}</mi>`;
206
+ out += `<mi>${escapeXmlText(ch)}</mi>`;
206
207
  }
207
208
  flush();
208
209
  return out;
@@ -258,7 +259,7 @@ function renderNary(el) {
258
259
  const chr = (chrEl ? getAttrValue(chrEl, "val") : null) || "∫";
259
260
  const limLocEl = naryPr ? findChildByLocalName(naryPr, "limLoc") : null;
260
261
  const limLoc = (limLocEl ? getAttrValue(limLocEl, "val") : null) || "subSup";
261
- const opMml = `<mo>${escapeXml(chr)}</mo>`;
262
+ const opMml = `<mo>${escapeXmlText(chr)}</mo>`;
262
263
  const subMml = sub ? wrapMrow(renderChildren(sub)) : "";
263
264
  const supMml = sup ? wrapMrow(renderChildren(sup)) : "";
264
265
  const bodyMml = body ? wrapMrow(renderChildren(body)) : "<mrow/>";
@@ -277,8 +278,8 @@ function renderDelimiter(el) {
277
278
  const begChr = begChrEl ? getAttrValue(begChrEl, "val") ?? "(" : "(";
278
279
  const endChr = endChrEl ? getAttrValue(endChrEl, "val") ?? ")" : ")";
279
280
  const sepChr = sepChrEl ? getAttrValue(sepChrEl, "val") ?? "|" : "|";
280
- const inner = (el.elements ?? []).filter((c) => c.type === "element" && getLocalName(c.name ?? "") === "e").map((c) => wrapMrow(renderChildren(c))).join(`<mo>${escapeXml(sepChr)}</mo>`);
281
- return `<mrow><mo>${escapeXml(begChr)}</mo>${inner}<mo>${escapeXml(endChr)}</mo></mrow>`;
281
+ const inner = (el.elements ?? []).filter((c) => c.type === "element" && getLocalName(c.name ?? "") === "e").map((c) => wrapMrow(renderChildren(c))).join(`<mo>${escapeXmlText(sepChr)}</mo>`);
282
+ return `<mrow><mo>${escapeXmlText(begChr)}</mo>${inner}<mo>${escapeXmlText(endChr)}</mo></mrow>`;
282
283
  }
283
284
  function renderMatrix(el) {
284
285
  return `<mtable>${(el.elements ?? []).filter((c) => c.type === "element" && getLocalName(c.name ?? "") === "mr").map(renderMatrixRow).join("")}</mtable>`;
@@ -291,7 +292,7 @@ function renderAccent(el) {
291
292
  const chrEl = accPr ? findChildByLocalName(accPr, "chr") : null;
292
293
  const chr = chrEl ? getAttrValue(chrEl, "val") ?? "̂" : "̂";
293
294
  const base = findChildByLocalName(el, "e");
294
- return `<mover accent="true">${base ? wrapMrow(renderChildren(base)) : "<mrow/>"}<mo>${escapeXml(chr)}</mo></mover>`;
295
+ return `<mover accent="true">${base ? wrapMrow(renderChildren(base)) : "<mrow/>"}<mo>${escapeXmlText(chr)}</mo></mover>`;
295
296
  }
296
297
  function renderBar(el) {
297
298
  const barPr = findChildByLocalName(el, "barPr");
@@ -311,7 +312,7 @@ function renderGroupChr(el) {
311
312
  const chr = chrEl ? getAttrValue(chrEl, "val") ?? defaultChr : defaultChr;
312
313
  const base = findChildByLocalName(el, "e");
313
314
  const baseMml = base ? wrapMrow(renderChildren(base)) : "<mrow/>";
314
- const grouper = `<mo stretchy="true">${escapeXml(chr)}</mo>`;
315
+ const grouper = `<mo stretchy="true">${escapeXmlText(chr)}</mo>`;
315
316
  return pos === "top" ? `<mover>${baseMml}${grouper}</mover>` : `<munder>${baseMml}${grouper}</munder>`;
316
317
  }
317
318
  function renderLimLow(el) {
@@ -350,8 +351,5 @@ function getAttrValue(el, localAttr) {
350
351
  return String(v);
351
352
  }
352
353
  }
353
- function escapeXml(s) {
354
- return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
355
- }
356
354
  //#endregion
357
355
  export { ommlToMathml };
@@ -0,0 +1,34 @@
1
+ import { XmlElement } from "./xmlParser.js";
2
+ //#region src/docx/nonVisualDrawingProps.d.ts
3
+ /** The authored name, alt text and title of a drawing object. */
4
+ type NonVisualDrawingNames = {
5
+ /** `@name`, schema-required, so `""` means the object was never named. */
6
+ name?: string;
7
+ /** `@descr`: alt text, accessibility content. */
8
+ alt?: string;
9
+ /** `@title`. */
10
+ title?: string;
11
+ };
12
+ /**
13
+ * Read `@name`, `@descr` and `@title` off a `CT_NonVisualDrawingProps` element.
14
+ *
15
+ * An attribute that is not there stays absent. `@name` is schema-required, so
16
+ * an unnamed object still writes one, and `name=""` is the marker the writer
17
+ * below mints for exactly that: reading it back as an authored name would make
18
+ * `absent → save → parse` land on `""` instead of absent, and no reader can
19
+ * tell the two apart. `@descr` and `@title` are optional and written only when
20
+ * authored, so `""` in either is a string someone wrote.
21
+ */
22
+ declare const parseNonVisualDrawingNames: (element: XmlElement | null | undefined) => NonVisualDrawingNames;
23
+ /**
24
+ * The `name`, `descr` and `title` attributes of a `CT_NonVisualDrawingProps`
25
+ * element, leading space included.
26
+ *
27
+ * `@name` is schema-required, so an object carrying none writes the empty
28
+ * string. folio does not name an object it did not author: a generated
29
+ * "Shape 3" would overwrite the author's own name for every drawing whose
30
+ * name did not survive the model, and read back as authored content.
31
+ */
32
+ declare const serializeNonVisualDrawingNames: (names: NonVisualDrawingNames) => string;
33
+ //#endregion
34
+ export { NonVisualDrawingNames, parseNonVisualDrawingNames, serializeNonVisualDrawingNames };
@@ -0,0 +1,46 @@
1
+ import { getAttribute } from "./xmlParser.js";
2
+ import { escapeXmlAttribute } from "@stll/docx-core";
3
+ //#region src/docx/nonVisualDrawingProps.ts
4
+ /**
5
+ * `CT_NonVisualDrawingProps` (`wp:docPr`, `wps:cNvPr`, `pic:cNvPr`) carries the
6
+ * three authored strings every drawing object has: its name, its alt text
7
+ * (`descr`) and its title. They are authored content, not folio's to mint, so
8
+ * one reader and one writer own them for every drawing kind.
9
+ */
10
+ /**
11
+ * Read `@name`, `@descr` and `@title` off a `CT_NonVisualDrawingProps` element.
12
+ *
13
+ * An attribute that is not there stays absent. `@name` is schema-required, so
14
+ * an unnamed object still writes one, and `name=""` is the marker the writer
15
+ * below mints for exactly that: reading it back as an authored name would make
16
+ * `absent → save → parse` land on `""` instead of absent, and no reader can
17
+ * tell the two apart. `@descr` and `@title` are optional and written only when
18
+ * authored, so `""` in either is a string someone wrote.
19
+ */
20
+ const parseNonVisualDrawingNames = (element) => {
21
+ if (!element) return {};
22
+ const name = getAttribute(element, null, "name");
23
+ const descr = getAttribute(element, null, "descr");
24
+ const title = getAttribute(element, null, "title");
25
+ return {
26
+ ...name !== null && name !== "" ? { name } : {},
27
+ ...descr !== null ? { alt: descr } : {},
28
+ ...title !== null ? { title } : {}
29
+ };
30
+ };
31
+ /**
32
+ * The `name`, `descr` and `title` attributes of a `CT_NonVisualDrawingProps`
33
+ * element, leading space included.
34
+ *
35
+ * `@name` is schema-required, so an object carrying none writes the empty
36
+ * string. folio does not name an object it did not author: a generated
37
+ * "Shape 3" would overwrite the author's own name for every drawing whose
38
+ * name did not survive the model, and read back as authored content.
39
+ */
40
+ const serializeNonVisualDrawingNames = (names) => {
41
+ const descr = names.alt === void 0 ? "" : ` descr="${escapeXmlAttribute(names.alt)}"`;
42
+ const title = names.title === void 0 ? "" : ` title="${escapeXmlAttribute(names.title)}"`;
43
+ return ` name="${escapeXmlAttribute(names.name ?? "")}"${descr}${title}`;
44
+ };
45
+ //#endregion
46
+ export { parseNonVisualDrawingNames, serializeNonVisualDrawingNames };