@beyondwork/docx-react-component 1.0.18 → 1.0.20
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/README.md +8 -2
- package/package.json +24 -34
- package/src/api/README.md +5 -1
- package/src/api/public-types.ts +710 -4
- package/src/api/session-state.ts +60 -0
- package/src/core/commands/formatting-commands.ts +2 -1
- package/src/core/commands/image-commands.ts +147 -0
- package/src/core/commands/index.ts +19 -3
- package/src/core/commands/list-commands.ts +231 -36
- package/src/core/commands/paragraph-layout-commands.ts +339 -0
- package/src/core/commands/section-layout-commands.ts +680 -0
- package/src/core/commands/style-commands.ts +262 -0
- package/src/core/search/search-text.ts +357 -0
- package/src/core/selection/mapping.ts +41 -0
- package/src/core/state/editor-state.ts +4 -1
- package/src/index.ts +51 -0
- package/src/io/docx-session.ts +623 -56
- package/src/io/export/serialize-comments.ts +104 -34
- package/src/io/export/serialize-footnotes.ts +198 -1
- package/src/io/export/serialize-headers-footers.ts +203 -10
- package/src/io/export/serialize-main-document.ts +285 -8
- package/src/io/export/serialize-numbering.ts +28 -7
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +144 -32
- package/src/io/ooxml/highlight-colors.ts +39 -0
- package/src/io/ooxml/numbering-sentinels.ts +44 -0
- package/src/io/ooxml/parse-comments.ts +85 -19
- package/src/io/ooxml/parse-fields.ts +396 -0
- package/src/io/ooxml/parse-footnotes.ts +452 -22
- package/src/io/ooxml/parse-headers-footers.ts +657 -29
- package/src/io/ooxml/parse-inline-media.ts +30 -0
- package/src/io/ooxml/parse-main-document.ts +807 -20
- package/src/io/ooxml/parse-numbering.ts +7 -0
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/io/ooxml/parse-settings.ts +184 -0
- package/src/io/ooxml/parse-shapes.ts +25 -0
- package/src/io/ooxml/parse-styles.ts +463 -0
- package/src/io/ooxml/parse-theme.ts +32 -0
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +250 -4
- package/src/model/cds-1.0.0.ts +13 -0
- package/src/model/snapshot.ts +87 -2
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-layout.ts +332 -0
- package/src/runtime/document-navigation.ts +603 -0
- package/src/runtime/document-runtime.ts +1754 -78
- package/src/runtime/document-search.ts +145 -0
- package/src/runtime/numbering-prefix.ts +47 -26
- package/src/runtime/page-layout-estimation.ts +212 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +9 -0
- package/src/runtime/session-capabilities.ts +35 -3
- package/src/runtime/story-context.ts +164 -0
- package/src/runtime/story-targeting.ts +162 -0
- package/src/runtime/surface-projection.ts +324 -36
- package/src/runtime/table-schema.ts +89 -7
- package/src/runtime/view-state.ts +477 -0
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +2469 -1344
- package/src/ui/browser-export.ts +52 -0
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1422 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +51 -0
- package/src/ui/headless/preserve-editor-selection.ts +5 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/headless/selection-helpers.ts +20 -0
- package/src/ui/headless/selection-toolbar-model.ts +22 -0
- package/src/ui/headless/use-editor-keyboard.ts +6 -1
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +18 -2
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +129 -0
- package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
- package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +34 -0
- package/src/ui-tailwind/chrome/tw-page-ruler.tsx +386 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +150 -14
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +46 -7
- package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +35 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +3 -3
- package/src/ui-tailwind/editor-surface/pm-schema.ts +186 -13
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +191 -68
- package/src/ui-tailwind/editor-surface/search-plugin.ts +19 -68
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +51 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +11 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +528 -85
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +0 -1
- package/src/ui-tailwind/index.ts +2 -1
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +277 -147
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +8 -8
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +127 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +4 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +829 -12
- package/src/ui-tailwind/tw-review-workspace.tsx +1238 -42
- package/src/validation/compatibility-engine.ts +119 -24
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/diagnostics.ts +1 -0
- package/src/validation/docx-comment-proof.ts +707 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BlockNode,
|
|
2
3
|
FooterDocument,
|
|
3
4
|
HeaderDocument,
|
|
4
5
|
InlineNode,
|
|
5
6
|
ParagraphNode,
|
|
7
|
+
TableCellNode,
|
|
8
|
+
TableNode,
|
|
9
|
+
TableRowNode,
|
|
6
10
|
TextMark,
|
|
7
11
|
} from "../../model/canonical-document.ts";
|
|
8
12
|
|
|
@@ -13,6 +17,17 @@ export const WORD_FOOTER_CONTENT_TYPE =
|
|
|
13
17
|
|
|
14
18
|
const W_NS = `xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"`;
|
|
15
19
|
const R_NS = `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"`;
|
|
20
|
+
const MC_NS = `xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"`;
|
|
21
|
+
const O_NS = `xmlns:o="urn:schemas-microsoft-com:office:office"`;
|
|
22
|
+
const V_NS = `xmlns:v="urn:schemas-microsoft-com:vml"`;
|
|
23
|
+
const WP_NS = `xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"`;
|
|
24
|
+
const WPS_NS = `xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"`;
|
|
25
|
+
const A_NS = `xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"`;
|
|
26
|
+
const W10_NS = `xmlns:w10="urn:schemas-microsoft-com:office:word"`;
|
|
27
|
+
const W14_NS = `xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"`;
|
|
28
|
+
const W15_NS = `xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"`;
|
|
29
|
+
const WP14_NS = `xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"`;
|
|
30
|
+
const EXTENDED_ROOT_ATTRS = `${MC_NS} ${O_NS} ${V_NS} ${WP_NS} ${WPS_NS} ${A_NS} ${W10_NS} ${W14_NS} ${W15_NS} ${WP14_NS} mc:Ignorable="wps wp14 w14 w15"`;
|
|
16
31
|
|
|
17
32
|
/**
|
|
18
33
|
* Serialize a HeaderDocument into a headerN.xml string.
|
|
@@ -21,7 +36,7 @@ export function serializeHeaderXml(header: HeaderDocument): string {
|
|
|
21
36
|
const body = serializeBlocks(header.blocks);
|
|
22
37
|
return [
|
|
23
38
|
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
24
|
-
`<w:hdr ${W_NS} ${R_NS}>${body}</w:hdr>`,
|
|
39
|
+
`<w:hdr ${W_NS} ${R_NS} ${EXTENDED_ROOT_ATTRS}>${body}</w:hdr>`,
|
|
25
40
|
].join("\n");
|
|
26
41
|
}
|
|
27
42
|
|
|
@@ -32,7 +47,7 @@ export function serializeFooterXml(footer: FooterDocument): string {
|
|
|
32
47
|
const body = serializeBlocks(footer.blocks);
|
|
33
48
|
return [
|
|
34
49
|
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`,
|
|
35
|
-
`<w:ftr ${W_NS} ${R_NS}>${body}</w:ftr>`,
|
|
50
|
+
`<w:ftr ${W_NS} ${R_NS} ${EXTENDED_ROOT_ATTRS}>${body}</w:ftr>`,
|
|
36
51
|
].join("\n");
|
|
37
52
|
}
|
|
38
53
|
|
|
@@ -51,6 +66,12 @@ function serializeBlocks(
|
|
|
51
66
|
if (block.type === "paragraph") {
|
|
52
67
|
return serializeParagraph(block);
|
|
53
68
|
}
|
|
69
|
+
if (block.type === "table") {
|
|
70
|
+
return serializeTable(block);
|
|
71
|
+
}
|
|
72
|
+
if (block.type === "opaque_block" && typeof block.rawXml === "string") {
|
|
73
|
+
return block.rawXml;
|
|
74
|
+
}
|
|
54
75
|
throw new Error(`Cannot safely serialize ${block.type} content in header/footer sub-parts.`);
|
|
55
76
|
})
|
|
56
77
|
.join("");
|
|
@@ -73,15 +94,88 @@ function serializeParagraph(paragraph: ParagraphNode): string {
|
|
|
73
94
|
return xml;
|
|
74
95
|
}
|
|
75
96
|
|
|
97
|
+
function serializeTable(table: TableNode): string {
|
|
98
|
+
let xml = "<w:tbl>";
|
|
99
|
+
if (table.propertiesXml) {
|
|
100
|
+
xml += table.propertiesXml;
|
|
101
|
+
}
|
|
102
|
+
if (table.gridColumns.length > 0) {
|
|
103
|
+
xml += "<w:tblGrid>";
|
|
104
|
+
for (const width of table.gridColumns) {
|
|
105
|
+
xml += `<w:gridCol w:w="${width}"/>`;
|
|
106
|
+
}
|
|
107
|
+
xml += "</w:tblGrid>";
|
|
108
|
+
}
|
|
109
|
+
for (const row of table.rows) {
|
|
110
|
+
xml += serializeTableRow(row);
|
|
111
|
+
}
|
|
112
|
+
xml += "</w:tbl>";
|
|
113
|
+
return xml;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function serializeTableRow(row: TableRowNode): string {
|
|
117
|
+
let xml = "<w:tr>";
|
|
118
|
+
if (row.propertiesXml) {
|
|
119
|
+
xml += wrapPropertiesXml("w:trPr", row.propertiesXml);
|
|
120
|
+
}
|
|
121
|
+
for (const cell of row.cells) {
|
|
122
|
+
xml += serializeTableCell(cell);
|
|
123
|
+
}
|
|
124
|
+
xml += "</w:tr>";
|
|
125
|
+
return xml;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function serializeTableCell(cell: TableCellNode): string {
|
|
129
|
+
let xml = "<w:tc>";
|
|
130
|
+
const propertiesXml = buildTableCellPropertiesXml(cell);
|
|
131
|
+
if (propertiesXml) {
|
|
132
|
+
xml += propertiesXml;
|
|
133
|
+
}
|
|
134
|
+
for (const child of cell.children) {
|
|
135
|
+
if (child.type === "paragraph") {
|
|
136
|
+
xml += serializeParagraph(child);
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error(`Cannot safely serialize ${child.type} content in header/footer table cells.`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
xml += "</w:tc>";
|
|
142
|
+
return xml;
|
|
143
|
+
}
|
|
144
|
+
|
|
76
145
|
function buildParagraphPropertiesXml(paragraph: ParagraphNode): string {
|
|
77
146
|
const parts: string[] = [];
|
|
78
147
|
|
|
79
148
|
if (paragraph.styleId) {
|
|
80
149
|
parts.push(`<w:pStyle w:val="${escapeAttribute(paragraph.styleId)}"/>`);
|
|
81
150
|
}
|
|
151
|
+
if (paragraph.spacing) {
|
|
152
|
+
const s = paragraph.spacing;
|
|
153
|
+
const attrs: string[] = [];
|
|
154
|
+
if (s.before !== undefined) attrs.push(`w:before="${s.before}"`);
|
|
155
|
+
if (s.after !== undefined) attrs.push(`w:after="${s.after}"`);
|
|
156
|
+
if (s.line !== undefined) attrs.push(`w:line="${s.line}"`);
|
|
157
|
+
if (s.lineRule) attrs.push(`w:lineRule="${escapeAttribute(s.lineRule)}"`);
|
|
158
|
+
if (attrs.length > 0) parts.push(`<w:spacing ${attrs.join(" ")}/>`);
|
|
159
|
+
}
|
|
160
|
+
if (paragraph.indentation) {
|
|
161
|
+
const ind = paragraph.indentation;
|
|
162
|
+
const attrs: string[] = [];
|
|
163
|
+
if (ind.left !== undefined) attrs.push(`w:left="${ind.left}"`);
|
|
164
|
+
if (ind.right !== undefined) attrs.push(`w:right="${ind.right}"`);
|
|
165
|
+
if (ind.firstLine !== undefined) attrs.push(`w:firstLine="${ind.firstLine}"`);
|
|
166
|
+
if (ind.hanging !== undefined) attrs.push(`w:hanging="${ind.hanging}"`);
|
|
167
|
+
if (attrs.length > 0) parts.push(`<w:ind ${attrs.join(" ")}/>`);
|
|
168
|
+
}
|
|
82
169
|
if (paragraph.alignment) {
|
|
83
170
|
parts.push(`<w:jc w:val="${escapeAttribute(paragraph.alignment)}"/>`);
|
|
84
171
|
}
|
|
172
|
+
if (paragraph.tabStops && paragraph.tabStops.length > 0) {
|
|
173
|
+
const tabsXml = paragraph.tabStops.map((tab) => {
|
|
174
|
+
const leaderAttr = tab.leader ? ` w:leader="${escapeAttribute(tab.leader)}"` : "";
|
|
175
|
+
return `<w:tab w:val="${tab.align}" w:pos="${tab.position}"${leaderAttr}/>`;
|
|
176
|
+
}).join("");
|
|
177
|
+
parts.push(`<w:tabs>${tabsXml}</w:tabs>`);
|
|
178
|
+
}
|
|
85
179
|
|
|
86
180
|
return parts.length > 0 ? `<w:pPr>${parts.join("")}</w:pPr>` : "";
|
|
87
181
|
}
|
|
@@ -106,25 +200,51 @@ function serializeInlineNode(node: InlineNode): string {
|
|
|
106
200
|
: `<w:endnoteReference w:id="${escapeAttribute(node.noteId)}"/>`;
|
|
107
201
|
return `<w:r><w:rPr><w:rStyle w:val="${node.noteKind === "footnote" ? "FootnoteReference" : "EndnoteReference"}"/></w:rPr>${refElement}</w:r>`;
|
|
108
202
|
}
|
|
109
|
-
case "opaque_inline":
|
|
110
|
-
throw new Error(`Cannot safely serialize ${node.type} content in header/footer sub-parts.`);
|
|
111
|
-
case "hyperlink":
|
|
112
|
-
case "image":
|
|
113
|
-
case "field":
|
|
114
203
|
case "bookmark_start":
|
|
204
|
+
return `<w:bookmarkStart w:id="${escapeAttribute(node.bookmarkId)}" w:name="${escapeAttribute(node.name)}"/>`;
|
|
115
205
|
case "bookmark_end":
|
|
116
|
-
|
|
117
|
-
case "
|
|
206
|
+
return `<w:bookmarkEnd w:id="${escapeAttribute(node.bookmarkId)}"/>`;
|
|
207
|
+
case "field":
|
|
208
|
+
if (node.children && node.children.length > 0) {
|
|
209
|
+
const childrenXml = node.children.map((child) => serializeInlineNode(child)).join("");
|
|
210
|
+
if (node.fieldType === "complex") {
|
|
211
|
+
return (
|
|
212
|
+
`<w:r><w:fldChar w:fldCharType="begin"/></w:r>` +
|
|
213
|
+
`<w:r><w:instrText xml:space="preserve">${escapeXml(node.instruction)}</w:instrText></w:r>` +
|
|
214
|
+
`<w:r><w:fldChar w:fldCharType="separate"/></w:r>` +
|
|
215
|
+
childrenXml +
|
|
216
|
+
`<w:r><w:fldChar w:fldCharType="end"/></w:r>`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}">${childrenXml}</w:fldSimple>`;
|
|
220
|
+
}
|
|
221
|
+
if (node.fieldType === "simple") {
|
|
222
|
+
return `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}"/>`;
|
|
223
|
+
}
|
|
224
|
+
return `<w:r><w:fldChar w:fldCharType="begin"/></w:r><w:r><w:instrText xml:space="preserve">${escapeXml(node.instruction)}</w:instrText></w:r><w:r><w:fldChar w:fldCharType="separate"/></w:r><w:r><w:fldChar w:fldCharType="end"/></w:r>`;
|
|
225
|
+
case "hyperlink":
|
|
226
|
+
return serializeHyperlinkNode(node);
|
|
118
227
|
case "chart_preview":
|
|
119
228
|
case "smartart_preview":
|
|
120
229
|
case "shape":
|
|
121
230
|
case "wordart":
|
|
122
231
|
case "vml_shape":
|
|
232
|
+
return wrapInlineRawXml(node.rawXml);
|
|
233
|
+
case "opaque_inline":
|
|
234
|
+
throw new Error(`Cannot safely serialize ${node.type} content in header/footer sub-parts.`);
|
|
235
|
+
case "image":
|
|
236
|
+
case "column_break":
|
|
237
|
+
case "symbol":
|
|
123
238
|
default:
|
|
124
239
|
throw new Error(`Cannot safely serialize ${node.type} content in header/footer sub-parts.`);
|
|
125
240
|
}
|
|
126
241
|
}
|
|
127
242
|
|
|
243
|
+
function wrapInlineRawXml(rawXml: string): string {
|
|
244
|
+
const trimmed = rawXml.trimStart();
|
|
245
|
+
return trimmed.startsWith("<w:r") ? rawXml : `<w:r>${rawXml}</w:r>`;
|
|
246
|
+
}
|
|
247
|
+
|
|
128
248
|
function buildRunPropertiesXml(marks: TextMark[] | undefined): string {
|
|
129
249
|
if (!marks || marks.length === 0) {
|
|
130
250
|
return "";
|
|
@@ -148,8 +268,31 @@ function buildRunPropertiesXml(marks: TextMark[] | undefined): string {
|
|
|
148
268
|
case "doubleStrikethrough":
|
|
149
269
|
parts.push("<w:dstrike/>");
|
|
150
270
|
break;
|
|
271
|
+
case "fontFamily":
|
|
272
|
+
parts.push(`<w:rFonts w:ascii="${escapeAttribute(mark.val)}" w:hAnsi="${escapeAttribute(mark.val)}"/>`);
|
|
273
|
+
break;
|
|
274
|
+
case "fontSize":
|
|
275
|
+
parts.push(`<w:sz w:val="${mark.val}"/>`);
|
|
276
|
+
break;
|
|
277
|
+
case "textColor":
|
|
278
|
+
parts.push(`<w:color w:val="${escapeAttribute(mark.color)}"/>`);
|
|
279
|
+
break;
|
|
280
|
+
case "highlight":
|
|
281
|
+
parts.push(`<w:highlight w:val="${escapeAttribute(mark.val)}"/>`);
|
|
282
|
+
break;
|
|
283
|
+
case "backgroundColor":
|
|
284
|
+
parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${escapeAttribute(mark.color)}"/>`);
|
|
285
|
+
break;
|
|
286
|
+
case "smallCaps":
|
|
287
|
+
parts.push("<w:smallCaps/>");
|
|
288
|
+
break;
|
|
289
|
+
case "allCaps":
|
|
290
|
+
parts.push("<w:caps/>");
|
|
291
|
+
break;
|
|
151
292
|
default:
|
|
152
|
-
|
|
293
|
+
// Marks outside the secondary-story contract are silently dropped
|
|
294
|
+
// to avoid export failure on content the parser extracted safely.
|
|
295
|
+
break;
|
|
153
296
|
}
|
|
154
297
|
}
|
|
155
298
|
|
|
@@ -177,3 +320,53 @@ function escapeAttribute(value: string): string {
|
|
|
177
320
|
.replace(/>/g, ">")
|
|
178
321
|
.replace(/"/g, """);
|
|
179
322
|
}
|
|
323
|
+
|
|
324
|
+
function serializeHyperlinkNode(node: Extract<InlineNode, { type: "hyperlink" }>): string {
|
|
325
|
+
const childrenXml = node.children.map((child) => serializeInlineNode(child)).join("");
|
|
326
|
+
if (node.href.startsWith("#")) {
|
|
327
|
+
return `<w:hyperlink w:anchor="${escapeAttribute(node.href.slice(1))}">${childrenXml}</w:hyperlink>`;
|
|
328
|
+
}
|
|
329
|
+
if (!/^rId[A-Za-z0-9._-]+$/u.test(node.href)) {
|
|
330
|
+
throw new Error("Cannot safely serialize URL-backed header/footer hyperlinks without relationship context.");
|
|
331
|
+
}
|
|
332
|
+
return `<w:hyperlink r:id="${escapeAttribute(node.href)}">${childrenXml}</w:hyperlink>`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function wrapPropertiesXml(tagName: "w:trPr" | "w:tcPr", xml: string): string {
|
|
336
|
+
const trimmed = xml.trim();
|
|
337
|
+
if (trimmed.startsWith(`<${tagName}`)) {
|
|
338
|
+
return trimmed;
|
|
339
|
+
}
|
|
340
|
+
return `<${tagName}>${trimmed}</${tagName}>`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function extractWrappedChildren(tagName: "w:tcPr", xml: string | undefined): string {
|
|
344
|
+
if (!xml) {
|
|
345
|
+
return "";
|
|
346
|
+
}
|
|
347
|
+
const trimmed = xml.trim();
|
|
348
|
+
const wrapped = new RegExp(`^<${tagName}\\b[^>]*>([\\s\\S]*)</${tagName}>$`, "u").exec(trimmed);
|
|
349
|
+
return wrapped?.[1] ?? trimmed;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function buildTableCellPropertiesXml(cell: TableCellNode): string {
|
|
353
|
+
const innerXml = extractWrappedChildren("w:tcPr", cell.propertiesXml)
|
|
354
|
+
.replace(/<w:gridSpan\b[^>]*\/>/gu, "")
|
|
355
|
+
.replace(/<w:vMerge\b[^>]*\/>/gu, "")
|
|
356
|
+
.trim();
|
|
357
|
+
const parts: string[] = [];
|
|
358
|
+
if (cell.gridSpan && cell.gridSpan > 1) {
|
|
359
|
+
parts.push(`<w:gridSpan w:val="${cell.gridSpan}"/>`);
|
|
360
|
+
}
|
|
361
|
+
if (cell.verticalMerge) {
|
|
362
|
+
parts.push(
|
|
363
|
+
cell.verticalMerge === "restart"
|
|
364
|
+
? `<w:vMerge w:val="restart"/>`
|
|
365
|
+
: `<w:vMerge/>`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
if (innerXml.length > 0) {
|
|
369
|
+
parts.push(innerXml);
|
|
370
|
+
}
|
|
371
|
+
return parts.length > 0 ? `<w:tcPr>${parts.join("")}</w:tcPr>` : "";
|
|
372
|
+
}
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ParagraphNode,
|
|
9
9
|
PreservationStore,
|
|
10
10
|
SdtNode,
|
|
11
|
+
SectionProperties,
|
|
11
12
|
TableNode,
|
|
12
13
|
TableCellNode,
|
|
13
14
|
TextMark,
|
|
@@ -30,6 +31,7 @@ export interface SerializedMainDocument {
|
|
|
30
31
|
export interface SerializeMainDocumentOptions {
|
|
31
32
|
documentAttributes?: Record<string, string>;
|
|
32
33
|
media?: MediaCatalog;
|
|
34
|
+
finalSectionProperties?: SectionProperties;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
interface SerializationState {
|
|
@@ -90,7 +92,9 @@ export function serializeMainDocument(
|
|
|
90
92
|
const bodyPieces: string[] = [];
|
|
91
93
|
const paragraphBoundaries: RevisionParagraphBoundary[] = [];
|
|
92
94
|
let bodyLength = 0;
|
|
93
|
-
let sectionPropertiesXml =
|
|
95
|
+
let sectionPropertiesXml = options.finalSectionProperties
|
|
96
|
+
? serializeSectionPropertiesXml(options.finalSectionProperties)
|
|
97
|
+
: "<w:sectPr/>";
|
|
94
98
|
let cursor = 0;
|
|
95
99
|
let paragraphIndex = -1;
|
|
96
100
|
let previousWasParagraph = false;
|
|
@@ -154,9 +158,20 @@ export function serializeMainDocument(
|
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
if (block.type === "section_break") {
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
// Inline section breaks must be emitted as a paragraph with <w:sectPr>
|
|
162
|
+
// in its <w:pPr> element (OOXML compliance). The body-level <w:sectPr>
|
|
163
|
+
// is reserved for the final section only.
|
|
164
|
+
let inlineSectPr: string;
|
|
165
|
+
if (block.sectionPropertiesXml ?? block.propertiesXml) {
|
|
166
|
+
inlineSectPr = block.sectionPropertiesXml ?? block.propertiesXml!;
|
|
167
|
+
} else if (block.sectionProperties) {
|
|
168
|
+
inlineSectPr = serializeSectionPropertiesXml(block.sectionProperties);
|
|
169
|
+
} else {
|
|
170
|
+
inlineSectPr = "<w:sectPr/>";
|
|
159
171
|
}
|
|
172
|
+
const sectionParagraphXml = `<w:p><w:pPr>${inlineSectPr}</w:pPr></w:p>`;
|
|
173
|
+
bodyPieces.push(sectionParagraphXml);
|
|
174
|
+
bodyLength += sectionParagraphXml.length;
|
|
160
175
|
cursor += 1;
|
|
161
176
|
previousWasParagraph = false;
|
|
162
177
|
continue;
|
|
@@ -242,7 +257,11 @@ function serializeBlockNode(
|
|
|
242
257
|
case "opaque_block":
|
|
243
258
|
return lookupOpaqueXml(block.fragmentId, state);
|
|
244
259
|
case "section_break":
|
|
245
|
-
|
|
260
|
+
if (block.sectionPropertiesXml ?? block.propertiesXml) {
|
|
261
|
+
return block.sectionPropertiesXml ?? block.propertiesXml!;
|
|
262
|
+
}
|
|
263
|
+
if (block.sectionProperties) return serializeSectionPropertiesXml(block.sectionProperties);
|
|
264
|
+
return "<w:sectPr/>";
|
|
246
265
|
}
|
|
247
266
|
}
|
|
248
267
|
|
|
@@ -303,7 +322,40 @@ function buildSdtPropertiesXml(block: SdtNode): string {
|
|
|
303
322
|
if (block.properties.lock) {
|
|
304
323
|
children.push(`<w:lock w:val="${escapeAttribute(block.properties.lock)}"/>`);
|
|
305
324
|
}
|
|
306
|
-
if (block.properties.
|
|
325
|
+
if (block.properties.showingPlcHdr) {
|
|
326
|
+
children.push(`<w:showingPlcHdr/>`);
|
|
327
|
+
}
|
|
328
|
+
if (block.properties.checkbox) {
|
|
329
|
+
const cb = block.properties.checkbox;
|
|
330
|
+
const cbParts: string[] = [];
|
|
331
|
+
cbParts.push(`<w14:checked w14:val="${cb.checked ? "1" : "0"}"/>`);
|
|
332
|
+
if (cb.checkedChar) cbParts.push(`<w14:checkedState w14:val="${escapeAttribute(cb.checkedChar)}"/>`);
|
|
333
|
+
if (cb.uncheckedChar) cbParts.push(`<w14:uncheckedState w14:val="${escapeAttribute(cb.uncheckedChar)}"/>`);
|
|
334
|
+
children.push(`<w14:checkbox>${cbParts.join("")}</w14:checkbox>`);
|
|
335
|
+
} else if (block.properties.datePicker) {
|
|
336
|
+
const dp = block.properties.datePicker;
|
|
337
|
+
const dateAttrs = dp.fullDate ? ` w:fullDate="${escapeAttribute(dp.fullDate)}"` : "";
|
|
338
|
+
const dpParts: string[] = [];
|
|
339
|
+
if (dp.dateFormat) dpParts.push(`<w:dateFormat w:val="${escapeAttribute(dp.dateFormat)}"/>`);
|
|
340
|
+
if (dp.lid) dpParts.push(`<w:lid w:val="${escapeAttribute(dp.lid)}"/>`);
|
|
341
|
+
children.push(dpParts.length > 0 ? `<w:date${dateAttrs}>${dpParts.join("")}</w:date>` : `<w:date${dateAttrs}/>`);
|
|
342
|
+
} else if (block.properties.dropdownList) {
|
|
343
|
+
const items = block.properties.dropdownList.map((item) => {
|
|
344
|
+
const dt = item.displayText ? ` w:displayText="${escapeAttribute(item.displayText)}"` : "";
|
|
345
|
+
return `<w:listItem${dt} w:value="${escapeAttribute(item.value)}"/>`;
|
|
346
|
+
}).join("");
|
|
347
|
+
children.push(`<w:dropDownList>${items}</w:dropDownList>`);
|
|
348
|
+
} else if (block.properties.comboBox) {
|
|
349
|
+
const items = block.properties.comboBox.map((item) => {
|
|
350
|
+
const dt = item.displayText ? ` w:displayText="${escapeAttribute(item.displayText)}"` : "";
|
|
351
|
+
return `<w:listItem${dt} w:value="${escapeAttribute(item.value)}"/>`;
|
|
352
|
+
}).join("");
|
|
353
|
+
children.push(`<w:comboBox>${items}</w:comboBox>`);
|
|
354
|
+
} else if (block.properties.sdtType === "plainText") {
|
|
355
|
+
children.push(`<w:text/>`);
|
|
356
|
+
} else if (block.properties.sdtType === "richText") {
|
|
357
|
+
children.push(`<w:richText/>`);
|
|
358
|
+
} else if (block.properties.sdtType) {
|
|
307
359
|
children.push(`<w:${block.properties.sdtType}/>`);
|
|
308
360
|
}
|
|
309
361
|
return children.length > 0 ? `<w:sdtPr>${children.join("")}</w:sdtPr>` : "<w:sdtPr/>";
|
|
@@ -355,7 +407,7 @@ function serializeTableInlineNode(
|
|
|
355
407
|
case "shape":
|
|
356
408
|
case "wordart":
|
|
357
409
|
case "vml_shape":
|
|
358
|
-
return node.rawXml;
|
|
410
|
+
return wrapInlineRawXml(node.rawXml);
|
|
359
411
|
case "hyperlink": {
|
|
360
412
|
const hyperlinkOpen = node.href.startsWith("#")
|
|
361
413
|
? `<w:hyperlink w:anchor="${escapeAttribute(node.href.slice(1))}">`
|
|
@@ -374,8 +426,24 @@ function serializeTableInlineNode(
|
|
|
374
426
|
const childrenXml = node.children.map((child) => serializeTableInlineNode(child, state)).join("");
|
|
375
427
|
return `${hyperlinkOpen}${childrenXml}</w:hyperlink>`;
|
|
376
428
|
}
|
|
377
|
-
case "field":
|
|
429
|
+
case "field": {
|
|
430
|
+
if (node.children && node.children.length > 0) {
|
|
431
|
+
const childrenXml = node.children
|
|
432
|
+
.map((child) => serializeTableInlineNode(child, state))
|
|
433
|
+
.join("");
|
|
434
|
+
if (node.fieldType === "complex") {
|
|
435
|
+
return (
|
|
436
|
+
`<w:r><w:fldChar w:fldCharType="begin"/></w:r>` +
|
|
437
|
+
`<w:r><w:instrText xml:space="preserve"> ${escapeXml(node.instruction)} </w:instrText></w:r>` +
|
|
438
|
+
`<w:r><w:fldChar w:fldCharType="separate"/></w:r>` +
|
|
439
|
+
childrenXml +
|
|
440
|
+
`<w:r><w:fldChar w:fldCharType="end"/></w:r>`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
return `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}">${childrenXml}</w:fldSimple>`;
|
|
444
|
+
}
|
|
378
445
|
return `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}"/>`;
|
|
446
|
+
}
|
|
379
447
|
case "bookmark_start":
|
|
380
448
|
return (
|
|
381
449
|
`<w:bookmarkStart w:id="${escapeAttribute(node.bookmarkId)}"` +
|
|
@@ -432,6 +500,13 @@ function buildParagraphPropertiesXml(paragraph: ParagraphNode): string {
|
|
|
432
500
|
if (s.lineRule !== undefined) attrs.push(`w:lineRule="${s.lineRule}"`);
|
|
433
501
|
if (attrs.length > 0) children.push(`<w:spacing ${attrs.join(" ")}/>`);
|
|
434
502
|
}
|
|
503
|
+
if (paragraph.contextualSpacing !== undefined) {
|
|
504
|
+
children.push(
|
|
505
|
+
paragraph.contextualSpacing
|
|
506
|
+
? "<w:contextualSpacing/>"
|
|
507
|
+
: `<w:contextualSpacing w:val="0"/>`,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
435
510
|
if (paragraph.indentation) {
|
|
436
511
|
const ind = paragraph.indentation;
|
|
437
512
|
const attrs: string[] = [];
|
|
@@ -754,7 +829,7 @@ function serializeInlineNode(
|
|
|
754
829
|
case "wordart":
|
|
755
830
|
case "vml_shape": {
|
|
756
831
|
// Reattach original XML unchanged for lossless round-trip.
|
|
757
|
-
const xml = node.rawXml;
|
|
832
|
+
const xml = wrapInlineRawXml(node.rawXml);
|
|
758
833
|
const boundaries = new Map<number, number>();
|
|
759
834
|
boundaries.set(cursor, xmlOffset);
|
|
760
835
|
boundaries.set(cursor + 1, xmlOffset + xml.length);
|
|
@@ -804,6 +879,55 @@ function serializeInlineNode(
|
|
|
804
879
|
};
|
|
805
880
|
}
|
|
806
881
|
case "field": {
|
|
882
|
+
if (node.children && node.children.length > 0) {
|
|
883
|
+
const boundaries = new Map<number, number>();
|
|
884
|
+
boundaries.set(cursor, xmlOffset);
|
|
885
|
+
let nextCursor = cursor;
|
|
886
|
+
let nextOffset = xmlOffset;
|
|
887
|
+
|
|
888
|
+
if (node.fieldType === "complex") {
|
|
889
|
+
const beginXml =
|
|
890
|
+
`<w:r><w:fldChar w:fldCharType="begin"/></w:r>` +
|
|
891
|
+
`<w:r><w:instrText xml:space="preserve"> ${escapeXml(node.instruction)} </w:instrText></w:r>` +
|
|
892
|
+
`<w:r><w:fldChar w:fldCharType="separate"/></w:r>`;
|
|
893
|
+
nextOffset += beginXml.length;
|
|
894
|
+
|
|
895
|
+
const children: string[] = [beginXml];
|
|
896
|
+
for (const child of node.children) {
|
|
897
|
+
const result = serializeInlineNode(child, state, nextCursor, nextOffset);
|
|
898
|
+
children.push(result.xml);
|
|
899
|
+
for (const [position, index] of result.boundaries) {
|
|
900
|
+
boundaries.set(position, index);
|
|
901
|
+
}
|
|
902
|
+
nextCursor = result.cursor;
|
|
903
|
+
nextOffset += result.xml.length;
|
|
904
|
+
}
|
|
905
|
+
const endXml = `<w:r><w:fldChar w:fldCharType="end"/></w:r>`;
|
|
906
|
+
children.push(endXml);
|
|
907
|
+
nextOffset += endXml.length;
|
|
908
|
+
boundaries.set(nextCursor, nextOffset);
|
|
909
|
+
return { xml: children.join(""), cursor: nextCursor, boundaries };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Simple field with children
|
|
913
|
+
const openXml = `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}">`;
|
|
914
|
+
nextOffset += openXml.length;
|
|
915
|
+
const children: string[] = [openXml];
|
|
916
|
+
for (const child of node.children) {
|
|
917
|
+
const result = serializeInlineNode(child, state, nextCursor, nextOffset);
|
|
918
|
+
children.push(result.xml);
|
|
919
|
+
for (const [position, index] of result.boundaries) {
|
|
920
|
+
boundaries.set(position, index);
|
|
921
|
+
}
|
|
922
|
+
nextCursor = result.cursor;
|
|
923
|
+
nextOffset += result.xml.length;
|
|
924
|
+
}
|
|
925
|
+
children.push("</w:fldSimple>");
|
|
926
|
+
nextOffset += "</w:fldSimple>".length;
|
|
927
|
+
boundaries.set(nextCursor, nextOffset);
|
|
928
|
+
return { xml: children.join(""), cursor: nextCursor, boundaries };
|
|
929
|
+
}
|
|
930
|
+
|
|
807
931
|
const xml = `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}"/>`;
|
|
808
932
|
const boundaries = new Map<number, number>();
|
|
809
933
|
boundaries.set(cursor, xmlOffset);
|
|
@@ -914,6 +1038,9 @@ function serializeRunProperties(marks: TextMark[] | undefined): string {
|
|
|
914
1038
|
case "lang":
|
|
915
1039
|
markParts.push(`<w:lang w:val="${escapeAttribute(mark.val)}"/>`);
|
|
916
1040
|
break;
|
|
1041
|
+
case "highlight":
|
|
1042
|
+
markParts.push(`<w:highlight w:val="${escapeAttribute(mark.val)}"/>`);
|
|
1043
|
+
break;
|
|
917
1044
|
case "backgroundColor":
|
|
918
1045
|
markParts.push(
|
|
919
1046
|
`<w:shd w:val="clear" w:color="auto" w:fill="${escapeAttribute(mark.color)}"/>`,
|
|
@@ -1049,6 +1176,156 @@ function serializeDocumentAttributes(
|
|
|
1049
1176
|
.join("");
|
|
1050
1177
|
}
|
|
1051
1178
|
|
|
1179
|
+
export function serializeSectionPropertiesXml(props: SectionProperties): string {
|
|
1180
|
+
const children: string[] = [];
|
|
1181
|
+
|
|
1182
|
+
// Header references
|
|
1183
|
+
if (props.headerReferences) {
|
|
1184
|
+
for (const ref of props.headerReferences) {
|
|
1185
|
+
children.push(`<w:headerReference w:type="${escapeAttribute(ref.variant)}" r:id="${escapeAttribute(ref.relationshipId)}"/>`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Footer references
|
|
1190
|
+
if (props.footerReferences) {
|
|
1191
|
+
for (const ref of props.footerReferences) {
|
|
1192
|
+
children.push(`<w:footerReference w:type="${escapeAttribute(ref.variant)}" r:id="${escapeAttribute(ref.relationshipId)}"/>`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Section type
|
|
1197
|
+
if (props.sectionType) {
|
|
1198
|
+
children.push(`<w:type w:val="${escapeAttribute(props.sectionType)}"/>`);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Page size
|
|
1202
|
+
if (props.pageSize) {
|
|
1203
|
+
let pgSz = `<w:pgSz w:w="${props.pageSize.width}" w:h="${props.pageSize.height}"`;
|
|
1204
|
+
if (props.pageSize.orientation) {
|
|
1205
|
+
pgSz += ` w:orient="${props.pageSize.orientation}"`;
|
|
1206
|
+
}
|
|
1207
|
+
pgSz += "/>";
|
|
1208
|
+
children.push(pgSz);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Page margins
|
|
1212
|
+
if (props.pageMargins) {
|
|
1213
|
+
let pgMar = `<w:pgMar w:top="${props.pageMargins.top}" w:right="${props.pageMargins.right}" w:bottom="${props.pageMargins.bottom}" w:left="${props.pageMargins.left}"`;
|
|
1214
|
+
if (props.pageMargins.header !== undefined) pgMar += ` w:header="${props.pageMargins.header}"`;
|
|
1215
|
+
if (props.pageMargins.footer !== undefined) pgMar += ` w:footer="${props.pageMargins.footer}"`;
|
|
1216
|
+
if (props.pageMargins.gutter !== undefined) pgMar += ` w:gutter="${props.pageMargins.gutter}"`;
|
|
1217
|
+
pgMar += "/>";
|
|
1218
|
+
children.push(pgMar);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Columns
|
|
1222
|
+
if (props.columns) {
|
|
1223
|
+
let cols = "<w:cols";
|
|
1224
|
+
if (props.columns.count !== undefined) cols += ` w:num="${props.columns.count}"`;
|
|
1225
|
+
if (props.columns.space !== undefined) cols += ` w:space="${props.columns.space}"`;
|
|
1226
|
+
if (props.columns.equalWidth !== undefined) cols += ` w:equalWidth="${props.columns.equalWidth ? "1" : "0"}"`;
|
|
1227
|
+
if (props.columns.separator) cols += ` w:sep="1"`;
|
|
1228
|
+
if (props.columns.columns && props.columns.columns.length > 0) {
|
|
1229
|
+
cols += ">";
|
|
1230
|
+
for (const col of props.columns.columns) {
|
|
1231
|
+
let colXml = `<w:col w:w="${col.width}"`;
|
|
1232
|
+
if (col.space !== undefined) colXml += ` w:space="${col.space}"`;
|
|
1233
|
+
colXml += "/>";
|
|
1234
|
+
cols += colXml;
|
|
1235
|
+
}
|
|
1236
|
+
cols += "</w:cols>";
|
|
1237
|
+
} else {
|
|
1238
|
+
cols += "/>";
|
|
1239
|
+
}
|
|
1240
|
+
children.push(cols);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Page numbering
|
|
1244
|
+
if (props.pageNumbering) {
|
|
1245
|
+
let pgNum = "<w:pgNumType";
|
|
1246
|
+
if (props.pageNumbering.format) pgNum += ` w:fmt="${escapeAttribute(props.pageNumbering.format)}"`;
|
|
1247
|
+
if (props.pageNumbering.start !== undefined) pgNum += ` w:start="${props.pageNumbering.start}"`;
|
|
1248
|
+
if (props.pageNumbering.chapStyle) pgNum += ` w:chapStyle="${escapeAttribute(props.pageNumbering.chapStyle)}"`;
|
|
1249
|
+
if (props.pageNumbering.chapSep) pgNum += ` w:chapSep="${escapeAttribute(props.pageNumbering.chapSep)}"`;
|
|
1250
|
+
pgNum += "/>";
|
|
1251
|
+
children.push(pgNum);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
if (props.lineNumbering) {
|
|
1255
|
+
let lineNumbering = "<w:lnNumType";
|
|
1256
|
+
if (props.lineNumbering.countBy !== undefined) {
|
|
1257
|
+
lineNumbering += ` w:countBy="${props.lineNumbering.countBy}"`;
|
|
1258
|
+
}
|
|
1259
|
+
if (props.lineNumbering.start !== undefined) {
|
|
1260
|
+
lineNumbering += ` w:start="${props.lineNumbering.start}"`;
|
|
1261
|
+
}
|
|
1262
|
+
if (props.lineNumbering.distance !== undefined) {
|
|
1263
|
+
lineNumbering += ` w:distance="${props.lineNumbering.distance}"`;
|
|
1264
|
+
}
|
|
1265
|
+
if (props.lineNumbering.restart) {
|
|
1266
|
+
lineNumbering += ` w:restart="${escapeAttribute(props.lineNumbering.restart)}"`;
|
|
1267
|
+
}
|
|
1268
|
+
lineNumbering += "/>";
|
|
1269
|
+
children.push(lineNumbering);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (props.pageBorders) {
|
|
1273
|
+
const attrs: string[] = [];
|
|
1274
|
+
if (props.pageBorders.offsetFrom) {
|
|
1275
|
+
attrs.push(`w:offsetFrom="${escapeAttribute(props.pageBorders.offsetFrom)}"`);
|
|
1276
|
+
}
|
|
1277
|
+
if (props.pageBorders.display) {
|
|
1278
|
+
attrs.push(`w:display="${escapeAttribute(props.pageBorders.display)}"`);
|
|
1279
|
+
}
|
|
1280
|
+
if (props.pageBorders.zOrder) {
|
|
1281
|
+
attrs.push(`w:zOrder="${escapeAttribute(props.pageBorders.zOrder)}"`);
|
|
1282
|
+
}
|
|
1283
|
+
const borderXml = [
|
|
1284
|
+
serializeBorder("top", props.pageBorders.top),
|
|
1285
|
+
serializeBorder("left", props.pageBorders.left),
|
|
1286
|
+
serializeBorder("bottom", props.pageBorders.bottom),
|
|
1287
|
+
serializeBorder("right", props.pageBorders.right),
|
|
1288
|
+
].filter((entry) => entry.length > 0);
|
|
1289
|
+
if (attrs.length > 0 || borderXml.length > 0) {
|
|
1290
|
+
children.push(
|
|
1291
|
+
`<w:pgBorders${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}>${borderXml.join("")}</w:pgBorders>`,
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Title page
|
|
1297
|
+
if (props.titlePage) {
|
|
1298
|
+
children.push("<w:titlePg/>");
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (props.documentGrid) {
|
|
1302
|
+
const attrs: string[] = [];
|
|
1303
|
+
if (props.documentGrid.type) {
|
|
1304
|
+
attrs.push(`w:type="${escapeAttribute(props.documentGrid.type)}"`);
|
|
1305
|
+
}
|
|
1306
|
+
if (props.documentGrid.linePitch !== undefined) {
|
|
1307
|
+
attrs.push(`w:linePitch="${props.documentGrid.linePitch}"`);
|
|
1308
|
+
}
|
|
1309
|
+
if (props.documentGrid.charSpace !== undefined) {
|
|
1310
|
+
attrs.push(`w:charSpace="${props.documentGrid.charSpace}"`);
|
|
1311
|
+
}
|
|
1312
|
+
if (attrs.length > 0) {
|
|
1313
|
+
children.push(`<w:docGrid ${attrs.join(" ")}/>`);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (children.length === 0) {
|
|
1318
|
+
return "<w:sectPr/>";
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return `<w:sectPr>${children.join("")}</w:sectPr>`;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function wrapInlineRawXml(rawXml: string): string {
|
|
1325
|
+
const trimmed = rawXml.trimStart();
|
|
1326
|
+
return trimmed.startsWith("<w:r") ? rawXml : `<w:r>${rawXml}</w:r>`;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1052
1329
|
function documentNeedsW14Namespace(content: DocumentRootNode): boolean {
|
|
1053
1330
|
const blockQueue = [...content.children];
|
|
1054
1331
|
while (blockQueue.length > 0) {
|