@beyondwork/docx-react-component 1.0.55 → 1.0.57
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 +43 -32
- package/src/api/public-types.ts +157 -0
- package/src/compare/diff-engine.ts +3 -0
- package/src/core/commands/formatting-commands.ts +1 -0
- package/src/core/commands/index.ts +17 -11
- package/src/core/selection/mapping.ts +18 -1
- package/src/core/selection/review-anchors.ts +29 -18
- package/src/io/chart-preview-resolver.ts +175 -41
- package/src/io/docx-session.ts +57 -2
- package/src/io/export/serialize-main-document.ts +82 -0
- package/src/io/export/serialize-styles.ts +61 -3
- package/src/io/export/table-properties-xml.ts +19 -4
- package/src/io/normalize/normalize-text.ts +33 -0
- package/src/io/ooxml/parse-anchor.ts +182 -0
- package/src/io/ooxml/parse-drawing.ts +319 -0
- package/src/io/ooxml/parse-fields.ts +115 -2
- package/src/io/ooxml/parse-fill.ts +215 -0
- package/src/io/ooxml/parse-font-table.ts +190 -0
- package/src/io/ooxml/parse-footnotes.ts +52 -1
- package/src/io/ooxml/parse-main-document.ts +241 -1
- package/src/io/ooxml/parse-numbering.ts +96 -0
- package/src/io/ooxml/parse-picture.ts +107 -0
- package/src/io/ooxml/parse-settings.ts +34 -0
- package/src/io/ooxml/parse-shapes.ts +87 -0
- package/src/io/ooxml/parse-solid-fill.ts +11 -0
- package/src/io/ooxml/parse-styles.ts +74 -1
- package/src/io/ooxml/parse-theme.ts +60 -0
- package/src/io/paste/html-clipboard.ts +449 -0
- package/src/io/paste/word-clipboard.ts +5 -1
- package/src/legal/_document-root.ts +26 -0
- package/src/legal/bookmarks.ts +4 -3
- package/src/legal/cross-references.ts +3 -2
- package/src/legal/defined-terms.ts +2 -1
- package/src/legal/signature-blocks.ts +2 -1
- package/src/model/canonical-document.ts +415 -3
- package/src/runtime/chart/chart-model-store.ts +73 -10
- package/src/runtime/document-runtime.ts +693 -41
- package/src/runtime/edit-ops/index.ts +129 -0
- package/src/runtime/event-refresh-hints.ts +7 -0
- package/src/runtime/field-resolver.ts +341 -0
- package/src/runtime/footnote-resolver.ts +55 -0
- package/src/runtime/hyperlink-color-resolver.ts +13 -10
- package/src/runtime/object-grab/index.ts +51 -0
- package/src/runtime/paragraph-style-resolver.ts +105 -0
- package/src/runtime/resolved-numbering-geometry.ts +12 -0
- package/src/runtime/selection/cursor-ops.ts +186 -15
- package/src/runtime/selection/index.ts +17 -1
- package/src/runtime/structure-ops/index.ts +77 -0
- package/src/runtime/styles-cascade.ts +33 -0
- package/src/runtime/surface-projection.ts +186 -12
- package/src/runtime/theme-color-resolver.ts +189 -44
- package/src/runtime/units.ts +46 -0
- package/src/runtime/view-state.ts +13 -2
- package/src/ui/WordReviewEditor.tsx +168 -10
- package/src/ui/editor-runtime-boundary.ts +94 -1
- package/src/ui/editor-shell-view.tsx +1 -1
- package/src/ui/runtime-shortcut-dispatch.ts +17 -3
- package/src/ui-tailwind/chart/ChartSurface.tsx +36 -10
- package/src/ui-tailwind/chart/layout/plot-area.ts +120 -45
- package/src/ui-tailwind/chart/render/area.tsx +22 -4
- package/src/ui-tailwind/chart/render/bar-column.tsx +37 -11
- package/src/ui-tailwind/chart/render/bubble.tsx +6 -2
- package/src/ui-tailwind/chart/render/combo.tsx +37 -4
- package/src/ui-tailwind/chart/render/line.tsx +28 -5
- package/src/ui-tailwind/chart/render/pie.tsx +36 -16
- package/src/ui-tailwind/chart/render/progressive-render.ts +8 -1
- package/src/ui-tailwind/chart/render/scatter.tsx +9 -4
- package/src/ui-tailwind/chrome/avatar-initials.ts +15 -0
- package/src/ui-tailwind/chrome/tw-comment-preview.tsx +3 -1
- package/src/ui-tailwind/chrome/tw-context-menu.tsx +14 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +3 -2
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +30 -11
- package/src/ui-tailwind/chrome/tw-shortcut-hint.tsx +15 -2
- package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +1 -1
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +24 -7
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +31 -12
- package/src/ui-tailwind/chrome-overlay/page-border-resolver.ts +211 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +1 -0
- package/src/ui-tailwind/chrome-overlay/tw-comment-balloon-layer.tsx +74 -0
- package/src/ui-tailwind/chrome-overlay/tw-locked-block-layer.tsx +65 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-border-overlay.tsx +233 -0
- package/src/ui-tailwind/chrome-overlay/tw-page-stack-overlay-layer.tsx +135 -13
- package/src/ui-tailwind/chrome-overlay/tw-revision-margin-bar-layer.tsx +51 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-card-layer.tsx +12 -4
- package/src/ui-tailwind/chrome-overlay/tw-scope-card.tsx +32 -12
- package/src/ui-tailwind/chrome-overlay/tw-toc-outline-sidebar.tsx +133 -0
- package/src/ui-tailwind/editor-surface/chart-node-view.tsx +49 -10
- package/src/ui-tailwind/editor-surface/float-wrap-resolver.ts +119 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +236 -9
- package/src/ui-tailwind/editor-surface/pm-schema.ts +192 -11
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +28 -3
- package/src/ui-tailwind/editor-surface/shape-renderer.ts +206 -0
- package/src/ui-tailwind/editor-surface/surface-layer.ts +66 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +29 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +7 -1
- package/src/ui-tailwind/page-stack/tw-page-stack-chrome-layer.tsx +22 -6
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +10 -16
- package/src/ui-tailwind/review/tw-health-panel.tsx +0 -25
- package/src/ui-tailwind/review/tw-rail-card.tsx +38 -17
- package/src/ui-tailwind/review/tw-review-rail.tsx +2 -2
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +5 -12
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +2 -2
- package/src/ui-tailwind/theme/editor-theme.css +1 -0
- package/src/ui-tailwind/theme/tokens.css +6 -0
- package/src/ui-tailwind/theme/tokens.ts +10 -0
- package/src/validation/compatibility-engine.ts +2 -0
- package/src/validation/docx-comment-proof.ts +12 -3
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
TableIndent,
|
|
15
15
|
TableLook,
|
|
16
16
|
TableWidth,
|
|
17
|
+
FootnoteProperties,
|
|
17
18
|
SectionProperties,
|
|
18
19
|
PageSize,
|
|
19
20
|
PageMargins,
|
|
@@ -24,6 +25,8 @@ import type {
|
|
|
24
25
|
SectionDocumentGrid,
|
|
25
26
|
SectionLineNumbering,
|
|
26
27
|
SectionPageBorders,
|
|
28
|
+
DrawingFrameNode,
|
|
29
|
+
UnknownPropertyChild,
|
|
27
30
|
} from "../../model/canonical-document.ts";
|
|
28
31
|
import type { OpcRelationship } from "./part-manifest.ts";
|
|
29
32
|
import { SCOPE_MARKER_BOOKMARK_PREFIX } from "./parse-scope-markers.ts";
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
import { toCanonicalNumberingInstanceId } from "./parse-numbering.ts";
|
|
35
38
|
import { parseComplexContentXml, type ChartPartLookup } from "./parse-complex-content.ts";
|
|
36
39
|
import { parseShapeXml, parseVmlXml } from "./parse-shapes.ts";
|
|
40
|
+
import { parseDrawingFrame } from "./parse-drawing.ts";
|
|
37
41
|
import { classifyFieldInstruction } from "./parse-fields.ts";
|
|
38
42
|
import { resolveHighlightColor } from "./highlight-colors.ts";
|
|
39
43
|
import {
|
|
@@ -83,7 +87,9 @@ import {
|
|
|
83
87
|
const SECT_PR_MODELLED_CHILDREN: ReadonlySet<string> = new Set([
|
|
84
88
|
"cols",
|
|
85
89
|
"docGrid",
|
|
90
|
+
"endnotePr",
|
|
86
91
|
"footerReference",
|
|
92
|
+
"footnotePr",
|
|
87
93
|
"headerReference",
|
|
88
94
|
"lnNumType",
|
|
89
95
|
"pgBorders",
|
|
@@ -99,6 +105,111 @@ const SECT_PR_GRAB_BAG_DESCRIPTOR: PropertyGrabBagDescriptor = {
|
|
|
99
105
|
modelledChildAttributes: new Map(),
|
|
100
106
|
};
|
|
101
107
|
|
|
108
|
+
// Phase 7 Slice A — typed grab-bag descriptors for tblPr / trPr / tcPr.
|
|
109
|
+
// Mirrors what table-properties-xml.ts's TABLE/ROW/CELL_PROPERTY_STRIP_SPEC
|
|
110
|
+
// declared as "modelled" elements on the serializer side. Anything outside
|
|
111
|
+
// this set is captured as `unknownPropertyChildren` and re-emitted verbatim.
|
|
112
|
+
const TBL_PR_MODELLED_CHILDREN: ReadonlySet<string> = new Set([
|
|
113
|
+
"tblStyle", "tblW", "jc", "tblBorders", "tblCellMar",
|
|
114
|
+
"tblLook", "tblInd", "tblLayout", "tblCellSpacing",
|
|
115
|
+
"tblCaption", "tblDescription", "bidiVisual", "tblpPr", "tblOverlap",
|
|
116
|
+
]);
|
|
117
|
+
const TR_PR_MODELLED_CHILDREN: ReadonlySet<string> = new Set([
|
|
118
|
+
"gridBefore", "wBefore", "gridAfter", "wAfter",
|
|
119
|
+
"trHeight", "tblHeader", "cantSplit", "jc", "cnfStyle",
|
|
120
|
+
]);
|
|
121
|
+
const TC_PR_MODELLED_CHILDREN: ReadonlySet<string> = new Set([
|
|
122
|
+
"tcW", "gridSpan", "vMerge", "tcBorders", "tcMar", "shd",
|
|
123
|
+
"vAlign", "textDirection", "noWrap", "tcFitText", "cnfStyle",
|
|
124
|
+
]);
|
|
125
|
+
// Slice B — modelled attributes per modelled table-container child.
|
|
126
|
+
const TABLE_WIDTH_ATTRS = new Set(["w:w", "w:type"]);
|
|
127
|
+
// TABLE_BORDER_ATTRS: the per-side attrs for tblBorders/tcBorders children
|
|
128
|
+
// ("w:val", "w:sz", "w:space", "w:color", "w:themeColor", ...) would populate
|
|
129
|
+
// the tblPr/tcPr descriptors if nested-side capture were in scope.
|
|
130
|
+
// Deferred: nested-side descriptors need a separate pass; see B.5 lane doc.
|
|
131
|
+
const TABLE_SHD_ATTRS = new Set([
|
|
132
|
+
"w:val", "w:fill", "w:color",
|
|
133
|
+
"w:themeColor", "w:themeFill", "w:themeShade", "w:themeTint",
|
|
134
|
+
"w:themeFillShade", "w:themeFillTint",
|
|
135
|
+
]);
|
|
136
|
+
const TBL_PR_MODELLED_CHILD_ATTRIBUTES = new Map<string, ReadonlySet<string>>([
|
|
137
|
+
["tblStyle", new Set(["w:val"])],
|
|
138
|
+
["tblW", TABLE_WIDTH_ATTRS],
|
|
139
|
+
["jc", new Set(["w:val"])],
|
|
140
|
+
["tblLook", new Set([
|
|
141
|
+
"w:val", "w:firstRow", "w:lastRow", "w:firstColumn",
|
|
142
|
+
"w:lastColumn", "w:noHBand", "w:noVBand",
|
|
143
|
+
])],
|
|
144
|
+
["tblInd", TABLE_WIDTH_ATTRS],
|
|
145
|
+
["tblLayout", new Set(["w:type"])],
|
|
146
|
+
["tblCellSpacing", TABLE_WIDTH_ATTRS],
|
|
147
|
+
["tblCaption", new Set(["w:val"])],
|
|
148
|
+
["tblDescription", new Set(["w:val"])],
|
|
149
|
+
["bidiVisual", new Set(["w:val"])],
|
|
150
|
+
["tblpPr", new Set([
|
|
151
|
+
"w:leftFromText", "w:rightFromText", "w:topFromText", "w:bottomFromText",
|
|
152
|
+
"w:vertAnchor", "w:horzAnchor",
|
|
153
|
+
"w:tblpX", "w:tblpXSpec", "w:tblpY", "w:tblpYSpec",
|
|
154
|
+
])],
|
|
155
|
+
["tblOverlap", new Set(["w:val"])],
|
|
156
|
+
]);
|
|
157
|
+
const TR_PR_MODELLED_CHILD_ATTRIBUTES = new Map<string, ReadonlySet<string>>([
|
|
158
|
+
["gridBefore", new Set(["w:val"])],
|
|
159
|
+
["gridAfter", new Set(["w:val"])],
|
|
160
|
+
["wBefore", TABLE_WIDTH_ATTRS],
|
|
161
|
+
["wAfter", TABLE_WIDTH_ATTRS],
|
|
162
|
+
["trHeight", new Set(["w:val", "w:hRule"])],
|
|
163
|
+
["tblHeader", new Set(["w:val"])],
|
|
164
|
+
["cantSplit", new Set(["w:val"])],
|
|
165
|
+
["jc", new Set(["w:val"])],
|
|
166
|
+
["cnfStyle", new Set([
|
|
167
|
+
"w:val", "w:firstRow", "w:lastRow", "w:firstColumn", "w:lastColumn",
|
|
168
|
+
"w:oddVBand", "w:evenVBand", "w:oddHBand", "w:evenHBand",
|
|
169
|
+
"w:firstRowFirstColumn", "w:firstRowLastColumn",
|
|
170
|
+
"w:lastRowFirstColumn", "w:lastRowLastColumn",
|
|
171
|
+
])],
|
|
172
|
+
]);
|
|
173
|
+
const TC_PR_MODELLED_CHILD_ATTRIBUTES = new Map<string, ReadonlySet<string>>([
|
|
174
|
+
["tcW", TABLE_WIDTH_ATTRS],
|
|
175
|
+
["gridSpan", new Set(["w:val"])],
|
|
176
|
+
["vMerge", new Set(["w:val"])],
|
|
177
|
+
["shd", TABLE_SHD_ATTRS],
|
|
178
|
+
["vAlign", new Set(["w:val"])],
|
|
179
|
+
["textDirection", new Set(["w:val"])],
|
|
180
|
+
["noWrap", new Set(["w:val"])],
|
|
181
|
+
["tcFitText", new Set(["w:val"])],
|
|
182
|
+
["cnfStyle", TR_PR_MODELLED_CHILD_ATTRIBUTES.get("cnfStyle")!],
|
|
183
|
+
// Border-side container w:tcBorders has nested side children (top/bottom/...);
|
|
184
|
+
// those each carry TABLE_BORDER_ATTRS but nested-attr capture is out of B
|
|
185
|
+
// scope (would require a separate descriptor).
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
const TBL_PR_GRAB_BAG_DESCRIPTOR: PropertyGrabBagDescriptor = {
|
|
189
|
+
modelledChildNames: TBL_PR_MODELLED_CHILDREN,
|
|
190
|
+
modelledChildAttributes: new Map(),
|
|
191
|
+
};
|
|
192
|
+
const TR_PR_GRAB_BAG_DESCRIPTOR: PropertyGrabBagDescriptor = {
|
|
193
|
+
modelledChildNames: TR_PR_MODELLED_CHILDREN,
|
|
194
|
+
modelledChildAttributes: new Map(),
|
|
195
|
+
};
|
|
196
|
+
const TC_PR_GRAB_BAG_DESCRIPTOR: PropertyGrabBagDescriptor = {
|
|
197
|
+
modelledChildNames: TC_PR_MODELLED_CHILDREN,
|
|
198
|
+
modelledChildAttributes: new Map(),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Returns only the children slot (back-compat); table parsers that want both
|
|
202
|
+
// slots call captureGrabBagFullFromContainer instead.
|
|
203
|
+
function captureGrabBagFromContainer(
|
|
204
|
+
container: XmlElementNode,
|
|
205
|
+
descriptor: PropertyGrabBagDescriptor,
|
|
206
|
+
) {
|
|
207
|
+
const sourceChildren = container.children
|
|
208
|
+
.filter((child): child is XmlElementNode => child.type === "element")
|
|
209
|
+
.map((child) => buildGrabBagSourceChildFromParsed(child));
|
|
210
|
+
return capturePropertyGrabBag(sourceChildren, descriptor);
|
|
211
|
+
}
|
|
212
|
+
|
|
102
213
|
export interface ParsedMainDocument {
|
|
103
214
|
blocks: ParsedBlockNode[];
|
|
104
215
|
finalSectionProperties?: SectionProperties;
|
|
@@ -171,7 +282,8 @@ export type ParsedInlineNode =
|
|
|
171
282
|
| ParsedFootnoteRefInlineNode
|
|
172
283
|
| ParsedFieldInlineNode
|
|
173
284
|
| ParsedPermStartInlineNode
|
|
174
|
-
| ParsedPermEndInlineNode
|
|
285
|
+
| ParsedPermEndInlineNode
|
|
286
|
+
| DrawingFrameNode;
|
|
175
287
|
|
|
176
288
|
export interface ParsedTextNode {
|
|
177
289
|
type: "text";
|
|
@@ -382,6 +494,7 @@ export interface ParsedTableBlockNode {
|
|
|
382
494
|
styleId?: string;
|
|
383
495
|
tblLook?: TableLook;
|
|
384
496
|
propertiesXml?: string;
|
|
497
|
+
unknownPropertyChildren?: UnknownPropertyChild[];
|
|
385
498
|
gridColumns: number[];
|
|
386
499
|
rows: ParsedTableRowNode[];
|
|
387
500
|
width?: TableWidth;
|
|
@@ -401,6 +514,7 @@ export interface ParsedTableBlockNode {
|
|
|
401
514
|
export interface ParsedTableRowNode {
|
|
402
515
|
type: "table_row";
|
|
403
516
|
propertiesXml?: string;
|
|
517
|
+
unknownPropertyChildren?: UnknownPropertyChild[];
|
|
404
518
|
cells: ParsedTableCellNode[];
|
|
405
519
|
gridBefore?: number;
|
|
406
520
|
widthBefore?: TableWidth;
|
|
@@ -418,6 +532,7 @@ export interface ParsedTableRowNode {
|
|
|
418
532
|
export interface ParsedTableCellNode {
|
|
419
533
|
type: "table_cell";
|
|
420
534
|
propertiesXml?: string;
|
|
535
|
+
unknownPropertyChildren?: UnknownPropertyChild[];
|
|
421
536
|
gridSpan?: number;
|
|
422
537
|
verticalMerge?: "restart" | "continue";
|
|
423
538
|
children: ParsedBlockNode[];
|
|
@@ -604,6 +719,42 @@ function rewriteScopeMarkerBookmarks(blocks: ParsedBlockNode[]): void {
|
|
|
604
719
|
rewriteInPlace(blocks as unknown as { [key: string]: unknown }[]);
|
|
605
720
|
}
|
|
606
721
|
|
|
722
|
+
/**
|
|
723
|
+
* CO4 F3.3 — parse a `w:txbxContent` (or any body-child stream) XML slice into
|
|
724
|
+
* ParsedBlockNode[]. Thin wrapper around the internal `parseBodyChild` for
|
|
725
|
+
* `parse-drawing.ts` to recursively parse shape text-box content.
|
|
726
|
+
*
|
|
727
|
+
* Input XML must be a root element whose children are body-child elements
|
|
728
|
+
* (w:p, w:tbl, w:sdt, etc.) — matches the structure of `w:txbxContent`.
|
|
729
|
+
*/
|
|
730
|
+
export function parseBlockStreamFromXml(
|
|
731
|
+
xml: string,
|
|
732
|
+
ctx: {
|
|
733
|
+
relationships: readonly OpcRelationship[];
|
|
734
|
+
mediaParts: ReadonlyMap<string, InlineMediaPart>;
|
|
735
|
+
sourcePartPath: string;
|
|
736
|
+
},
|
|
737
|
+
): ParsedBlockNode[] {
|
|
738
|
+
const root = parseXml(xml);
|
|
739
|
+
const relationshipMap = new Map(
|
|
740
|
+
ctx.relationships.map((r) => [r.id, r] as const),
|
|
741
|
+
);
|
|
742
|
+
const wrapper =
|
|
743
|
+
root.children.find((c): c is XmlElementNode => c.type === "element") ?? root;
|
|
744
|
+
return wrapper.children
|
|
745
|
+
.filter((n): n is XmlElementNode => n.type === "element")
|
|
746
|
+
.map((n) =>
|
|
747
|
+
parseBodyChild(
|
|
748
|
+
n,
|
|
749
|
+
xml,
|
|
750
|
+
relationshipMap,
|
|
751
|
+
ctx.relationships,
|
|
752
|
+
ctx.mediaParts,
|
|
753
|
+
ctx.sourcePartPath,
|
|
754
|
+
),
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
|
|
607
758
|
function parseBodyChild(
|
|
608
759
|
node: XmlElementNode,
|
|
609
760
|
sourceXml: string,
|
|
@@ -1032,6 +1183,7 @@ function parseTableElement(
|
|
|
1032
1183
|
let styleId: string | undefined;
|
|
1033
1184
|
let tblLook: TableLook | undefined;
|
|
1034
1185
|
let propertiesXml: string | undefined;
|
|
1186
|
+
let unknownPropertyChildren: UnknownPropertyChild[] | undefined;
|
|
1035
1187
|
let gridColumns: number[] = [];
|
|
1036
1188
|
let width: TableWidth | undefined;
|
|
1037
1189
|
let alignment: ParsedTableBlockNode["alignment"];
|
|
@@ -1052,6 +1204,7 @@ function parseTableElement(
|
|
|
1052
1204
|
switch (localName(child.name)) {
|
|
1053
1205
|
case "tblPr": {
|
|
1054
1206
|
propertiesXml = sourceXml.slice(child.start, child.end);
|
|
1207
|
+
unknownPropertyChildren = captureGrabBagFromContainer(child, TBL_PR_GRAB_BAG_DESCRIPTOR);
|
|
1055
1208
|
styleId = readTableStyleId(child);
|
|
1056
1209
|
tblLook = readTableLook(child);
|
|
1057
1210
|
width = readTableWidth(child);
|
|
@@ -1083,6 +1236,7 @@ function parseTableElement(
|
|
|
1083
1236
|
...(styleId ? { styleId } : {}),
|
|
1084
1237
|
...(tblLook ? { tblLook } : {}),
|
|
1085
1238
|
...(propertiesXml ? { propertiesXml } : {}),
|
|
1239
|
+
...(unknownPropertyChildren ? { unknownPropertyChildren } : {}),
|
|
1086
1240
|
gridColumns,
|
|
1087
1241
|
rows,
|
|
1088
1242
|
...(width ? { width } : {}),
|
|
@@ -1109,6 +1263,7 @@ function parseTableRowElement(
|
|
|
1109
1263
|
sourcePartPath: string,
|
|
1110
1264
|
): ParsedTableRowNode {
|
|
1111
1265
|
let propertiesXml: string | undefined;
|
|
1266
|
+
let unknownPropertyChildren: UnknownPropertyChild[] | undefined;
|
|
1112
1267
|
let gridBefore: number | undefined;
|
|
1113
1268
|
let widthBefore: TableWidth | undefined;
|
|
1114
1269
|
let gridAfter: number | undefined;
|
|
@@ -1127,6 +1282,7 @@ function parseTableRowElement(
|
|
|
1127
1282
|
switch (localName(child.name)) {
|
|
1128
1283
|
case "trPr":
|
|
1129
1284
|
propertiesXml = sourceXml.slice(child.start, child.end);
|
|
1285
|
+
unknownPropertyChildren = captureGrabBagFromContainer(child, TR_PR_GRAB_BAG_DESCRIPTOR);
|
|
1130
1286
|
gridBefore = readTableRowGridPosition(child, "gridBefore");
|
|
1131
1287
|
widthBefore = readTableRowWidth(child, "wBefore");
|
|
1132
1288
|
gridAfter = readTableRowGridPosition(child, "gridAfter");
|
|
@@ -1147,6 +1303,7 @@ function parseTableRowElement(
|
|
|
1147
1303
|
return {
|
|
1148
1304
|
type: "table_row",
|
|
1149
1305
|
...(propertiesXml ? { propertiesXml } : {}),
|
|
1306
|
+
...(unknownPropertyChildren ? { unknownPropertyChildren } : {}),
|
|
1150
1307
|
...(gridBefore !== undefined ? { gridBefore } : {}),
|
|
1151
1308
|
...(widthBefore ? { widthBefore } : {}),
|
|
1152
1309
|
...(gridAfter !== undefined ? { gridAfter } : {}),
|
|
@@ -1171,6 +1328,7 @@ function parseTableCellElement(
|
|
|
1171
1328
|
sourcePartPath: string,
|
|
1172
1329
|
): ParsedTableCellNode {
|
|
1173
1330
|
let propertiesXml: string | undefined;
|
|
1331
|
+
let unknownPropertyChildren: UnknownPropertyChild[] | undefined;
|
|
1174
1332
|
let gridSpan: number | undefined;
|
|
1175
1333
|
let verticalMerge: "restart" | "continue" | undefined;
|
|
1176
1334
|
let width: TableWidth | undefined;
|
|
@@ -1190,6 +1348,7 @@ function parseTableCellElement(
|
|
|
1190
1348
|
switch (localName(child.name)) {
|
|
1191
1349
|
case "tcPr": {
|
|
1192
1350
|
propertiesXml = sourceXml.slice(child.start, child.end);
|
|
1351
|
+
unknownPropertyChildren = captureGrabBagFromContainer(child, TC_PR_GRAB_BAG_DESCRIPTOR);
|
|
1193
1352
|
gridSpan = readCellGridSpan(child);
|
|
1194
1353
|
verticalMerge = readCellVerticalMerge(child);
|
|
1195
1354
|
width = readCellWidth(child);
|
|
@@ -1214,6 +1373,7 @@ function parseTableCellElement(
|
|
|
1214
1373
|
return {
|
|
1215
1374
|
type: "table_cell",
|
|
1216
1375
|
...(propertiesXml ? { propertiesXml } : {}),
|
|
1376
|
+
...(unknownPropertyChildren ? { unknownPropertyChildren } : {}),
|
|
1217
1377
|
...(gridSpan ? { gridSpan } : {}),
|
|
1218
1378
|
...(verticalMerge ? { verticalMerge } : {}),
|
|
1219
1379
|
children,
|
|
@@ -2048,6 +2208,28 @@ function parseRun(
|
|
|
2048
2208
|
case "drawing": {
|
|
2049
2209
|
const drawingXml = sourceXml.slice(child.start, child.end);
|
|
2050
2210
|
|
|
2211
|
+
// CO4: canonical DrawingFrame path — normalize all w:drawing under one intermediate
|
|
2212
|
+
try {
|
|
2213
|
+
const frame = parseDrawingFrame(drawingXml, {
|
|
2214
|
+
relationships,
|
|
2215
|
+
mediaParts,
|
|
2216
|
+
sourcePartPath,
|
|
2217
|
+
chartPartLookup: activeChartPartLookup,
|
|
2218
|
+
// F3.3: recursive block parser for w:txbxContent inside shape content.
|
|
2219
|
+
// ParsedBlockNode is structurally compatible with TxbxBlockParser's
|
|
2220
|
+
// `{ type: string; ... }` contract — each ParsedBlockNode variant
|
|
2221
|
+
// has a discriminant `type` field (paragraph | table | sdt | ...).
|
|
2222
|
+
blockParser: (xml) =>
|
|
2223
|
+
parseBlockStreamFromXml(xml, { relationships, mediaParts, sourcePartPath }) as unknown as ReadonlyArray<{ type: string; [key: string]: unknown }>,
|
|
2224
|
+
});
|
|
2225
|
+
if (frame) {
|
|
2226
|
+
result.push(frame);
|
|
2227
|
+
break;
|
|
2228
|
+
}
|
|
2229
|
+
} catch {
|
|
2230
|
+
// fall through to legacy chain
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2051
2233
|
// Try complex content (charts / SmartArt) first
|
|
2052
2234
|
try {
|
|
2053
2235
|
const complexContent = parseComplexContentXml(
|
|
@@ -3198,6 +3380,16 @@ export function parseSectionPropertiesFromElement(
|
|
|
3198
3380
|
props.titlePage = val !== "false" && val !== "0";
|
|
3199
3381
|
break;
|
|
3200
3382
|
}
|
|
3383
|
+
case "footnotePr": {
|
|
3384
|
+
const parsed = readFootnoteLikeProperties(child);
|
|
3385
|
+
if (parsed) props.footnotePr = parsed;
|
|
3386
|
+
break;
|
|
3387
|
+
}
|
|
3388
|
+
case "endnotePr": {
|
|
3389
|
+
const parsed = readFootnoteLikeProperties(child);
|
|
3390
|
+
if (parsed) props.endnotePr = parsed;
|
|
3391
|
+
break;
|
|
3392
|
+
}
|
|
3201
3393
|
}
|
|
3202
3394
|
}
|
|
3203
3395
|
|
|
@@ -3220,6 +3412,54 @@ function safeParseInt(value: string | undefined): number | undefined {
|
|
|
3220
3412
|
return Number.isFinite(n) ? n : undefined;
|
|
3221
3413
|
}
|
|
3222
3414
|
|
|
3415
|
+
/**
|
|
3416
|
+
* Read the child-element shape shared by `<w:footnotePr>` and
|
|
3417
|
+
* `<w:endnotePr>` into a typed `FootnoteProperties` / `EndnoteProperties`.
|
|
3418
|
+
* Both elements carry the same four children (`w:pos`, `w:numFmt`,
|
|
3419
|
+
* `w:numStart`, `w:numRestart`) per ECMA-376 §17.11.11–.18.
|
|
3420
|
+
*
|
|
3421
|
+
* Returns `undefined` when none of the recognized children are present so
|
|
3422
|
+
* callers never stamp an empty `{}` onto `SectionProperties`.
|
|
3423
|
+
*/
|
|
3424
|
+
function readFootnoteLikeProperties(
|
|
3425
|
+
node: XmlElementNode,
|
|
3426
|
+
): FootnoteProperties | undefined {
|
|
3427
|
+
const result: FootnoteProperties = {};
|
|
3428
|
+
|
|
3429
|
+
for (const child of node.children) {
|
|
3430
|
+
if (child.type !== "element") continue;
|
|
3431
|
+
const name = localName(child.name);
|
|
3432
|
+
const val = child.attributes["w:val"];
|
|
3433
|
+
|
|
3434
|
+
if (name === "pos") {
|
|
3435
|
+
if (val === "pageBottom" || val === "beneathText" || val === "sectEnd" || val === "docEnd") {
|
|
3436
|
+
result.pos = val;
|
|
3437
|
+
}
|
|
3438
|
+
} else if (name === "numFmt") {
|
|
3439
|
+
if (
|
|
3440
|
+
val === "decimal" ||
|
|
3441
|
+
val === "upperRoman" ||
|
|
3442
|
+
val === "lowerRoman" ||
|
|
3443
|
+
val === "upperLetter" ||
|
|
3444
|
+
val === "lowerLetter" ||
|
|
3445
|
+
val === "chicago" ||
|
|
3446
|
+
val === "none"
|
|
3447
|
+
) {
|
|
3448
|
+
result.numFmt = val;
|
|
3449
|
+
}
|
|
3450
|
+
} else if (name === "numStart") {
|
|
3451
|
+
const n = safeParseInt(val);
|
|
3452
|
+
if (n !== undefined) result.numStart = n;
|
|
3453
|
+
} else if (name === "numRestart") {
|
|
3454
|
+
if (val === "continuous" || val === "eachSect" || val === "eachPage") {
|
|
3455
|
+
result.numRestart = val;
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3223
3463
|
function parseBorderSpec(node: XmlElementNode): BorderSpec | undefined {
|
|
3224
3464
|
const border: BorderSpec = {};
|
|
3225
3465
|
const value = node.attributes["w:val"];
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
NumberingLevelParagraphGeometry,
|
|
5
5
|
NumberingLevelOverride,
|
|
6
6
|
NumberingLevelOverrideDefinition,
|
|
7
|
+
NumPicBullet,
|
|
7
8
|
ParagraphSpacing,
|
|
8
9
|
ParagraphIndentation,
|
|
9
10
|
TabStop,
|
|
@@ -35,12 +36,26 @@ export function parseNumberingXml(xml: string): NumberingCatalog {
|
|
|
35
36
|
const numberingElement = findChildElement(root, "numbering");
|
|
36
37
|
const abstractDefinitions: NumberingCatalog["abstractDefinitions"] = {};
|
|
37
38
|
const instances: NumberingCatalog["instances"] = {};
|
|
39
|
+
const numPicBullets: NonNullable<NumberingCatalog["numPicBullets"]> = {};
|
|
38
40
|
|
|
39
41
|
for (const child of numberingElement.children) {
|
|
40
42
|
if (child.type !== "element") {
|
|
41
43
|
continue;
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
// ECMA-376 §17.9.19 — <w:numPicBullet w:numPicBulletId="N"> is a sibling
|
|
47
|
+
// of <w:abstractNum> / <w:num> at the top of the numbering part. Captured
|
|
48
|
+
// as a catalog entry keyed by numPicBulletId; raw XML is preserved for
|
|
49
|
+
// byte-identical round-trip.
|
|
50
|
+
if (localName(child.name) === "numPicBullet") {
|
|
51
|
+
const rawId =
|
|
52
|
+
child.attributes["w:numPicBulletId"] ?? child.attributes.numPicBulletId;
|
|
53
|
+
if (rawId) {
|
|
54
|
+
numPicBullets[rawId] = readNumPicBullet(child, rawId);
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
switch (localName(child.name)) {
|
|
45
60
|
case "abstractNum": {
|
|
46
61
|
const rawId = child.attributes["w:abstractNumId"] ?? child.attributes.abstractNumId;
|
|
@@ -102,9 +117,86 @@ export function parseNumberingXml(xml: string): NumberingCatalog {
|
|
|
102
117
|
return {
|
|
103
118
|
abstractDefinitions,
|
|
104
119
|
instances,
|
|
120
|
+
...(Object.keys(numPicBullets).length > 0 ? { numPicBullets } : {}),
|
|
105
121
|
};
|
|
106
122
|
}
|
|
107
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Read a `<w:numPicBullet>` element into a catalog entry. Extracts the
|
|
126
|
+
* inner `wp:extent` when present (drawingML path); the raw XML is
|
|
127
|
+
* preserved verbatim so the export round-trips byte-for-byte regardless
|
|
128
|
+
* of whether the bullet uses drawing or VML markup.
|
|
129
|
+
*/
|
|
130
|
+
function readNumPicBullet(
|
|
131
|
+
node: XmlElementNode,
|
|
132
|
+
numPicBulletId: string,
|
|
133
|
+
): NumPicBullet {
|
|
134
|
+
let widthEmu: number | undefined;
|
|
135
|
+
let heightEmu: number | undefined;
|
|
136
|
+
|
|
137
|
+
// Walk drawing → inline/anchor → extent for the EMU dimensions. VML
|
|
138
|
+
// paths skip this branch; rendering will fall back to a default size.
|
|
139
|
+
const drawing = findChildElementOptional(node, "drawing");
|
|
140
|
+
if (drawing) {
|
|
141
|
+
const inline = findChildElementOptional(drawing, "inline");
|
|
142
|
+
const anchor = findChildElementOptional(drawing, "anchor");
|
|
143
|
+
const envelope = inline ?? anchor;
|
|
144
|
+
if (envelope) {
|
|
145
|
+
const extent = findChildElementOptional(envelope, "extent");
|
|
146
|
+
if (extent) {
|
|
147
|
+
const cx = parseInteger(extent.attributes.cx ?? "");
|
|
148
|
+
const cy = parseInteger(extent.attributes.cy ?? "");
|
|
149
|
+
if (cx !== undefined) widthEmu = cx;
|
|
150
|
+
if (cy !== undefined) heightEmu = cy;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
numPicBulletId,
|
|
157
|
+
rawXml: reconstructElementXml(node),
|
|
158
|
+
...(widthEmu !== undefined ? { widthEmu } : {}),
|
|
159
|
+
...(heightEmu !== undefined ? { heightEmu } : {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Best-effort reconstruction of the source XML for a `<w:numPicBullet>`.
|
|
165
|
+
* Mirrors `buildGrabBagSourceChildFromParsed` in property-grab-bag.ts —
|
|
166
|
+
* attribute/element semantic content round-trips, whitespace between
|
|
167
|
+
* elements + quote styles normalize. For the Lane 3b scope this is the
|
|
168
|
+
* right trade-off: picture-bullet catalog entries usually round-trip
|
|
169
|
+
* once-per-document and their mediaId lookup is what matters.
|
|
170
|
+
*/
|
|
171
|
+
function reconstructElementXml(node: XmlElementNode): string {
|
|
172
|
+
const attrs = Object.entries(node.attributes)
|
|
173
|
+
.map(([name, value]) => ` ${name}="${escapeAttr(value)}"`)
|
|
174
|
+
.join("");
|
|
175
|
+
if (node.children.length === 0) {
|
|
176
|
+
return `<${node.name}${attrs}/>`;
|
|
177
|
+
}
|
|
178
|
+
const body = node.children
|
|
179
|
+
.map((child) => {
|
|
180
|
+
if (child.type === "text") return escapeText(child.text);
|
|
181
|
+
if (child.type === "element") return reconstructElementXml(child);
|
|
182
|
+
return "";
|
|
183
|
+
})
|
|
184
|
+
.join("");
|
|
185
|
+
return `<${node.name}${attrs}>${body}</${node.name}>`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function escapeAttr(value: string): string {
|
|
189
|
+
return value
|
|
190
|
+
.replace(/&/gu, "&")
|
|
191
|
+
.replace(/</gu, "<")
|
|
192
|
+
.replace(/>/gu, ">")
|
|
193
|
+
.replace(/"/gu, """);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function escapeText(value: string): string {
|
|
197
|
+
return value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">");
|
|
198
|
+
}
|
|
199
|
+
|
|
108
200
|
export function parseParagraphNumberingReferences(
|
|
109
201
|
documentXml: string,
|
|
110
202
|
): ParsedParagraphNumberingReference[] {
|
|
@@ -248,6 +340,9 @@ function readLevelDefinition(
|
|
|
248
340
|
const restartAfterLevel = rawRestart !== undefined ? parseInteger(rawRestart) : undefined;
|
|
249
341
|
const rPrNode = findChildElementOptional(levelNode, "rPr");
|
|
250
342
|
const runProperties = readRunProperties(rPrNode);
|
|
343
|
+
const lvlPicBulletNode = findChildElementOptional(levelNode, "lvlPicBulletId");
|
|
344
|
+
const picBulletId =
|
|
345
|
+
lvlPicBulletNode?.attributes["w:val"] ?? lvlPicBulletNode?.attributes.val;
|
|
251
346
|
|
|
252
347
|
return {
|
|
253
348
|
level,
|
|
@@ -260,6 +355,7 @@ function readLevelDefinition(
|
|
|
260
355
|
...(paragraphGeometry ? { paragraphGeometry } : {}),
|
|
261
356
|
...(runProperties ? { runProperties } : {}),
|
|
262
357
|
...(restartAfterLevel !== undefined ? { restartAfterLevel } : {}),
|
|
358
|
+
...(picBulletId ? { picBulletId } : {}),
|
|
263
359
|
};
|
|
264
360
|
}
|
|
265
361
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { PictureContent } from "../../model/canonical-document.ts";
|
|
2
|
+
|
|
3
|
+
interface XmlElementNode {
|
|
4
|
+
type: "element";
|
|
5
|
+
name: string;
|
|
6
|
+
attributes: Record<string, string>;
|
|
7
|
+
children: XmlNode[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface XmlTextNode {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type XmlNode = XmlElementNode | XmlTextNode;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse a pic:pic element (child of a:graphicData) into PictureContent.
|
|
19
|
+
*
|
|
20
|
+
* srcRect values are 0..100000 = 0..100% of the source bitmap dimension.
|
|
21
|
+
* rotation is in 60000ths of a degree (same unit as EMU-based angle fields).
|
|
22
|
+
* LO reference: oox/source/drawingml/shape.cxx for xfrm + srcRect scaling.
|
|
23
|
+
*/
|
|
24
|
+
export function parsePicture(graphicDataEl: XmlElementNode): PictureContent | null {
|
|
25
|
+
const pic = findFirstDescendant(graphicDataEl, "pic");
|
|
26
|
+
if (!pic) return null;
|
|
27
|
+
|
|
28
|
+
// blipFill/blip r:embed
|
|
29
|
+
const blipFill = findFirstChild(pic, "blipFill");
|
|
30
|
+
if (!blipFill) return null;
|
|
31
|
+
const blip = findFirstChild(blipFill, "blip");
|
|
32
|
+
if (!blip) return null;
|
|
33
|
+
const blipRef = blip.attributes["r:embed"] ?? blip.attributes.embed ?? "";
|
|
34
|
+
if (!blipRef) return null;
|
|
35
|
+
|
|
36
|
+
// srcRect (percentage crop: 0..100000 = 0..100%)
|
|
37
|
+
const srcRectEl = findFirstChild(blipFill, "srcRect");
|
|
38
|
+
const srcRect = srcRectEl
|
|
39
|
+
? {
|
|
40
|
+
top: readPercentAttr(srcRectEl, "t"),
|
|
41
|
+
bottom: readPercentAttr(srcRectEl, "b"),
|
|
42
|
+
left: readPercentAttr(srcRectEl, "l"),
|
|
43
|
+
right: readPercentAttr(srcRectEl, "r"),
|
|
44
|
+
}
|
|
45
|
+
: undefined;
|
|
46
|
+
|
|
47
|
+
// stretch
|
|
48
|
+
const stretchEl = findFirstChild(blipFill, "stretch");
|
|
49
|
+
const stretch = stretchEl ? true : undefined;
|
|
50
|
+
|
|
51
|
+
// spPr/xfrm — rotation and flip
|
|
52
|
+
const spPr = findFirstChild(pic, "spPr");
|
|
53
|
+
const xfrm = spPr ? findFirstChild(spPr, "xfrm") : undefined;
|
|
54
|
+
const rotRaw = xfrm?.attributes.rot;
|
|
55
|
+
const rotation = rotRaw !== undefined ? parseInt(rotRaw, 10) || undefined : undefined;
|
|
56
|
+
const flipH = xfrm ? readBoolAttr(xfrm, "flipH") : undefined;
|
|
57
|
+
const flipV = xfrm ? readBoolAttr(xfrm, "flipV") : undefined;
|
|
58
|
+
|
|
59
|
+
// prstGeom
|
|
60
|
+
const prstGeom = spPr ? findFirstChild(spPr, "prstGeom") : undefined;
|
|
61
|
+
const presetGeom = prstGeom?.attributes.prst;
|
|
62
|
+
|
|
63
|
+
const result: PictureContent = { type: "picture", blipRef };
|
|
64
|
+
if (srcRect) result.srcRect = srcRect;
|
|
65
|
+
if (stretch !== undefined) result.stretch = stretch;
|
|
66
|
+
if (rotation !== undefined) result.rotation = rotation;
|
|
67
|
+
if (flipH !== undefined) result.flipH = flipH;
|
|
68
|
+
if (flipV !== undefined) result.flipV = flipV;
|
|
69
|
+
if (presetGeom) result.presetGeom = presetGeom;
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readPercentAttr(el: XmlElementNode, name: string): number {
|
|
74
|
+
const v = el.attributes[name];
|
|
75
|
+
if (v === undefined) return 0;
|
|
76
|
+
return parseInt(v, 10) || 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readBoolAttr(el: XmlElementNode, name: string): boolean | undefined {
|
|
80
|
+
const v = el.attributes[name];
|
|
81
|
+
if (v === undefined) return undefined;
|
|
82
|
+
return v !== "0" && v !== "false";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function findFirstChild(node: XmlElementNode, local: string): XmlElementNode | undefined {
|
|
86
|
+
for (const child of node.children) {
|
|
87
|
+
if (child.type === "element" && localName(child.name) === local) return child;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findFirstDescendant(node: XmlElementNode, local: string): XmlElementNode | undefined {
|
|
93
|
+
for (const child of node.children) {
|
|
94
|
+
if (child.type !== "element") continue;
|
|
95
|
+
if (localName(child.name) === local) return child;
|
|
96
|
+
const found = findFirstDescendant(child, local);
|
|
97
|
+
if (found) return found;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function localName(name: string): string {
|
|
103
|
+
const i = name.indexOf(":");
|
|
104
|
+
return i >= 0 ? name.slice(i + 1) : name;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { type XmlElementNode as PictureXmlElement };
|