@beyondwork/docx-react-component 1.0.59 → 1.0.61
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 +33 -44
- package/src/api/public-types.ts +43 -0
- package/src/core/state/editor-state.ts +2 -0
- package/src/io/docx-session.ts +167 -8
- package/src/io/export/serialize-footnotes.ts +36 -5
- package/src/io/export/serialize-headers-footers.ts +7 -0
- package/src/io/export/serialize-main-document.ts +25 -18
- package/src/io/export/serialize-paragraph-formatting.ts +6 -0
- package/src/io/export/serialize-settings.ts +130 -3
- package/src/io/normalize/normalize-text.ts +8 -4
- package/src/io/ooxml/parse-footnotes.ts +11 -0
- package/src/io/ooxml/parse-headers-footers.ts +117 -42
- package/src/io/ooxml/parse-main-document.ts +20 -8
- package/src/io/ooxml/parse-paragraph-formatting.ts +25 -1
- package/src/io/ooxml/parse-settings.ts +91 -1
- package/src/io/ooxml/workflow-payload.ts +6 -1
- package/src/model/canonical-document.ts +36 -2
- package/src/model/snapshot.ts +2 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +2 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +9 -0
- package/src/runtime/document-runtime.ts +770 -21
- package/src/runtime/footnote-resolver.ts +32 -8
- package/src/runtime/layout/layout-engine-version.ts +7 -1
- package/src/runtime/layout/measurement-backend-canvas.ts +1 -1
- package/src/runtime/layout/measurement-backend-empirical.ts +1 -1
- package/src/runtime/layout/paginated-layout-engine.ts +41 -8
- package/src/runtime/layout/resolved-formatting-document.ts +11 -9
- package/src/runtime/layout/resolved-formatting-state.ts +4 -0
- package/src/runtime/numbering-prefix.ts +26 -2
- package/src/runtime/query-scopes.ts +103 -2
- package/src/runtime/surface-projection.ts +75 -14
- package/src/runtime/table-schema.ts +26 -0
- package/src/ui/WordReviewEditor.tsx +25 -0
- package/src/ui/editor-runtime-boundary.ts +1 -0
- package/src/ui/editor-shell-view.tsx +8 -0
- package/src/ui-tailwind/chrome/tw-runtime-repl-dialog.tsx +514 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +14 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +55 -6
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +2 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +4 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +9 -1
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +16 -0
- package/src/ui-tailwind/page-stack/floating-image-overlay-model.ts +319 -0
- package/src/ui-tailwind/page-stack/tw-floating-image-layer.tsx +248 -0
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +4 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +54 -3
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
64
|
import type {
|
|
65
|
+
ClrSchemeMappingSlot,
|
|
65
66
|
CompatSetting,
|
|
66
67
|
DocumentSettings,
|
|
67
68
|
} from "../../model/canonical-document.ts";
|
|
@@ -81,6 +82,21 @@ export const WORD_SETTINGS_CONTENT_TYPE =
|
|
|
81
82
|
const WORDPROCESSINGML_2006_MAIN_NS =
|
|
82
83
|
"http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
83
84
|
|
|
85
|
+
const CLRSCHEME_MAPPING_EMIT_ORDER: readonly ClrSchemeMappingSlot[] = [
|
|
86
|
+
"bg1",
|
|
87
|
+
"t1",
|
|
88
|
+
"bg2",
|
|
89
|
+
"t2",
|
|
90
|
+
"accent1",
|
|
91
|
+
"accent2",
|
|
92
|
+
"accent3",
|
|
93
|
+
"accent4",
|
|
94
|
+
"accent5",
|
|
95
|
+
"accent6",
|
|
96
|
+
"hlink",
|
|
97
|
+
"followedHyperlink",
|
|
98
|
+
];
|
|
99
|
+
|
|
84
100
|
/**
|
|
85
101
|
* Render a complete `<w:settings>` XML document from canonical
|
|
86
102
|
* `DocumentSettings`. The output is the standard XML declaration plus a
|
|
@@ -89,9 +105,13 @@ const WORDPROCESSINGML_2006_MAIN_NS =
|
|
|
89
105
|
* Emit order is OOXML-schema-friendly to maximize Word's tolerance:
|
|
90
106
|
* 1. <w:evenAndOddHeaders>
|
|
91
107
|
* 2. <w:zoom>
|
|
92
|
-
* 3.
|
|
93
|
-
* 4. <w:
|
|
94
|
-
* 5. <w:
|
|
108
|
+
* 3. <w:defaultTabStop>
|
|
109
|
+
* 4. <w:footnotePr>
|
|
110
|
+
* 5. <w:endnotePr>
|
|
111
|
+
* 6. root-level compat-adjacent flags (e.g. <w:doNotEmbedSmartTags/>)
|
|
112
|
+
* 7. <w:compat> wrapping flags then compatSetting triples
|
|
113
|
+
* 8. <w:themeFontLang>
|
|
114
|
+
* 9. <w:clrSchemeMapping>
|
|
95
115
|
*
|
|
96
116
|
* Insertion order of `compatSettings` array entries and `compatFlags` /
|
|
97
117
|
* `rootCompatFlags` / `themeFontLang` keys is preserved so a byte-stable
|
|
@@ -112,9 +132,13 @@ function synthesizeSettingsXml(settings: DocumentSettings): string {
|
|
|
112
132
|
const parts: string[] = [];
|
|
113
133
|
parts.push(emitEvenAndOddHeaders(settings));
|
|
114
134
|
parts.push(emitZoom(settings));
|
|
135
|
+
parts.push(emitDefaultTabStop(settings));
|
|
136
|
+
parts.push(emitFootnoteLikeProperties("w:footnotePr", settings.footnotePr));
|
|
137
|
+
parts.push(emitFootnoteLikeProperties("w:endnotePr", settings.endnotePr));
|
|
115
138
|
parts.push(emitRootCompatFlags(settings));
|
|
116
139
|
parts.push(emitCompatBlock(settings));
|
|
117
140
|
parts.push(emitThemeFontLang(settings));
|
|
141
|
+
parts.push(emitClrSchemeMapping(settings));
|
|
118
142
|
|
|
119
143
|
const body = parts.filter((p) => p.length > 0).join("");
|
|
120
144
|
return [
|
|
@@ -195,6 +219,24 @@ function graftSettingsXml(
|
|
|
195
219
|
) {
|
|
196
220
|
appendedParts.push(emitZoom(settings));
|
|
197
221
|
}
|
|
222
|
+
if (
|
|
223
|
+
!emittedTopLevel.has("defaultTabStop") &&
|
|
224
|
+
settings.defaultTabStop !== undefined
|
|
225
|
+
) {
|
|
226
|
+
appendedParts.push(emitDefaultTabStop(settings));
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
!emittedTopLevel.has("footnotePr") &&
|
|
230
|
+
settings.footnotePr !== undefined
|
|
231
|
+
) {
|
|
232
|
+
appendedParts.push(emitFootnoteLikeProperties("w:footnotePr", settings.footnotePr));
|
|
233
|
+
}
|
|
234
|
+
if (
|
|
235
|
+
!emittedTopLevel.has("endnotePr") &&
|
|
236
|
+
settings.endnotePr !== undefined
|
|
237
|
+
) {
|
|
238
|
+
appendedParts.push(emitFootnoteLikeProperties("w:endnotePr", settings.endnotePr));
|
|
239
|
+
}
|
|
198
240
|
// Any rootCompatFlags entries that didn't have a source counterpart.
|
|
199
241
|
for (const [name, value] of pendingRootFlags) {
|
|
200
242
|
appendedParts.push(emitOnOffElement(name, value));
|
|
@@ -209,6 +251,12 @@ function graftSettingsXml(
|
|
|
209
251
|
) {
|
|
210
252
|
appendedParts.push(emitThemeFontLang(settings));
|
|
211
253
|
}
|
|
254
|
+
if (
|
|
255
|
+
!emittedTopLevel.has("clrSchemeMapping") &&
|
|
256
|
+
settings.clrSchemeMapping !== undefined
|
|
257
|
+
) {
|
|
258
|
+
appendedParts.push(emitClrSchemeMapping(settings));
|
|
259
|
+
}
|
|
212
260
|
|
|
213
261
|
return (
|
|
214
262
|
blueprint.prelude +
|
|
@@ -229,8 +277,12 @@ function graftSettingsXml(
|
|
|
229
277
|
const MODELLED_TOP_LEVEL_NAMES: ReadonlySet<string> = new Set([
|
|
230
278
|
"evenAndOddHeaders",
|
|
231
279
|
"zoom",
|
|
280
|
+
"defaultTabStop",
|
|
281
|
+
"footnotePr",
|
|
282
|
+
"endnotePr",
|
|
232
283
|
"compat",
|
|
233
284
|
"themeFontLang",
|
|
285
|
+
"clrSchemeMapping",
|
|
234
286
|
]);
|
|
235
287
|
|
|
236
288
|
type ChildReplacement =
|
|
@@ -282,6 +334,36 @@ function computeChildReplacement(
|
|
|
282
334
|
const xml = emitZoom(settings);
|
|
283
335
|
return xml.length > 0 ? { kind: "replace", xml } : { kind: "drop" };
|
|
284
336
|
}
|
|
337
|
+
case "defaultTabStop": {
|
|
338
|
+
if (
|
|
339
|
+
emitDefaultTabStop(parseModelledChild(child.rawXml)) ===
|
|
340
|
+
emitDefaultTabStop(settings)
|
|
341
|
+
) {
|
|
342
|
+
return { kind: "keep" };
|
|
343
|
+
}
|
|
344
|
+
const xml = emitDefaultTabStop(settings);
|
|
345
|
+
return xml.length > 0 ? { kind: "replace", xml } : { kind: "drop" };
|
|
346
|
+
}
|
|
347
|
+
case "footnotePr": {
|
|
348
|
+
if (
|
|
349
|
+
emitFootnoteLikeProperties("w:footnotePr", parseModelledChild(child.rawXml).footnotePr) ===
|
|
350
|
+
emitFootnoteLikeProperties("w:footnotePr", settings.footnotePr)
|
|
351
|
+
) {
|
|
352
|
+
return { kind: "keep" };
|
|
353
|
+
}
|
|
354
|
+
const xml = emitFootnoteLikeProperties("w:footnotePr", settings.footnotePr);
|
|
355
|
+
return xml.length > 0 ? { kind: "replace", xml } : { kind: "drop" };
|
|
356
|
+
}
|
|
357
|
+
case "endnotePr": {
|
|
358
|
+
if (
|
|
359
|
+
emitFootnoteLikeProperties("w:endnotePr", parseModelledChild(child.rawXml).endnotePr) ===
|
|
360
|
+
emitFootnoteLikeProperties("w:endnotePr", settings.endnotePr)
|
|
361
|
+
) {
|
|
362
|
+
return { kind: "keep" };
|
|
363
|
+
}
|
|
364
|
+
const xml = emitFootnoteLikeProperties("w:endnotePr", settings.endnotePr);
|
|
365
|
+
return xml.length > 0 ? { kind: "replace", xml } : { kind: "drop" };
|
|
366
|
+
}
|
|
285
367
|
case "compat": {
|
|
286
368
|
if (
|
|
287
369
|
emitCompatBlock(parseModelledChild(child.rawXml)) ===
|
|
@@ -302,6 +384,16 @@ function computeChildReplacement(
|
|
|
302
384
|
const xml = emitThemeFontLang(settings);
|
|
303
385
|
return xml.length > 0 ? { kind: "replace", xml } : { kind: "drop" };
|
|
304
386
|
}
|
|
387
|
+
case "clrSchemeMapping": {
|
|
388
|
+
if (
|
|
389
|
+
emitClrSchemeMapping(parseModelledChild(child.rawXml)) ===
|
|
390
|
+
emitClrSchemeMapping(settings)
|
|
391
|
+
) {
|
|
392
|
+
return { kind: "keep" };
|
|
393
|
+
}
|
|
394
|
+
const xml = emitClrSchemeMapping(settings);
|
|
395
|
+
return xml.length > 0 ? { kind: "replace", xml } : { kind: "drop" };
|
|
396
|
+
}
|
|
305
397
|
}
|
|
306
398
|
// Root compat flag?
|
|
307
399
|
if (ROOT_COMPAT_FLAG_NAMES.has(child.localName)) {
|
|
@@ -361,6 +453,28 @@ function emitZoom(settings: DocumentSettings): string {
|
|
|
361
453
|
return "";
|
|
362
454
|
}
|
|
363
455
|
|
|
456
|
+
function emitDefaultTabStop(settings: DocumentSettings): string {
|
|
457
|
+
const { defaultTabStop } = settings;
|
|
458
|
+
if (defaultTabStop === undefined || !Number.isFinite(defaultTabStop)) return "";
|
|
459
|
+
return `<w:defaultTabStop w:val="${Math.round(defaultTabStop)}"/>`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function emitFootnoteLikeProperties(
|
|
463
|
+
elementName: "w:footnotePr" | "w:endnotePr",
|
|
464
|
+
props: DocumentSettings["footnotePr"] | DocumentSettings["endnotePr"],
|
|
465
|
+
): string {
|
|
466
|
+
if (!props) return "";
|
|
467
|
+
const parts: string[] = [];
|
|
468
|
+
if (props.pos) parts.push(`<w:pos w:val="${escapeXmlAttribute(props.pos)}"/>`);
|
|
469
|
+
if (props.numFmt) parts.push(`<w:numFmt w:val="${escapeXmlAttribute(props.numFmt)}"/>`);
|
|
470
|
+
if (props.numStart !== undefined && Number.isFinite(props.numStart)) {
|
|
471
|
+
parts.push(`<w:numStart w:val="${Math.round(props.numStart)}"/>`);
|
|
472
|
+
}
|
|
473
|
+
if (props.numRestart) parts.push(`<w:numRestart w:val="${escapeXmlAttribute(props.numRestart)}"/>`);
|
|
474
|
+
if (parts.length === 0) return "";
|
|
475
|
+
return `<${elementName}>${parts.join("")}</${elementName}>`;
|
|
476
|
+
}
|
|
477
|
+
|
|
364
478
|
function emitRootCompatFlags(settings: DocumentSettings): string {
|
|
365
479
|
const flags = settings.rootCompatFlags;
|
|
366
480
|
if (!flags) return "";
|
|
@@ -410,6 +524,19 @@ function emitThemeFontLang(settings: DocumentSettings): string {
|
|
|
410
524
|
return `<w:themeFontLang${attrs}/>`;
|
|
411
525
|
}
|
|
412
526
|
|
|
527
|
+
function emitClrSchemeMapping(settings: DocumentSettings): string {
|
|
528
|
+
const mapping = settings.clrSchemeMapping;
|
|
529
|
+
if (!mapping) return "";
|
|
530
|
+
const attrs = CLRSCHEME_MAPPING_EMIT_ORDER
|
|
531
|
+
.map((slot) => {
|
|
532
|
+
const value = mapping[slot];
|
|
533
|
+
return value ? ` w:${slot}="${escapeXmlAttribute(value)}"` : "";
|
|
534
|
+
})
|
|
535
|
+
.join("");
|
|
536
|
+
if (attrs.length === 0) return "";
|
|
537
|
+
return `<w:clrSchemeMapping${attrs}/>`;
|
|
538
|
+
}
|
|
539
|
+
|
|
413
540
|
/**
|
|
414
541
|
* Emit a ST_OnOff element. true → bare self-closing tag; false → explicit
|
|
415
542
|
* `w:val="false"` so the parser doesn't infer the default-true. Symmetric
|
|
@@ -260,13 +260,17 @@ function normalizeParagraph(
|
|
|
260
260
|
: {}),
|
|
261
261
|
...(paragraph.indentation ? { indentation: paragraph.indentation } : {}),
|
|
262
262
|
...(paragraph.tabStops && paragraph.tabStops.length > 0 ? { tabStops: paragraph.tabStops } : {}),
|
|
263
|
-
...(paragraph.keepNext ? { keepNext: paragraph.keepNext } : {}),
|
|
264
|
-
...(paragraph.keepLines ? { keepLines: paragraph.keepLines } : {}),
|
|
263
|
+
...(paragraph.keepNext !== undefined ? { keepNext: paragraph.keepNext } : {}),
|
|
264
|
+
...(paragraph.keepLines !== undefined ? { keepLines: paragraph.keepLines } : {}),
|
|
265
265
|
...(paragraph.outlineLevel !== undefined ? { outlineLevel: paragraph.outlineLevel } : {}),
|
|
266
|
-
...(paragraph.pageBreakBefore ? { pageBreakBefore: paragraph.pageBreakBefore } : {}),
|
|
267
|
-
...(paragraph.
|
|
266
|
+
...(paragraph.pageBreakBefore !== undefined ? { pageBreakBefore: paragraph.pageBreakBefore } : {}),
|
|
267
|
+
...(paragraph.widowControl !== undefined ? { widowControl: paragraph.widowControl } : {}),
|
|
268
|
+
...(paragraph.bidi !== undefined ? { bidi: paragraph.bidi } : {}),
|
|
268
269
|
...(paragraph.borders ? { borders: paragraph.borders } : {}),
|
|
269
270
|
...(paragraph.shading ? { shading: paragraph.shading } : {}),
|
|
271
|
+
...(paragraph.suppressLineNumbers !== undefined
|
|
272
|
+
? { suppressLineNumbers: paragraph.suppressLineNumbers }
|
|
273
|
+
: {}),
|
|
270
274
|
// A.7: preserve w14:paraId / w14:textId across import → export so
|
|
271
275
|
// downstream tools that diff documents by paragraph id stay stable.
|
|
272
276
|
...(paragraph.wordExtensionIds
|
|
@@ -154,9 +154,13 @@ export function parseEndnotesXml(
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const endnoteSeparators = parseFootnoteSeparators(xml);
|
|
158
|
+
|
|
157
159
|
return {
|
|
158
160
|
footnotes: existing?.footnotes ?? {},
|
|
159
161
|
endnotes,
|
|
162
|
+
...(existing?.footnoteSeparators ? { footnoteSeparators: existing.footnoteSeparators } : {}),
|
|
163
|
+
...(Object.keys(endnoteSeparators).length > 0 ? { endnoteSeparators } : {}),
|
|
160
164
|
};
|
|
161
165
|
}
|
|
162
166
|
|
|
@@ -174,7 +178,9 @@ export function parseFootnoteSeparators(xml: string): FootnoteSeparators {
|
|
|
174
178
|
if (!containerEl) return {};
|
|
175
179
|
|
|
176
180
|
let separatorContent: string | undefined;
|
|
181
|
+
let separatorParagraphXml: string | undefined;
|
|
177
182
|
let continuationSeparatorContent: string | undefined;
|
|
183
|
+
let continuationSeparatorParagraphXml: string | undefined;
|
|
178
184
|
|
|
179
185
|
for (const child of containerEl.children) {
|
|
180
186
|
if (child.type !== "element") continue;
|
|
@@ -186,6 +192,7 @@ export function parseFootnoteSeparators(xml: string): FootnoteSeparators {
|
|
|
186
192
|
|
|
187
193
|
const paraEl = findChildElementOptional(child, "p");
|
|
188
194
|
if (!paraEl) continue;
|
|
195
|
+
const paragraphXml = serializeElementToXml(paraEl);
|
|
189
196
|
|
|
190
197
|
const runXml = paraEl.children
|
|
191
198
|
.filter((c): c is XmlElementNode => c.type === "element" && localName(c.name) === "r")
|
|
@@ -194,14 +201,18 @@ export function parseFootnoteSeparators(xml: string): FootnoteSeparators {
|
|
|
194
201
|
|
|
195
202
|
if (rawType === "separator") {
|
|
196
203
|
separatorContent = runXml;
|
|
204
|
+
separatorParagraphXml = paragraphXml;
|
|
197
205
|
} else {
|
|
198
206
|
continuationSeparatorContent = runXml;
|
|
207
|
+
continuationSeparatorParagraphXml = paragraphXml;
|
|
199
208
|
}
|
|
200
209
|
}
|
|
201
210
|
|
|
202
211
|
return {
|
|
203
212
|
...(separatorContent !== undefined ? { separatorContent } : {}),
|
|
213
|
+
...(separatorParagraphXml !== undefined ? { separatorParagraphXml } : {}),
|
|
204
214
|
...(continuationSeparatorContent !== undefined ? { continuationSeparatorContent } : {}),
|
|
215
|
+
...(continuationSeparatorParagraphXml !== undefined ? { continuationSeparatorParagraphXml } : {}),
|
|
205
216
|
};
|
|
206
217
|
}
|
|
207
218
|
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from "../../model/canonical-document.ts";
|
|
15
15
|
import type { LegacyFormFieldNode } from "../../model/canonical-document.ts";
|
|
16
16
|
import { resolveHighlightColor } from "./highlight-colors.ts";
|
|
17
|
+
import type { ParseDrawingOpts } from "./parse-drawing.ts";
|
|
17
18
|
import { parseFFDataFromFldChar } from "./parse-ffdata.ts";
|
|
18
19
|
import { classifyFieldInstruction } from "./parse-fields.ts";
|
|
19
20
|
import { parseXmlWithOffsets as parseXml } from "./xml-parser.ts";
|
|
@@ -49,6 +50,7 @@ import {
|
|
|
49
50
|
readTableStyleId,
|
|
50
51
|
readTableWidth,
|
|
51
52
|
} from "./parse-tables.ts";
|
|
53
|
+
import { parseDrawingFrame } from "./parse-drawing.ts";
|
|
52
54
|
import { parseShapeXml, parseVmlXml } from "./parse-shapes.ts";
|
|
53
55
|
|
|
54
56
|
const TAB_ALIGN_VOCAB = new Set<TabStop["align"]>(["left", "center", "right", "decimal", "num", "bar", "clear"]);
|
|
@@ -67,6 +69,8 @@ export interface ParsedHeaderFooterDocument {
|
|
|
67
69
|
blocks: BlockNode[];
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
export type ParseHeaderFooterOpts = Omit<ParseDrawingOpts, "blockParser">;
|
|
73
|
+
|
|
70
74
|
// ---- XML node types (inline, no external dep) ----
|
|
71
75
|
|
|
72
76
|
interface XmlElementNode {
|
|
@@ -121,15 +125,21 @@ export function parseHeaderFooterReferences(
|
|
|
121
125
|
/**
|
|
122
126
|
* Parse a headerN.xml part (<w:hdr> root) into block nodes.
|
|
123
127
|
*/
|
|
124
|
-
export function parseHeaderXml(
|
|
125
|
-
|
|
128
|
+
export function parseHeaderXml(
|
|
129
|
+
xml: string,
|
|
130
|
+
opts: ParseHeaderFooterOpts = { relationships: [] },
|
|
131
|
+
): ParsedHeaderFooterDocument {
|
|
132
|
+
return parseHdrFtrXml(xml, "hdr", opts);
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
/**
|
|
129
136
|
* Parse a footerN.xml part (<w:ftr> root) into block nodes.
|
|
130
137
|
*/
|
|
131
|
-
export function parseFooterXml(
|
|
132
|
-
|
|
138
|
+
export function parseFooterXml(
|
|
139
|
+
xml: string,
|
|
140
|
+
opts: ParseHeaderFooterOpts = { relationships: [] },
|
|
141
|
+
): ParsedHeaderFooterDocument {
|
|
142
|
+
return parseHdrFtrXml(xml, "ftr", opts);
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
// ---- Internal helpers ----
|
|
@@ -213,6 +223,7 @@ function toHeaderFooterVariant(raw: string): HeaderFooterVariant {
|
|
|
213
223
|
function parseHdrFtrXml(
|
|
214
224
|
xml: string,
|
|
215
225
|
rootLocalName: "hdr" | "ftr",
|
|
226
|
+
opts: ParseHeaderFooterOpts = { relationships: [] },
|
|
216
227
|
): ParsedHeaderFooterDocument {
|
|
217
228
|
currentSourceXml = xml;
|
|
218
229
|
let root: XmlElementNode;
|
|
@@ -239,12 +250,12 @@ function parseHdrFtrXml(
|
|
|
239
250
|
const name = localName(child.name);
|
|
240
251
|
|
|
241
252
|
if (name === "p") {
|
|
242
|
-
blocks.push(parseParagraphElement(child, xml));
|
|
253
|
+
blocks.push(parseParagraphElement(child, xml, opts));
|
|
243
254
|
} else if (name === "tbl") {
|
|
244
255
|
// Simple tables (no revisions, fields, or nested tables) are promoted
|
|
245
256
|
// to supported-roundtrip; structurally risky tables stay opaque.
|
|
246
257
|
if (isSimpleSecondaryStoryTable(child)) {
|
|
247
|
-
blocks.push(parseSimpleTableElement(child, xml));
|
|
258
|
+
blocks.push(parseSimpleTableElement(child, xml, opts));
|
|
248
259
|
} else {
|
|
249
260
|
blocks.push({
|
|
250
261
|
type: "opaque_block",
|
|
@@ -267,7 +278,11 @@ function parseHdrFtrXml(
|
|
|
267
278
|
return { blocks };
|
|
268
279
|
}
|
|
269
280
|
|
|
270
|
-
function parseParagraphElement(
|
|
281
|
+
function parseParagraphElement(
|
|
282
|
+
pElement: XmlElementNode,
|
|
283
|
+
sourceXml: string,
|
|
284
|
+
opts: ParseHeaderFooterOpts,
|
|
285
|
+
): ParagraphNode {
|
|
271
286
|
let styleId: string | undefined;
|
|
272
287
|
let alignment: ParagraphNode["alignment"];
|
|
273
288
|
let spacing: ParagraphNode["spacing"];
|
|
@@ -295,9 +310,9 @@ function parseParagraphElement(pElement: XmlElementNode, sourceXml: string): Par
|
|
|
295
310
|
indentation = readParagraphIndentation(child);
|
|
296
311
|
tabStops = readParagraphTabStops(child);
|
|
297
312
|
} else if (name === "r") {
|
|
298
|
-
activeComplexField = appendRunNodes(child, children, activeComplexField, sourceXml);
|
|
313
|
+
activeComplexField = appendRunNodes(child, children, activeComplexField, sourceXml, opts);
|
|
299
314
|
} else if (name === "hyperlink") {
|
|
300
|
-
children.push(parseHyperlinkElement(child));
|
|
315
|
+
children.push(parseHyperlinkElement(child, opts));
|
|
301
316
|
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
302
317
|
children.push(parseBookmarkElement(child));
|
|
303
318
|
} else if (name === "fldSimple") {
|
|
@@ -350,6 +365,7 @@ function appendRunNodes(
|
|
|
350
365
|
nodes: InlineNode[],
|
|
351
366
|
activeComplexField: ActiveComplexField | null,
|
|
352
367
|
sourceXml: string,
|
|
368
|
+
opts: ParseHeaderFooterOpts,
|
|
353
369
|
): ActiveComplexField | null {
|
|
354
370
|
const marks: TextMark[] = parseRunProperties(rElement);
|
|
355
371
|
|
|
@@ -398,7 +414,7 @@ function appendRunNodes(
|
|
|
398
414
|
continue;
|
|
399
415
|
}
|
|
400
416
|
|
|
401
|
-
const inlineNode = parseRunChildNode(child, marks);
|
|
417
|
+
const inlineNode = parseRunChildNode(child, marks, opts);
|
|
402
418
|
if (!inlineNode) {
|
|
403
419
|
continue;
|
|
404
420
|
}
|
|
@@ -420,7 +436,10 @@ function appendRunNodes(
|
|
|
420
436
|
return activeComplexField;
|
|
421
437
|
}
|
|
422
438
|
|
|
423
|
-
function parseRunElement(
|
|
439
|
+
function parseRunElement(
|
|
440
|
+
rElement: XmlElementNode,
|
|
441
|
+
opts: ParseHeaderFooterOpts,
|
|
442
|
+
): InlineNode[] {
|
|
424
443
|
const nodes: InlineNode[] = [];
|
|
425
444
|
const marks: TextMark[] = parseRunProperties(rElement);
|
|
426
445
|
|
|
@@ -472,9 +491,9 @@ function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
|
472
491
|
pushFieldNode(nodes, child, "complex");
|
|
473
492
|
} else if (name === "drawing") {
|
|
474
493
|
const drawingXml = currentSourceXml.slice(child.start, child.end);
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
477
|
-
nodes.push(
|
|
494
|
+
const drawingResult = parseDrawingInlineNode(drawingXml, opts);
|
|
495
|
+
if (drawingResult) {
|
|
496
|
+
nodes.push(drawingResult);
|
|
478
497
|
}
|
|
479
498
|
} else if (name === "pict") {
|
|
480
499
|
const pictXml = currentSourceXml.slice(child.start, child.end);
|
|
@@ -483,17 +502,19 @@ function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
|
483
502
|
nodes.push(vmlResult);
|
|
484
503
|
}
|
|
485
504
|
} else if (name === "AlternateContent") {
|
|
505
|
+
const alternateXml = currentSourceXml.slice(child.start, child.end);
|
|
486
506
|
const drawingNode = findFirstDescendant(child, "drawing");
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
507
|
+
const legacyDrawingXml = drawingNode
|
|
508
|
+
? currentSourceXml.slice(drawingNode.start, drawingNode.end)
|
|
509
|
+
: undefined;
|
|
510
|
+
const alternateDrawingResult = parseDrawingInlineNode(
|
|
511
|
+
alternateXml,
|
|
512
|
+
opts,
|
|
513
|
+
legacyDrawingXml,
|
|
514
|
+
);
|
|
515
|
+
if (alternateDrawingResult) {
|
|
516
|
+
nodes.push(alternateDrawingResult);
|
|
517
|
+
continue;
|
|
497
518
|
}
|
|
498
519
|
const pictNode = findFirstDescendant(child, "pict");
|
|
499
520
|
if (pictNode) {
|
|
@@ -515,6 +536,7 @@ function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
|
515
536
|
function parseRunChildNode(
|
|
516
537
|
child: XmlElementNode,
|
|
517
538
|
marks: TextMark[],
|
|
539
|
+
opts: ParseHeaderFooterOpts,
|
|
518
540
|
): InlineNode | null {
|
|
519
541
|
const name = localName(child.name);
|
|
520
542
|
|
|
@@ -566,23 +588,21 @@ function parseRunChildNode(
|
|
|
566
588
|
}
|
|
567
589
|
if (name === "drawing") {
|
|
568
590
|
const drawingXml = currentSourceXml.slice(child.start, child.end);
|
|
569
|
-
return
|
|
591
|
+
return parseDrawingInlineNode(drawingXml, opts);
|
|
570
592
|
}
|
|
571
593
|
if (name === "pict") {
|
|
572
594
|
const pictXml = currentSourceXml.slice(child.start, child.end);
|
|
573
595
|
return parseVmlXml(pictXml);
|
|
574
596
|
}
|
|
575
597
|
if (name === "AlternateContent") {
|
|
598
|
+
const alternateXml = currentSourceXml.slice(child.start, child.end);
|
|
576
599
|
const drawingNode = findFirstDescendant(child, "drawing");
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
rawXml: currentSourceXml.slice(child.start, child.end),
|
|
584
|
-
};
|
|
585
|
-
}
|
|
600
|
+
const drawingXml = drawingNode
|
|
601
|
+
? currentSourceXml.slice(drawingNode.start, drawingNode.end)
|
|
602
|
+
: undefined;
|
|
603
|
+
const drawingResult = parseDrawingInlineNode(alternateXml, opts, drawingXml);
|
|
604
|
+
if (drawingResult) {
|
|
605
|
+
return drawingResult;
|
|
586
606
|
}
|
|
587
607
|
const pictNode = findFirstDescendant(child, "pict");
|
|
588
608
|
if (pictNode) {
|
|
@@ -600,7 +620,10 @@ function parseRunChildNode(
|
|
|
600
620
|
return null;
|
|
601
621
|
}
|
|
602
622
|
|
|
603
|
-
function parseHyperlinkElement(
|
|
623
|
+
function parseHyperlinkElement(
|
|
624
|
+
element: XmlElementNode,
|
|
625
|
+
opts: ParseHeaderFooterOpts,
|
|
626
|
+
): Extract<InlineNode, { type: "hyperlink" }> {
|
|
604
627
|
const href = element.attributes["w:anchor"]
|
|
605
628
|
? `#${element.attributes["w:anchor"]}`
|
|
606
629
|
: element.attributes["r:id"] ?? "relationship:unknown";
|
|
@@ -608,7 +631,7 @@ function parseHyperlinkElement(element: XmlElementNode): Extract<InlineNode, { t
|
|
|
608
631
|
|
|
609
632
|
for (const child of element.children) {
|
|
610
633
|
if (child.type === "element" && localName(child.name) === "r") {
|
|
611
|
-
for (const runChild of parseRunElement(child)) {
|
|
634
|
+
for (const runChild of parseRunElement(child, opts)) {
|
|
612
635
|
if (runChild.type === "text" || runChild.type === "hard_break" || runChild.type === "tab") {
|
|
613
636
|
children.push(runChild);
|
|
614
637
|
}
|
|
@@ -623,6 +646,46 @@ function parseHyperlinkElement(element: XmlElementNode): Extract<InlineNode, { t
|
|
|
623
646
|
};
|
|
624
647
|
}
|
|
625
648
|
|
|
649
|
+
function parseDrawingInlineNode(
|
|
650
|
+
rawXml: string,
|
|
651
|
+
opts: ParseHeaderFooterOpts,
|
|
652
|
+
legacyDrawingXml?: string,
|
|
653
|
+
): InlineNode | null {
|
|
654
|
+
try {
|
|
655
|
+
const frame = parseDrawingFrame(rawXml, {
|
|
656
|
+
...opts,
|
|
657
|
+
relationships: opts.relationships ?? [],
|
|
658
|
+
});
|
|
659
|
+
if (
|
|
660
|
+
frame &&
|
|
661
|
+
!(
|
|
662
|
+
frame.content.type === "shape" &&
|
|
663
|
+
frame.content.isTextBox
|
|
664
|
+
)
|
|
665
|
+
) {
|
|
666
|
+
return frame;
|
|
667
|
+
}
|
|
668
|
+
if (frame?.content.type !== "shape" || !frame.content.isTextBox) {
|
|
669
|
+
return frame;
|
|
670
|
+
}
|
|
671
|
+
} catch {
|
|
672
|
+
// Fall through to the legacy shape path.
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const shapeXml = legacyDrawingXml ?? rawXml;
|
|
676
|
+
const legacyShape = parseShapeXml(shapeXml);
|
|
677
|
+
if (!legacyShape) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
if (legacyDrawingXml) {
|
|
681
|
+
return {
|
|
682
|
+
...legacyShape,
|
|
683
|
+
rawXml,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
return legacyShape;
|
|
687
|
+
}
|
|
688
|
+
|
|
626
689
|
function parseBookmarkElement(
|
|
627
690
|
element: XmlElementNode,
|
|
628
691
|
): Extract<InlineNode, { type: "bookmark_start" | "bookmark_end" }> {
|
|
@@ -958,7 +1021,11 @@ function isSafeSecondaryStoryFieldFamily(family: string): boolean {
|
|
|
958
1021
|
);
|
|
959
1022
|
}
|
|
960
1023
|
|
|
961
|
-
function parseSimpleTableElement(
|
|
1024
|
+
function parseSimpleTableElement(
|
|
1025
|
+
tblElement: XmlElementNode,
|
|
1026
|
+
sourceXml: string,
|
|
1027
|
+
opts: ParseHeaderFooterOpts,
|
|
1028
|
+
): TableNode {
|
|
962
1029
|
let gridColumns: number[] = [];
|
|
963
1030
|
const rows: TableRowNode[] = [];
|
|
964
1031
|
let propertiesXml: string | undefined;
|
|
@@ -998,7 +1065,7 @@ function parseSimpleTableElement(tblElement: XmlElementNode, sourceXml: string):
|
|
|
998
1065
|
} else if (name === "tblGrid") {
|
|
999
1066
|
gridColumns = readGridColumns(child);
|
|
1000
1067
|
} else if (name === "tr") {
|
|
1001
|
-
rows.push(parseSimpleTableRow(child, sourceXml));
|
|
1068
|
+
rows.push(parseSimpleTableRow(child, sourceXml, opts));
|
|
1002
1069
|
}
|
|
1003
1070
|
}
|
|
1004
1071
|
|
|
@@ -1027,7 +1094,11 @@ function readGridColumns(tblGrid: XmlElementNode): number[] {
|
|
|
1027
1094
|
return readSharedGridColumns(tblGrid);
|
|
1028
1095
|
}
|
|
1029
1096
|
|
|
1030
|
-
function parseSimpleTableRow(
|
|
1097
|
+
function parseSimpleTableRow(
|
|
1098
|
+
trElement: XmlElementNode,
|
|
1099
|
+
sourceXml: string,
|
|
1100
|
+
opts: ParseHeaderFooterOpts,
|
|
1101
|
+
): TableRowNode {
|
|
1031
1102
|
const cells: TableCellNode[] = [];
|
|
1032
1103
|
let propertiesXml: string | undefined;
|
|
1033
1104
|
let height: TableRowNode["height"];
|
|
@@ -1050,7 +1121,7 @@ function parseSimpleTableRow(trElement: XmlElementNode, sourceXml: string): Tabl
|
|
|
1050
1121
|
horizontalAlignment = readRowHorizontalAlignment(child);
|
|
1051
1122
|
cnfStyle = readRowCnfStyle(child);
|
|
1052
1123
|
} else if (name === "tc") {
|
|
1053
|
-
cells.push(parseSimpleTableCell(child, sourceXml));
|
|
1124
|
+
cells.push(parseSimpleTableCell(child, sourceXml, opts));
|
|
1054
1125
|
}
|
|
1055
1126
|
}
|
|
1056
1127
|
|
|
@@ -1067,7 +1138,11 @@ function parseSimpleTableRow(trElement: XmlElementNode, sourceXml: string): Tabl
|
|
|
1067
1138
|
};
|
|
1068
1139
|
}
|
|
1069
1140
|
|
|
1070
|
-
function parseSimpleTableCell(
|
|
1141
|
+
function parseSimpleTableCell(
|
|
1142
|
+
tcElement: XmlElementNode,
|
|
1143
|
+
sourceXml: string,
|
|
1144
|
+
opts: ParseHeaderFooterOpts,
|
|
1145
|
+
): TableCellNode {
|
|
1071
1146
|
const children: BlockNode[] = [];
|
|
1072
1147
|
let propertiesXml: string | undefined;
|
|
1073
1148
|
let gridSpan: number | undefined;
|
|
@@ -1107,7 +1182,7 @@ function parseSimpleTableCell(tcElement: XmlElementNode, sourceXml: string): Tab
|
|
|
1107
1182
|
margins = readCellMargins(child);
|
|
1108
1183
|
cnfStyle = readCellCnfStyle(child);
|
|
1109
1184
|
} else if (name === "p") {
|
|
1110
|
-
children.push(parseParagraphElement(child, sourceXml));
|
|
1185
|
+
children.push(parseParagraphElement(child, sourceXml, opts));
|
|
1111
1186
|
}
|
|
1112
1187
|
}
|
|
1113
1188
|
|
|
@@ -1150,15 +1150,15 @@ function parseBodyChild(
|
|
|
1150
1150
|
...(contextualSpacing !== undefined ? { contextualSpacing } : {}),
|
|
1151
1151
|
...(indentation ? { indentation } : {}),
|
|
1152
1152
|
...(tabStops && tabStops.length > 0 ? { tabStops } : {}),
|
|
1153
|
-
...(keepNext ? { keepNext } : {}),
|
|
1154
|
-
...(keepLines ? { keepLines } : {}),
|
|
1153
|
+
...(keepNext !== undefined ? { keepNext } : {}),
|
|
1154
|
+
...(keepLines !== undefined ? { keepLines } : {}),
|
|
1155
1155
|
...(outlineLevel !== undefined ? { outlineLevel } : {}),
|
|
1156
|
-
...(pageBreakBefore ? { pageBreakBefore } : {}),
|
|
1157
|
-
...(widowControl ? { widowControl } : {}),
|
|
1156
|
+
...(pageBreakBefore !== undefined ? { pageBreakBefore } : {}),
|
|
1157
|
+
...(widowControl !== undefined ? { widowControl } : {}),
|
|
1158
1158
|
...(borders ? { borders } : {}),
|
|
1159
1159
|
...(shading ? { shading } : {}),
|
|
1160
|
-
...(bidi ? { bidi } : {}),
|
|
1161
|
-
...(suppressLineNumbers ? { suppressLineNumbers } : {}),
|
|
1160
|
+
...(bidi !== undefined ? { bidi } : {}),
|
|
1161
|
+
...(suppressLineNumbers !== undefined ? { suppressLineNumbers } : {}),
|
|
1162
1162
|
...(cnfStyle ? { cnfStyle } : {}),
|
|
1163
1163
|
...(wordExtensionIds ? { wordExtensionIds } : {}),
|
|
1164
1164
|
...(sectionProperties ? { sectionProperties } : {}),
|
|
@@ -1848,7 +1848,7 @@ function tableRequiresOpaquePreservation(rawXml: string): boolean {
|
|
|
1848
1848
|
// nested tables, floating images, VML preview atoms, and bounded field
|
|
1849
1849
|
// families already owned by the current field slice. Risky table-local
|
|
1850
1850
|
// semantics still fail closed to preserve-only.
|
|
1851
|
-
if (/<w:(ins|del|rPrChange|pPrChange|tblPrChange|trPrChange|tcPrChange|sectPrChange|cellIns|cellDel|cellMerge|smartTag
|
|
1851
|
+
if (/<w:(ins|del|rPrChange|pPrChange|tblPrChange|trPrChange|tcPrChange|sectPrChange|cellIns|cellDel|cellMerge|smartTag)\b/.test(rawXml)) {
|
|
1852
1852
|
return true;
|
|
1853
1853
|
}
|
|
1854
1854
|
|
|
@@ -2103,7 +2103,7 @@ function readOnOffParagraphProperty(node: XmlElementNode, name: string): boolean
|
|
|
2103
2103
|
);
|
|
2104
2104
|
if (!propNode) return undefined;
|
|
2105
2105
|
const val = (propNode.attributes["w:val"] ?? propNode.attributes.val ?? "true").toLowerCase();
|
|
2106
|
-
return val !== "false" && val !== "0" && val !== "off"
|
|
2106
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
2107
2107
|
}
|
|
2108
2108
|
|
|
2109
2109
|
function readOptionalOnOffParagraphProperty(
|
|
@@ -2172,9 +2172,21 @@ function readParagraphShading(node: XmlElementNode): ParagraphShading | undefine
|
|
|
2172
2172
|
const fill = shadingNode.attributes["w:fill"] ?? shadingNode.attributes.fill;
|
|
2173
2173
|
const color = shadingNode.attributes["w:color"] ?? shadingNode.attributes.color;
|
|
2174
2174
|
const val = shadingNode.attributes["w:val"] ?? shadingNode.attributes.val;
|
|
2175
|
+
const themeFill = shadingNode.attributes["w:themeFill"] ?? shadingNode.attributes.themeFill;
|
|
2176
|
+
const themeFillTint = shadingNode.attributes["w:themeFillTint"] ?? shadingNode.attributes.themeFillTint;
|
|
2177
|
+
const themeFillShade = shadingNode.attributes["w:themeFillShade"] ?? shadingNode.attributes.themeFillShade;
|
|
2178
|
+
const themeColor = shadingNode.attributes["w:themeColor"] ?? shadingNode.attributes.themeColor;
|
|
2179
|
+
const themeColorTint = shadingNode.attributes["w:themeColorTint"] ?? shadingNode.attributes.themeColorTint;
|
|
2180
|
+
const themeColorShade = shadingNode.attributes["w:themeColorShade"] ?? shadingNode.attributes.themeColorShade;
|
|
2175
2181
|
if (fill) shading.fill = fill;
|
|
2176
2182
|
if (color) shading.color = color;
|
|
2177
2183
|
if (val) shading.val = val;
|
|
2184
|
+
if (themeFill) shading.themeFill = themeFill;
|
|
2185
|
+
if (themeFillTint) shading.themeFillTint = themeFillTint;
|
|
2186
|
+
if (themeFillShade) shading.themeFillShade = themeFillShade;
|
|
2187
|
+
if (themeColor) shading.themeColor = themeColor;
|
|
2188
|
+
if (themeColorTint) shading.themeColorTint = themeColorTint;
|
|
2189
|
+
if (themeColorShade) shading.themeColorShade = themeColorShade;
|
|
2178
2190
|
return Object.keys(shading).length > 0 ? shading : undefined;
|
|
2179
2191
|
}
|
|
2180
2192
|
|