@stll/folio-core 0.44.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 (90) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +1 -0
  3. package/dist/compare/content-alignment.js +78 -53
  4. package/dist/compare/inline-atoms.js +34 -20
  5. package/dist/content-controls/mutateContentControls.js +4 -2
  6. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  7. package/dist/document-operations.js +14 -3
  8. package/dist/docx/borderParser.js +5 -5
  9. package/dist/docx/commentParser.js +47 -36
  10. package/dist/docx/commentThreadKey.d.ts +18 -0
  11. package/dist/docx/commentThreadKey.js +22 -0
  12. package/dist/docx/diagramPreview.js +87 -27
  13. package/dist/docx/groupDrawingParser.js +3 -3
  14. package/dist/docx/hyperlinkParser.js +2 -2
  15. package/dist/docx/imageParser.d.ts +9 -1
  16. package/dist/docx/imageParser.js +58 -12
  17. package/dist/docx/imageRawXml.d.ts +14 -1
  18. package/dist/docx/imageRawXml.js +30 -6
  19. package/dist/docx/mathToMathml.js +12 -14
  20. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  21. package/dist/docx/nonVisualDrawingProps.js +46 -0
  22. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  23. package/dist/docx/parser.js +2 -2
  24. package/dist/docx/previewBudget.d.ts +64 -0
  25. package/dist/docx/previewBudget.js +88 -0
  26. package/dist/docx/revisionIdNormalization.js +17 -5
  27. package/dist/docx/rezip.js +17 -18
  28. package/dist/docx/sdtPropertiesPatch.js +24 -18
  29. package/dist/docx/sectionReferenceHistory.js +2 -2
  30. package/dist/docx/selectiveSave.js +6 -6
  31. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  32. package/dist/docx/serializer/borderSerializer.d.ts +1 -1
  33. package/dist/docx/serializer/borderSerializer.js +13 -12
  34. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  35. package/dist/docx/serializer/commentSerializer.js +82 -85
  36. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  37. package/dist/docx/serializer/headerFooterSerializer.js +5 -5
  38. package/dist/docx/serializer/markupRangeAttributes.js +2 -2
  39. package/dist/docx/serializer/numberingSerializer.js +7 -6
  40. package/dist/docx/serializer/paragraphSerializer.js +19 -18
  41. package/dist/docx/serializer/partNamespaces.js +2 -2
  42. package/dist/docx/serializer/runSerializer.js +48 -28
  43. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  44. package/dist/docx/serializer/settingsSerializer.js +4 -3
  45. package/dist/docx/serializer/stylesSerializer.js +6 -6
  46. package/dist/docx/serializer/tableSerializer.js +10 -9
  47. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  48. package/dist/docx/serializer/themeSerializer.js +6 -6
  49. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  50. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  51. package/dist/docx/serializer/xmlUtils.js +1 -13
  52. package/dist/docx/server/boundedArchive.d.ts +12 -0
  53. package/dist/docx/server/boundedArchive.js +20 -1
  54. package/dist/docx/server/validateDocxConformance.js +22 -1
  55. package/dist/docx/shapeParser.js +7 -5
  56. package/dist/docx/textBoxParser.js +7 -2
  57. package/dist/docx/unzip.d.ts +23 -0
  58. package/dist/docx/unzip.js +32 -22
  59. package/dist/docx/verbatimCapture.js +1 -1
  60. package/dist/docx/vmlImageParser.js +3 -2
  61. package/dist/docx/vmlPreview.d.ts +1 -3
  62. package/dist/docx/vmlPreview.js +2 -30
  63. package/dist/docx/xmlParser.d.ts +16 -1
  64. package/dist/docx/xmlParser.js +56 -26
  65. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  66. package/dist/docx/xmlResourceLimits.js +105 -24
  67. package/dist/internal/paragraphFormattingSerialization.js +3 -2
  68. package/dist/layout-painter/renderImage.js +4 -3
  69. package/dist/layout-painter/renderParagraph.js +4 -3
  70. package/dist/managers/autoSaveCodec.js +2 -8
  71. package/dist/markdown/images.js +1 -4
  72. package/dist/prosemirror/attrs/index.js +69 -0
  73. package/dist/prosemirror/commands/image.js +1 -0
  74. package/dist/prosemirror/conversion/fromProseDoc.js +69 -29
  75. package/dist/prosemirror/conversion/toProseDoc.js +83 -19
  76. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  77. package/dist/prosemirror/extensions/nodes/ImageExtension.js +4 -0
  78. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +7 -2
  79. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  80. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +6 -3
  81. package/dist/prosemirror/paragraphFormattingProvenance.js +12 -3
  82. package/dist/prosemirror/schema/nodes.d.ts +50 -1
  83. package/dist/utils/base64.d.ts +36 -0
  84. package/dist/utils/base64.js +40 -0
  85. package/dist/utils/clipboard.js +2 -1
  86. package/dist/utils/units.d.ts +10 -1
  87. package/dist/utils/units.js +12 -1
  88. package/dist/utils/urlSecurity.d.ts +8 -2
  89. package/dist/utils/urlSecurity.js +21 -3
  90. package/package.json +2 -2
@@ -10,8 +10,9 @@ import { serializeRun } from "./runSerializer.js";
10
10
  import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
11
11
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
12
12
  import { serializeTrackedChangeAttributes, trackedChangeAttributeRecord } from "./trackedChangeAttributes.js";
13
- import { escapeXml, isSingleWellFormedElement } from "./xmlUtils.js";
13
+ import { isSingleWellFormedElement } from "./xmlUtils.js";
14
14
  import { panic } from "better-result";
15
+ import { escapeXmlAttribute, escapeXmlText } from "@stll/docx-core";
15
16
  import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
16
17
  //#region src/docx/serializer/paragraphSerializer.ts
17
18
  /**
@@ -331,13 +332,13 @@ function serializeParagraphPropertyChange(change) {
331
332
  /** The attribute list of a `w:hyperlink`, without its children. */
332
333
  function hyperlinkAttributes(hyperlink) {
333
334
  const attrs = [];
334
- if (hyperlink.rId) attrs.push(`r:id="${escapeXml(hyperlink.rId)}"`);
335
- if (hyperlink.anchor) attrs.push(`w:anchor="${escapeXml(hyperlink.anchor)}"`);
336
- if (hyperlink.tooltip) attrs.push(`w:tooltip="${escapeXml(hyperlink.tooltip)}"`);
337
- 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)}"`);
338
339
  if (hyperlink.history === true) attrs.push("w:history=\"1\"");
339
340
  else if (hyperlink.history === false) attrs.push("w:history=\"0\"");
340
- if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXml(hyperlink.docLocation)}"`);
341
+ if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXmlAttribute(hyperlink.docLocation)}"`);
341
342
  return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
342
343
  }
343
344
  /** One `w:hyperlink` child, with the caller deciding how a run is written. */
@@ -366,7 +367,7 @@ function serializeBookmarkEnd(bookmark) {
366
367
  }
367
368
  /** Serialize a simple field without changing its authored OOXML field form. */
368
369
  function serializeSimpleField(field) {
369
- const attrs = [`w:instr="${escapeXml(field.instruction)}"`];
370
+ const attrs = [`w:instr="${escapeXmlAttribute(field.instruction)}"`];
370
371
  if (field.fldLock) attrs.push("w:fldLock=\"true\"");
371
372
  if (field.dirty) attrs.push("w:dirty=\"true\"");
372
373
  const contentXml = field.content.map((item) => item.type === "run" ? serializeRun(item) : serializeHyperlink(item)).join("");
@@ -388,7 +389,7 @@ function serializeComplexField(field) {
388
389
  if (field.fieldCode.length > 0) parts.push(...field.fieldCode.map((run) => serializeRun(run)));
389
390
  else if (field.instruction.length > 0) {
390
391
  const spaceAttr = field.instruction.startsWith(" ") || field.instruction.endsWith(" ") || field.instruction.includes(" ") ? " xml:space=\"preserve\"" : "";
391
- 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>`);
392
393
  }
393
394
  parts.push(`<w:r>${rPrXml}<w:fldChar w:fldCharType="separate"/></w:r>`);
394
395
  parts.push(...field.fieldResult.map((run) => serializeRun(run)));
@@ -406,29 +407,29 @@ function serializeComplexField(field) {
406
407
  function synthesizeInlineSdtPr(props) {
407
408
  const prParts = [];
408
409
  if (typeof props.id === "number") prParts.push(`<w:id w:val="${props.id}"/>`);
409
- if (props.alias) prParts.push(`<w:alias w:val="${escapeXml(props.alias)}"/>`);
410
- 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)}"/>`);
411
412
  if (props.lock && props.lock !== "unlocked") prParts.push(`<w:lock w:val="${props.lock}"/>`);
412
- 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>`);
413
414
  if (props.showingPlaceholder) prParts.push("<w:showingPlcHdr/>");
414
415
  switch (props.sdtType) {
415
416
  case "plainText":
416
417
  prParts.push("<w:text/>");
417
418
  break;
418
419
  case "date": {
419
- const fullDateAttr = props.dateValueISO ? ` w:fullDate="${escapeXml(props.dateValueISO)}"` : "";
420
- 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)}"/>` : "";
421
422
  if (fullDateAttr || formatChild) prParts.push(`<w:date${fullDateAttr}>${formatChild}</w:date>`);
422
423
  else prParts.push("<w:date/>");
423
424
  break;
424
425
  }
425
426
  case "dropdown": {
426
- 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("");
427
428
  prParts.push(`<w:dropDownList>${items}</w:dropDownList>`);
428
429
  break;
429
430
  }
430
431
  case "comboBox": {
431
- 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("");
432
433
  prParts.push(`<w:comboBox>${items}</w:comboBox>`);
433
434
  break;
434
435
  }
@@ -585,7 +586,7 @@ function serializeParagraphContent(content, explicitCommentReferenceIds) {
585
586
  case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(content).join(" ")}/>`;
586
587
  case "bidiWrapper": {
587
588
  const tag = content.control === "override" ? "bdo" : "dir";
588
- return `<w:${tag}${content.direction === void 0 ? "" : ` w:val="${escapeXml(content.direction)}"`}>${content.content.map((child) => serializeParagraphContent(child, explicitCommentReferenceIds)).join("")}</w:${tag}>`;
589
+ return `<w:${tag}${content.direction === void 0 ? "" : ` w:val="${escapeXmlAttribute(content.direction)}"`}>${content.content.map((child) => serializeParagraphContent(child, explicitCommentReferenceIds)).join("")}</w:${tag}>`;
589
590
  }
590
591
  case "mathEquation": return content.ommlXml || "";
591
592
  default: return "";
@@ -600,8 +601,8 @@ function serializeParagraphContent(content, explicitCommentReferenceIds) {
600
601
  function serializeParagraph(paragraph) {
601
602
  const parts = [];
602
603
  const attrs = [];
603
- if (paragraph.paraId) attrs.push(`w14:paraId="${escapeXml(paragraph.paraId)}"`);
604
- 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)}"`);
605
606
  if (paragraph.reviewCarrier) attrs.push(`folio:reviewCarrier="${paragraph.reviewCarrier}"`);
606
607
  const attrsStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
607
608
  parts.push(serializeParagraphFormattingWithOptions(paragraph.formatting, {
@@ -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";
6
+ import { serializeNonVisualDrawingNames } from "../nonVisualDrawingProps.js";
5
7
  import { serializeParagraph } from "./paragraphSerializer.js";
6
8
  import { serializeTable } from "./tableSerializer.js";
7
9
  import { serializeTextFormatting } from "./textFormattingSerializer.js";
8
10
  import { getSingularRunPropertyChange, serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
9
- import { escapeXml, intAttr } from "./xmlUtils.js";
11
+ import { intAttr } from "./xmlUtils.js";
10
12
  import { panic } from "better-result";
11
- import { requiresXmlSpacePreserve } from "@stll/docx-core";
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${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${content.font === "" ? "" : ` w:font="${escapeXml(content.font)}"`}${content.char === "" ? "" : ` 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
  }
@@ -216,7 +218,7 @@ function serializeWrap(wrap) {
216
218
  function serializePicGraphic(image, imageRId, sharedId) {
217
219
  const cx = image.size.width;
218
220
  const cy = image.size.height;
219
- const rId = escapeXml(imageRId);
221
+ const rId = escapeXmlAttribute(imageRId);
220
222
  const id = sharedId;
221
223
  const name = image.filename || `image${id}`;
222
224
  let xfrmAttrs = "";
@@ -236,7 +238,7 @@ function serializePicGraphic(image, imageRId, sharedId) {
236
238
  "<a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">",
237
239
  "<pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">",
238
240
  "<pic:nvPicPr>",
239
- `<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)}"` : ""}/>`,
240
242
  "<pic:cNvPicPr/>",
241
243
  "</pic:nvPicPr>",
242
244
  "<pic:blipFill>",
@@ -270,6 +272,19 @@ function serializeWrapDistanceAttrs(wrap) {
270
272
  return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
271
273
  }
272
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
+ /**
273
288
  * Serialize drawing/image content (w:drawing) to full DrawingML XML
274
289
  */
275
290
  function serializeDrawingContent(content) {
@@ -284,14 +299,15 @@ function serializeDrawingContent(content) {
284
299
  const effB = image.padding?.bottom ?? 0;
285
300
  const effectExtentEl = `<wp:effectExtent l="${intAttr(effL)}" t="${intAttr(effT)}" r="${intAttr(effR)}" b="${intAttr(effB)}"/>`;
286
301
  const docPrId = getUniqueId(image.id);
287
- const docPrName = image.docPrName ?? image.filename ?? `Picture ${docPrId}`;
288
- const docPrDescription = image.alt !== void 0 ? ` descr="${escapeXml(image.alt)}"` : "";
289
- const docPrTitle = image.title !== void 0 ? ` title="${escapeXml(image.title)}"` : "";
290
- const hlinkClick = image.hlinkRId ? `<a:hlinkClick r:id="${escapeXml(image.hlinkRId)}"/>` : "";
291
- const inlineDocPrAttrs = `id="${docPrId}" name="${escapeXml(docPrName)}"${docPrDescription}${docPrTitle}${image.decorative ? " hidden=\"1\"" : ""}`;
292
- const inlineDocPr = hlinkClick ? `<wp:docPr ${inlineDocPrAttrs}>${hlinkClick}</wp:docPr>` : `<wp:docPr ${inlineDocPrAttrs}/>`;
293
- const anchorDocPrAttrs = `id="${docPrId}" name="${escapeXml(docPrName)}"${docPrDescription}${docPrTitle}`;
294
- 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}/>`;
295
311
  const graphicFramePr = serializeGraphicFrameLocks(image.frameLocks);
296
312
  const graphic = image.rId === void 0 ? "" : serializePicGraphic(image, image.rId, docPrId);
297
313
  if (!isFloating) return [
@@ -299,7 +315,7 @@ function serializeDrawingContent(content) {
299
315
  `<wp:inline${wrapDistanceAttrs}>`,
300
316
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
301
317
  effectExtentEl,
302
- inlineDocPr,
318
+ docPr,
303
319
  graphicFramePr,
304
320
  graphic,
305
321
  "</wp:inline>",
@@ -316,7 +332,7 @@ function serializeDrawingContent(content) {
316
332
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
317
333
  effectExtentEl,
318
334
  wrap,
319
- anchorDocPr,
335
+ docPr,
320
336
  graphicFramePr,
321
337
  graphic,
322
338
  "</wp:anchor>",
@@ -339,7 +355,7 @@ const serializeTextBodyAnchor = (anchor) => {
339
355
  };
340
356
  function serializeGeometryAdjustments(shape) {
341
357
  if (!shape.geometryAdjustments || shape.geometryAdjustments.length === 0) return "<a:avLst/>";
342
- 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>`;
343
359
  }
344
360
  /**
345
361
  * Serialize shape content to full DrawingML XML (wps:wsp inside w:drawing)
@@ -352,7 +368,11 @@ function serializeShapeContent(content) {
352
368
  const isFloating = shape.wrap && shape.wrap.type !== "inline";
353
369
  const wrapDistances = serializeWrapDistanceAttrs(shape.wrap);
354
370
  const docPrId = getUniqueId(shape.id);
355
- 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
+ });
356
376
  let xfrmAttrs = "";
357
377
  if (shape.transform?.rotation) xfrmAttrs += ` rot="${Math.round(shape.transform.rotation * 6e4)}"`;
358
378
  if (shape.transform?.flipH) xfrmAttrs += " flipH=\"1\"";
@@ -386,7 +406,7 @@ function serializeShapeContent(content) {
386
406
  if (tb.autoFit === "shape") autoFitXml = "<a:spAutoFit/>";
387
407
  else if (tb.autoFit === "normal") autoFitXml = "<a:normAutofit/>";
388
408
  else if (tb.autoFit === "none") autoFitXml = "<a:noAutofit/>";
389
- 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}`;
390
410
  const bodyPrXml = bodyPrChildren ? `<wps:bodyPr ${bpAttrs.join(" ")}>${bodyPrChildren}</wps:bodyPr>` : `<wps:bodyPr ${bpAttrs.join(" ")}/>`;
391
411
  if (isTextBox) textBody = [
392
412
  "<wps:txbx><w:txbxContent>",
@@ -414,7 +434,7 @@ function serializeShapeContent(content) {
414
434
  `<wp:inline${wrapDistances}>`,
415
435
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
416
436
  "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>",
417
- `<wp:docPr id="${docPrId}" name="${escapeXml(docPrName)}"/>`,
437
+ `<wp:docPr id="${docPrId}"${docPrNames}/>`,
418
438
  "<wp:cNvGraphicFramePr/>",
419
439
  graphic,
420
440
  "</wp:inline>",
@@ -432,7 +452,7 @@ function serializeShapeContent(content) {
432
452
  `<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
433
453
  "<wp:effectExtent l=\"0\" t=\"0\" r=\"0\" b=\"0\"/>",
434
454
  wrap,
435
- `<wp:docPr id="${docPrId}" name="${escapeXml(docPrName)}"/>`,
455
+ `<wp:docPr id="${docPrId}"${docPrNames}/>`,
436
456
  "<wp:cNvGraphicFramePr/>",
437
457
  graphic,
438
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);
@@ -7,7 +7,8 @@ import { sanitizeCapturedXmlElement } from "../verbatimCapture.js";
7
7
  import { NAMESPACES, OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
8
8
  import { serializeBorder } from "./borderSerializer.js";
9
9
  import { serializeTrackedChangeAttributes } from "./trackedChangeAttributes.js";
10
- import { escapeXml, intAttr } from "./xmlUtils.js";
10
+ import { intAttr } from "./xmlUtils.js";
11
+ import { escapeXmlAttribute } from "@stll/docx-core";
11
12
  //#region src/docx/serializer/tableSerializer.ts
12
13
  /**
13
14
  * The captured element with the revision children written back into it.
@@ -130,15 +131,15 @@ function serializeCellMargins(margins, elementName) {
130
131
  function serializeShading(shading) {
131
132
  if (!shading) return "";
132
133
  const attrs = [];
133
- if (shading.pattern) attrs.push(`w:val="${escapeXml(shading.pattern)}"`);
134
+ if (shading.pattern) attrs.push(`w:val="${escapeXmlAttribute(shading.pattern)}"`);
134
135
  else attrs.push("w:val=\"clear\"");
135
- if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXml(shading.color.rgb)}"`);
136
+ if (shading.color?.rgb && isValidHexColor(shading.color.rgb)) attrs.push(`w:color="${escapeXmlAttribute(shading.color.rgb)}"`);
136
137
  else if (shading.color?.auto) attrs.push("w:color=\"auto\"");
137
- if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXml(shading.fill.rgb)}"`);
138
+ if (shading.fill?.rgb && isValidHexColor(shading.fill.rgb)) attrs.push(`w:fill="${escapeXmlAttribute(shading.fill.rgb)}"`);
138
139
  else if (shading.fill?.auto) attrs.push("w:fill=\"auto\"");
139
- if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXml(shading.fill.themeColor)}"`);
140
- if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXml(shading.fill.themeTint)}"`);
141
- if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXml(shading.fill.themeShade)}"`);
140
+ if (shading.fill?.themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(shading.fill.themeColor)}"`);
141
+ if (shading.fill?.themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(shading.fill.themeTint)}"`);
142
+ if (shading.fill?.themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(shading.fill.themeShade)}"`);
142
143
  if (attrs.length === 0) return "";
143
144
  return `<w:shd ${attrs.join(" ")}/>`;
144
145
  }
@@ -154,7 +155,7 @@ function serializeShading(shading) {
154
155
  function serializeTableLook(look) {
155
156
  if (!look) return "";
156
157
  const attrs = [];
157
- if (look.val !== void 0) attrs.push(`w:val="${escapeXml(look.val)}"`);
158
+ if (look.val !== void 0) attrs.push(`w:val="${escapeXmlAttribute(look.val)}"`);
158
159
  for (const flag of TABLE_LOOK_FLAGS) {
159
160
  const stated = look[flag];
160
161
  if (stated !== void 0) attrs.push(`w:${flag}="${stated ? "1" : "0"}"`);
@@ -189,7 +190,7 @@ function serializeTableFormatting(formatting, propertyChanges) {
189
190
  if (tableSource !== null) return withRevisionChildren(tableSource, (propertyChanges ?? []).map((change) => serializeTablePropertyChange(change)));
190
191
  const parts = [];
191
192
  if (formatting) {
192
- if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXml(formatting.styleId)}"/>`);
193
+ if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXmlAttribute(formatting.styleId)}"/>`);
193
194
  const floatingXml = serializeFloatingTableProperties(formatting.floating);
194
195
  if (floatingXml) parts.push(floatingXml);
195
196
  if (formatting.overlap) parts.push(`<w:tblOverlap w:val="${formatting.overlap}"/>`);
@@ -1,7 +1,8 @@
1
1
  import { HIGHLIGHT_COLOR_VALUES } from "../../types/documentEnumValues.js";
2
2
  import { isValidHexColor } from "../../utils/colorResolver.js";
3
3
  import { roundHorizontalScalePercentForSerialization } from "../../utils/horizontalScale.js";
4
- import { escapeXml, intAttr } from "./xmlUtils.js";
4
+ import { intAttr } from "./xmlUtils.js";
5
+ import { escapeXmlAttribute } from "@stll/docx-core";
5
6
  //#region src/docx/serializer/textFormattingSerializer.ts
6
7
  const VALID_HIGHLIGHT_COLORS = new Set(HIGHLIGHT_COLOR_VALUES);
7
8
  /**
@@ -12,10 +13,10 @@ function serializeColorElement(color) {
12
13
  const { auto, rgb, themeColor, themeTint, themeShade } = color;
13
14
  const attrs = [];
14
15
  if (auto) attrs.push("w:val=\"auto\"");
15
- else if (rgb && isValidHexColor(rgb)) attrs.push(`w:val="${escapeXml(rgb)}"`);
16
- if (themeColor) attrs.push(`w:themeColor="${escapeXml(themeColor)}"`);
17
- if (themeTint) attrs.push(`w:themeTint="${escapeXml(themeTint)}"`);
18
- if (themeShade) attrs.push(`w:themeShade="${escapeXml(themeShade)}"`);
16
+ else if (rgb && isValidHexColor(rgb)) attrs.push(`w:val="${escapeXmlAttribute(rgb)}"`);
17
+ if (themeColor) attrs.push(`w:themeColor="${escapeXmlAttribute(themeColor)}"`);
18
+ if (themeTint) attrs.push(`w:themeTint="${escapeXmlAttribute(themeTint)}"`);
19
+ if (themeShade) attrs.push(`w:themeShade="${escapeXmlAttribute(themeShade)}"`);
19
20
  return attrs.length === 0 ? "" : `<w:color ${attrs.join(" ")}/>`;
20
21
  }
21
22
  /**
@@ -25,20 +26,20 @@ function serializeShading(shading) {
25
26
  if (!shading) return "";
26
27
  const { pattern, color, fill } = shading;
27
28
  const attrs = [];
28
- if (pattern) attrs.push(`w:val="${escapeXml(pattern)}"`);
29
+ if (pattern) attrs.push(`w:val="${escapeXmlAttribute(pattern)}"`);
29
30
  else attrs.push("w:val=\"clear\"");
30
31
  if (color) {
31
32
  const { rgb, auto, themeColor: _themeColor, themeTint: _themeTint, themeShade: _themeShade } = color;
32
- if (rgb && isValidHexColor(rgb)) attrs.push(`w:color="${escapeXml(rgb)}"`);
33
+ if (rgb && isValidHexColor(rgb)) attrs.push(`w:color="${escapeXmlAttribute(rgb)}"`);
33
34
  else if (auto) attrs.push("w:color=\"auto\"");
34
35
  }
35
36
  if (fill) {
36
37
  const { rgb, auto, themeColor, themeTint, themeShade } = fill;
37
- if (rgb && isValidHexColor(rgb)) attrs.push(`w:fill="${escapeXml(rgb)}"`);
38
+ if (rgb && isValidHexColor(rgb)) attrs.push(`w:fill="${escapeXmlAttribute(rgb)}"`);
38
39
  else if (auto) attrs.push("w:fill=\"auto\"");
39
- if (themeColor) attrs.push(`w:themeFill="${escapeXml(themeColor)}"`);
40
- if (themeTint) attrs.push(`w:themeFillTint="${escapeXml(themeTint)}"`);
41
- if (themeShade) attrs.push(`w:themeFillShade="${escapeXml(themeShade)}"`);
40
+ if (themeColor) attrs.push(`w:themeFill="${escapeXmlAttribute(themeColor)}"`);
41
+ if (themeTint) attrs.push(`w:themeFillTint="${escapeXmlAttribute(themeTint)}"`);
42
+ if (themeShade) attrs.push(`w:themeFillShade="${escapeXmlAttribute(themeShade)}"`);
42
43
  }
43
44
  return attrs.length === 0 ? "" : `<w:shd ${attrs.join(" ")}/>`;
44
45
  }
@@ -49,19 +50,19 @@ function serializeTextFormatting(input) {
49
50
  if (!input) return "";
50
51
  const { bold, boldCs, italic, italicCs, underline, strike, doubleStrike, vertAlign, smallCaps, allCaps, hidden, noProof, color, highlight, shading, fontSize, fontSizeCs, fontFamily, language, spacing, position, scale, kerning, effect, emphasisMark, emboss, imprint, outline, shadow, rtl, cs, styleId } = input;
51
52
  const parts = [];
52
- if (styleId) parts.push(`<w:rStyle w:val="${escapeXml(styleId)}"/>`);
53
+ if (styleId) parts.push(`<w:rStyle w:val="${escapeXmlAttribute(styleId)}"/>`);
53
54
  if (fontFamily) {
54
55
  const { ascii, hAnsi, eastAsia, cs: complexScript, hint, asciiTheme, hAnsiTheme, eastAsiaTheme, csTheme } = fontFamily;
55
56
  const fontAttrs = [];
56
- if (ascii) fontAttrs.push(`w:ascii="${escapeXml(ascii)}"`);
57
- if (hAnsi) fontAttrs.push(`w:hAnsi="${escapeXml(hAnsi)}"`);
58
- if (eastAsia) fontAttrs.push(`w:eastAsia="${escapeXml(eastAsia)}"`);
59
- if (complexScript) fontAttrs.push(`w:cs="${escapeXml(complexScript)}"`);
60
- if (hint) fontAttrs.push(`w:hint="${escapeXml(hint)}"`);
61
- if (asciiTheme) fontAttrs.push(`w:asciiTheme="${escapeXml(asciiTheme)}"`);
62
- if (hAnsiTheme) fontAttrs.push(`w:hAnsiTheme="${escapeXml(hAnsiTheme)}"`);
63
- if (eastAsiaTheme) fontAttrs.push(`w:eastAsiaTheme="${escapeXml(eastAsiaTheme)}"`);
64
- if (csTheme) fontAttrs.push(`w:cstheme="${escapeXml(csTheme)}"`);
57
+ if (ascii) fontAttrs.push(`w:ascii="${escapeXmlAttribute(ascii)}"`);
58
+ if (hAnsi) fontAttrs.push(`w:hAnsi="${escapeXmlAttribute(hAnsi)}"`);
59
+ if (eastAsia) fontAttrs.push(`w:eastAsia="${escapeXmlAttribute(eastAsia)}"`);
60
+ if (complexScript) fontAttrs.push(`w:cs="${escapeXmlAttribute(complexScript)}"`);
61
+ if (hint) fontAttrs.push(`w:hint="${escapeXmlAttribute(hint)}"`);
62
+ if (asciiTheme) fontAttrs.push(`w:asciiTheme="${escapeXmlAttribute(asciiTheme)}"`);
63
+ if (hAnsiTheme) fontAttrs.push(`w:hAnsiTheme="${escapeXmlAttribute(hAnsiTheme)}"`);
64
+ if (eastAsiaTheme) fontAttrs.push(`w:eastAsiaTheme="${escapeXmlAttribute(eastAsiaTheme)}"`);
65
+ if (csTheme) fontAttrs.push(`w:cstheme="${escapeXmlAttribute(csTheme)}"`);
65
66
  if (fontAttrs.length > 0) parts.push(`<w:rFonts ${fontAttrs.join(" ")}/>`);
66
67
  }
67
68
  if (bold === true) parts.push("<w:b/>");
@@ -114,10 +115,10 @@ function serializeTextFormatting(input) {
114
115
  const uAttrs = [`w:val="${style}"`];
115
116
  if (underlineColor) {
116
117
  const { rgb, themeColor, themeTint, themeShade, auto: _auto } = underlineColor;
117
- if (rgb && isValidHexColor(rgb)) uAttrs.push(`w:color="${escapeXml(rgb)}"`);
118
- if (themeColor) uAttrs.push(`w:themeColor="${escapeXml(themeColor)}"`);
119
- if (themeTint) uAttrs.push(`w:themeTint="${escapeXml(themeTint)}"`);
120
- if (themeShade) uAttrs.push(`w:themeShade="${escapeXml(themeShade)}"`);
118
+ if (rgb && isValidHexColor(rgb)) uAttrs.push(`w:color="${escapeXmlAttribute(rgb)}"`);
119
+ if (themeColor) uAttrs.push(`w:themeColor="${escapeXmlAttribute(themeColor)}"`);
120
+ if (themeTint) uAttrs.push(`w:themeTint="${escapeXmlAttribute(themeTint)}"`);
121
+ if (themeShade) uAttrs.push(`w:themeShade="${escapeXmlAttribute(themeShade)}"`);
121
122
  }
122
123
  parts.push(`<w:u ${uAttrs.join(" ")}/>`);
123
124
  }
@@ -133,9 +134,9 @@ function serializeTextFormatting(input) {
133
134
  if (language) {
134
135
  const { val, eastAsia, bidi } = language;
135
136
  const languageAttrs = [];
136
- if (val) languageAttrs.push(`w:val="${escapeXml(val)}"`);
137
- if (eastAsia) languageAttrs.push(`w:eastAsia="${escapeXml(eastAsia)}"`);
138
- if (bidi) languageAttrs.push(`w:bidi="${escapeXml(bidi)}"`);
137
+ if (val) languageAttrs.push(`w:val="${escapeXmlAttribute(val)}"`);
138
+ if (eastAsia) languageAttrs.push(`w:eastAsia="${escapeXmlAttribute(eastAsia)}"`);
139
+ if (bidi) languageAttrs.push(`w:bidi="${escapeXmlAttribute(bidi)}"`);
139
140
  if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
140
141
  }
141
142
  return parts.length === 0 ? "" : `<w:rPr>${parts.join("")}</w:rPr>`;