@beyondwork/docx-react-component 1.0.58 → 1.0.59
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 +2 -2
- package/package.json +2 -1
- package/src/api/awareness-identity-types.ts +4 -2
- package/src/api/comment-negotiation-types.ts +4 -1
- package/src/api/external-custody-types.ts +16 -0
- package/src/api/internal/build-ref-projections.ts +108 -0
- package/src/api/package-version.ts +1 -1
- package/src/api/participants-types.ts +11 -1
- package/src/api/public-types.ts +978 -10
- package/src/api/scope-metadata-resolver-types.ts +6 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +225 -16
- package/src/core/commands/legacy-form-field-commands.ts +181 -0
- package/src/core/commands/table-structure-commands.ts +149 -31
- package/src/core/selection/mapping.ts +20 -0
- package/src/core/state/editor-state.ts +2 -1
- package/src/index.ts +28 -0
- package/src/io/docx-session.ts +22 -3
- package/src/io/export/export-session.ts +11 -7
- package/src/io/export/ooxml-namespaces.ts +47 -0
- package/src/io/export/reattach-preserved-parts.ts +4 -16
- package/src/io/export/serialize-comments.ts +3 -131
- package/src/io/export/serialize-ffdata.ts +89 -0
- package/src/io/export/serialize-headers-footers.ts +5 -0
- package/src/io/export/serialize-main-document.ts +224 -34
- package/src/io/export/serialize-numbering.ts +22 -2
- package/src/io/export/serialize-revisions.ts +99 -0
- package/src/io/export/serialize-tables.ts +9 -0
- package/src/io/export/split-review-boundaries.ts +1 -0
- package/src/io/export/table-properties-xml.ts +14 -0
- package/src/io/load-scheduler.ts +70 -28
- package/src/io/normalize/normalize-text.ts +13 -0
- package/src/io/ooxml/_mini-xml.ts +198 -0
- package/src/io/ooxml/canonicalize-payload.ts +1 -4
- package/src/io/ooxml/chart/chart-style-table.ts +4 -3
- package/src/io/ooxml/chart/parse-chart-space.ts +2 -4
- package/src/io/ooxml/chart/parse-series.ts +2 -1
- package/src/io/ooxml/chart/resolve-color.ts +2 -2
- package/src/io/ooxml/chart/types.ts +6 -434
- package/src/io/ooxml/comment-presentation-payload.ts +6 -5
- package/src/io/ooxml/highlight-colors.ts +8 -5
- package/src/io/ooxml/parse-anchor.ts +68 -53
- package/src/io/ooxml/parse-comments.ts +14 -142
- package/src/io/ooxml/parse-complex-content.ts +3 -106
- package/src/io/ooxml/parse-drawing.ts +100 -195
- package/src/io/ooxml/parse-ffdata.ts +93 -0
- package/src/io/ooxml/parse-fields.ts +7 -146
- package/src/io/ooxml/parse-fill.ts +88 -8
- package/src/io/ooxml/parse-font-table.ts +5 -105
- package/src/io/ooxml/parse-footnotes.ts +28 -152
- package/src/io/ooxml/parse-headers-footers.ts +106 -212
- package/src/io/ooxml/parse-inline-media.ts +3 -200
- package/src/io/ooxml/parse-main-document.ts +180 -217
- package/src/io/ooxml/parse-numbering.ts +154 -335
- package/src/io/ooxml/parse-object.ts +147 -0
- package/src/io/ooxml/parse-ole-relationship.ts +82 -0
- package/src/io/ooxml/parse-paragraph-formatting.ts +7 -10
- package/src/io/ooxml/parse-picture-sdt.ts +85 -0
- package/src/io/ooxml/parse-picture.ts +72 -42
- package/src/io/ooxml/parse-revisions.ts +285 -51
- package/src/io/ooxml/parse-settings.ts +6 -99
- package/src/io/ooxml/parse-shapes.ts +25 -140
- package/src/io/ooxml/parse-styles.ts +3 -218
- package/src/io/ooxml/parse-tables.ts +76 -256
- package/src/io/ooxml/parse-theme.ts +1 -4
- package/src/io/ooxml/property-grab-bag.ts +5 -47
- package/src/io/ooxml/xml-element-serialize.ts +32 -0
- package/src/io/ooxml/xml-parser.ts +183 -0
- package/src/legal/bookmarks.ts +1 -1
- package/src/legal/cross-references.ts +1 -1
- package/src/legal/defined-terms.ts +1 -1
- package/src/legal/{_document-root.ts → document-root.ts} +8 -0
- package/src/legal/signature-blocks.ts +1 -1
- package/src/model/canonical-document.ts +159 -6
- package/src/model/chart-types.ts +439 -0
- package/src/model/snapshot.ts +3 -1
- package/src/review/store/comment-remapping.ts +24 -11
- package/src/review/store/revision-actions.ts +482 -2
- package/src/review/store/revision-store.ts +15 -0
- package/src/review/store/revision-types.ts +76 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +24 -0
- package/src/runtime/collab/runtime-collab-sync.ts +33 -0
- package/src/runtime/diagnostics/build-diagnostic.ts +151 -0
- package/src/runtime/diagnostics/code-metadata-table.ts +221 -0
- package/src/runtime/document-runtime.ts +476 -34
- package/src/runtime/document-search.ts +115 -0
- package/src/runtime/edit-ops/index.ts +18 -2
- package/src/runtime/footnote-resolver.ts +130 -0
- package/src/runtime/layout/layout-engine-instance.ts +31 -4
- package/src/runtime/layout/layout-engine-version.ts +37 -1
- package/src/runtime/layout/page-graph.ts +14 -1
- package/src/runtime/layout/resolved-formatting-state.ts +21 -0
- package/src/runtime/numbering-prefix.ts +17 -0
- package/src/runtime/query-scopes.ts +5 -8
- package/src/runtime/resolved-numbering-geometry.ts +37 -6
- package/src/runtime/revision-runtime.ts +27 -1
- package/src/runtime/selection/post-edit-validator.ts +60 -6
- package/src/runtime/structure-ops/index.ts +20 -4
- package/src/runtime/surface-projection.ts +290 -21
- package/src/runtime/table-schema.ts +6 -0
- package/src/runtime/theme-color-resolver.ts +2 -2
- package/src/runtime/units.ts +9 -0
- package/src/runtime/workflow-rail-segments.ts +4 -0
- package/src/ui/WordReviewEditor.tsx +187 -43
- package/src/ui/editor-runtime-boundary.ts +10 -0
- package/src/ui/editor-shell-view.tsx +4 -1
- package/src/ui/headless/chrome-registry.ts +53 -0
- package/src/ui/headless/selection-tool-resolver.ts +11 -1
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +13 -0
- package/src/ui-tailwind/chrome/tw-command-palette-mount.tsx +96 -0
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +2 -1
- package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +5 -4
- package/src/ui-tailwind/chrome/tw-mode-dock.tsx +6 -2
- package/src/ui-tailwind/chrome/use-container-breakpoint.ts +111 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +0 -9
- package/src/ui-tailwind/chrome-overlay/tw-object-selection-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +6 -7
- package/src/ui-tailwind/editor-surface/pm-schema.ts +87 -25
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +9 -0
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +76 -14
- package/src/ui-tailwind/editor-surface/tw-page-block-view.helpers.ts +18 -1
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +2 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +18 -2
- package/src/ui-tailwind/index.ts +9 -0
- package/src/ui-tailwind/page-chrome-model.ts +77 -5
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +56 -1
- package/src/ui-tailwind/page-stack/tw-region-block-renderer.tsx +2 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +116 -113
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +2 -2
- package/src/ui-tailwind/theme/tokens.ts +14 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +5 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +29 -87
- package/src/validation/diagnostics.ts +1 -0
|
@@ -37,8 +37,10 @@ import {
|
|
|
37
37
|
import { toCanonicalNumberingInstanceId } from "./parse-numbering.ts";
|
|
38
38
|
import { parseComplexContentXml, type ChartPartLookup } from "./parse-complex-content.ts";
|
|
39
39
|
import { parseShapeXml, parseVmlXml } from "./parse-shapes.ts";
|
|
40
|
+
import { parseObject } from "./parse-object.ts";
|
|
40
41
|
import { parseDrawingFrame } from "./parse-drawing.ts";
|
|
41
42
|
import { classifyFieldInstruction } from "./parse-fields.ts";
|
|
43
|
+
import { parseFFDataFromFldChar } from "./parse-ffdata.ts";
|
|
42
44
|
import { resolveHighlightColor } from "./highlight-colors.ts";
|
|
43
45
|
import {
|
|
44
46
|
readCellBorders as readSharedCellBorders,
|
|
@@ -76,6 +78,8 @@ import {
|
|
|
76
78
|
capturePropertyGrabBag,
|
|
77
79
|
type PropertyGrabBagDescriptor,
|
|
78
80
|
} from "./property-grab-bag.ts";
|
|
81
|
+
import { parseXmlWithOffsets as parseXml } from "./xml-parser.ts";
|
|
82
|
+
import { localName } from "./xml-attr-helpers.ts";
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
85
|
* Modelled direct children of `<w:sectPr>` that `parseSectionPropertiesFromElement`
|
|
@@ -277,6 +281,7 @@ export type ParsedInlineNode =
|
|
|
277
281
|
| ParsedShapeInlineNode
|
|
278
282
|
| ParsedWordArtInlineNode
|
|
279
283
|
| ParsedVmlShapeInlineNode
|
|
284
|
+
| ParsedOleEmbedInlineNode
|
|
280
285
|
| ParsedBookmarkStartInlineNode
|
|
281
286
|
| ParsedBookmarkEndInlineNode
|
|
282
287
|
| ParsedFootnoteRefInlineNode
|
|
@@ -393,6 +398,20 @@ export interface ParsedVmlShapeInlineNode {
|
|
|
393
398
|
rawXml: string;
|
|
394
399
|
}
|
|
395
400
|
|
|
401
|
+
export interface ParsedOleEmbedInlineNode {
|
|
402
|
+
type: "ole_embed";
|
|
403
|
+
id: string;
|
|
404
|
+
progId?: string;
|
|
405
|
+
embedType: "oleObject";
|
|
406
|
+
relationshipId: string;
|
|
407
|
+
metadata: {
|
|
408
|
+
originalFilename?: string;
|
|
409
|
+
classId?: string;
|
|
410
|
+
shapeId?: string;
|
|
411
|
+
};
|
|
412
|
+
rawXml: string;
|
|
413
|
+
}
|
|
414
|
+
|
|
396
415
|
export interface ParsedBookmarkStartInlineNode {
|
|
397
416
|
type: "bookmark_start";
|
|
398
417
|
bookmarkId: string;
|
|
@@ -419,6 +438,7 @@ export interface ParsedFieldInlineNode {
|
|
|
419
438
|
contentXml?: string;
|
|
420
439
|
children?: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
421
440
|
rawXml: string;
|
|
441
|
+
legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
|
|
422
442
|
}
|
|
423
443
|
|
|
424
444
|
export interface ParsedPermStartInlineNode {
|
|
@@ -580,14 +600,21 @@ const HYPERLINK_RELATIONSHIP_TYPE =
|
|
|
580
600
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
|
|
581
601
|
|
|
582
602
|
/**
|
|
583
|
-
* Request-scoped chart-part lookup. Set by `parseMainDocumentXml`
|
|
584
|
-
*
|
|
585
|
-
* the `<w:drawing>` →
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
* try/finally in
|
|
589
|
-
* leaks across
|
|
590
|
-
*
|
|
603
|
+
* Request-scoped chart-part lookup. Set by `parseMainDocumentXml` (and
|
|
604
|
+
* `parseSdtXml` for isolated SDT parsing) for the duration of a single
|
|
605
|
+
* top-level parse; read by `parseRun` where the `<w:drawing>` →
|
|
606
|
+
* `parseComplexContentXml` call site lives. Using a module variable instead
|
|
607
|
+
* of threading the callback through ~8 intermediate function signatures keeps
|
|
608
|
+
* the call sites readable; the try/finally in each entry point ensures the
|
|
609
|
+
* variable never leaks across calls.
|
|
610
|
+
*
|
|
611
|
+
* **Re-entrancy invariant:** Node.js is single-threaded and the parser is
|
|
612
|
+
* fully synchronous, so no two top-level entry points can interleave.
|
|
613
|
+
* `parseSdtXml` must NEVER be called from within a `blockParser` callback
|
|
614
|
+
* that is itself executing inside a `parseMainDocumentXml` session — doing
|
|
615
|
+
* so would clobber the outer lookup and clear it on the inner finally, leaving
|
|
616
|
+
* the outer session with `undefined`. In the current codebase this path does
|
|
617
|
+
* not exist; `parseSdtXml` is only callable externally via `parsePictureSdt`.
|
|
591
618
|
*/
|
|
592
619
|
let activeChartPartLookup: ChartPartLookup | undefined;
|
|
593
620
|
|
|
@@ -606,13 +633,49 @@ export function parseMainDocumentXml(
|
|
|
606
633
|
}
|
|
607
634
|
}
|
|
608
635
|
|
|
636
|
+
/**
|
|
637
|
+
* CO4.5 — Parse a raw `<w:sdt>` XML fragment in isolation, with full drawing-
|
|
638
|
+
* frame support. Used by `parse-picture-sdt.ts` and tests.
|
|
639
|
+
*
|
|
640
|
+
* Falls back to `{ type: "opaque_block", rawXml }` on any parse failure
|
|
641
|
+
* (malformed XML, no `<w:sdt>` root, etc.) so callers never need to
|
|
642
|
+
* try/catch. The `activeChartPartLookup` module variable is guarded via
|
|
643
|
+
* try/catch/finally so it is always cleared even on error — see the
|
|
644
|
+
* re-entrancy invariant comment on the variable declaration above.
|
|
645
|
+
*/
|
|
646
|
+
export function parseSdtXml(
|
|
647
|
+
rawXml: string,
|
|
648
|
+
relationships: readonly OpcRelationship[] = [],
|
|
649
|
+
mediaParts: ReadonlyMap<string, InlineMediaPart> = new Map(),
|
|
650
|
+
sourcePartPath = "/word/document.xml",
|
|
651
|
+
chartPartLookup?: ChartPartLookup,
|
|
652
|
+
): ParsedBlockNode {
|
|
653
|
+
activeChartPartLookup = chartPartLookup;
|
|
654
|
+
try {
|
|
655
|
+
const root = parseXml(rawXml) as XmlElementNode;
|
|
656
|
+
const sdtEl = root.children.find(
|
|
657
|
+
(c): c is XmlElementNode =>
|
|
658
|
+
c.type === "element" && localName(c.name) === "sdt",
|
|
659
|
+
);
|
|
660
|
+
if (!sdtEl) {
|
|
661
|
+
return { type: "opaque_block", rawXml };
|
|
662
|
+
}
|
|
663
|
+
const relationshipMap = new Map(relationships.map((r) => [r.id, r]));
|
|
664
|
+
return parseSdtElement(sdtEl, rawXml, relationshipMap, relationships, mediaParts, sourcePartPath);
|
|
665
|
+
} catch {
|
|
666
|
+
return { type: "opaque_block", rawXml };
|
|
667
|
+
} finally {
|
|
668
|
+
activeChartPartLookup = undefined;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
609
672
|
function parseMainDocumentXmlInner(
|
|
610
673
|
xml: string,
|
|
611
674
|
relationships: readonly OpcRelationship[],
|
|
612
675
|
mediaParts: ReadonlyMap<string, InlineMediaPart>,
|
|
613
676
|
sourcePartPath: string,
|
|
614
677
|
): ParsedMainDocument {
|
|
615
|
-
const root = parseXml(xml);
|
|
678
|
+
const root = parseXml(xml) as XmlElementNode;
|
|
616
679
|
const documentElement = findChildElement(root, "document");
|
|
617
680
|
const bodyElement = findChildElement(documentElement, "body");
|
|
618
681
|
const relationshipMap = new Map(relationships.map((relationship) => [relationship.id, relationship]));
|
|
@@ -727,32 +790,68 @@ function rewriteScopeMarkerBookmarks(blocks: ParsedBlockNode[]): void {
|
|
|
727
790
|
* Input XML must be a root element whose children are body-child elements
|
|
728
791
|
* (w:p, w:tbl, w:sdt, etc.) — matches the structure of `w:txbxContent`.
|
|
729
792
|
*/
|
|
793
|
+
/**
|
|
794
|
+
* Phase 4.3 G3 — maximum recursion depth for nested
|
|
795
|
+
* `w:drawing → w:txbxContent → w:drawing → …` chains. OOXML nesting past 3
|
|
796
|
+
* is already pathological; 8 is a generous ceiling that guards against stack
|
|
797
|
+
* overflow on malicious / corrupt inputs.
|
|
798
|
+
*/
|
|
799
|
+
const TXBX_BLOCK_STREAM_MAX_DEPTH = 8;
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Module-local counter set by `parseBlockStreamFromXml` during its sync scope.
|
|
803
|
+
* Consumed by the blockParser closure in `case "drawing"` to forward the
|
|
804
|
+
* current depth into the recursive call. Parsing is synchronous, so a simple
|
|
805
|
+
* scalar is safe (no concurrent calls on the same stack).
|
|
806
|
+
*/
|
|
807
|
+
let activeTxbxBlockStreamDepth = 0;
|
|
808
|
+
|
|
730
809
|
export function parseBlockStreamFromXml(
|
|
731
810
|
xml: string,
|
|
732
811
|
ctx: {
|
|
733
812
|
relationships: readonly OpcRelationship[];
|
|
734
813
|
mediaParts: ReadonlyMap<string, InlineMediaPart>;
|
|
735
814
|
sourcePartPath: string;
|
|
815
|
+
/** Recursion depth — incremented each time blockParser is invoked from
|
|
816
|
+
* inside parseDrawingFrame's txbxContent handling. Default 0. */
|
|
817
|
+
depth?: number;
|
|
736
818
|
},
|
|
737
819
|
): ParsedBlockNode[] {
|
|
738
|
-
const
|
|
820
|
+
const depth = ctx.depth ?? 0;
|
|
821
|
+
if (depth > TXBX_BLOCK_STREAM_MAX_DEPTH) {
|
|
822
|
+
// Depth limit reached — return empty to halt infinite recursion. The
|
|
823
|
+
// outer ShapeContent.txbxContentXml still preserves the raw XML for
|
|
824
|
+
// round-trip, so no data is lost, only deep-nested structural parse.
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
const root = parseXml(xml) as XmlElementNode;
|
|
739
828
|
const relationshipMap = new Map(
|
|
740
829
|
ctx.relationships.map((r) => [r.id, r] as const),
|
|
741
830
|
);
|
|
742
831
|
const wrapper =
|
|
743
832
|
root.children.find((c): c is XmlElementNode => c.type === "element") ?? root;
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
833
|
+
|
|
834
|
+
// Set module-local depth for the duration of the sync walk so the drawing
|
|
835
|
+
// case's blockParser closure sees our depth and can bump it further.
|
|
836
|
+
const priorDepth = activeTxbxBlockStreamDepth;
|
|
837
|
+
activeTxbxBlockStreamDepth = depth;
|
|
838
|
+
try {
|
|
839
|
+
return wrapper.children
|
|
840
|
+
.filter((n): n is XmlElementNode => n.type === "element")
|
|
841
|
+
.map((n) =>
|
|
842
|
+
parseBodyChild(
|
|
843
|
+
n,
|
|
844
|
+
xml,
|
|
845
|
+
relationshipMap,
|
|
846
|
+
ctx.relationships,
|
|
847
|
+
ctx.mediaParts,
|
|
848
|
+
ctx.sourcePartPath,
|
|
849
|
+
depth,
|
|
850
|
+
),
|
|
851
|
+
);
|
|
852
|
+
} finally {
|
|
853
|
+
activeTxbxBlockStreamDepth = priorDepth;
|
|
854
|
+
}
|
|
756
855
|
}
|
|
757
856
|
|
|
758
857
|
function parseBodyChild(
|
|
@@ -762,6 +861,10 @@ function parseBodyChild(
|
|
|
762
861
|
relationships: readonly OpcRelationship[],
|
|
763
862
|
mediaParts: ReadonlyMap<string, InlineMediaPart>,
|
|
764
863
|
sourcePartPath: string,
|
|
864
|
+
/** Phase 4.3 G3 — unused marker (depth tracked via module-local
|
|
865
|
+
* activeTxbxBlockStreamDepth); kept for interface compatibility with
|
|
866
|
+
* parseBlockStreamFromXml's mapping callback. */
|
|
867
|
+
_depth = 0,
|
|
765
868
|
): ParsedBlockNode {
|
|
766
869
|
const nodeType = localName(node.name);
|
|
767
870
|
|
|
@@ -845,6 +948,7 @@ function parseBodyChild(
|
|
|
845
948
|
instruction: string;
|
|
846
949
|
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
847
950
|
mode: "instruction" | "result";
|
|
951
|
+
legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
|
|
848
952
|
} | null = null;
|
|
849
953
|
|
|
850
954
|
for (const child of node.children) {
|
|
@@ -1071,6 +1175,7 @@ function appendParagraphRunNodes(
|
|
|
1071
1175
|
instruction: string;
|
|
1072
1176
|
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
1073
1177
|
mode: "instruction" | "result";
|
|
1178
|
+
legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
|
|
1074
1179
|
} | null,
|
|
1075
1180
|
sourceXml: string,
|
|
1076
1181
|
relationships: readonly OpcRelationship[],
|
|
@@ -1080,6 +1185,7 @@ function appendParagraphRunNodes(
|
|
|
1080
1185
|
instruction: string;
|
|
1081
1186
|
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
1082
1187
|
mode: "instruction" | "result";
|
|
1188
|
+
legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
|
|
1083
1189
|
} | null {
|
|
1084
1190
|
const hasFieldMarkers = node.children.some(
|
|
1085
1191
|
(child) =>
|
|
@@ -1115,7 +1221,13 @@ function appendParagraphRunNodes(
|
|
|
1115
1221
|
if (name === "fldChar") {
|
|
1116
1222
|
const fldType = child.attributes["w:fldCharType"] ?? child.attributes.fldCharType;
|
|
1117
1223
|
if (fldType === "begin") {
|
|
1118
|
-
|
|
1224
|
+
const legacyFormField = parseFFDataFromFldChar(child, sourceXml);
|
|
1225
|
+
activeComplexField = {
|
|
1226
|
+
instruction: "",
|
|
1227
|
+
children: [],
|
|
1228
|
+
mode: "instruction",
|
|
1229
|
+
...(legacyFormField ? { legacyFormField } : {}),
|
|
1230
|
+
};
|
|
1119
1231
|
} else if (fldType === "separate" && activeComplexField) {
|
|
1120
1232
|
activeComplexField.mode = "result";
|
|
1121
1233
|
} else if (fldType === "end" && activeComplexField) {
|
|
@@ -1156,6 +1268,7 @@ function flushActiveComplexField(
|
|
|
1156
1268
|
instruction: string;
|
|
1157
1269
|
children: Array<ParsedTextNode | ParsedBreakNode | ParsedTabNode>;
|
|
1158
1270
|
mode: "instruction" | "result";
|
|
1271
|
+
legacyFormField?: import("../../model/canonical-document.ts").LegacyFormFieldNode;
|
|
1159
1272
|
} | null,
|
|
1160
1273
|
): void {
|
|
1161
1274
|
if (!activeComplexField || activeComplexField.instruction.trim().length === 0) {
|
|
@@ -1168,6 +1281,7 @@ function flushActiveComplexField(
|
|
|
1168
1281
|
instruction: activeComplexField.instruction,
|
|
1169
1282
|
children: activeComplexField.children,
|
|
1170
1283
|
rawXml: "",
|
|
1284
|
+
...(activeComplexField.legacyFormField ? { legacyFormField: activeComplexField.legacyFormField } : {}),
|
|
1171
1285
|
});
|
|
1172
1286
|
reset();
|
|
1173
1287
|
}
|
|
@@ -2220,7 +2334,15 @@ function parseRun(
|
|
|
2220
2334
|
// `{ type: string; ... }` contract — each ParsedBlockNode variant
|
|
2221
2335
|
// has a discriminant `type` field (paragraph | table | sdt | ...).
|
|
2222
2336
|
blockParser: (xml) =>
|
|
2223
|
-
parseBlockStreamFromXml(xml, {
|
|
2337
|
+
parseBlockStreamFromXml(xml, {
|
|
2338
|
+
relationships,
|
|
2339
|
+
mediaParts,
|
|
2340
|
+
sourcePartPath,
|
|
2341
|
+
// Phase 4.3 G3 — forward module-local recursion counter so
|
|
2342
|
+
// pathological nested chains (drawing → txbx → drawing → txbx
|
|
2343
|
+
// → …) terminate at TXBX_BLOCK_STREAM_MAX_DEPTH.
|
|
2344
|
+
depth: activeTxbxBlockStreamDepth + 1,
|
|
2345
|
+
}) as unknown as ReadonlyArray<{ type: string; [key: string]: unknown }>,
|
|
2224
2346
|
});
|
|
2225
2347
|
if (frame) {
|
|
2226
2348
|
result.push(frame);
|
|
@@ -2323,6 +2445,30 @@ function parseRun(
|
|
|
2323
2445
|
});
|
|
2324
2446
|
break;
|
|
2325
2447
|
}
|
|
2448
|
+
case "object": {
|
|
2449
|
+
// Lane 7c Slice 7c.1 — attempt OLE-object extraction. Falls
|
|
2450
|
+
// through to the generic opaque-fragment path when the element
|
|
2451
|
+
// does not contain a resolvable <o:OLEObject> relationship
|
|
2452
|
+
// (legacy VML <w:object>, unknown ProgIDs without a relationship
|
|
2453
|
+
// target, etc.), preserving the existing opaque-fragment
|
|
2454
|
+
// semantics.
|
|
2455
|
+
const objectXml = sourceXml.slice(child.start, child.end);
|
|
2456
|
+
try {
|
|
2457
|
+
const oleNode = parseObject(child, objectXml, relationships);
|
|
2458
|
+
if (oleNode) {
|
|
2459
|
+
result.push(oleNode);
|
|
2460
|
+
break;
|
|
2461
|
+
}
|
|
2462
|
+
} catch {
|
|
2463
|
+
// Fall through to opaque on any unexpected parse-time error.
|
|
2464
|
+
}
|
|
2465
|
+
encounteredUnsupportedChild = true;
|
|
2466
|
+
result.push({
|
|
2467
|
+
type: "opaque_inline",
|
|
2468
|
+
rawXml: objectXml,
|
|
2469
|
+
});
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2326
2472
|
case "commentReference":
|
|
2327
2473
|
break;
|
|
2328
2474
|
case "footnoteReference": {
|
|
@@ -2934,10 +3080,6 @@ function findChildElement(node: XmlElementNode, childLocalName: string): XmlElem
|
|
|
2934
3080
|
return child;
|
|
2935
3081
|
}
|
|
2936
3082
|
|
|
2937
|
-
function localName(name: string): string {
|
|
2938
|
-
const separatorIndex = name.indexOf(":");
|
|
2939
|
-
return separatorIndex >= 0 ? name.slice(separatorIndex + 1) : name;
|
|
2940
|
-
}
|
|
2941
3083
|
|
|
2942
3084
|
function readOptionalAttribute(node: XmlElementNode, name: string): string | undefined {
|
|
2943
3085
|
return node.attributes[`w:${name}`]
|
|
@@ -2945,194 +3087,6 @@ function readOptionalAttribute(node: XmlElementNode, name: string): string | und
|
|
|
2945
3087
|
?? node.attributes[name];
|
|
2946
3088
|
}
|
|
2947
3089
|
|
|
2948
|
-
function parseXml(xml: string): XmlElementNode {
|
|
2949
|
-
const root: XmlElementNode = {
|
|
2950
|
-
type: "element",
|
|
2951
|
-
name: "__root__",
|
|
2952
|
-
attributes: {},
|
|
2953
|
-
children: [],
|
|
2954
|
-
start: 0,
|
|
2955
|
-
end: xml.length,
|
|
2956
|
-
};
|
|
2957
|
-
const stack: XmlElementNode[] = [root];
|
|
2958
|
-
let cursor = 0;
|
|
2959
|
-
|
|
2960
|
-
while (cursor < xml.length) {
|
|
2961
|
-
if (xml.startsWith("<!--", cursor)) {
|
|
2962
|
-
const end = xml.indexOf("-->", cursor);
|
|
2963
|
-
cursor = end >= 0 ? end + 3 : xml.length;
|
|
2964
|
-
continue;
|
|
2965
|
-
}
|
|
2966
|
-
|
|
2967
|
-
if (xml.startsWith("<?", cursor)) {
|
|
2968
|
-
const end = xml.indexOf("?>", cursor);
|
|
2969
|
-
cursor = end >= 0 ? end + 2 : xml.length;
|
|
2970
|
-
continue;
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
if (xml.startsWith("<![CDATA[", cursor)) {
|
|
2974
|
-
const end = xml.indexOf("]]>", cursor);
|
|
2975
|
-
const textEnd = end >= 0 ? end : xml.length;
|
|
2976
|
-
stack[stack.length - 1]?.children.push({
|
|
2977
|
-
type: "text",
|
|
2978
|
-
text: xml.slice(cursor + 9, textEnd),
|
|
2979
|
-
start: cursor,
|
|
2980
|
-
end: end >= 0 ? end + 3 : xml.length,
|
|
2981
|
-
});
|
|
2982
|
-
cursor = end >= 0 ? end + 3 : xml.length;
|
|
2983
|
-
continue;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
const currentChar = xml[cursor];
|
|
2987
|
-
if (currentChar !== "<") {
|
|
2988
|
-
const nextTag = xml.indexOf("<", cursor);
|
|
2989
|
-
const end = nextTag >= 0 ? nextTag : xml.length;
|
|
2990
|
-
const text = decodeXmlEntities(xml.slice(cursor, end));
|
|
2991
|
-
if (text.length > 0) {
|
|
2992
|
-
stack[stack.length - 1]?.children.push({
|
|
2993
|
-
type: "text",
|
|
2994
|
-
text,
|
|
2995
|
-
start: cursor,
|
|
2996
|
-
end,
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
|
-
cursor = end;
|
|
3000
|
-
continue;
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
if (xml[cursor + 1] === "/") {
|
|
3004
|
-
const end = xml.indexOf(">", cursor);
|
|
3005
|
-
if (end < 0) {
|
|
3006
|
-
throw new Error("Malformed XML: missing closing >.");
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
const name = xml.slice(cursor + 2, end).trim();
|
|
3010
|
-
const current = stack.pop();
|
|
3011
|
-
if (!current || localName(current.name) !== localName(name)) {
|
|
3012
|
-
throw new Error(`Malformed XML: unexpected closing tag </${name}>.`);
|
|
3013
|
-
}
|
|
3014
|
-
current.end = end + 1;
|
|
3015
|
-
cursor = end + 1;
|
|
3016
|
-
continue;
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
const tagEnd = findTagEnd(xml, cursor);
|
|
3020
|
-
const tagBody = xml.slice(cursor + 1, tagEnd);
|
|
3021
|
-
const selfClosing = /\/\s*$/.test(tagBody);
|
|
3022
|
-
const { name, attributes } = parseTag(tagBody.replace(/\/\s*$/, "").trim());
|
|
3023
|
-
const element: XmlElementNode = {
|
|
3024
|
-
type: "element",
|
|
3025
|
-
name,
|
|
3026
|
-
attributes,
|
|
3027
|
-
children: [],
|
|
3028
|
-
start: cursor,
|
|
3029
|
-
end: tagEnd + 1,
|
|
3030
|
-
};
|
|
3031
|
-
stack[stack.length - 1]?.children.push(element);
|
|
3032
|
-
|
|
3033
|
-
if (!selfClosing) {
|
|
3034
|
-
stack.push(element);
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
cursor = tagEnd + 1;
|
|
3038
|
-
}
|
|
3039
|
-
|
|
3040
|
-
if (stack.length !== 1) {
|
|
3041
|
-
throw new Error("Malformed XML: unclosed element in main document XML.");
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
return root;
|
|
3045
|
-
}
|
|
3046
|
-
|
|
3047
|
-
function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
|
|
3048
|
-
let cursor = 0;
|
|
3049
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
3050
|
-
cursor += 1;
|
|
3051
|
-
}
|
|
3052
|
-
|
|
3053
|
-
const nameStart = cursor;
|
|
3054
|
-
while (cursor < tagBody.length && !/\s/.test(tagBody[cursor] ?? "")) {
|
|
3055
|
-
cursor += 1;
|
|
3056
|
-
}
|
|
3057
|
-
const name = tagBody.slice(nameStart, cursor);
|
|
3058
|
-
const attributes: Record<string, string> = {};
|
|
3059
|
-
|
|
3060
|
-
while (cursor < tagBody.length) {
|
|
3061
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
3062
|
-
cursor += 1;
|
|
3063
|
-
}
|
|
3064
|
-
if (cursor >= tagBody.length) {
|
|
3065
|
-
break;
|
|
3066
|
-
}
|
|
3067
|
-
|
|
3068
|
-
const keyStart = cursor;
|
|
3069
|
-
while (cursor < tagBody.length && !/[\s=]/.test(tagBody[cursor] ?? "")) {
|
|
3070
|
-
cursor += 1;
|
|
3071
|
-
}
|
|
3072
|
-
const key = tagBody.slice(keyStart, cursor);
|
|
3073
|
-
|
|
3074
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
3075
|
-
cursor += 1;
|
|
3076
|
-
}
|
|
3077
|
-
|
|
3078
|
-
if (tagBody[cursor] !== "=") {
|
|
3079
|
-
attributes[key] = "";
|
|
3080
|
-
continue;
|
|
3081
|
-
}
|
|
3082
|
-
cursor += 1;
|
|
3083
|
-
|
|
3084
|
-
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
3085
|
-
cursor += 1;
|
|
3086
|
-
}
|
|
3087
|
-
|
|
3088
|
-
const quote = tagBody[cursor];
|
|
3089
|
-
if (quote !== `"` && quote !== `'`) {
|
|
3090
|
-
throw new Error(`Malformed XML attribute ${key}.`);
|
|
3091
|
-
}
|
|
3092
|
-
cursor += 1;
|
|
3093
|
-
|
|
3094
|
-
const valueStart = cursor;
|
|
3095
|
-
while (cursor < tagBody.length && tagBody[cursor] !== quote) {
|
|
3096
|
-
cursor += 1;
|
|
3097
|
-
}
|
|
3098
|
-
const rawValue = tagBody.slice(valueStart, cursor);
|
|
3099
|
-
attributes[key] = decodeXmlEntities(rawValue);
|
|
3100
|
-
cursor += 1;
|
|
3101
|
-
}
|
|
3102
|
-
|
|
3103
|
-
return { name, attributes };
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3106
|
-
function findTagEnd(xml: string, start: number): number {
|
|
3107
|
-
let cursor = start + 1;
|
|
3108
|
-
let quote: string | null = null;
|
|
3109
|
-
|
|
3110
|
-
while (cursor < xml.length) {
|
|
3111
|
-
const current = xml[cursor];
|
|
3112
|
-
if (quote) {
|
|
3113
|
-
if (current === quote) {
|
|
3114
|
-
quote = null;
|
|
3115
|
-
}
|
|
3116
|
-
cursor += 1;
|
|
3117
|
-
continue;
|
|
3118
|
-
}
|
|
3119
|
-
|
|
3120
|
-
if (current === `"` || current === `'`) {
|
|
3121
|
-
quote = current;
|
|
3122
|
-
cursor += 1;
|
|
3123
|
-
continue;
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
if (current === ">") {
|
|
3127
|
-
return cursor;
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
cursor += 1;
|
|
3131
|
-
}
|
|
3132
|
-
|
|
3133
|
-
throw new Error("Malformed XML: missing >.");
|
|
3134
|
-
}
|
|
3135
|
-
|
|
3136
3090
|
function decodeXmlEntities(value: string): string {
|
|
3137
3091
|
return value.replace(/&(#x[0-9a-fA-F]+|#\d+|amp|lt|gt|quot|apos);/g, (match, entity) => {
|
|
3138
3092
|
switch (entity) {
|
|
@@ -3442,14 +3396,23 @@ function readFootnoteLikeProperties(
|
|
|
3442
3396
|
val === "lowerRoman" ||
|
|
3443
3397
|
val === "upperLetter" ||
|
|
3444
3398
|
val === "lowerLetter" ||
|
|
3399
|
+
val === "ordinal" ||
|
|
3400
|
+
val === "cardinalText" ||
|
|
3401
|
+
val === "ordinalText" ||
|
|
3402
|
+
val === "hex" ||
|
|
3445
3403
|
val === "chicago" ||
|
|
3404
|
+
val === "bullet" ||
|
|
3405
|
+
val === "ideographDigital" ||
|
|
3406
|
+
val === "japaneseCounting" ||
|
|
3407
|
+
val === "arabicAbjad" ||
|
|
3408
|
+
val === "arabicAlpha" ||
|
|
3446
3409
|
val === "none"
|
|
3447
3410
|
) {
|
|
3448
3411
|
result.numFmt = val;
|
|
3449
3412
|
}
|
|
3450
3413
|
} else if (name === "numStart") {
|
|
3451
3414
|
const n = safeParseInt(val);
|
|
3452
|
-
if (n !== undefined) result.numStart = n;
|
|
3415
|
+
if (n !== undefined) result.numStart = Math.max(1, n);
|
|
3453
3416
|
} else if (name === "numRestart") {
|
|
3454
3417
|
if (val === "continuous" || val === "eachSect" || val === "eachPage") {
|
|
3455
3418
|
result.numRestart = val;
|