@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
@@ -5,12 +5,14 @@ import { DATE_UTC_ATTRIBUTE } from "../trackedChangeInfo.js";
5
5
  import { toTransitionalNamespaceUri } from "../transitionalSpelling.js";
6
6
  import { captureVerbatimXml, sanitizeCapturedXmlElement } from "../verbatimCapture.js";
7
7
  import { NAMESPACES, OOXML_NAMESPACE_SCOPE, cloneElement, getChildElements, getLocalName, parseXml } from "../xmlParser.js";
8
+ import { bookmarkRangeAttributes, markupRangeAttributes, moveBookmarkAttributes } from "./markupRangeAttributes.js";
8
9
  import { serializeRun } from "./runSerializer.js";
9
10
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
10
11
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
11
12
  import { serializeTrackedChangeAttributes, trackedChangeAttributeRecord } from "./trackedChangeAttributes.js";
12
- import { escapeXml, isSingleWellFormedElement } from "./xmlUtils.js";
13
+ import { isSingleWellFormedElement } from "./xmlUtils.js";
13
14
  import { panic } from "better-result";
15
+ import { escapeXmlAttribute, escapeXmlText } from "@stll/docx-core";
14
16
  import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
15
17
  //#region src/docx/serializer/paragraphSerializer.ts
16
18
  /**
@@ -21,13 +23,20 @@ function serializeParagraphMarkChange(mark) {
21
23
  return `<w:${mark.kind} ${attrs}/>`;
22
24
  }
23
25
  const RESERVED_PARAGRAPH_PROPERTY_CHILDREN = /* @__PURE__ */ new Set(["pPrChange", "sectPr"]);
26
+ /**
27
+ * Revision records a captured `w:pPr` may not carry into a replay.
28
+ *
29
+ * `w:numberingChange` is not among them: the model holds it
30
+ * (`ParagraphFormatting.numberingChangeXml`) and the serializer writes it
31
+ * back, so both paths keep it. Refusing the capture used to force a rebuild
32
+ * that could not write it, which turned a replay gate into a lost revision.
33
+ */
24
34
  const RESERVED_PARAGRAPH_CAPTURE_CHILDREN = /* @__PURE__ */ new Set([
25
35
  ...RESERVED_PARAGRAPH_PROPERTY_CHILDREN,
26
36
  ...PARAGRAPH_MARK_CHANGE_KINDS,
27
37
  "cellDel",
28
38
  "cellIns",
29
39
  "cellMerge",
30
- "numberingChange",
31
40
  "rPrChange",
32
41
  "tblGridChange",
33
42
  "tblPrChange",
@@ -119,7 +128,11 @@ const PARAGRAPH_MARK_BASE_CHILDREN = /* @__PURE__ */ new Set([
119
128
  "oMath"
120
129
  ]);
121
130
  const PARAGRAPH_NESTED_PROPERTY_CHILDREN = /* @__PURE__ */ new Map([
122
- ["numPr", /* @__PURE__ */ new Set(["ilvl", "numId"])],
131
+ ["numPr", /* @__PURE__ */ new Set([
132
+ "ilvl",
133
+ "numId",
134
+ "numberingChange"
135
+ ])],
123
136
  ["pBdr", /* @__PURE__ */ new Set([
124
137
  "top",
125
138
  "left",
@@ -319,13 +332,13 @@ function serializeParagraphPropertyChange(change) {
319
332
  /** The attribute list of a `w:hyperlink`, without its children. */
320
333
  function hyperlinkAttributes(hyperlink) {
321
334
  const attrs = [];
322
- if (hyperlink.rId) attrs.push(`r:id="${escapeXml(hyperlink.rId)}"`);
323
- if (hyperlink.anchor) attrs.push(`w:anchor="${escapeXml(hyperlink.anchor)}"`);
324
- if (hyperlink.tooltip) attrs.push(`w:tooltip="${escapeXml(hyperlink.tooltip)}"`);
325
- if (hyperlink.target) attrs.push(`w:tgtFrame="${escapeXml(hyperlink.target)}"`);
335
+ if (hyperlink.rId) attrs.push(`r:id="${escapeXmlAttribute(hyperlink.rId)}"`);
336
+ if (hyperlink.anchor) attrs.push(`w:anchor="${escapeXmlAttribute(hyperlink.anchor)}"`);
337
+ if (hyperlink.tooltip) attrs.push(`w:tooltip="${escapeXmlAttribute(hyperlink.tooltip)}"`);
338
+ if (hyperlink.target) attrs.push(`w:tgtFrame="${escapeXmlAttribute(hyperlink.target)}"`);
326
339
  if (hyperlink.history === true) attrs.push("w:history=\"1\"");
327
340
  else if (hyperlink.history === false) attrs.push("w:history=\"0\"");
328
- if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXml(hyperlink.docLocation)}"`);
341
+ if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXmlAttribute(hyperlink.docLocation)}"`);
329
342
  return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
330
343
  }
331
344
  /** One `w:hyperlink` child, with the caller deciding how a run is written. */
@@ -344,20 +357,17 @@ function serializeHyperlink(hyperlink) {
344
357
  * Serialize bookmark start (w:bookmarkStart)
345
358
  */
346
359
  function serializeBookmarkStart(bookmark) {
347
- const attrs = [`w:id="${bookmark.id}"`, `w:name="${escapeXml(bookmark.name)}"`];
348
- if (bookmark.colFirst !== void 0) attrs.push(`w:colFirst="${bookmark.colFirst}"`);
349
- if (bookmark.colLast !== void 0) attrs.push(`w:colLast="${bookmark.colLast}"`);
350
- return `<w:bookmarkStart ${attrs.join(" ")}/>`;
360
+ return `<w:bookmarkStart ${bookmarkRangeAttributes(bookmark).join(" ")}/>`;
351
361
  }
352
362
  /**
353
363
  * Serialize bookmark end (w:bookmarkEnd)
354
364
  */
355
365
  function serializeBookmarkEnd(bookmark) {
356
- return `<w:bookmarkEnd w:id="${bookmark.id}"/>`;
366
+ return `<w:bookmarkEnd ${markupRangeAttributes(bookmark).join(" ")}/>`;
357
367
  }
358
368
  /** Serialize a simple field without changing its authored OOXML field form. */
359
369
  function serializeSimpleField(field) {
360
- const attrs = [`w:instr="${escapeXml(field.instruction)}"`];
370
+ const attrs = [`w:instr="${escapeXmlAttribute(field.instruction)}"`];
361
371
  if (field.fldLock) attrs.push("w:fldLock=\"true\"");
362
372
  if (field.dirty) attrs.push("w:dirty=\"true\"");
363
373
  const contentXml = field.content.map((item) => item.type === "run" ? serializeRun(item) : serializeHyperlink(item)).join("");
@@ -379,7 +389,7 @@ function serializeComplexField(field) {
379
389
  if (field.fieldCode.length > 0) parts.push(...field.fieldCode.map((run) => serializeRun(run)));
380
390
  else if (field.instruction.length > 0) {
381
391
  const spaceAttr = field.instruction.startsWith(" ") || field.instruction.endsWith(" ") || field.instruction.includes(" ") ? " xml:space=\"preserve\"" : "";
382
- parts.push(`<w:r>${rPrXml}<w:instrText${spaceAttr}>${escapeXml(field.instruction)}</w:instrText></w:r>`);
392
+ parts.push(`<w:r>${rPrXml}<w:instrText${spaceAttr}>${escapeXmlText(field.instruction)}</w:instrText></w:r>`);
383
393
  }
384
394
  parts.push(`<w:r>${rPrXml}<w:fldChar w:fldCharType="separate"/></w:r>`);
385
395
  parts.push(...field.fieldResult.map((run) => serializeRun(run)));
@@ -397,29 +407,29 @@ function serializeComplexField(field) {
397
407
  function synthesizeInlineSdtPr(props) {
398
408
  const prParts = [];
399
409
  if (typeof props.id === "number") prParts.push(`<w:id w:val="${props.id}"/>`);
400
- if (props.alias) prParts.push(`<w:alias w:val="${escapeXml(props.alias)}"/>`);
401
- if (props.tag) prParts.push(`<w:tag w:val="${escapeXml(props.tag)}"/>`);
410
+ if (props.alias) prParts.push(`<w:alias w:val="${escapeXmlAttribute(props.alias)}"/>`);
411
+ if (props.tag) prParts.push(`<w:tag w:val="${escapeXmlAttribute(props.tag)}"/>`);
402
412
  if (props.lock && props.lock !== "unlocked") prParts.push(`<w:lock w:val="${props.lock}"/>`);
403
- if (props.placeholder) prParts.push(`<w:placeholder><w:docPart w:val="${escapeXml(props.placeholder)}"/></w:placeholder>`);
413
+ if (props.placeholder) prParts.push(`<w:placeholder><w:docPart w:val="${escapeXmlAttribute(props.placeholder)}"/></w:placeholder>`);
404
414
  if (props.showingPlaceholder) prParts.push("<w:showingPlcHdr/>");
405
415
  switch (props.sdtType) {
406
416
  case "plainText":
407
417
  prParts.push("<w:text/>");
408
418
  break;
409
419
  case "date": {
410
- const fullDateAttr = props.dateValueISO ? ` w:fullDate="${escapeXml(props.dateValueISO)}"` : "";
411
- const formatChild = props.dateFormat ? `<w:dateFormat w:val="${escapeXml(props.dateFormat)}"/>` : "";
420
+ const fullDateAttr = props.dateValueISO ? ` w:fullDate="${escapeXmlAttribute(props.dateValueISO)}"` : "";
421
+ const formatChild = props.dateFormat ? `<w:dateFormat w:val="${escapeXmlAttribute(props.dateFormat)}"/>` : "";
412
422
  if (fullDateAttr || formatChild) prParts.push(`<w:date${fullDateAttr}>${formatChild}</w:date>`);
413
423
  else prParts.push("<w:date/>");
414
424
  break;
415
425
  }
416
426
  case "dropdown": {
417
- const items = (props.listItems ?? []).map((i) => `<w:listItem w:displayText="${escapeXml(i.displayText)}" w:value="${escapeXml(i.value)}"/>`).join("");
427
+ const items = (props.listItems ?? []).map((i) => `<w:listItem w:displayText="${escapeXmlAttribute(i.displayText)}" w:value="${escapeXmlAttribute(i.value)}"/>`).join("");
418
428
  prParts.push(`<w:dropDownList>${items}</w:dropDownList>`);
419
429
  break;
420
430
  }
421
431
  case "comboBox": {
422
- const items = (props.listItems ?? []).map((i) => `<w:listItem w:displayText="${escapeXml(i.displayText)}" w:value="${escapeXml(i.value)}"/>`).join("");
432
+ const items = (props.listItems ?? []).map((i) => `<w:listItem w:displayText="${escapeXmlAttribute(i.displayText)}" w:value="${escapeXmlAttribute(i.value)}"/>`).join("");
423
433
  prParts.push(`<w:comboBox>${items}</w:comboBox>`);
424
434
  break;
425
435
  }
@@ -471,7 +481,7 @@ function serializeInlineSdt(sdt) {
471
481
  })}${props.rawEndPropertiesXml && isSingleWellFormedElement(props.rawEndPropertiesXml, "sdtEndPr") ? props.rawEndPropertiesXml : ""}<w:sdtContent>${contentXml}</w:sdtContent></w:sdt>`;
472
482
  }
473
483
  function serializeMoveRangeStart(tag, marker) {
474
- return `<w:${tag} ${[`w:id="${marker.id}"`, `w:name="${escapeXml(marker.name)}"`].join(" ")}/>`;
484
+ return `<w:${tag} ${moveBookmarkAttributes(marker).join(" ")}/>`;
475
485
  }
476
486
  /**
477
487
  * Serialize a tracked change wrapper (ins/del/moveFrom/moveTo)
@@ -560,17 +570,24 @@ function serializeParagraphContent(content, explicitCommentReferenceIds) {
560
570
  case "simpleField": return serializeSimpleField(content);
561
571
  case "complexField": return serializeComplexField(content);
562
572
  case "inlineSdt": return serializeInlineSdt(content);
563
- case "commentRangeStart": return `<w:commentRangeStart w:id="${content.id}"/>`;
564
- case "commentRangeEnd": return explicitCommentReferenceIds.has(content.id) ? `<w:commentRangeEnd w:id="${content.id}"/>` : `<w:commentRangeEnd w:id="${content.id}"/>${serializeCommentReferenceRun(content.id)}`;
573
+ case "commentRangeStart": return `<w:commentRangeStart ${markupRangeAttributes(content).join(" ")}/>`;
574
+ case "commentRangeEnd": {
575
+ const end = `<w:commentRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
576
+ return explicitCommentReferenceIds.has(content.id) ? end : `${end}${serializeCommentReferenceRun(content.id)}`;
577
+ }
565
578
  case "commentReference": return serializeCommentReferenceRun(content.id);
566
579
  case "insertion": return serializeTrackedChange("ins", content);
567
580
  case "deletion": return serializeTrackedChange("del", content);
568
581
  case "moveFrom": return serializeTrackedChange("moveFrom", content);
569
582
  case "moveTo": return serializeTrackedChange("moveTo", content);
570
583
  case "moveFromRangeStart": return serializeMoveRangeStart("moveFromRangeStart", content);
571
- case "moveFromRangeEnd": return `<w:moveFromRangeEnd w:id="${content.id}"/>`;
584
+ case "moveFromRangeEnd": return `<w:moveFromRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
572
585
  case "moveToRangeStart": return serializeMoveRangeStart("moveToRangeStart", content);
573
- case "moveToRangeEnd": return `<w:moveToRangeEnd w:id="${content.id}"/>`;
586
+ case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
587
+ case "bidiWrapper": {
588
+ const tag = content.control === "override" ? "bdo" : "dir";
589
+ return `<w:${tag}${content.direction === void 0 ? "" : ` w:val="${escapeXmlAttribute(content.direction)}"`}>${content.content.map((child) => serializeParagraphContent(child, explicitCommentReferenceIds)).join("")}</w:${tag}>`;
590
+ }
574
591
  case "mathEquation": return content.ommlXml || "";
575
592
  default: return "";
576
593
  }
@@ -584,8 +601,8 @@ function serializeParagraphContent(content, explicitCommentReferenceIds) {
584
601
  function serializeParagraph(paragraph) {
585
602
  const parts = [];
586
603
  const attrs = [];
587
- if (paragraph.paraId) attrs.push(`w14:paraId="${escapeXml(paragraph.paraId)}"`);
588
- if (paragraph.textId) attrs.push(`w14:textId="${escapeXml(paragraph.textId)}"`);
604
+ if (paragraph.paraId) attrs.push(`w14:paraId="${escapeXmlAttribute(paragraph.paraId)}"`);
605
+ if (paragraph.textId) attrs.push(`w14:textId="${escapeXmlAttribute(paragraph.textId)}"`);
589
606
  if (paragraph.reviewCarrier) attrs.push(`folio:reviewCarrier="${paragraph.reviewCarrier}"`);
590
607
  const attrsStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
591
608
  parts.push(serializeParagraphFormattingWithOptions(paragraph.formatting, {
@@ -657,28 +674,6 @@ function hasParagraphFormatting(paragraph) {
657
674
  return paragraph.formatting !== void 0 && Object.keys(paragraph.formatting).length > 0;
658
675
  }
659
676
  /**
660
- * Get plain text from a paragraph (for comparison/debugging)
661
- */
662
- function getParagraphPlainText(paragraph) {
663
- const texts = [];
664
- for (const content of paragraph.content) if (content.type === "run") {
665
- for (const item of content.content) if (item.type === "text") texts.push(item.text);
666
- else if (item.type === "tab") texts.push(" ");
667
- else if (item.type === "break") texts.push("\n");
668
- } else if (content.type === "hyperlink") {
669
- for (const child of content.children) if (child.type === "run") {
670
- for (const item of child.content) if (item.type === "text") texts.push(item.text);
671
- }
672
- } else if (content.type === "simpleField" || content.type === "inlineSdt" || content.type === "insertion" || content.type === "deletion" || content.type === "moveFrom" || content.type === "moveTo") {
673
- for (const item of content.content) if (item.type === "run") {
674
- for (const subItem of item.content) if (subItem.type === "text") texts.push(subItem.text);
675
- }
676
- } else if (content.type === "complexField") {
677
- for (const run of content.fieldResult) for (const item of run.content) if (item.type === "text") texts.push(item.text);
678
- }
679
- return texts.join("");
680
- }
681
- /**
682
677
  * Create an empty paragraph
683
678
  */
684
679
  function createEmptyParagraph(formatting) {
@@ -718,4 +713,4 @@ function getListLevel(paragraph) {
718
713
  return paragraph.formatting?.numPr?.ilvl ?? -1;
719
714
  }
720
715
  //#endregion
721
- export { createEmptyParagraph, createTextParagraph, getListLevel, getParagraphPlainText, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
716
+ export { createEmptyParagraph, createTextParagraph, getListLevel, hasParagraphContent, hasParagraphFormatting, isListParagraph, serializeParagraph, serializeParagraphFormatting, serializeParagraphs };
@@ -1,6 +1,6 @@
1
1
  import { toTransitionalNamespaceUri } from "../transitionalSpelling.js";
2
- import { escapeXml } from "./xmlUtils.js";
3
2
  import { TaggedError } from "better-result";
3
+ import { escapeXmlAttribute } from "@stll/docx-core";
4
4
  import { OOXML_NS } from "@stll/docx-utils";
5
5
  //#region src/docx/serializer/partNamespaces.ts
6
6
  /**
@@ -319,7 +319,7 @@ const serializePartElement = ({ partPath, rootName, rootAttributes, baselinePref
319
319
  ...[...NAMESPACE_TABLE.keys()].filter((prefix) => extra.has(prefix)),
320
320
  ...[...extra].filter((prefix) => !NAMESPACE_TABLE.has(prefix)).sort()
321
321
  ];
322
- const attributes = orderedPrefixes.map((prefix) => `xmlns:${prefix}="${escapeXml(bindings.get(prefix))}"`);
322
+ const attributes = orderedPrefixes.map((prefix) => `xmlns:${prefix}="${escapeXmlAttribute(bindings.get(prefix))}"`);
323
323
  const ignorable = orderedPrefixes.filter((prefix) => NAMESPACE_TABLE.get(prefix)?.ignorable === true);
324
324
  if (ignorable.length > 0) attributes.push(`mc:Ignorable="${ignorable.join(" ")}"`);
325
325
  return `${openingTag} ${attributes.join(" ")}>${body}</${rootName}>`;
@@ -1,14 +1,16 @@
1
1
  import { isValidHexColor } from "../../utils/colorResolver.js";
2
2
  import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
3
3
  import { serializeGraphicFrameLocks } from "../graphicFrameLocks.js";
4
+ import { DECORATIVE_EXTENSION_URI, DECORATIVE_NAMESPACE } from "../imageParser.js";
4
5
  import { canReplayEditableImageRawXml } from "../imageRawXml.js";
5
- import { requiresXmlSpacePreserve } from "../textWhitespace.js";
6
+ import { serializeNonVisualDrawingNames } from "../nonVisualDrawingProps.js";
6
7
  import { serializeParagraph } from "./paragraphSerializer.js";
7
8
  import { serializeTable } from "./tableSerializer.js";
8
9
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
9
10
  import { getSingularRunPropertyChange, serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
10
- import { escapeXml, intAttr } from "./xmlUtils.js";
11
+ import { intAttr } from "./xmlUtils.js";
11
12
  import { panic } from "better-result";
13
+ import { escapeXmlAttribute, escapeXmlText, requiresXmlSpacePreserve } from "@stll/docx-core";
12
14
  //#region src/docx/serializer/runSerializer.ts
13
15
  /**
14
16
  * Run Serializer - Serialize runs to OOXML XML
@@ -59,7 +61,7 @@ function serializeRunProperties(formatting, propertyChanges) {
59
61
  * Serialize text content (w:t)
60
62
  */
61
63
  function serializeTextContent(content) {
62
- return `<w:t${content.preserveSpace || requiresXmlSpacePreserve(content.text) ? " xml:space=\"preserve\"" : ""}>${escapeXml(content.text)}</w:t>`;
64
+ return `<w:t${requiresXmlSpacePreserve(content.text) ? " xml:space=\"preserve\"" : ""}>${escapeXmlText(content.text)}</w:t>`;
63
65
  }
64
66
  /**
65
67
  * Serialize tab content (w:tab)
@@ -67,9 +69,9 @@ function serializeTextContent(content) {
67
69
  function serializeTabContent(content) {
68
70
  if (!content.positional) return "<w:tab/>";
69
71
  return `<w:ptab${[
70
- content.positional.relativeTo ? ` w:relativeTo="${escapeXml(content.positional.relativeTo)}"` : "",
71
- content.positional.alignment ? ` w:alignment="${escapeXml(content.positional.alignment)}"` : "",
72
- content.positional.leader ? ` w:leader="${escapeXml(content.positional.leader)}"` : ""
72
+ content.positional.relativeTo ? ` w:relativeTo="${escapeXmlAttribute(content.positional.relativeTo)}"` : "",
73
+ content.positional.alignment ? ` w:alignment="${escapeXmlAttribute(content.positional.alignment)}"` : "",
74
+ content.positional.leader ? ` w:leader="${escapeXmlAttribute(content.positional.leader)}"` : ""
73
75
  ].join("")}/>`;
74
76
  }
75
77
  /**
@@ -88,7 +90,7 @@ function serializeBreakContent(content) {
88
90
  * Serialize symbol content (w:sym)
89
91
  */
90
92
  function serializeSymbolContent(content) {
91
- return `<w:sym w:font="${escapeXml(content.font)}" w:char="${escapeXml(content.char)}"/>`;
93
+ return `<w:sym${content.font === "" ? "" : ` w:font="${escapeXmlAttribute(content.font)}"`}${content.char === "" ? "" : ` w:char="${escapeXmlAttribute(content.char)}"`}/>`;
92
94
  }
93
95
  /**
94
96
  * Serialize footnote/endnote reference
@@ -110,7 +112,7 @@ function serializeFieldChar(content) {
110
112
  * Serialize field instruction text (w:instrText)
111
113
  */
112
114
  function serializeInstrText(content) {
113
- return `<w:instrText${content.text.startsWith(" ") || content.text.endsWith(" ") || content.text.includes(" ") ? " xml:space=\"preserve\"" : ""}>${escapeXml(content.text)}</w:instrText>`;
115
+ return `<w:instrText${content.text.startsWith(" ") || content.text.endsWith(" ") || content.text.includes(" ") ? " xml:space=\"preserve\"" : ""}>${escapeXmlText(content.text)}</w:instrText>`;
114
116
  }
115
117
  /**
116
118
  * Serialize soft hyphen (w:softHyphen)
@@ -127,11 +129,11 @@ function serializeNoBreakHyphen(_content) {
127
129
  /** Serialize a color value to DrawingML a:srgbClr or a:schemeClr */
128
130
  function serializeDrawingColor(color) {
129
131
  if (!color) return "";
130
- if (color.rgb && isValidHexColor(color.rgb)) return `<a:srgbClr val="${escapeXml(color.rgb.replace("#", ""))}"/>`;
132
+ if (color.rgb && isValidHexColor(color.rgb)) return `<a:srgbClr val="${escapeXmlAttribute(color.rgb.replace("#", ""))}"/>`;
131
133
  if (color.themeColor) {
132
134
  let clr = `<a:schemeClr val="${THEME_COLOR_TO_DRAWING_SCHEME[color.themeColor]}"`;
133
- if (color.themeTint) clr += `><a:tint val="${escapeXml(color.themeTint)}"/></a:schemeClr>`;
134
- else if (color.themeShade) clr += `><a:shade val="${escapeXml(color.themeShade)}"/></a:schemeClr>`;
135
+ if (color.themeTint) clr += `><a:tint val="${escapeXmlAttribute(color.themeTint)}"/></a:schemeClr>`;
136
+ else if (color.themeShade) clr += `><a:shade val="${escapeXmlAttribute(color.themeShade)}"/></a:schemeClr>`;
135
137
  else clr += `/>`;
136
138
  return clr;
137
139
  }
@@ -206,11 +208,17 @@ function serializeWrap(wrap) {
206
208
  case "inline": return "<wp:wrapNone/>";
207
209
  }
208
210
  }
209
- /** Build the common a:graphic > pic:pic element for images */
210
- function serializePicGraphic(image, sharedId) {
211
+ /**
212
+ * Build the common a:graphic > pic:pic element for images.
213
+ *
214
+ * Takes the relationship id as a separate argument so the caller has to have
215
+ * one. There is no default: a picture written against an invented id binds to
216
+ * whichever relationship the part happens to hold under that name.
217
+ */
218
+ function serializePicGraphic(image, imageRId, sharedId) {
211
219
  const cx = image.size.width;
212
220
  const cy = image.size.height;
213
- const rId = escapeXml(image.rId || "rId1");
221
+ const rId = escapeXmlAttribute(imageRId);
214
222
  const id = sharedId;
215
223
  const name = image.filename || `image${id}`;
216
224
  let xfrmAttrs = "";
@@ -230,7 +238,7 @@ function serializePicGraphic(image, sharedId) {
230
238
  "<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">",
231
239
  "<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">",
232
240
  "<pic:nvPicPr>",
233
- `<pic:cNvPr id="${id}" name="${escapeXml(name)}"${image.alt ? ` descr="${escapeXml(image.alt)}"` : ""}/>`,
241
+ `<pic:cNvPr id="${id}" name="${escapeXmlAttribute(name)}"${image.alt ? ` descr="${escapeXmlAttribute(image.alt)}"` : ""}/>`,
234
242
  "<pic:cNvPicPr/>",
235
243
  "</pic:nvPicPr>",
236
244
  "<pic:blipFill>",
@@ -264,6 +272,19 @@ function serializeWrapDistanceAttrs(wrap) {
264
272
  return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
265
273
  }
266
274
  /**
275
+ * Serialize the `wp:docPr` extension list.
276
+ *
277
+ * The decorative flag is the one extension folio models; every other `a:ext`
278
+ * the source carried is replayed as it was captured, in its original order,
279
+ * because an extension dropped on save is a fact the document had and lost.
280
+ * The decorative extension goes first: it is the one folio may have added.
281
+ */
282
+ function serializeDocPrExtensions(image) {
283
+ const preserved = image.docPrExtensions ?? [];
284
+ const entries = `${image.decorative === void 0 ? "" : `<a:ext uri="${DECORATIVE_EXTENSION_URI}"><adec:decorative xmlns:adec="${DECORATIVE_NAMESPACE}" val="${image.decorative ? "1" : "0"}"/></a:ext>`}${preserved.join("")}`;
285
+ return entries ? `<a:extLst>${entries}</a:extLst>` : "";
286
+ }
287
+ /**
267
288
  * Serialize drawing/image content (w:drawing) to full DrawingML XML
268
289
  */
269
290
  function serializeDrawingContent(content) {
@@ -278,22 +299,23 @@ function serializeDrawingContent(content) {
278
299
  const effB = image.padding?.bottom ?? 0;
279
300
  const effectExtentEl = `<wp:effectExtent l="${intAttr(effL)}" t="${intAttr(effT)}" r="${intAttr(effR)}" b="${intAttr(effB)}"/>`;
280
301
  const docPrId = getUniqueId(image.id);
281
- const docPrName = image.docPrName ?? image.filename ?? `Picture ${docPrId}`;
282
- const docPrDescription = image.alt !== void 0 ? ` descr="${escapeXml(image.alt)}"` : "";
283
- const docPrTitle = image.title !== void 0 ? ` title="${escapeXml(image.title)}"` : "";
284
- const hlinkClick = image.hlinkRId ? `<a:hlinkClick r:id="${escapeXml(image.hlinkRId)}"/>` : "";
285
- const inlineDocPrAttrs = `id="${docPrId}" name="${escapeXml(docPrName)}"${docPrDescription}${docPrTitle}${image.decorative ? " hidden=\"1\"" : ""}`;
286
- const inlineDocPr = hlinkClick ? `<wp:docPr ${inlineDocPrAttrs}>${hlinkClick}</wp:docPr>` : `<wp:docPr ${inlineDocPrAttrs}/>`;
287
- const anchorDocPrAttrs = `id="${docPrId}" name="${escapeXml(docPrName)}"${docPrDescription}${docPrTitle}`;
288
- const anchorDocPr = hlinkClick ? `<wp:docPr ${anchorDocPrAttrs}>${hlinkClick}</wp:docPr>` : `<wp:docPr ${anchorDocPrAttrs}/>`;
302
+ const docPrNames = serializeNonVisualDrawingNames({
303
+ ...image.docPrName !== void 0 ? { name: image.docPrName } : {},
304
+ ...image.alt !== void 0 ? { alt: image.alt } : {},
305
+ ...image.title !== void 0 ? { title: image.title } : {}
306
+ });
307
+ const hlinkClick = image.hlinkRId ? `<a:hlinkClick r:id="${escapeXmlAttribute(image.hlinkRId)}"/>` : "";
308
+ const docPrAttrs = `id="${docPrId}"${docPrNames}${image.hidden === void 0 ? "" : ` hidden="${image.hidden ? "1" : "0"}"`}`;
309
+ const docPrChildren = `${hlinkClick}${serializeDocPrExtensions(image)}`;
310
+ const docPr = docPrChildren ? `<wp:docPr ${docPrAttrs}>${docPrChildren}</wp:docPr>` : `<wp:docPr ${docPrAttrs}/>`;
289
311
  const graphicFramePr = serializeGraphicFrameLocks(image.frameLocks);
290
- const graphic = serializePicGraphic(image, docPrId);
312
+ const graphic = image.rId === void 0 ? "" : serializePicGraphic(image, image.rId, docPrId);
291
313
  if (!isFloating) return [
292
314
  "<w:drawing>",
293
315
  `<wp:inline${wrapDistanceAttrs}>`,
294
316
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
295
317
  effectExtentEl,
296
- inlineDocPr,
318
+ docPr,
297
319
  graphicFramePr,
298
320
  graphic,
299
321
  "</wp:inline>",
@@ -310,7 +332,7 @@ function serializeDrawingContent(content) {
310
332
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
311
333
  effectExtentEl,
312
334
  wrap,
313
- anchorDocPr,
335
+ docPr,
314
336
  graphicFramePr,
315
337
  graphic,
316
338
  "</wp:anchor>",
@@ -333,7 +355,7 @@ const serializeTextBodyAnchor = (anchor) => {
333
355
  };
334
356
  function serializeGeometryAdjustments(shape) {
335
357
  if (!shape.geometryAdjustments || shape.geometryAdjustments.length === 0) return "<a:avLst/>";
336
- return `<a:avLst>${shape.geometryAdjustments.map(({ name, formula }) => `<a:gd name="${escapeXml(name)}" fmla="${escapeXml(formula)}"/>`).join("")}</a:avLst>`;
358
+ return `<a:avLst>${shape.geometryAdjustments.map(({ name, formula }) => `<a:gd name="${escapeXmlAttribute(name)}" fmla="${escapeXmlAttribute(formula)}"/>`).join("")}</a:avLst>`;
337
359
  }
338
360
  /**
339
361
  * Serialize shape content to full DrawingML XML (wps:wsp inside w:drawing)
@@ -346,7 +368,11 @@ function serializeShapeContent(content) {
346
368
  const isFloating = shape.wrap && shape.wrap.type !== "inline";
347
369
  const wrapDistances = serializeWrapDistanceAttrs(shape.wrap);
348
370
  const docPrId = getUniqueId(shape.id);
349
- const docPrName = shape.name ?? (isTextBox ? `TextBox ${docPrId}` : `Shape ${docPrId}`);
371
+ const docPrNames = serializeNonVisualDrawingNames({
372
+ ...shape.name !== void 0 ? { name: shape.name } : {},
373
+ ...shape.alt !== void 0 ? { alt: shape.alt } : {},
374
+ ...shape.title !== void 0 ? { title: shape.title } : {}
375
+ });
350
376
  let xfrmAttrs = "";
351
377
  if (shape.transform?.rotation) xfrmAttrs += ` rot="${Math.round(shape.transform.rotation * 6e4)}"`;
352
378
  if (shape.transform?.flipH) xfrmAttrs += " flipH=\"1\"";
@@ -380,7 +406,7 @@ function serializeShapeContent(content) {
380
406
  if (tb.autoFit === "shape") autoFitXml = "<a:spAutoFit/>";
381
407
  else if (tb.autoFit === "normal") autoFitXml = "<a:normAutofit/>";
382
408
  else if (tb.autoFit === "none") autoFitXml = "<a:noAutofit/>";
383
- const bodyPrChildren = `${tb.wordArt?.preset ? `<a:prstTxWarp prst="${escapeXml(tb.wordArt.preset)}">${tb.wordArt.adjustments && tb.wordArt.adjustments.length > 0 ? `<a:avLst>${tb.wordArt.adjustments.map(({ name, formula }) => `<a:gd name="${escapeXml(name)}" fmla="${escapeXml(formula)}"/>`).join("")}</a:avLst>` : "<a:avLst/>"}</a:prstTxWarp>` : ""}${autoFitXml}`;
409
+ const bodyPrChildren = `${tb.wordArt?.preset ? `<a:prstTxWarp prst="${escapeXmlAttribute(tb.wordArt.preset)}">${tb.wordArt.adjustments && tb.wordArt.adjustments.length > 0 ? `<a:avLst>${tb.wordArt.adjustments.map(({ name, formula }) => `<a:gd name="${escapeXmlAttribute(name)}" fmla="${escapeXmlAttribute(formula)}"/>`).join("")}</a:avLst>` : "<a:avLst/>"}</a:prstTxWarp>` : ""}${autoFitXml}`;
384
410
  const bodyPrXml = bodyPrChildren ? `<wps:bodyPr ${bpAttrs.join(" ")}>${bodyPrChildren}</wps:bodyPr>` : `<wps:bodyPr ${bpAttrs.join(" ")}/>`;
385
411
  if (isTextBox) textBody = [
386
412
  "<wps:txbx><w:txbxContent>",
@@ -408,7 +434,7 @@ function serializeShapeContent(content) {
408
434
  `<wp:inline${wrapDistances}>`,
409
435
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
410
436
  "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>",
411
- `<wp:docPr id="${docPrId}" name="${escapeXml(docPrName)}"/>`,
437
+ `<wp:docPr id="${docPrId}"${docPrNames}/>`,
412
438
  "<wp:cNvGraphicFramePr/>",
413
439
  graphic,
414
440
  "</wp:inline>",
@@ -426,7 +452,7 @@ function serializeShapeContent(content) {
426
452
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
427
453
  "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>",
428
454
  wrap,
429
- `<wp:docPr id="${docPrId}" name="${escapeXml(docPrName)}"/>`,
455
+ `<wp:docPr id="${docPrId}"${docPrNames}/>`,
430
456
  "<wp:cNvGraphicFramePr/>",
431
457
  graphic,
432
458
  "</wp:anchor>",
@@ -2,10 +2,11 @@ import { getUnserializedSectionPropertyChildNames } from "../sectionParser.js";
2
2
  import { serializeSectionReferenceHistory } from "../sectionReferenceHistory.js";
3
3
  import { serializeBorder } from "./borderSerializer.js";
4
4
  import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
5
- import { escapeXml, intAttr } from "./xmlUtils.js";
5
+ import { intAttr } from "./xmlUtils.js";
6
+ import { escapeXmlAttribute } from "@stll/docx-core";
6
7
  //#region src/docx/serializer/sectionPropertiesSerializer.ts
7
- const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${escapeXml(ref.rId)}"/>`;
8
- const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${escapeXml(ref.rId)}"/>`;
8
+ const serializeHeaderReference = (ref) => `<w:headerReference w:type="${ref.type}" r:id="${escapeXmlAttribute(ref.rId)}"/>`;
9
+ const serializeFooterReference = (ref) => `<w:footerReference w:type="${ref.type}" r:id="${escapeXmlAttribute(ref.rId)}"/>`;
9
10
  function serializeFootnoteProperties(props) {
10
11
  if (!props) return "";
11
12
  const parts = [];
@@ -84,7 +85,7 @@ function serializePageNumbering(props) {
84
85
  if (pageNumbering.format) attrs.push(`w:fmt="${pageNumbering.format}"`);
85
86
  if (pageNumbering.start !== void 0) attrs.push(`w:start="${intAttr(pageNumbering.start)}"`);
86
87
  if (pageNumbering.chapterStyle !== void 0) attrs.push(`w:chapStyle="${intAttr(pageNumbering.chapterStyle)}"`);
87
- if (pageNumbering.chapterSeparator) attrs.push(`w:chapSep="${escapeXml(pageNumbering.chapterSeparator)}"`);
88
+ if (pageNumbering.chapterSeparator) attrs.push(`w:chapSep="${escapeXmlAttribute(pageNumbering.chapterSeparator)}"`);
88
89
  return attrs.length > 0 ? `<w:pgNumType ${attrs.join(" ")}/>` : "";
89
90
  }
90
91
  function serializePageBorders(props) {
@@ -114,10 +115,10 @@ function serializeBackground(props) {
114
115
  const attrs = [];
115
116
  const { background } = props;
116
117
  if (background.color?.auto) attrs.push("w:color=\"auto\"");
117
- else if (background.color?.rgb) attrs.push(`w:color="${escapeXml(background.color.rgb)}"`);
118
- if (background.themeColor ?? background.color?.themeColor) attrs.push(`w:themeColor="${escapeXml(background.themeColor ?? background.color?.themeColor ?? "")}"`);
119
- if (background.themeTint ?? background.color?.themeTint) attrs.push(`w:themeTint="${escapeXml(background.themeTint ?? background.color?.themeTint ?? "")}"`);
120
- if (background.themeShade ?? background.color?.themeShade) attrs.push(`w:themeShade="${escapeXml(background.themeShade ?? background.color?.themeShade ?? "")}"`);
118
+ else if (background.color?.rgb) attrs.push(`w:color="${escapeXmlAttribute(background.color.rgb)}"`);
119
+ if (background.themeColor ?? background.color?.themeColor) attrs.push(`w:themeColor="${escapeXmlAttribute(background.themeColor ?? background.color?.themeColor ?? "")}"`);
120
+ if (background.themeTint ?? background.color?.themeTint) attrs.push(`w:themeTint="${escapeXmlAttribute(background.themeTint ?? background.color?.themeTint ?? "")}"`);
121
+ if (background.themeShade ?? background.color?.themeShade) attrs.push(`w:themeShade="${escapeXmlAttribute(background.themeShade ?? background.color?.themeShade ?? "")}"`);
121
122
  return attrs.length > 0 ? `<w:background ${attrs.join(" ")}/>` : "";
122
123
  }
123
124
  function serializeDocGrid(props) {
@@ -127,7 +128,7 @@ function serializeDocGrid(props) {
127
128
  if (dg.type) attrs.push(`w:type="${dg.type}"`);
128
129
  if (dg.linePitch !== void 0) attrs.push(`w:linePitch="${intAttr(dg.linePitch)}"`);
129
130
  if (dg.charSpace !== void 0) attrs.push(`w:charSpace="${intAttr(dg.charSpace)}"`);
130
- return attrs.length > 0 ? `<w:docGrid ${attrs.join(" ")}/>` : "";
131
+ return attrs.length > 0 ? `<w:docGrid ${attrs.join(" ")}/>` : "<w:docGrid/>";
131
132
  }
132
133
  function serializeOnOffElement(value, name) {
133
134
  if (value === void 0) return "";
@@ -173,7 +174,7 @@ function serializeSectionProperties(props) {
173
174
  serializeOnOffElement(props.noEndnote, "noEndnote"),
174
175
  serializeOnOffElement(props.rtlGutter, "rtlGutter")
175
176
  ]) if (xml) parts.push(xml);
176
- if (props.printerSettingsRelationshipId) parts.push(`<w:printerSettings r:id="${escapeXml(props.printerSettingsRelationshipId)}"/>`);
177
+ if (props.printerSettingsRelationshipId) parts.push(`<w:printerSettings r:id="${escapeXmlAttribute(props.printerSettingsRelationshipId)}"/>`);
177
178
  for (const change of props.propertyChanges ?? []) parts.push(serializeSectionPropertyChange(change));
178
179
  const unserializedChildNames = getUnserializedSectionPropertyChildNames(props);
179
180
  if (unserializedChildNames && unserializedChildNames.length > 0) return "";
@@ -1,5 +1,6 @@
1
1
  import { serializePartElement } from "./partNamespaces.js";
2
- import { escapeXml, intAttr } from "./xmlUtils.js";
2
+ import { intAttr } from "./xmlUtils.js";
3
+ import { escapeXmlAttribute } from "@stll/docx-core";
3
4
  //#region src/docx/serializer/settingsSerializer.ts
4
5
  const serializeSettingsXml = (settings) => {
5
6
  const parts = [`<w:defaultTabStop w:val="${intAttr(settings.defaultTabStop)}"/>`];
@@ -7,8 +8,8 @@ const serializeSettingsXml = (settings) => {
7
8
  if (settings.updateFields) parts.push("<w:updateFields w:val=\"true\"/>");
8
9
  if (settings.themeFontLang) {
9
10
  const attrs = [];
10
- if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${escapeXml(settings.themeFontLang.eastAsia)}"`);
11
- if (settings.themeFontLang.bidi) attrs.push(`w:bidi="${escapeXml(settings.themeFontLang.bidi)}"`);
11
+ if (settings.themeFontLang.eastAsia) attrs.push(`w:eastAsia="${escapeXmlAttribute(settings.themeFontLang.eastAsia)}"`);
12
+ if (settings.themeFontLang.bidi) attrs.push(`w:bidi="${escapeXmlAttribute(settings.themeFontLang.bidi)}"`);
12
13
  if (attrs.length > 0) parts.push(`<w:themeFontLang ${attrs.join(" ")}/>`);
13
14
  }
14
15
  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
@@ -2,7 +2,7 @@ import { serializeParagraphFormatting } from "./paragraphSerializer.js";
2
2
  import { serializePartElement } from "./partNamespaces.js";
3
3
  import { serializeTableCellFormatting, serializeTableFormatting, serializeTableRowFormatting } from "./tableSerializer.js";
4
4
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
5
- import { escapeXml } from "./xmlUtils.js";
5
+ import { escapeXmlAttribute } from "@stll/docx-core";
6
6
  //#region src/docx/serializer/stylesSerializer.ts
7
7
  const serializeStylesXml = (definitions) => {
8
8
  return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + serializePartElement({
@@ -28,12 +28,12 @@ const serializeLatentStyles = (definitions) => {
28
28
  return `<w:latentStyles${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}/>`;
29
29
  };
30
30
  const serializeStyle = (style) => {
31
- const attrs = [`w:type="${style.type}"`, `w:styleId="${escapeXml(style.styleId)}"`];
31
+ const attrs = [`w:type="${style.type}"`, `w:styleId="${escapeXmlAttribute(style.styleId)}"`];
32
32
  if (style.default) attrs.push("w:default=\"1\"");
33
- const parts = [`<w:name w:val="${escapeXml(style.name ?? style.styleId)}"/>`];
34
- if (style.basedOn) parts.push(`<w:basedOn w:val="${escapeXml(style.basedOn)}"/>`);
35
- if (style.next) parts.push(`<w:next w:val="${escapeXml(style.next)}"/>`);
36
- if (style.link) parts.push(`<w:link w:val="${escapeXml(style.link)}"/>`);
33
+ const parts = [`<w:name w:val="${escapeXmlAttribute(style.name ?? style.styleId)}"/>`];
34
+ if (style.basedOn) parts.push(`<w:basedOn w:val="${escapeXmlAttribute(style.basedOn)}"/>`);
35
+ if (style.next) parts.push(`<w:next w:val="${escapeXmlAttribute(style.next)}"/>`);
36
+ if (style.link) parts.push(`<w:link w:val="${escapeXmlAttribute(style.link)}"/>`);
37
37
  if (style.uiPriority !== void 0) parts.push(`<w:uiPriority w:val="${style.uiPriority}"/>`);
38
38
  pushBooleanElement(parts, "hidden", style.hidden);
39
39
  pushBooleanElement(parts, "semiHidden", style.semiHidden);