@beyondwork/docx-react-component 1.0.19 → 1.0.21
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 +44 -25
- package/src/api/public-types.ts +336 -0
- package/src/api/session-state.ts +2 -0
- package/src/core/commands/formatting-commands.ts +1 -1
- package/src/core/commands/index.ts +14 -2
- package/src/core/search/search-text.ts +28 -0
- package/src/core/state/editor-state.ts +3 -0
- package/src/index.ts +21 -0
- package/src/io/docx-session.ts +363 -17
- 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 +83 -3
- package/src/io/export/split-review-boundaries.ts +181 -19
- package/src/io/normalize/normalize-text.ts +82 -8
- package/src/io/ooxml/highlight-colors.ts +39 -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 +240 -2
- package/src/io/ooxml/parse-headers-footers.ts +431 -7
- package/src/io/ooxml/parse-inline-media.ts +15 -1
- package/src/io/ooxml/parse-main-document.ts +396 -14
- package/src/io/ooxml/parse-revisions.ts +317 -38
- package/src/legal/bookmarks.ts +44 -0
- package/src/legal/cross-references.ts +59 -1
- package/src/model/canonical-document.ts +117 -1
- package/src/model/snapshot.ts +85 -1
- package/src/review/store/revision-store.ts +6 -0
- package/src/review/store/revision-types.ts +1 -0
- package/src/runtime/document-navigation.ts +52 -13
- package/src/runtime/document-runtime.ts +1521 -75
- package/src/runtime/read-only-diagnostics-runtime.ts +8 -0
- package/src/runtime/session-capabilities.ts +33 -3
- package/src/runtime/surface-projection.ts +86 -25
- package/src/runtime/table-schema.ts +2 -2
- package/src/runtime/view-state.ts +24 -6
- package/src/runtime/workflow-markup.ts +349 -0
- package/src/ui/WordReviewEditor.tsx +915 -1314
- package/src/ui/editor-command-bag.ts +120 -0
- package/src/ui/editor-runtime-boundary.ts +1448 -0
- package/src/ui/editor-shell-view.tsx +134 -0
- package/src/ui/editor-surface-controller.tsx +55 -0
- package/src/ui/headless/revision-decoration-model.ts +4 -4
- package/src/ui/runtime-snapshot-selectors.ts +197 -0
- package/src/ui/workflow-surface-blocked-rails.ts +94 -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-selection-toolbar.tsx +27 -2
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +128 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +86 -14
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +2 -2
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +237 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +1 -1
- package/src/ui-tailwind/editor-surface/pm-schema.ts +139 -8
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +98 -48
- package/src/ui-tailwind/editor-surface/surface-build-keys.ts +55 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +7 -1
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +190 -48
- package/src/ui-tailwind/page-chrome-model.ts +27 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +7 -7
- package/src/ui-tailwind/review/tw-health-panel.tsx +31 -2
- package/src/ui-tailwind/review/tw-review-rail.tsx +3 -3
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +15 -15
- package/src/ui-tailwind/theme/editor-theme.css +130 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +543 -5
- package/src/ui-tailwind/tw-review-workspace.tsx +316 -19
- package/src/validation/compatibility-engine.ts +27 -4
- package/src/validation/compatibility-report.ts +1 -0
- package/src/validation/docx-comment-proof.ts +220 -0
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
import { toCanonicalNumberingInstanceId } from "./parse-numbering.ts";
|
|
27
27
|
import { parseComplexContentXml } from "./parse-complex-content.ts";
|
|
28
28
|
import { parseShapeXml, parseVmlXml } from "./parse-shapes.ts";
|
|
29
|
+
import { classifyFieldInstruction } from "./parse-fields.ts";
|
|
30
|
+
import { resolveHighlightColor } from "./highlight-colors.ts";
|
|
29
31
|
|
|
30
32
|
export interface ParsedMainDocument {
|
|
31
33
|
blocks: ParsedBlockNode[];
|
|
@@ -56,6 +58,7 @@ export interface ParsedParagraphNode {
|
|
|
56
58
|
};
|
|
57
59
|
alignment?: "left" | "center" | "right" | "both" | "distribute";
|
|
58
60
|
spacing?: ParagraphSpacing;
|
|
61
|
+
contextualSpacing?: boolean;
|
|
59
62
|
indentation?: ParagraphIndentation;
|
|
60
63
|
tabStops?: TabStop[];
|
|
61
64
|
keepNext?: boolean;
|
|
@@ -90,7 +93,10 @@ export type ParsedInlineNode =
|
|
|
90
93
|
| ParsedVmlShapeInlineNode
|
|
91
94
|
| ParsedBookmarkStartInlineNode
|
|
92
95
|
| ParsedBookmarkEndInlineNode
|
|
93
|
-
|
|
|
96
|
+
| ParsedFootnoteRefInlineNode
|
|
97
|
+
| ParsedFieldInlineNode
|
|
98
|
+
| ParsedPermStartInlineNode
|
|
99
|
+
| ParsedPermEndInlineNode;
|
|
94
100
|
|
|
95
101
|
export interface ParsedTextNode {
|
|
96
102
|
type: "text";
|
|
@@ -125,6 +131,8 @@ export interface ParsedImageNode {
|
|
|
125
131
|
contentType?: string;
|
|
126
132
|
filename?: string;
|
|
127
133
|
altText?: string;
|
|
134
|
+
widthEmu?: number;
|
|
135
|
+
heightEmu?: number;
|
|
128
136
|
placementXml?: string;
|
|
129
137
|
display?: "inline" | "floating";
|
|
130
138
|
floating?: {
|
|
@@ -204,11 +212,32 @@ export interface ParsedBookmarkEndInlineNode {
|
|
|
204
212
|
rawXml: string;
|
|
205
213
|
}
|
|
206
214
|
|
|
215
|
+
export interface ParsedFootnoteRefInlineNode {
|
|
216
|
+
type: "footnote_ref";
|
|
217
|
+
noteId: string;
|
|
218
|
+
noteKind: "footnote" | "endnote";
|
|
219
|
+
}
|
|
220
|
+
|
|
207
221
|
export interface ParsedFieldInlineNode {
|
|
208
222
|
type: "field";
|
|
209
|
-
fieldType: "simple";
|
|
223
|
+
fieldType: "simple" | "complex";
|
|
210
224
|
instruction: string;
|
|
211
|
-
contentXml
|
|
225
|
+
contentXml?: string;
|
|
226
|
+
children?: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
227
|
+
rawXml: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface ParsedPermStartInlineNode {
|
|
231
|
+
type: "perm_start";
|
|
232
|
+
rangeId: string;
|
|
233
|
+
editorGroup?: string;
|
|
234
|
+
editor?: string;
|
|
235
|
+
rawXml: string;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface ParsedPermEndInlineNode {
|
|
239
|
+
type: "perm_end";
|
|
240
|
+
rangeId: string;
|
|
212
241
|
rawXml: string;
|
|
213
242
|
}
|
|
214
243
|
|
|
@@ -412,6 +441,7 @@ function parseBodyChild(
|
|
|
412
441
|
let numbering: ParsedParagraphNode["numbering"];
|
|
413
442
|
let alignment: ParsedParagraphNode["alignment"];
|
|
414
443
|
let spacing: ParsedParagraphNode["spacing"];
|
|
444
|
+
let contextualSpacing: ParsedParagraphNode["contextualSpacing"];
|
|
415
445
|
let indentation: ParsedParagraphNode["indentation"];
|
|
416
446
|
let tabStops: ParsedParagraphNode["tabStops"];
|
|
417
447
|
let keepNext: ParsedParagraphNode["keepNext"];
|
|
@@ -428,6 +458,11 @@ function parseBodyChild(
|
|
|
428
458
|
let sectionPropertiesXml: string | undefined;
|
|
429
459
|
let paragraphSupported = true;
|
|
430
460
|
const children: ParsedInlineNode[] = [];
|
|
461
|
+
let activeComplexField: {
|
|
462
|
+
instruction: string;
|
|
463
|
+
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
464
|
+
mode: "instruction" | "result";
|
|
465
|
+
} | null = null;
|
|
431
466
|
|
|
432
467
|
for (const child of node.children) {
|
|
433
468
|
if (child.type !== "element") {
|
|
@@ -440,6 +475,7 @@ function parseBodyChild(
|
|
|
440
475
|
numbering = readParagraphNumbering(child);
|
|
441
476
|
alignment = readParagraphAlignment(child);
|
|
442
477
|
spacing = readParagraphSpacing(child);
|
|
478
|
+
contextualSpacing = readOptionalOnOffParagraphProperty(child, "contextualSpacing");
|
|
443
479
|
indentation = readParagraphIndentation(child);
|
|
444
480
|
tabStops = readParagraphTabStops(child);
|
|
445
481
|
keepNext = readOnOffParagraphProperty(child, "keepNext");
|
|
@@ -457,33 +493,148 @@ function parseBodyChild(
|
|
|
457
493
|
paragraphSupported = paragraphSupported && supportsParagraphProperties(child);
|
|
458
494
|
break;
|
|
459
495
|
case "r":
|
|
460
|
-
|
|
496
|
+
activeComplexField = appendParagraphRunNodes(
|
|
497
|
+
child,
|
|
498
|
+
children,
|
|
499
|
+
activeComplexField,
|
|
500
|
+
sourceXml,
|
|
501
|
+
relationships,
|
|
502
|
+
mediaParts,
|
|
503
|
+
sourcePartPath,
|
|
504
|
+
);
|
|
461
505
|
break;
|
|
462
506
|
case "hyperlink": {
|
|
507
|
+
flushActiveComplexField(children, () => {
|
|
508
|
+
activeComplexField = null;
|
|
509
|
+
}, activeComplexField);
|
|
463
510
|
const hyperlink = parseHyperlink(child, sourceXml, relationshipMap);
|
|
464
511
|
children.push(hyperlink);
|
|
465
512
|
break;
|
|
466
513
|
}
|
|
467
514
|
case "ins":
|
|
468
515
|
case "del": {
|
|
516
|
+
flushActiveComplexField(children, () => {
|
|
517
|
+
activeComplexField = null;
|
|
518
|
+
}, activeComplexField);
|
|
469
519
|
children.push(...parseRevisionContainer(child, sourceXml, relationshipMap));
|
|
470
520
|
break;
|
|
471
521
|
}
|
|
472
522
|
case "commentRangeStart":
|
|
473
523
|
case "commentRangeEnd":
|
|
474
524
|
break;
|
|
475
|
-
case "bookmarkStart":
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
525
|
+
case "bookmarkStart": {
|
|
526
|
+
const bkId = child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
527
|
+
const bkName = child.attributes["w:name"] ?? child.attributes.name ?? "";
|
|
528
|
+
if (bkId) {
|
|
529
|
+
const bookmarkNode = {
|
|
530
|
+
type: "bookmark_start",
|
|
531
|
+
bookmarkId: bkId,
|
|
532
|
+
name: bkName,
|
|
533
|
+
rawXml: sourceXml.slice(child.start, child.end),
|
|
534
|
+
} as ParsedBookmarkStartInlineNode;
|
|
535
|
+
flushActiveComplexField(children, () => {
|
|
536
|
+
activeComplexField = null;
|
|
537
|
+
}, activeComplexField);
|
|
538
|
+
children.push(bookmarkNode);
|
|
539
|
+
} else {
|
|
540
|
+
flushActiveComplexField(children, () => {
|
|
541
|
+
activeComplexField = null;
|
|
542
|
+
}, activeComplexField);
|
|
543
|
+
children.push({ type: "opaque_inline", rawXml: sourceXml.slice(child.start, child.end) });
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case "bookmarkEnd": {
|
|
548
|
+
const bkEndId = child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
549
|
+
if (bkEndId) {
|
|
550
|
+
const bookmarkNode = {
|
|
551
|
+
type: "bookmark_end",
|
|
552
|
+
bookmarkId: bkEndId,
|
|
553
|
+
rawXml: sourceXml.slice(child.start, child.end),
|
|
554
|
+
} as ParsedBookmarkEndInlineNode;
|
|
555
|
+
flushActiveComplexField(children, () => {
|
|
556
|
+
activeComplexField = null;
|
|
557
|
+
}, activeComplexField);
|
|
558
|
+
children.push(bookmarkNode);
|
|
559
|
+
} else {
|
|
560
|
+
flushActiveComplexField(children, () => {
|
|
561
|
+
activeComplexField = null;
|
|
562
|
+
}, activeComplexField);
|
|
563
|
+
children.push({ type: "opaque_inline", rawXml: sourceXml.slice(child.start, child.end) });
|
|
564
|
+
}
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case "fldSimple": {
|
|
568
|
+
flushActiveComplexField(children, () => {
|
|
569
|
+
activeComplexField = null;
|
|
570
|
+
}, activeComplexField);
|
|
571
|
+
const fieldInstr = child.attributes["w:instr"] ?? child.attributes.instr ?? "";
|
|
572
|
+
const fieldContentXml = child.children
|
|
573
|
+
.filter((c): c is XmlElementNode => c.type === "element")
|
|
574
|
+
.map((c) => sourceXml.slice(c.start, c.end))
|
|
575
|
+
.join("");
|
|
576
|
+
children.push({
|
|
577
|
+
type: "field",
|
|
578
|
+
fieldType: "simple",
|
|
579
|
+
instruction: fieldInstr,
|
|
580
|
+
contentXml: fieldContentXml,
|
|
581
|
+
rawXml: sourceXml.slice(child.start, child.end),
|
|
582
|
+
} as ParsedFieldInlineNode);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
480
585
|
case "proofErr":
|
|
586
|
+
flushActiveComplexField(children, () => {
|
|
587
|
+
activeComplexField = null;
|
|
588
|
+
}, activeComplexField);
|
|
481
589
|
children.push({
|
|
482
590
|
type: "opaque_inline",
|
|
483
591
|
rawXml: sourceXml.slice(child.start, child.end),
|
|
484
592
|
});
|
|
485
593
|
break;
|
|
594
|
+
case "permStart":
|
|
595
|
+
flushActiveComplexField(children, () => {
|
|
596
|
+
activeComplexField = null;
|
|
597
|
+
}, activeComplexField);
|
|
598
|
+
children.push(parsePermStartNode(child, sourceXml));
|
|
599
|
+
break;
|
|
600
|
+
case "permEnd":
|
|
601
|
+
flushActiveComplexField(children, () => {
|
|
602
|
+
activeComplexField = null;
|
|
603
|
+
}, activeComplexField);
|
|
604
|
+
children.push(parsePermEndNode(child, sourceXml));
|
|
605
|
+
break;
|
|
606
|
+
case "footnoteReference": {
|
|
607
|
+
flushActiveComplexField(children, () => {
|
|
608
|
+
activeComplexField = null;
|
|
609
|
+
}, activeComplexField);
|
|
610
|
+
const noteId = child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
611
|
+
if (noteId) {
|
|
612
|
+
children.push({
|
|
613
|
+
type: "footnote_ref",
|
|
614
|
+
noteId,
|
|
615
|
+
noteKind: "footnote",
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
case "endnoteReference": {
|
|
621
|
+
flushActiveComplexField(children, () => {
|
|
622
|
+
activeComplexField = null;
|
|
623
|
+
}, activeComplexField);
|
|
624
|
+
const noteId = child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
625
|
+
if (noteId) {
|
|
626
|
+
children.push({
|
|
627
|
+
type: "footnote_ref",
|
|
628
|
+
noteId,
|
|
629
|
+
noteKind: "endnote",
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
486
634
|
default:
|
|
635
|
+
flushActiveComplexField(children, () => {
|
|
636
|
+
activeComplexField = null;
|
|
637
|
+
}, activeComplexField);
|
|
487
638
|
children.push({
|
|
488
639
|
type: "opaque_inline",
|
|
489
640
|
rawXml: sourceXml.slice(child.start, child.end),
|
|
@@ -492,6 +643,10 @@ function parseBodyChild(
|
|
|
492
643
|
}
|
|
493
644
|
}
|
|
494
645
|
|
|
646
|
+
flushActiveComplexField(children, () => {
|
|
647
|
+
activeComplexField = null;
|
|
648
|
+
}, activeComplexField);
|
|
649
|
+
|
|
495
650
|
if (!paragraphSupported) {
|
|
496
651
|
return {
|
|
497
652
|
type: "opaque_block",
|
|
@@ -505,6 +660,7 @@ function parseBodyChild(
|
|
|
505
660
|
...(numbering ? { numbering } : {}),
|
|
506
661
|
...(alignment ? { alignment } : {}),
|
|
507
662
|
...(spacing ? { spacing } : {}),
|
|
663
|
+
...(contextualSpacing !== undefined ? { contextualSpacing } : {}),
|
|
508
664
|
...(indentation ? { indentation } : {}),
|
|
509
665
|
...(tabStops && tabStops.length > 0 ? { tabStops } : {}),
|
|
510
666
|
...(keepNext ? { keepNext } : {}),
|
|
@@ -524,6 +680,114 @@ function parseBodyChild(
|
|
|
524
680
|
};
|
|
525
681
|
}
|
|
526
682
|
|
|
683
|
+
function appendParagraphRunNodes(
|
|
684
|
+
node: XmlElementNode,
|
|
685
|
+
children: ParsedInlineNode[],
|
|
686
|
+
activeComplexField: {
|
|
687
|
+
instruction: string;
|
|
688
|
+
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
689
|
+
mode: "instruction" | "result";
|
|
690
|
+
} | null,
|
|
691
|
+
sourceXml: string,
|
|
692
|
+
relationships: readonly OpcRelationship[],
|
|
693
|
+
mediaParts: ReadonlyMap<string, InlineMediaPart>,
|
|
694
|
+
sourcePartPath: string,
|
|
695
|
+
): {
|
|
696
|
+
instruction: string;
|
|
697
|
+
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
698
|
+
mode: "instruction" | "result";
|
|
699
|
+
} | null {
|
|
700
|
+
const hasFieldMarkers = node.children.some(
|
|
701
|
+
(child) =>
|
|
702
|
+
child.type === "element" &&
|
|
703
|
+
(localName(child.name) === "fldChar" || localName(child.name) === "instrText"),
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
if (activeComplexField?.mode === "result" && !hasFieldMarkers) {
|
|
707
|
+
const run = parseRunContentOnly(node, sourceXml);
|
|
708
|
+
if (
|
|
709
|
+
run.supported &&
|
|
710
|
+
run.nodes.every(
|
|
711
|
+
(child) =>
|
|
712
|
+
child.type === "text" || child.type === "hard_break" || child.type === "tab",
|
|
713
|
+
)
|
|
714
|
+
) {
|
|
715
|
+
activeComplexField.children.push(...run.nodes);
|
|
716
|
+
return activeComplexField;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (!hasFieldMarkers) {
|
|
721
|
+
children.push(...parseRun(node, sourceXml, relationships, mediaParts, sourcePartPath));
|
|
722
|
+
return activeComplexField;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
for (const child of node.children) {
|
|
726
|
+
if (child.type !== "element") {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const name = localName(child.name);
|
|
731
|
+
if (name === "fldChar") {
|
|
732
|
+
const fldType = child.attributes["w:fldCharType"] ?? child.attributes.fldCharType;
|
|
733
|
+
if (fldType === "begin") {
|
|
734
|
+
activeComplexField = { instruction: "", children: [], mode: "instruction" };
|
|
735
|
+
} else if (fldType === "separate" && activeComplexField) {
|
|
736
|
+
activeComplexField.mode = "result";
|
|
737
|
+
} else if (fldType === "end" && activeComplexField) {
|
|
738
|
+
flushActiveComplexField(children, () => {
|
|
739
|
+
activeComplexField = null;
|
|
740
|
+
}, activeComplexField);
|
|
741
|
+
}
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (name === "instrText") {
|
|
746
|
+
const instruction = child.children
|
|
747
|
+
.filter((entry): entry is XmlTextNode => entry.type === "text")
|
|
748
|
+
.map((entry) => entry.text)
|
|
749
|
+
.join("");
|
|
750
|
+
if (activeComplexField) {
|
|
751
|
+
activeComplexField.instruction += instruction;
|
|
752
|
+
} else if (instruction.trim().length > 0) {
|
|
753
|
+
children.push({
|
|
754
|
+
type: "field",
|
|
755
|
+
fieldType: "complex",
|
|
756
|
+
instruction,
|
|
757
|
+
children: [],
|
|
758
|
+
rawXml: sourceXml.slice(node.start, node.end),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return activeComplexField;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function flushActiveComplexField(
|
|
769
|
+
children: ParsedInlineNode[],
|
|
770
|
+
reset: () => void,
|
|
771
|
+
activeComplexField: {
|
|
772
|
+
instruction: string;
|
|
773
|
+
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
774
|
+
mode: "instruction" | "result";
|
|
775
|
+
} | null,
|
|
776
|
+
): void {
|
|
777
|
+
if (!activeComplexField || activeComplexField.instruction.trim().length === 0) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
children.push({
|
|
782
|
+
type: "field",
|
|
783
|
+
fieldType: "complex",
|
|
784
|
+
instruction: activeComplexField.instruction,
|
|
785
|
+
children: activeComplexField.children,
|
|
786
|
+
rawXml: "",
|
|
787
|
+
});
|
|
788
|
+
reset();
|
|
789
|
+
}
|
|
790
|
+
|
|
527
791
|
function parseTableElement(
|
|
528
792
|
node: XmlElementNode,
|
|
529
793
|
sourceXml: string,
|
|
@@ -918,9 +1182,35 @@ function readTableGridColumns(node: XmlElementNode): number[] {
|
|
|
918
1182
|
*/
|
|
919
1183
|
function tableRequiresOpaquePreservation(rawXml: string): boolean {
|
|
920
1184
|
// Safe table-local content now includes hyperlinks, bookmarks, comments,
|
|
921
|
-
// nested tables, floating images,
|
|
922
|
-
//
|
|
923
|
-
|
|
1185
|
+
// nested tables, floating images, VML preview atoms, and bounded field
|
|
1186
|
+
// families already owned by the current field slice. Risky table-local
|
|
1187
|
+
// semantics still fail closed to preserve-only.
|
|
1188
|
+
if (/<w:(ins|del|rPrChange|pPrChange|tblPrChange|trPrChange|tcPrChange|sectPrChange|cellIns|cellDel|cellMerge|smartTag|gridAfter|gridBefore|tblCellSpacing)\b/.test(rawXml)) {
|
|
1189
|
+
return true;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const fldSimpleInstructions = [...rawXml.matchAll(/\bw:instr="([^"]*)"/g)].map((match) => match[1] ?? "");
|
|
1193
|
+
const complexInstructions = [...rawXml.matchAll(/<(?:\w+:)?instrText\b[^>]*>([\s\S]*?)<\/(?:\w+:)?instrText>/gu)]
|
|
1194
|
+
.map((match) => decodeXmlEntities(match[1] ?? ""));
|
|
1195
|
+
for (const instruction of [...fldSimpleInstructions, ...complexInstructions]) {
|
|
1196
|
+
const classification = classifyFieldInstruction(instruction);
|
|
1197
|
+
if (!isSafeMainStoryTableFieldFamily(classification.family)) {
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function isSafeMainStoryTableFieldFamily(family: string): boolean {
|
|
1206
|
+
return (
|
|
1207
|
+
family === "REF" ||
|
|
1208
|
+
family === "PAGEREF" ||
|
|
1209
|
+
family === "NOTEREF" ||
|
|
1210
|
+
family === "TOC" ||
|
|
1211
|
+
family === "PAGE" ||
|
|
1212
|
+
family === "NUMPAGES"
|
|
1213
|
+
);
|
|
924
1214
|
}
|
|
925
1215
|
|
|
926
1216
|
function readCellGridSpan(node: XmlElementNode): number | undefined {
|
|
@@ -1091,6 +1381,18 @@ function readOnOffParagraphProperty(node: XmlElementNode, name: string): boolean
|
|
|
1091
1381
|
return val !== "false" && val !== "0" && val !== "off" ? true : undefined;
|
|
1092
1382
|
}
|
|
1093
1383
|
|
|
1384
|
+
function readOptionalOnOffParagraphProperty(
|
|
1385
|
+
node: XmlElementNode,
|
|
1386
|
+
name: string,
|
|
1387
|
+
): boolean | undefined {
|
|
1388
|
+
const propNode = node.children.find(
|
|
1389
|
+
(child): child is XmlElementNode => child.type === "element" && localName(child.name) === name,
|
|
1390
|
+
);
|
|
1391
|
+
if (!propNode) return undefined;
|
|
1392
|
+
const val = (propNode.attributes["w:val"] ?? propNode.attributes.val ?? "true").toLowerCase();
|
|
1393
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1094
1396
|
function readParagraphOutlineLevel(node: XmlElementNode): number | undefined {
|
|
1095
1397
|
const propNode = node.children.find(
|
|
1096
1398
|
(child): child is XmlElementNode => child.type === "element" && localName(child.name) === "outlineLvl",
|
|
@@ -1355,6 +1657,8 @@ function parseRun(
|
|
|
1355
1657
|
contentType: media.contentType,
|
|
1356
1658
|
filename: media.filename,
|
|
1357
1659
|
...(media.altText ? { altText: media.altText } : {}),
|
|
1660
|
+
...(media.widthEmu !== undefined ? { widthEmu: media.widthEmu } : {}),
|
|
1661
|
+
...(media.heightEmu !== undefined ? { heightEmu: media.heightEmu } : {}),
|
|
1358
1662
|
placementXml,
|
|
1359
1663
|
...(media.display ? { display: media.display } : {}),
|
|
1360
1664
|
...(media.floating ? { floating: media.floating } : {}),
|
|
@@ -1387,6 +1691,28 @@ function parseRun(
|
|
|
1387
1691
|
}
|
|
1388
1692
|
case "commentReference":
|
|
1389
1693
|
break;
|
|
1694
|
+
case "footnoteReference": {
|
|
1695
|
+
const noteId = child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
1696
|
+
if (noteId) {
|
|
1697
|
+
result.push({
|
|
1698
|
+
type: "footnote_ref",
|
|
1699
|
+
noteId,
|
|
1700
|
+
noteKind: "footnote",
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
case "endnoteReference": {
|
|
1706
|
+
const noteId = child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
1707
|
+
if (noteId) {
|
|
1708
|
+
result.push({
|
|
1709
|
+
type: "footnote_ref",
|
|
1710
|
+
noteId,
|
|
1711
|
+
noteKind: "endnote",
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
break;
|
|
1715
|
+
}
|
|
1390
1716
|
case "lastRenderedPageBreak":
|
|
1391
1717
|
case "proofErr":
|
|
1392
1718
|
result.push({
|
|
@@ -1466,8 +1792,6 @@ function parseRevisionContainer(
|
|
|
1466
1792
|
case "commentRangeEnd":
|
|
1467
1793
|
case "bookmarkStart":
|
|
1468
1794
|
case "bookmarkEnd":
|
|
1469
|
-
case "permStart":
|
|
1470
|
-
case "permEnd":
|
|
1471
1795
|
case "proofErr":
|
|
1472
1796
|
case "lastRenderedPageBreak":
|
|
1473
1797
|
return [
|
|
@@ -1476,6 +1800,10 @@ function parseRevisionContainer(
|
|
|
1476
1800
|
rawXml: sourceXml.slice(node.start, node.end),
|
|
1477
1801
|
},
|
|
1478
1802
|
];
|
|
1803
|
+
case "permStart":
|
|
1804
|
+
return [parsePermStartNode(node, sourceXml)];
|
|
1805
|
+
case "permEnd":
|
|
1806
|
+
return [parsePermEndNode(node, sourceXml)];
|
|
1479
1807
|
default:
|
|
1480
1808
|
return [
|
|
1481
1809
|
{
|
|
@@ -1697,6 +2025,11 @@ function readRunMarks(node: XmlElementNode, sourceXml: string): MarksParseResult
|
|
|
1697
2025
|
marks.push(backgroundColorMark);
|
|
1698
2026
|
}
|
|
1699
2027
|
|
|
2028
|
+
const highlightMark = readRunHighlight(properties);
|
|
2029
|
+
if (highlightMark) {
|
|
2030
|
+
marks.push(highlightMark);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1700
2033
|
const charSpacingMark = readNumericRunMark(properties, "spacing", "charSpacing");
|
|
1701
2034
|
if (charSpacingMark) {
|
|
1702
2035
|
marks.push(charSpacingMark);
|
|
@@ -1809,6 +2142,28 @@ function readRunBackgroundColor(properties: XmlElementNode): TextMark | undefine
|
|
|
1809
2142
|
return { type: "backgroundColor", color: fill };
|
|
1810
2143
|
}
|
|
1811
2144
|
|
|
2145
|
+
function readRunHighlight(properties: XmlElementNode): TextMark | undefined {
|
|
2146
|
+
const highlightNode = properties.children.find(
|
|
2147
|
+
(child): child is XmlElementNode =>
|
|
2148
|
+
child.type === "element" && localName(child.name) === "highlight",
|
|
2149
|
+
);
|
|
2150
|
+
if (!highlightNode) {
|
|
2151
|
+
return undefined;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
const highlightValue = highlightNode.attributes["w:val"] ?? highlightNode.attributes.val;
|
|
2155
|
+
const resolvedHighlight = resolveHighlightColor(highlightValue);
|
|
2156
|
+
if (!resolvedHighlight) {
|
|
2157
|
+
return undefined;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
return {
|
|
2161
|
+
type: "highlight",
|
|
2162
|
+
color: resolvedHighlight.color,
|
|
2163
|
+
val: resolvedHighlight.val,
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
|
|
1812
2167
|
function readNumericRunMark(
|
|
1813
2168
|
properties: XmlElementNode,
|
|
1814
2169
|
elementName: "spacing" | "kern" | "position",
|
|
@@ -2412,3 +2767,30 @@ function parseBorderSpec(node: XmlElementNode): BorderSpec | undefined {
|
|
|
2412
2767
|
|
|
2413
2768
|
return Object.keys(border).length > 0 ? border : undefined;
|
|
2414
2769
|
}
|
|
2770
|
+
|
|
2771
|
+
function parsePermStartNode(
|
|
2772
|
+
node: XmlElementNode,
|
|
2773
|
+
sourceXml: string,
|
|
2774
|
+
): ParsedPermStartInlineNode {
|
|
2775
|
+
const rangeId = node.attributes["w:id"] ?? node.attributes.id ?? "";
|
|
2776
|
+
const edGrp = node.attributes["w:edGrp"] ?? node.attributes.edGrp;
|
|
2777
|
+
const ed = node.attributes["w:ed"] ?? node.attributes.ed;
|
|
2778
|
+
return {
|
|
2779
|
+
type: "perm_start",
|
|
2780
|
+
rangeId,
|
|
2781
|
+
...(edGrp ? { editorGroup: edGrp } : {}),
|
|
2782
|
+
...(ed ? { editor: ed } : {}),
|
|
2783
|
+
rawXml: sourceXml.slice(node.start, node.end),
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
function parsePermEndNode(
|
|
2788
|
+
node: XmlElementNode,
|
|
2789
|
+
sourceXml: string,
|
|
2790
|
+
): ParsedPermEndInlineNode {
|
|
2791
|
+
return {
|
|
2792
|
+
type: "perm_end",
|
|
2793
|
+
rangeId: node.attributes["w:id"] ?? node.attributes.id ?? "",
|
|
2794
|
+
rawXml: sourceXml.slice(node.start, node.end),
|
|
2795
|
+
};
|
|
2796
|
+
}
|