@beyondwork/docx-react-component 1.0.41 → 1.0.43
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.
- package/package.json +38 -37
- package/src/api/awareness-identity-types.ts +35 -0
- package/src/api/comment-negotiation-types.ts +130 -0
- package/src/api/comment-presentation-types.ts +106 -0
- package/src/api/editor-state-types.ts +110 -0
- package/src/api/external-custody-types.ts +74 -0
- package/src/api/participants-types.ts +18 -0
- package/src/api/public-types.ts +541 -5
- package/src/api/scope-metadata-resolver-types.ts +88 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +601 -9
- package/src/core/search/search-text.ts +15 -2
- package/src/index.ts +131 -1
- package/src/io/docx-session.ts +672 -2
- package/src/io/export/escape-xml-attribute.ts +26 -0
- package/src/io/export/external-send.ts +188 -0
- package/src/io/export/serialize-comments.ts +13 -16
- package/src/io/export/serialize-footnotes.ts +17 -24
- package/src/io/export/serialize-headers-footers.ts +17 -24
- package/src/io/export/serialize-main-document.ts +59 -62
- package/src/io/export/serialize-numbering.ts +20 -27
- package/src/io/export/serialize-runtime-revisions.ts +2 -9
- package/src/io/export/serialize-tables.ts +8 -15
- package/src/io/export/table-properties-xml.ts +25 -32
- package/src/io/import/external-reimport.ts +40 -0
- package/src/io/load-scheduler.ts +230 -0
- package/src/io/normalize/normalize-text.ts +83 -0
- package/src/io/ooxml/bw-xml.ts +244 -0
- package/src/io/ooxml/canonicalize-payload.ts +301 -0
- package/src/io/ooxml/comment-negotiation-payload.ts +288 -0
- package/src/io/ooxml/comment-presentation-payload.ts +311 -0
- package/src/io/ooxml/external-custody-payload.ts +102 -0
- package/src/io/ooxml/participants-payload.ts +97 -0
- package/src/io/ooxml/payload-signature.ts +112 -0
- package/src/io/ooxml/workflow-payload-validator.ts +367 -0
- package/src/io/ooxml/workflow-payload.ts +317 -7
- package/src/runtime/awareness-identity.ts +173 -0
- package/src/runtime/collab/event-types.ts +27 -0
- package/src/runtime/collab-session-bridge.ts +157 -0
- package/src/runtime/collab-session-facet.ts +193 -0
- package/src/runtime/collab-session.ts +273 -0
- package/src/runtime/comment-negotiation-sync.ts +91 -0
- package/src/runtime/comment-negotiation.ts +158 -0
- package/src/runtime/comment-presentation.ts +223 -0
- package/src/runtime/document-runtime.ts +639 -124
- package/src/runtime/editor-state-channel.ts +544 -0
- package/src/runtime/editor-state-integration.ts +217 -0
- package/src/runtime/external-send-runtime.ts +117 -0
- package/src/runtime/layout/docx-font-loader.ts +11 -30
- package/src/runtime/layout/index.ts +2 -0
- package/src/runtime/layout/inert-layout-facet.ts +4 -0
- package/src/runtime/layout/layout-engine-instance.ts +139 -14
- package/src/runtime/layout/page-graph.ts +79 -7
- package/src/runtime/layout/paginated-layout-engine.ts +441 -48
- package/src/runtime/layout/public-facet.ts +585 -14
- package/src/runtime/layout/table-row-split.ts +316 -0
- package/src/runtime/markdown-sanitizer.ts +132 -0
- package/src/runtime/participants.ts +134 -0
- package/src/runtime/perf-counters.ts +28 -0
- package/src/runtime/render/render-frame-types.ts +17 -0
- package/src/runtime/render/render-kernel.ts +172 -29
- package/src/runtime/resign-payload.ts +120 -0
- package/src/runtime/surface-projection.ts +10 -5
- package/src/runtime/tamper-gate.ts +157 -0
- package/src/runtime/workflow-markup.ts +80 -16
- package/src/runtime/workflow-rail-segments.ts +244 -5
- package/src/ui/WordReviewEditor.tsx +654 -45
- package/src/ui/editor-command-bag.ts +14 -0
- package/src/ui/editor-runtime-boundary.ts +111 -11
- package/src/ui/editor-shell-view.tsx +21 -0
- package/src/ui/editor-surface-controller.tsx +5 -0
- package/src/ui/headless/selection-helpers.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +28 -0
- package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +62 -2
- package/src/ui-tailwind/chrome/collab-audience-chip.tsx +73 -0
- package/src/ui-tailwind/chrome/collab-negotiation-action-bar.tsx +244 -0
- package/src/ui-tailwind/chrome/collab-presence-strip.tsx +150 -0
- package/src/ui-tailwind/chrome/collab-role-chip.tsx +62 -0
- package/src/ui-tailwind/chrome/collab-send-to-supplier-button.tsx +68 -0
- package/src/ui-tailwind/chrome/collab-send-to-supplier-modal.tsx +149 -0
- package/src/ui-tailwind/chrome/collab-tamper-banner.tsx +68 -0
- package/src/ui-tailwind/chrome/collab-top-nav-container.tsx +281 -0
- package/src/ui-tailwind/chrome/forward-non-drag-click.ts +104 -0
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +1 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +7 -1
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +1 -38
- package/src/ui-tailwind/chrome-overlay/index.ts +6 -0
- package/src/ui-tailwind/chrome-overlay/scope-card-role-model.ts +78 -0
- package/src/ui-tailwind/chrome-overlay/scope-keyboard-cycle.ts +49 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +106 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +527 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +120 -22
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +310 -32
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +93 -14
- package/src/ui-tailwind/editor-surface/paste-plain-text.ts +72 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +118 -8
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -1
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +86 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +167 -17
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +35 -7
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +20 -3
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +265 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +10 -256
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +66 -0
- package/src/ui-tailwind/index.ts +37 -1
- package/src/ui-tailwind/page-stack/tw-endnote-area.tsx +57 -0
- package/src/ui-tailwind/page-stack/tw-footnote-area.tsx +71 -0
- package/src/ui-tailwind/page-stack/tw-page-footer-band.tsx +73 -0
- package/src/ui-tailwind/page-stack/tw-page-header-band.tsx +74 -0
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +477 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +374 -0
- package/src/ui-tailwind/review/comment-markdown-renderer.tsx +155 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +77 -16
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +29 -3
- package/src/ui-tailwind/status/tw-status-bar.tsx +52 -1
- package/src/ui-tailwind/theme/editor-theme.css +25 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +455 -118
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
serializeTableRowPropertiesXml,
|
|
25
25
|
} from "./table-properties-xml.ts";
|
|
26
26
|
import { twip } from "./twip.ts";
|
|
27
|
+
import { escapeXmlAttribute } from "./escape-xml-attribute.ts";
|
|
27
28
|
|
|
28
29
|
const HYPERLINK_RELATIONSHIP_TYPE =
|
|
29
30
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
@@ -338,10 +339,10 @@ function serializeCustomXmlNode(
|
|
|
338
339
|
}
|
|
339
340
|
const attrs: string[] = [];
|
|
340
341
|
if (block.uri) {
|
|
341
|
-
attrs.push(`w:uri="${
|
|
342
|
+
attrs.push(`w:uri="${escapeXmlAttribute(block.uri)}"`);
|
|
342
343
|
}
|
|
343
344
|
if (block.element) {
|
|
344
|
-
attrs.push(`w:element="${
|
|
345
|
+
attrs.push(`w:element="${escapeXmlAttribute(block.element)}"`);
|
|
345
346
|
}
|
|
346
347
|
const attrXml = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
347
348
|
const childrenXml = block.children.map((child) => serializeBlockNode(child, state)).join("");
|
|
@@ -351,19 +352,19 @@ function serializeCustomXmlNode(
|
|
|
351
352
|
function serializeAltChunkNode(
|
|
352
353
|
block: AltChunkNode,
|
|
353
354
|
): string {
|
|
354
|
-
return `<w:altChunk r:id="${
|
|
355
|
+
return `<w:altChunk r:id="${escapeXmlAttribute(block.relationshipId)}"/>`;
|
|
355
356
|
}
|
|
356
357
|
|
|
357
358
|
function buildSdtPropertiesXml(block: SdtNode): string {
|
|
358
359
|
const children: string[] = [];
|
|
359
360
|
if (block.properties.alias) {
|
|
360
|
-
children.push(`<w:alias w:val="${
|
|
361
|
+
children.push(`<w:alias w:val="${escapeXmlAttribute(block.properties.alias)}"/>`);
|
|
361
362
|
}
|
|
362
363
|
if (block.properties.tag) {
|
|
363
|
-
children.push(`<w:tag w:val="${
|
|
364
|
+
children.push(`<w:tag w:val="${escapeXmlAttribute(block.properties.tag)}"/>`);
|
|
364
365
|
}
|
|
365
366
|
if (block.properties.lock) {
|
|
366
|
-
children.push(`<w:lock w:val="${
|
|
367
|
+
children.push(`<w:lock w:val="${escapeXmlAttribute(block.properties.lock)}"/>`);
|
|
367
368
|
}
|
|
368
369
|
if (block.properties.showingPlcHdr) {
|
|
369
370
|
children.push(`<w:showingPlcHdr/>`);
|
|
@@ -374,26 +375,26 @@ function buildSdtPropertiesXml(block: SdtNode): string {
|
|
|
374
375
|
// ST_OnOff (A.3): w14:val is typed as ST_OnOff; emit "true"/"false"
|
|
375
376
|
// instead of "1"/"0" to satisfy DocumentFormat.OpenXml validation.
|
|
376
377
|
cbParts.push(`<w14:checked w14:val="${cb.checked ? "true" : "false"}"/>`);
|
|
377
|
-
if (cb.checkedChar) cbParts.push(`<w14:checkedState w14:val="${
|
|
378
|
-
if (cb.uncheckedChar) cbParts.push(`<w14:uncheckedState w14:val="${
|
|
378
|
+
if (cb.checkedChar) cbParts.push(`<w14:checkedState w14:val="${escapeXmlAttribute(cb.checkedChar)}"/>`);
|
|
379
|
+
if (cb.uncheckedChar) cbParts.push(`<w14:uncheckedState w14:val="${escapeXmlAttribute(cb.uncheckedChar)}"/>`);
|
|
379
380
|
children.push(`<w14:checkbox>${cbParts.join("")}</w14:checkbox>`);
|
|
380
381
|
} else if (block.properties.datePicker) {
|
|
381
382
|
const dp = block.properties.datePicker;
|
|
382
|
-
const dateAttrs = dp.fullDate ? ` w:fullDate="${
|
|
383
|
+
const dateAttrs = dp.fullDate ? ` w:fullDate="${escapeXmlAttribute(dp.fullDate)}"` : "";
|
|
383
384
|
const dpParts: string[] = [];
|
|
384
|
-
if (dp.dateFormat) dpParts.push(`<w:dateFormat w:val="${
|
|
385
|
-
if (dp.lid) dpParts.push(`<w:lid w:val="${
|
|
385
|
+
if (dp.dateFormat) dpParts.push(`<w:dateFormat w:val="${escapeXmlAttribute(dp.dateFormat)}"/>`);
|
|
386
|
+
if (dp.lid) dpParts.push(`<w:lid w:val="${escapeXmlAttribute(dp.lid)}"/>`);
|
|
386
387
|
children.push(dpParts.length > 0 ? `<w:date${dateAttrs}>${dpParts.join("")}</w:date>` : `<w:date${dateAttrs}/>`);
|
|
387
388
|
} else if (block.properties.dropdownList) {
|
|
388
389
|
const items = block.properties.dropdownList.map((item) => {
|
|
389
|
-
const dt = item.displayText ? ` w:displayText="${
|
|
390
|
-
return `<w:listItem${dt} w:value="${
|
|
390
|
+
const dt = item.displayText ? ` w:displayText="${escapeXmlAttribute(item.displayText)}"` : "";
|
|
391
|
+
return `<w:listItem${dt} w:value="${escapeXmlAttribute(item.value)}"/>`;
|
|
391
392
|
}).join("");
|
|
392
393
|
children.push(`<w:dropDownList>${items}</w:dropDownList>`);
|
|
393
394
|
} else if (block.properties.comboBox) {
|
|
394
395
|
const items = block.properties.comboBox.map((item) => {
|
|
395
|
-
const dt = item.displayText ? ` w:displayText="${
|
|
396
|
-
return `<w:listItem${dt} w:value="${
|
|
396
|
+
const dt = item.displayText ? ` w:displayText="${escapeXmlAttribute(item.displayText)}"` : "";
|
|
397
|
+
return `<w:listItem${dt} w:value="${escapeXmlAttribute(item.value)}"/>`;
|
|
397
398
|
}).join("");
|
|
398
399
|
children.push(`<w:comboBox>${items}</w:comboBox>`);
|
|
399
400
|
} else if (block.properties.sdtType === "plainText") {
|
|
@@ -514,8 +515,8 @@ function serializeTableInlineNode(
|
|
|
514
515
|
return "<w:r><w:br/></w:r>";
|
|
515
516
|
case "symbol": {
|
|
516
517
|
const properties = serializeRunPropertiesFromMarks(node.marks);
|
|
517
|
-
const fontAttribute = node.font ? ` w:font="${
|
|
518
|
-
return `<w:r>${properties}<w:sym${fontAttribute} w:char="${
|
|
518
|
+
const fontAttribute = node.font ? ` w:font="${escapeXmlAttribute(node.font)}"` : "";
|
|
519
|
+
return `<w:r>${properties}<w:sym${fontAttribute} w:char="${escapeXmlAttribute(node.char)}"/></w:r>`;
|
|
519
520
|
}
|
|
520
521
|
case "image":
|
|
521
522
|
return serializeImageNode(node, state);
|
|
@@ -529,7 +530,7 @@ function serializeTableInlineNode(
|
|
|
529
530
|
return wrapInlineRawXml(node.rawXml);
|
|
530
531
|
case "hyperlink": {
|
|
531
532
|
const hyperlinkOpen = node.href.startsWith("#")
|
|
532
|
-
? `<w:hyperlink w:anchor="${
|
|
533
|
+
? `<w:hyperlink w:anchor="${escapeXmlAttribute(node.href.slice(1))}">`
|
|
533
534
|
: (() => {
|
|
534
535
|
const relationshipId = `rIdHyperlink${state.nextHyperlinkRelationshipIndex}`;
|
|
535
536
|
state.nextHyperlinkRelationshipIndex += 1;
|
|
@@ -559,22 +560,22 @@ function serializeTableInlineNode(
|
|
|
559
560
|
`<w:r><w:fldChar w:fldCharType="end"/></w:r>`
|
|
560
561
|
);
|
|
561
562
|
}
|
|
562
|
-
return `<w:fldSimple w:instr="${
|
|
563
|
+
return `<w:fldSimple w:instr="${escapeXmlAttribute(node.instruction)}">${childrenXml}</w:fldSimple>`;
|
|
563
564
|
}
|
|
564
|
-
return `<w:fldSimple w:instr="${
|
|
565
|
+
return `<w:fldSimple w:instr="${escapeXmlAttribute(node.instruction)}"/>`;
|
|
565
566
|
}
|
|
566
567
|
case "bookmark_start":
|
|
567
568
|
return (
|
|
568
|
-
`<w:bookmarkStart w:id="${
|
|
569
|
-
` w:name="${
|
|
569
|
+
`<w:bookmarkStart w:id="${escapeXmlAttribute(node.bookmarkId)}"` +
|
|
570
|
+
` w:name="${escapeXmlAttribute(node.name)}"/>`
|
|
570
571
|
);
|
|
571
572
|
case "bookmark_end":
|
|
572
|
-
return `<w:bookmarkEnd w:id="${
|
|
573
|
+
return `<w:bookmarkEnd w:id="${escapeXmlAttribute(node.bookmarkId)}"/>`;
|
|
573
574
|
case "footnote_ref": {
|
|
574
575
|
const refElement =
|
|
575
576
|
node.noteKind === "footnote"
|
|
576
|
-
? `<w:footnoteReference w:id="${
|
|
577
|
-
: `<w:endnoteReference w:id="${
|
|
577
|
+
? `<w:footnoteReference w:id="${escapeXmlAttribute(node.noteId)}"/>`
|
|
578
|
+
: `<w:endnoteReference w:id="${escapeXmlAttribute(node.noteId)}"/>`;
|
|
578
579
|
const styleVal =
|
|
579
580
|
node.noteKind === "footnote"
|
|
580
581
|
? "FootnoteReference"
|
|
@@ -590,7 +591,7 @@ function buildParagraphPropertiesXml(paragraph: ParagraphNode): string {
|
|
|
590
591
|
const children: string[] = [];
|
|
591
592
|
|
|
592
593
|
if (paragraph.styleId) {
|
|
593
|
-
children.push(`<w:pStyle w:val="${
|
|
594
|
+
children.push(`<w:pStyle w:val="${escapeXmlAttribute(paragraph.styleId)}"/>`);
|
|
594
595
|
}
|
|
595
596
|
if (paragraph.keepNext) {
|
|
596
597
|
children.push("<w:keepNext/>");
|
|
@@ -662,7 +663,7 @@ function buildParagraphPropertiesXml(paragraph: ParagraphNode): string {
|
|
|
662
663
|
children.push("<w:suppressLineNumbers/>");
|
|
663
664
|
}
|
|
664
665
|
if (paragraph.cnfStyle) {
|
|
665
|
-
children.push(`<w:cnfStyle w:val="${
|
|
666
|
+
children.push(`<w:cnfStyle w:val="${escapeXmlAttribute(paragraph.cnfStyle)}"/>`);
|
|
666
667
|
}
|
|
667
668
|
if (paragraph.tabStops && paragraph.tabStops.length > 0) {
|
|
668
669
|
const tabsXml = paragraph.tabStops.map((tab) => {
|
|
@@ -706,10 +707,10 @@ function serializeBorder(name: string, border: BorderSpec | undefined): string {
|
|
|
706
707
|
return "";
|
|
707
708
|
}
|
|
708
709
|
const attrs: string[] = [];
|
|
709
|
-
if (border.value) attrs.push(`w:val="${
|
|
710
|
+
if (border.value) attrs.push(`w:val="${escapeXmlAttribute(border.value)}"`);
|
|
710
711
|
if (border.size !== undefined) attrs.push(`w:sz="${twip(border.size)}"`);
|
|
711
712
|
if (border.space !== undefined) attrs.push(`w:space="${twip(border.space)}"`);
|
|
712
|
-
if (border.color) attrs.push(`w:color="${
|
|
713
|
+
if (border.color) attrs.push(`w:color="${escapeXmlAttribute(border.color)}"`);
|
|
713
714
|
return attrs.length > 0 ? `<w:${name} ${attrs.join(" ")}/>` : "";
|
|
714
715
|
}
|
|
715
716
|
|
|
@@ -718,13 +719,13 @@ function serializeParagraphShading(shading: ParagraphNode["shading"]): string {
|
|
|
718
719
|
return "";
|
|
719
720
|
}
|
|
720
721
|
const attrs: string[] = [];
|
|
721
|
-
if (shading.val) attrs.push(`w:val="${
|
|
722
|
-
if (shading.color) attrs.push(`w:color="${
|
|
722
|
+
if (shading.val) attrs.push(`w:val="${escapeXmlAttribute(shading.val)}"`);
|
|
723
|
+
if (shading.color) attrs.push(`w:color="${escapeXmlAttribute(shading.color)}"`);
|
|
723
724
|
// A.9: w:shd w:val="clear" must always carry w:fill. Emit w:fill="auto"
|
|
724
725
|
// when the canonical model has no explicit fill; otherwise the SDK
|
|
725
726
|
// flags the shading as incomplete.
|
|
726
727
|
if (shading.fill !== undefined) {
|
|
727
|
-
attrs.push(`w:fill="${
|
|
728
|
+
attrs.push(`w:fill="${escapeXmlAttribute(shading.fill)}"`);
|
|
728
729
|
} else if (shading.val === "clear") {
|
|
729
730
|
attrs.push(`w:fill="auto"`);
|
|
730
731
|
}
|
|
@@ -946,7 +947,7 @@ function serializeInlineNode(
|
|
|
946
947
|
}
|
|
947
948
|
case "hyperlink": {
|
|
948
949
|
const hyperlinkOpen = node.href.startsWith("#")
|
|
949
|
-
? `<w:hyperlink w:anchor="${
|
|
950
|
+
? `<w:hyperlink w:anchor="${escapeXmlAttribute(node.href.slice(1))}">`
|
|
950
951
|
: (() => {
|
|
951
952
|
const relationshipId = `rIdHyperlink${state.nextHyperlinkRelationshipIndex}`;
|
|
952
953
|
state.nextHyperlinkRelationshipIndex += 1;
|
|
@@ -1015,7 +1016,7 @@ function serializeInlineNode(
|
|
|
1015
1016
|
}
|
|
1016
1017
|
|
|
1017
1018
|
// Simple field with children
|
|
1018
|
-
const openXml = `<w:fldSimple w:instr="${
|
|
1019
|
+
const openXml = `<w:fldSimple w:instr="${escapeXmlAttribute(node.instruction)}">`;
|
|
1019
1020
|
nextOffset += openXml.length;
|
|
1020
1021
|
const children: string[] = [openXml];
|
|
1021
1022
|
for (const child of node.children) {
|
|
@@ -1033,7 +1034,7 @@ function serializeInlineNode(
|
|
|
1033
1034
|
return { xml: children.join(""), cursor: nextCursor, boundaries };
|
|
1034
1035
|
}
|
|
1035
1036
|
|
|
1036
|
-
const xml = `<w:fldSimple w:instr="${
|
|
1037
|
+
const xml = `<w:fldSimple w:instr="${escapeXmlAttribute(node.instruction)}"/>`;
|
|
1037
1038
|
const boundaries = new Map<number, number>();
|
|
1038
1039
|
boundaries.set(cursor, xmlOffset);
|
|
1039
1040
|
boundaries.set(cursor + 1, xmlOffset + xml.length);
|
|
@@ -1045,15 +1046,15 @@ function serializeInlineNode(
|
|
|
1045
1046
|
}
|
|
1046
1047
|
case "bookmark_start": {
|
|
1047
1048
|
const xml =
|
|
1048
|
-
`<w:bookmarkStart w:id="${
|
|
1049
|
-
` w:name="${
|
|
1049
|
+
`<w:bookmarkStart w:id="${escapeXmlAttribute(node.bookmarkId)}"` +
|
|
1050
|
+
` w:name="${escapeXmlAttribute(node.name)}"/>`;
|
|
1050
1051
|
const boundaries = new Map<number, number>();
|
|
1051
1052
|
boundaries.set(cursor, xmlOffset);
|
|
1052
1053
|
boundaries.set(cursor, xmlOffset + xml.length);
|
|
1053
1054
|
return { xml, cursor, boundaries };
|
|
1054
1055
|
}
|
|
1055
1056
|
case "bookmark_end": {
|
|
1056
|
-
const xml = `<w:bookmarkEnd w:id="${
|
|
1057
|
+
const xml = `<w:bookmarkEnd w:id="${escapeXmlAttribute(node.bookmarkId)}"/>`;
|
|
1057
1058
|
const boundaries = new Map<number, number>();
|
|
1058
1059
|
boundaries.set(cursor, xmlOffset);
|
|
1059
1060
|
boundaries.set(cursor, xmlOffset + xml.length);
|
|
@@ -1062,8 +1063,8 @@ function serializeInlineNode(
|
|
|
1062
1063
|
case "footnote_ref": {
|
|
1063
1064
|
const refElement =
|
|
1064
1065
|
node.noteKind === "footnote"
|
|
1065
|
-
? `<w:footnoteReference w:id="${
|
|
1066
|
-
: `<w:endnoteReference w:id="${
|
|
1066
|
+
? `<w:footnoteReference w:id="${escapeXmlAttribute(node.noteId)}"/>`
|
|
1067
|
+
: `<w:endnoteReference w:id="${escapeXmlAttribute(node.noteId)}"/>`;
|
|
1067
1068
|
const styleVal =
|
|
1068
1069
|
node.noteKind === "footnote"
|
|
1069
1070
|
? "FootnoteReference"
|
|
@@ -1098,7 +1099,7 @@ function serializeImageNode(
|
|
|
1098
1099
|
const mediaItem = state.media.items[node.mediaId];
|
|
1099
1100
|
if (mediaItem?.relationshipId && state.existingRelationshipMap.has(mediaItem.relationshipId)) {
|
|
1100
1101
|
const altText = node.altText ?? mediaItem.altText ?? mediaItem.filename;
|
|
1101
|
-
return `<w:r><w:drawing><wp:inline xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"><wp:extent cx="9525" cy="9525"/><wp:docPr id="1" name="${
|
|
1102
|
+
return `<w:r><w:drawing><wp:inline xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"><wp:extent cx="9525" cy="9525"/><wp:docPr id="1" name="${escapeXmlAttribute(mediaItem.filename)}" descr="${escapeXmlAttribute(altText ?? "")}"/><wp:cNvGraphicFramePr/><a:graphic><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic><pic:nvPicPr><pic:cNvPr id="0" name="${escapeXmlAttribute(mediaItem.filename)}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${escapeXmlAttribute(mediaItem.relationshipId)}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="9525" cy="9525"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
1102
1103
|
}
|
|
1103
1104
|
|
|
1104
1105
|
return serializeRun({
|
|
@@ -1141,14 +1142,14 @@ function serializeRunProperties(marks: TextMark[] | undefined): string {
|
|
|
1141
1142
|
markParts.push("<w:vanish/>");
|
|
1142
1143
|
break;
|
|
1143
1144
|
case "lang":
|
|
1144
|
-
markParts.push(`<w:lang w:val="${
|
|
1145
|
+
markParts.push(`<w:lang w:val="${escapeXmlAttribute(mark.val)}"/>`);
|
|
1145
1146
|
break;
|
|
1146
1147
|
case "highlight":
|
|
1147
|
-
markParts.push(`<w:highlight w:val="${
|
|
1148
|
+
markParts.push(`<w:highlight w:val="${escapeXmlAttribute(mark.val)}"/>`);
|
|
1148
1149
|
break;
|
|
1149
1150
|
case "backgroundColor":
|
|
1150
1151
|
markParts.push(
|
|
1151
|
-
`<w:shd w:val="clear" w:color="auto" w:fill="${
|
|
1152
|
+
`<w:shd w:val="clear" w:color="auto" w:fill="${escapeXmlAttribute(mark.color)}"/>`,
|
|
1152
1153
|
);
|
|
1153
1154
|
break;
|
|
1154
1155
|
case "charSpacing":
|
|
@@ -1173,13 +1174,13 @@ function serializeRunProperties(marks: TextMark[] | undefined): string {
|
|
|
1173
1174
|
markParts.push(mark.xml);
|
|
1174
1175
|
break;
|
|
1175
1176
|
case "fontFamily":
|
|
1176
|
-
markParts.push(`<w:rFonts w:ascii="${
|
|
1177
|
+
markParts.push(`<w:rFonts w:ascii="${escapeXmlAttribute(mark.val)}" w:hAnsi="${escapeXmlAttribute(mark.val)}"/>`);
|
|
1177
1178
|
break;
|
|
1178
1179
|
case "fontSize":
|
|
1179
1180
|
markParts.push(`<w:sz w:val="${mark.val}"/>`);
|
|
1180
1181
|
break;
|
|
1181
1182
|
case "textColor":
|
|
1182
|
-
markParts.push(`<w:color w:val="${
|
|
1183
|
+
markParts.push(`<w:color w:val="${escapeXmlAttribute(mark.color)}"/>`);
|
|
1183
1184
|
break;
|
|
1184
1185
|
case "smallCaps":
|
|
1185
1186
|
markParts.push("<w:smallCaps/>");
|
|
@@ -1228,10 +1229,6 @@ function escapeXml(value: string): string {
|
|
|
1228
1229
|
.replace(/>/g, ">");
|
|
1229
1230
|
}
|
|
1230
1231
|
|
|
1231
|
-
function escapeAttribute(value: string): string {
|
|
1232
|
-
return escapeXml(value).replace(/"/g, """);
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
1232
|
function offsetParagraphBoundary(
|
|
1236
1233
|
boundary: RevisionParagraphBoundary,
|
|
1237
1234
|
offset: number,
|
|
@@ -1426,7 +1423,7 @@ export function renderDeterministicRootAttributes(
|
|
|
1426
1423
|
...otherEntries,
|
|
1427
1424
|
];
|
|
1428
1425
|
return ordered
|
|
1429
|
-
.map(([name, value]) => ` ${name}="${
|
|
1426
|
+
.map(([name, value]) => ` ${name}="${escapeXmlAttribute(value)}"`)
|
|
1430
1427
|
.join("");
|
|
1431
1428
|
}
|
|
1432
1429
|
|
|
@@ -1436,20 +1433,20 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1436
1433
|
// Header references
|
|
1437
1434
|
if (props.headerReferences) {
|
|
1438
1435
|
for (const ref of props.headerReferences) {
|
|
1439
|
-
children.push(`<w:headerReference w:type="${
|
|
1436
|
+
children.push(`<w:headerReference w:type="${escapeXmlAttribute(ref.variant)}" r:id="${escapeXmlAttribute(ref.relationshipId)}"/>`);
|
|
1440
1437
|
}
|
|
1441
1438
|
}
|
|
1442
1439
|
|
|
1443
1440
|
// Footer references
|
|
1444
1441
|
if (props.footerReferences) {
|
|
1445
1442
|
for (const ref of props.footerReferences) {
|
|
1446
|
-
children.push(`<w:footerReference w:type="${
|
|
1443
|
+
children.push(`<w:footerReference w:type="${escapeXmlAttribute(ref.variant)}" r:id="${escapeXmlAttribute(ref.relationshipId)}"/>`);
|
|
1447
1444
|
}
|
|
1448
1445
|
}
|
|
1449
1446
|
|
|
1450
1447
|
// Section type
|
|
1451
1448
|
if (props.sectionType) {
|
|
1452
|
-
children.push(`<w:type w:val="${
|
|
1449
|
+
children.push(`<w:type w:val="${escapeXmlAttribute(props.sectionType)}"/>`);
|
|
1453
1450
|
}
|
|
1454
1451
|
|
|
1455
1452
|
// Page size
|
|
@@ -1502,10 +1499,10 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1502
1499
|
// Page numbering
|
|
1503
1500
|
if (props.pageNumbering) {
|
|
1504
1501
|
let pgNum = "<w:pgNumType";
|
|
1505
|
-
if (props.pageNumbering.format) pgNum += ` w:fmt="${
|
|
1502
|
+
if (props.pageNumbering.format) pgNum += ` w:fmt="${escapeXmlAttribute(props.pageNumbering.format)}"`;
|
|
1506
1503
|
if (props.pageNumbering.start !== undefined) pgNum += ` w:start="${twip(props.pageNumbering.start)}"`;
|
|
1507
|
-
if (props.pageNumbering.chapStyle) pgNum += ` w:chapStyle="${
|
|
1508
|
-
if (props.pageNumbering.chapSep) pgNum += ` w:chapSep="${
|
|
1504
|
+
if (props.pageNumbering.chapStyle) pgNum += ` w:chapStyle="${escapeXmlAttribute(props.pageNumbering.chapStyle)}"`;
|
|
1505
|
+
if (props.pageNumbering.chapSep) pgNum += ` w:chapSep="${escapeXmlAttribute(props.pageNumbering.chapSep)}"`;
|
|
1509
1506
|
pgNum += "/>";
|
|
1510
1507
|
children.push(pgNum);
|
|
1511
1508
|
}
|
|
@@ -1522,7 +1519,7 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1522
1519
|
lineNumbering += ` w:distance="${twip(props.lineNumbering.distance)}"`;
|
|
1523
1520
|
}
|
|
1524
1521
|
if (props.lineNumbering.restart) {
|
|
1525
|
-
lineNumbering += ` w:restart="${
|
|
1522
|
+
lineNumbering += ` w:restart="${escapeXmlAttribute(props.lineNumbering.restart)}"`;
|
|
1526
1523
|
}
|
|
1527
1524
|
lineNumbering += "/>";
|
|
1528
1525
|
children.push(lineNumbering);
|
|
@@ -1531,13 +1528,13 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1531
1528
|
if (props.pageBorders) {
|
|
1532
1529
|
const attrs: string[] = [];
|
|
1533
1530
|
if (props.pageBorders.offsetFrom) {
|
|
1534
|
-
attrs.push(`w:offsetFrom="${
|
|
1531
|
+
attrs.push(`w:offsetFrom="${escapeXmlAttribute(props.pageBorders.offsetFrom)}"`);
|
|
1535
1532
|
}
|
|
1536
1533
|
if (props.pageBorders.display) {
|
|
1537
|
-
attrs.push(`w:display="${
|
|
1534
|
+
attrs.push(`w:display="${escapeXmlAttribute(props.pageBorders.display)}"`);
|
|
1538
1535
|
}
|
|
1539
1536
|
if (props.pageBorders.zOrder) {
|
|
1540
|
-
attrs.push(`w:zOrder="${
|
|
1537
|
+
attrs.push(`w:zOrder="${escapeXmlAttribute(props.pageBorders.zOrder)}"`);
|
|
1541
1538
|
}
|
|
1542
1539
|
const borderXml = [
|
|
1543
1540
|
serializeBorder("top", props.pageBorders.top),
|
|
@@ -1560,7 +1557,7 @@ export function serializeSectionPropertiesXml(props: SectionProperties): string
|
|
|
1560
1557
|
if (props.documentGrid) {
|
|
1561
1558
|
const attrs: string[] = [];
|
|
1562
1559
|
if (props.documentGrid.type) {
|
|
1563
|
-
attrs.push(`w:type="${
|
|
1560
|
+
attrs.push(`w:type="${escapeXmlAttribute(props.documentGrid.type)}"`);
|
|
1564
1561
|
}
|
|
1565
1562
|
if (props.documentGrid.linePitch !== undefined) {
|
|
1566
1563
|
attrs.push(`w:linePitch="${props.documentGrid.linePitch}"`);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "../ooxml/numbering-sentinels.ts";
|
|
6
6
|
import { buildRunPropertiesXml } from "./serialize-run-formatting.ts";
|
|
7
7
|
import { twip } from "./twip.ts";
|
|
8
|
+
import { escapeXmlAttribute } from "./escape-xml-attribute.ts";
|
|
8
9
|
|
|
9
10
|
export const WORD_NUMBERING_CONTENT_TYPE =
|
|
10
11
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
|
|
@@ -50,7 +51,7 @@ export function serializeParagraphNumberingProperties(
|
|
|
50
51
|
return "";
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
return `<w:numPr><w:ilvl w:val="${clampIlvl(numbering.level)}"/><w:numId w:val="${
|
|
54
|
+
return `<w:numPr><w:ilvl w:val="${clampIlvl(numbering.level)}"/><w:numId w:val="${escapeXmlAttribute(
|
|
54
55
|
clampNonNegativeIdString(
|
|
55
56
|
stripCanonicalPrefix(numbering.numberingInstanceId, "num:"),
|
|
56
57
|
),
|
|
@@ -58,7 +59,7 @@ export function serializeParagraphNumberingProperties(
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function serializeAbstractDefinition(definition: NumberingCatalog["abstractDefinitions"][string]): string {
|
|
61
|
-
const abstractNumId =
|
|
62
|
+
const abstractNumId = escapeXmlAttribute(
|
|
62
63
|
clampNonNegativeIdString(
|
|
63
64
|
stripCanonicalPrefix(definition.abstractNumberingId, "abstract-num:"),
|
|
64
65
|
),
|
|
@@ -67,19 +68,19 @@ function serializeAbstractDefinition(definition: NumberingCatalog["abstractDefin
|
|
|
67
68
|
// ECMA-376 abstractNum child order: nsid, multiLevelType, tmpl, styleLink,
|
|
68
69
|
// numStyleLink — all before the <w:lvl> children.
|
|
69
70
|
const nsid = definition.nsid
|
|
70
|
-
? `<w:nsid w:val="${
|
|
71
|
+
? `<w:nsid w:val="${escapeXmlAttribute(definition.nsid)}"/>`
|
|
71
72
|
: "";
|
|
72
73
|
const multiLevelType = definition.multiLevelType
|
|
73
|
-
? `<w:multiLevelType w:val="${
|
|
74
|
+
? `<w:multiLevelType w:val="${escapeXmlAttribute(definition.multiLevelType)}"/>`
|
|
74
75
|
: "";
|
|
75
76
|
const tmpl = definition.tplc
|
|
76
|
-
? `<w:tmpl w:val="${
|
|
77
|
+
? `<w:tmpl w:val="${escapeXmlAttribute(definition.tplc)}"/>`
|
|
77
78
|
: "";
|
|
78
79
|
const styleLink = definition.styleLink
|
|
79
|
-
? `<w:styleLink w:val="${
|
|
80
|
+
? `<w:styleLink w:val="${escapeXmlAttribute(definition.styleLink)}"/>`
|
|
80
81
|
: "";
|
|
81
82
|
const numStyleLink = definition.numStyleLink
|
|
82
|
-
? `<w:numStyleLink w:val="${
|
|
83
|
+
? `<w:numStyleLink w:val="${escapeXmlAttribute(definition.numStyleLink)}"/>`
|
|
83
84
|
: "";
|
|
84
85
|
|
|
85
86
|
const levels = [...definition.levels]
|
|
@@ -100,19 +101,19 @@ function serializeLevel(
|
|
|
100
101
|
level.startAt !== undefined
|
|
101
102
|
? `<w:start w:val="${clampStart(level.startAt)}"/>`
|
|
102
103
|
: "";
|
|
103
|
-
const numFmt = `<w:numFmt w:val="${
|
|
104
|
+
const numFmt = `<w:numFmt w:val="${escapeXmlAttribute(level.format)}"/>`;
|
|
104
105
|
const lvlRestart =
|
|
105
106
|
level.restartAfterLevel !== undefined
|
|
106
107
|
? `<w:lvlRestart w:val="${level.restartAfterLevel}"/>`
|
|
107
108
|
: "";
|
|
108
109
|
const paragraphStyle = level.paragraphStyleId
|
|
109
|
-
? `<w:pStyle w:val="${
|
|
110
|
+
? `<w:pStyle w:val="${escapeXmlAttribute(level.paragraphStyleId)}"/>`
|
|
110
111
|
: "";
|
|
111
112
|
const isLegal = level.isLegalNumbering ? "<w:isLgl/>" : "";
|
|
112
|
-
const suffix = level.suffix ? `<w:suff w:val="${
|
|
113
|
-
const lvlText = `<w:lvlText w:val="${
|
|
113
|
+
const suffix = level.suffix ? `<w:suff w:val="${escapeXmlAttribute(level.suffix)}"/>` : "";
|
|
114
|
+
const lvlText = `<w:lvlText w:val="${escapeXmlAttribute(level.text)}"/>`;
|
|
114
115
|
const justification = level.paragraphGeometry?.justification
|
|
115
|
-
? `<w:lvlJc w:val="${
|
|
116
|
+
? `<w:lvlJc w:val="${escapeXmlAttribute(level.paragraphGeometry.justification)}"/>`
|
|
116
117
|
: "";
|
|
117
118
|
const paragraphProperties = serializeLevelParagraphGeometry(level.paragraphGeometry);
|
|
118
119
|
const runProperties = buildRunPropertiesXml(level.runProperties);
|
|
@@ -134,13 +135,13 @@ function serializeLevelOverride(
|
|
|
134
135
|
level.startAt !== undefined
|
|
135
136
|
? `<w:start w:val="${clampStart(level.startAt)}"/>`
|
|
136
137
|
: "";
|
|
137
|
-
const format = level.format ? `<w:numFmt w:val="${
|
|
138
|
+
const format = level.format ? `<w:numFmt w:val="${escapeXmlAttribute(level.format)}"/>` : "";
|
|
138
139
|
const lvlRestart =
|
|
139
140
|
level.restartAfterLevel !== undefined
|
|
140
141
|
? `<w:lvlRestart w:val="${level.restartAfterLevel}"/>`
|
|
141
142
|
: "";
|
|
142
143
|
const paragraphStyle = level.paragraphStyleId
|
|
143
|
-
? `<w:pStyle w:val="${
|
|
144
|
+
? `<w:pStyle w:val="${escapeXmlAttribute(level.paragraphStyleId)}"/>`
|
|
144
145
|
: "";
|
|
145
146
|
// ST_OnOff element (A.3):
|
|
146
147
|
// - true → <w:isLgl/>.
|
|
@@ -153,12 +154,12 @@ function serializeLevelOverride(
|
|
|
153
154
|
: level.isLegalNumbering === false
|
|
154
155
|
? `<w:isLgl w:val="false"/>`
|
|
155
156
|
: "";
|
|
156
|
-
const suffix = level.suffix ? `<w:suff w:val="${
|
|
157
|
+
const suffix = level.suffix ? `<w:suff w:val="${escapeXmlAttribute(level.suffix)}"/>` : "";
|
|
157
158
|
const text = level.text !== undefined
|
|
158
|
-
? `<w:lvlText w:val="${
|
|
159
|
+
? `<w:lvlText w:val="${escapeXmlAttribute(level.text)}"/>`
|
|
159
160
|
: "";
|
|
160
161
|
const justification = level.paragraphGeometry?.justification
|
|
161
|
-
? `<w:lvlJc w:val="${
|
|
162
|
+
? `<w:lvlJc w:val="${escapeXmlAttribute(level.paragraphGeometry.justification)}"/>`
|
|
162
163
|
: "";
|
|
163
164
|
const paragraphProperties = serializeLevelParagraphGeometry(level.paragraphGeometry);
|
|
164
165
|
const runProperties = buildRunPropertiesXml(level.runProperties);
|
|
@@ -170,10 +171,10 @@ function serializeLevelOverride(
|
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
function serializeInstance(instance: NumberingCatalog["instances"][string]): string {
|
|
173
|
-
const numId =
|
|
174
|
+
const numId = escapeXmlAttribute(
|
|
174
175
|
clampNonNegativeIdString(stripCanonicalPrefix(instance.numberingInstanceId, "num:")),
|
|
175
176
|
);
|
|
176
|
-
const abstractNumId =
|
|
177
|
+
const abstractNumId = escapeXmlAttribute(
|
|
177
178
|
clampNonNegativeIdString(
|
|
178
179
|
stripCanonicalPrefix(instance.abstractNumberingId, "abstract-num:"),
|
|
179
180
|
),
|
|
@@ -332,11 +333,3 @@ function stripKnownPrefix(value: string): string {
|
|
|
332
333
|
function stripCanonicalPrefix(value: string, prefix: "abstract-num:" | "num:"): string {
|
|
333
334
|
return value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
|
334
335
|
}
|
|
335
|
-
|
|
336
|
-
function escapeAttribute(value: string): string {
|
|
337
|
-
return value
|
|
338
|
-
.replace(/&/g, "&")
|
|
339
|
-
.replace(/"/g, """)
|
|
340
|
-
.replace(/</g, "<")
|
|
341
|
-
.replace(/>/g, ">");
|
|
342
|
-
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
mapRevisionBoundaries,
|
|
4
4
|
type RevisionParagraphBoundary,
|
|
5
5
|
} from "../ooxml/revision-boundaries.ts";
|
|
6
|
+
import { escapeXmlAttribute } from "./escape-xml-attribute.ts";
|
|
6
7
|
|
|
7
8
|
interface XmlReplacement {
|
|
8
9
|
start: number;
|
|
@@ -360,7 +361,7 @@ function serializeRevisionAttributes(revision: RevisionRecord): string {
|
|
|
360
361
|
|
|
361
362
|
return Object.entries(attributes)
|
|
362
363
|
.filter(([, value]) => value && value.length > 0)
|
|
363
|
-
.map(([name, value]) => ` ${name}="${
|
|
364
|
+
.map(([name, value]) => ` ${name}="${escapeXmlAttribute(value)}"`)
|
|
364
365
|
.join("");
|
|
365
366
|
}
|
|
366
367
|
|
|
@@ -453,11 +454,3 @@ function applyReplacements(documentXml: string, replacements: readonly XmlReplac
|
|
|
453
454
|
|
|
454
455
|
return output;
|
|
455
456
|
}
|
|
456
|
-
|
|
457
|
-
function escapeAttribute(value: string): string {
|
|
458
|
-
return value
|
|
459
|
-
.replace(/&/g, "&")
|
|
460
|
-
.replace(/</g, "<")
|
|
461
|
-
.replace(/>/g, ">")
|
|
462
|
-
.replace(/"/g, """);
|
|
463
|
-
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ParsedTableWidth,
|
|
12
12
|
} from "../ooxml/parse-tables.ts";
|
|
13
13
|
import { twip } from "./twip.ts";
|
|
14
|
+
import { escapeXmlAttribute } from "./escape-xml-attribute.ts";
|
|
14
15
|
|
|
15
16
|
export function serializeTable(table: ParsedTable): string {
|
|
16
17
|
const propertiesXml = table.propertiesXml ?? buildTablePropertiesXml(table);
|
|
@@ -60,10 +61,10 @@ function buildTablePropertiesXml(table: ParsedTable): string {
|
|
|
60
61
|
children.push(table.bidiVisual ? `<w:bidiVisual/>` : `<w:bidiVisual w:val="0"/>`);
|
|
61
62
|
}
|
|
62
63
|
if (table.caption !== undefined) {
|
|
63
|
-
children.push(`<w:tblCaption w:val="${
|
|
64
|
+
children.push(`<w:tblCaption w:val="${escapeXmlAttribute(table.caption)}"/>`);
|
|
64
65
|
}
|
|
65
66
|
if (table.description !== undefined) {
|
|
66
|
-
children.push(`<w:tblDescription w:val="${
|
|
67
|
+
children.push(`<w:tblDescription w:val="${escapeXmlAttribute(table.description)}"/>`);
|
|
67
68
|
}
|
|
68
69
|
if (table.floating) {
|
|
69
70
|
const floatingXml = serializeTableFloating(table.floating);
|
|
@@ -106,7 +107,7 @@ function serializeTableFloating(floating: NonNullable<ParsedTable["floating"]>):
|
|
|
106
107
|
function buildRowPropertiesXml(row: ParsedTableRow): string {
|
|
107
108
|
const children: string[] = [];
|
|
108
109
|
if (row.cnfStyle) {
|
|
109
|
-
children.push(`<w:cnfStyle w:val="${
|
|
110
|
+
children.push(`<w:cnfStyle w:val="${escapeXmlAttribute(row.cnfStyle)}"/>`);
|
|
110
111
|
}
|
|
111
112
|
if (row.cantSplit !== undefined) {
|
|
112
113
|
children.push(row.cantSplit ? `<w:cantSplit/>` : `<w:cantSplit w:val="0"/>`);
|
|
@@ -131,7 +132,7 @@ function ensureCellProperties(cell: ParsedTableCell): string {
|
|
|
131
132
|
|
|
132
133
|
const children: string[] = [];
|
|
133
134
|
if (cell.cnfStyle) {
|
|
134
|
-
children.push(`<w:cnfStyle w:val="${
|
|
135
|
+
children.push(`<w:cnfStyle w:val="${escapeXmlAttribute(cell.cnfStyle)}"/>`);
|
|
135
136
|
}
|
|
136
137
|
if (cell.width) {
|
|
137
138
|
children.push(serializeWidth("tcW", cell.width));
|
|
@@ -174,15 +175,15 @@ function ensureCellProperties(cell: ParsedTableCell): string {
|
|
|
174
175
|
}
|
|
175
176
|
|
|
176
177
|
function serializeWidth(element: string, width: ParsedTableWidth): string {
|
|
177
|
-
return `<w:${element} w:w="${twip(width.value)}" w:type="${width.type}"/>`;
|
|
178
|
+
return `<w:${element} w:w="${twip(width.value)}" w:type="${escapeXmlAttribute(width.type)}"/>`;
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
function serializeBorderSpec(element: string, spec: ParsedBorderSpec): string {
|
|
181
182
|
const attrs: string[] = [];
|
|
182
|
-
if (spec.value) attrs.push(`w:val="${spec.value}"`);
|
|
183
|
+
if (spec.value) attrs.push(`w:val="${escapeXmlAttribute(spec.value)}"`);
|
|
183
184
|
if (spec.size !== undefined) attrs.push(`w:sz="${twip(spec.size)}"`);
|
|
184
185
|
if (spec.space !== undefined) attrs.push(`w:space="${twip(spec.space)}"`);
|
|
185
|
-
if (spec.color) attrs.push(`w:color="${spec.color}"`);
|
|
186
|
+
if (spec.color) attrs.push(`w:color="${escapeXmlAttribute(spec.color)}"`);
|
|
186
187
|
const attrsStr = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
187
188
|
return `<w:${element}${attrsStr}/>`;
|
|
188
189
|
}
|
|
@@ -240,11 +241,3 @@ function serializeTableCellMargins(margins: ParsedCellMargins): string {
|
|
|
240
241
|
if (margins.right !== undefined) parts.push(`<w:right w:w="${twip(margins.right)}" w:type="dxa"/>`);
|
|
241
242
|
return parts.join("");
|
|
242
243
|
}
|
|
243
|
-
|
|
244
|
-
function escapeAttribute(value: string): string {
|
|
245
|
-
return value
|
|
246
|
-
.replace(/&/gu, "&")
|
|
247
|
-
.replace(/"/gu, """)
|
|
248
|
-
.replace(/</gu, "<")
|
|
249
|
-
.replace(/>/gu, ">");
|
|
250
|
-
}
|