@beyondwork/docx-react-component 1.0.37 → 1.0.39
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 +41 -31
- package/src/api/public-types.ts +496 -1
- package/src/core/commands/section-layout-commands.ts +58 -0
- package/src/core/commands/table-grid.ts +431 -0
- package/src/core/commands/table-structure-commands.ts +845 -56
- package/src/core/commands/text-commands.ts +122 -2
- package/src/io/docx-session.ts +1 -0
- package/src/io/export/serialize-main-document.ts +2 -11
- package/src/io/export/serialize-numbering.ts +43 -10
- package/src/io/export/serialize-paragraph-formatting.ts +152 -0
- package/src/io/export/serialize-run-formatting.ts +90 -0
- package/src/io/export/serialize-styles.ts +212 -0
- package/src/io/export/serialize-tables.ts +74 -0
- package/src/io/export/table-properties-xml.ts +139 -4
- package/src/io/normalize/normalize-text.ts +15 -0
- package/src/io/ooxml/parse-fields.ts +10 -3
- package/src/io/ooxml/parse-footnotes.ts +60 -0
- package/src/io/ooxml/parse-headers-footers.ts +60 -0
- package/src/io/ooxml/parse-main-document.ts +137 -0
- package/src/io/ooxml/parse-numbering.ts +41 -1
- package/src/io/ooxml/parse-paragraph-formatting.ts +188 -0
- package/src/io/ooxml/parse-run-formatting.ts +129 -0
- package/src/io/ooxml/parse-styles.ts +31 -0
- package/src/io/ooxml/parse-tables.ts +249 -0
- package/src/io/ooxml/xml-attr-helpers.ts +60 -0
- package/src/io/ooxml/xml-element.ts +19 -0
- package/src/model/canonical-document.ts +117 -3
- package/src/runtime/collab/event-types.ts +165 -0
- package/src/runtime/collab/index.ts +22 -0
- package/src/runtime/collab/remote-cursor-awareness.ts +93 -0
- package/src/runtime/collab/runtime-collab-sync.ts +273 -0
- package/src/runtime/document-layout.ts +4 -2
- package/src/runtime/document-navigation.ts +1 -1
- package/src/runtime/document-runtime.ts +248 -18
- package/src/runtime/layout/default-page-format.ts +96 -0
- package/src/runtime/layout/index.ts +47 -0
- package/src/runtime/layout/inert-layout-facet.ts +16 -0
- package/src/runtime/layout/layout-engine-instance.ts +100 -23
- package/src/runtime/layout/layout-invalidation.ts +14 -5
- package/src/runtime/layout/margin-preset-catalog.ts +178 -0
- package/src/runtime/layout/page-format-catalog.ts +233 -0
- package/src/runtime/layout/page-graph.ts +55 -0
- package/src/runtime/layout/paginate-paragraph-lines.ts +128 -0
- package/src/runtime/layout/paginated-layout-engine.ts +484 -37
- package/src/runtime/layout/project-block-fragments.ts +225 -0
- package/src/runtime/layout/public-facet.ts +748 -16
- package/src/runtime/layout/resolve-page-fields.ts +70 -0
- package/src/runtime/layout/resolve-page-previews.ts +185 -0
- package/src/runtime/layout/resolved-formatting-state.ts +30 -26
- package/src/runtime/layout/table-render-plan.ts +249 -0
- package/src/runtime/numbering-prefix.ts +5 -0
- package/src/runtime/paragraph-style-resolver.ts +194 -0
- package/src/runtime/render/block-fragment-projection.ts +35 -0
- package/src/runtime/render/decoration-resolver.ts +189 -0
- package/src/runtime/render/index.ts +57 -0
- package/src/runtime/render/pending-op-delta-reader.ts +129 -0
- package/src/runtime/render/render-frame-types.ts +317 -0
- package/src/runtime/render/render-kernel.ts +759 -0
- package/src/runtime/resolved-numbering-geometry.ts +9 -1
- package/src/runtime/surface-projection.ts +129 -9
- package/src/runtime/table-schema.ts +11 -0
- package/src/runtime/view-state.ts +67 -0
- package/src/runtime/workflow-markup.ts +1 -5
- package/src/runtime/workflow-rail-segments.ts +280 -0
- package/src/ui/WordReviewEditor.tsx +368 -19
- package/src/ui/editor-command-bag.ts +4 -0
- package/src/ui/editor-runtime-boundary.ts +16 -0
- package/src/ui/editor-shell-view.tsx +10 -0
- package/src/ui/editor-surface-controller.tsx +9 -1
- package/src/ui/headless/chrome-registry.ts +310 -15
- package/src/ui/headless/scoped-chrome-policy.ts +49 -1
- package/src/ui/headless/selection-tool-types.ts +10 -0
- package/src/ui-tailwind/chrome/chrome-preset-model.ts +23 -2
- package/src/ui-tailwind/chrome/review-queue-bar.tsx +2 -14
- package/src/ui-tailwind/chrome/role-action-sets.ts +80 -0
- package/src/ui-tailwind/chrome/tw-detach-handle.tsx +147 -0
- package/src/ui-tailwind/chrome/tw-selection-anchor-resolver.ts +160 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +68 -92
- package/src/ui-tailwind/chrome/tw-selection-tool-placement.ts +149 -0
- package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +11 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +15 -4
- package/src/ui-tailwind/chrome/tw-table-border-picker.tsx +245 -0
- package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +356 -140
- package/src/ui-tailwind/chrome/tw-table-grip-layer.tsx +284 -0
- package/src/ui-tailwind/chrome-overlay/chrome-overlay-projector.ts +94 -0
- package/src/ui-tailwind/chrome-overlay/index.ts +16 -0
- package/src/ui-tailwind/chrome-overlay/tw-chrome-overlay.tsx +96 -0
- package/src/ui-tailwind/chrome-overlay/tw-scope-rail-layer.tsx +178 -0
- package/src/ui-tailwind/editor-surface/fast-text-edit-lane.ts +4 -0
- package/src/ui-tailwind/editor-surface/local-edit-session-state.ts +11 -0
- package/src/ui-tailwind/editor-surface/page-slice-util.ts +15 -0
- package/src/ui-tailwind/editor-surface/perf-probe.ts +7 -1
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +40 -4
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +22 -12
- package/src/ui-tailwind/editor-surface/pm-page-break-decorations.ts +389 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +40 -2
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +144 -62
- package/src/ui-tailwind/editor-surface/remote-cursor-plugin.ts +179 -0
- package/src/ui-tailwind/editor-surface/tw-page-block-view.tsx +559 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +224 -75
- package/src/ui-tailwind/editor-surface/tw-table-bands.css +61 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +19 -0
- package/src/ui-tailwind/index.ts +29 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +2 -2
- package/src/ui-tailwind/review/tw-rail-card.tsx +150 -0
- package/src/ui-tailwind/review/tw-review-rail-footer.tsx +52 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +166 -11
- package/src/ui-tailwind/review/tw-workflow-tab.tsx +108 -0
- package/src/ui-tailwind/theme/editor-theme.css +498 -163
- package/src/ui-tailwind/toolbar/tw-role-action-region.tsx +680 -0
- package/src/ui-tailwind/toolbar/tw-scope-posture-menu.tsx +182 -0
- package/src/ui-tailwind/toolbar/tw-shell-header.tsx +162 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +104 -2
- package/src/ui-tailwind/tw-review-workspace.tsx +234 -21
- package/src/runtime/collab-review-sync.ts +0 -254
- package/src/ui-tailwind/editor-surface/pm-collab-plugins.ts +0 -40
|
@@ -69,6 +69,25 @@ export interface ParsedTableLook {
|
|
|
69
69
|
noVBand?: boolean;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export interface ParsedTableIndent {
|
|
73
|
+
value: number;
|
|
74
|
+
type: "dxa" | "auto" | "pct" | "nil";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ParsedTableFloating {
|
|
78
|
+
horizontalAnchor?: "margin" | "page" | "text";
|
|
79
|
+
verticalAnchor?: "margin" | "page" | "text";
|
|
80
|
+
horizontalAlign?: "left" | "center" | "right" | "inside" | "outside";
|
|
81
|
+
horizontalOffset?: number;
|
|
82
|
+
verticalAlign?: "top" | "center" | "bottom" | "inside" | "outside";
|
|
83
|
+
verticalOffset?: number;
|
|
84
|
+
leftFromText?: number;
|
|
85
|
+
rightFromText?: number;
|
|
86
|
+
topFromText?: number;
|
|
87
|
+
bottomFromText?: number;
|
|
88
|
+
overlap?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
export interface ParsedTableDocument {
|
|
73
92
|
tables: ParsedTable[];
|
|
74
93
|
}
|
|
@@ -85,6 +104,13 @@ export interface ParsedTable {
|
|
|
85
104
|
borders?: ParsedTableBorders;
|
|
86
105
|
cellMargins?: ParsedCellMargins;
|
|
87
106
|
tblLook?: ParsedTableLook;
|
|
107
|
+
indent?: ParsedTableIndent;
|
|
108
|
+
layoutMode?: "fixed" | "autofit";
|
|
109
|
+
cellSpacing?: ParsedTableWidth;
|
|
110
|
+
caption?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
bidiVisual?: boolean;
|
|
113
|
+
floating?: ParsedTableFloating;
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
export interface ParsedTableRow {
|
|
@@ -94,6 +120,9 @@ export interface ParsedTableRow {
|
|
|
94
120
|
height?: number;
|
|
95
121
|
heightRule?: "auto" | "atLeast" | "exact";
|
|
96
122
|
isHeader?: boolean;
|
|
123
|
+
cantSplit?: boolean;
|
|
124
|
+
horizontalAlignment?: "left" | "center" | "right";
|
|
125
|
+
cnfStyle?: string;
|
|
97
126
|
}
|
|
98
127
|
|
|
99
128
|
export interface ParsedTableCell {
|
|
@@ -106,6 +135,11 @@ export interface ParsedTableCell {
|
|
|
106
135
|
borders?: ParsedTableCellBorders;
|
|
107
136
|
shading?: ParsedCellShading;
|
|
108
137
|
verticalAlign?: "top" | "center" | "bottom";
|
|
138
|
+
textDirection?: "lrTb" | "tbRl" | "btLr";
|
|
139
|
+
noWrap?: boolean;
|
|
140
|
+
fitText?: boolean;
|
|
141
|
+
margins?: ParsedCellMargins;
|
|
142
|
+
cnfStyle?: string;
|
|
109
143
|
}
|
|
110
144
|
|
|
111
145
|
export function parseTablesFromDocumentXml(xml: string): ParsedTableDocument {
|
|
@@ -133,6 +167,13 @@ function parseTable(node: XmlElementNode, sourceXml: string): ParsedTable {
|
|
|
133
167
|
const borders = propertiesNode ? readTableBorders(propertiesNode) : undefined;
|
|
134
168
|
const cellMargins = propertiesNode ? readTableCellMargins(propertiesNode) : undefined;
|
|
135
169
|
const tblLook = propertiesNode ? readTableLook(propertiesNode) : undefined;
|
|
170
|
+
const indent = propertiesNode ? readTableIndent(propertiesNode) : undefined;
|
|
171
|
+
const layoutMode = propertiesNode ? readTableLayoutMode(propertiesNode) : undefined;
|
|
172
|
+
const cellSpacing = propertiesNode ? readTableCellSpacing(propertiesNode) : undefined;
|
|
173
|
+
const caption = propertiesNode ? readTableCaption(propertiesNode) : undefined;
|
|
174
|
+
const description = propertiesNode ? readTableDescription(propertiesNode) : undefined;
|
|
175
|
+
const bidiVisual = propertiesNode ? readTableBidiVisual(propertiesNode) : undefined;
|
|
176
|
+
const floating = propertiesNode ? readTableFloating(propertiesNode) : undefined;
|
|
136
177
|
|
|
137
178
|
return {
|
|
138
179
|
type: "table",
|
|
@@ -146,6 +187,13 @@ function parseTable(node: XmlElementNode, sourceXml: string): ParsedTable {
|
|
|
146
187
|
...(borders ? { borders } : {}),
|
|
147
188
|
...(cellMargins ? { cellMargins } : {}),
|
|
148
189
|
...(tblLook ? { tblLook } : {}),
|
|
190
|
+
...(indent ? { indent } : {}),
|
|
191
|
+
...(layoutMode ? { layoutMode } : {}),
|
|
192
|
+
...(cellSpacing ? { cellSpacing } : {}),
|
|
193
|
+
...(caption !== undefined ? { caption } : {}),
|
|
194
|
+
...(description !== undefined ? { description } : {}),
|
|
195
|
+
...(bidiVisual !== undefined ? { bidiVisual } : {}),
|
|
196
|
+
...(floating ? { floating } : {}),
|
|
149
197
|
};
|
|
150
198
|
}
|
|
151
199
|
|
|
@@ -154,6 +202,9 @@ function parseRow(node: XmlElementNode, sourceXml: string): ParsedTableRow {
|
|
|
154
202
|
const height = propertiesNode ? readRowHeight(propertiesNode) : undefined;
|
|
155
203
|
const heightRule = propertiesNode ? readRowHeightRule(propertiesNode) : undefined;
|
|
156
204
|
const isHeader = propertiesNode ? readRowIsHeader(propertiesNode) : undefined;
|
|
205
|
+
const cantSplit = propertiesNode ? readRowCantSplit(propertiesNode) : undefined;
|
|
206
|
+
const horizontalAlignment = propertiesNode ? readRowHorizontalAlignment(propertiesNode) : undefined;
|
|
207
|
+
const cnfStyle = propertiesNode ? readRowCnfStyle(propertiesNode) : undefined;
|
|
157
208
|
|
|
158
209
|
return {
|
|
159
210
|
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
@@ -164,6 +215,9 @@ function parseRow(node: XmlElementNode, sourceXml: string): ParsedTableRow {
|
|
|
164
215
|
...(height !== undefined ? { height } : {}),
|
|
165
216
|
...(heightRule ? { heightRule } : {}),
|
|
166
217
|
...(isHeader !== undefined ? { isHeader } : {}),
|
|
218
|
+
...(cantSplit !== undefined ? { cantSplit } : {}),
|
|
219
|
+
...(horizontalAlignment ? { horizontalAlignment } : {}),
|
|
220
|
+
...(cnfStyle ? { cnfStyle } : {}),
|
|
167
221
|
};
|
|
168
222
|
}
|
|
169
223
|
|
|
@@ -179,6 +233,11 @@ function parseCell(node: XmlElementNode, sourceXml: string): ParsedTableCell {
|
|
|
179
233
|
const borders = propertiesNode ? readCellBorders(propertiesNode) : undefined;
|
|
180
234
|
const shading = propertiesNode ? readCellShading(propertiesNode) : undefined;
|
|
181
235
|
const verticalAlign = propertiesNode ? readCellVerticalAlign(propertiesNode) : undefined;
|
|
236
|
+
const textDirection = propertiesNode ? readCellTextDirection(propertiesNode) : undefined;
|
|
237
|
+
const noWrap = propertiesNode ? readCellNoWrap(propertiesNode) : undefined;
|
|
238
|
+
const fitText = propertiesNode ? readCellFitText(propertiesNode) : undefined;
|
|
239
|
+
const margins = propertiesNode ? readCellMargins(propertiesNode) : undefined;
|
|
240
|
+
const cnfStyle = propertiesNode ? readCellCnfStyle(propertiesNode) : undefined;
|
|
182
241
|
|
|
183
242
|
return {
|
|
184
243
|
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
@@ -190,6 +249,11 @@ function parseCell(node: XmlElementNode, sourceXml: string): ParsedTableCell {
|
|
|
190
249
|
...(borders ? { borders } : {}),
|
|
191
250
|
...(shading ? { shading } : {}),
|
|
192
251
|
...(verticalAlign ? { verticalAlign } : {}),
|
|
252
|
+
...(textDirection ? { textDirection } : {}),
|
|
253
|
+
...(noWrap !== undefined ? { noWrap } : {}),
|
|
254
|
+
...(fitText !== undefined ? { fitText } : {}),
|
|
255
|
+
...(margins ? { margins } : {}),
|
|
256
|
+
...(cnfStyle ? { cnfStyle } : {}),
|
|
193
257
|
};
|
|
194
258
|
}
|
|
195
259
|
|
|
@@ -606,6 +670,191 @@ export function readRowIsHeader(propertiesNode: XmlElementNode): boolean | undef
|
|
|
606
670
|
return val !== "false" && val !== "0";
|
|
607
671
|
}
|
|
608
672
|
|
|
673
|
+
export function readTableIndent(propertiesNode: XmlElementNode): ParsedTableIndent | undefined {
|
|
674
|
+
const indentNode = findFirstChild(propertiesNode, "tblInd");
|
|
675
|
+
if (!indentNode) return undefined;
|
|
676
|
+
const valueRaw = indentNode.attributes["w:w"] ?? indentNode.attributes.w;
|
|
677
|
+
const value = valueRaw !== undefined ? Number.parseInt(valueRaw, 10) : 0;
|
|
678
|
+
if (!Number.isFinite(value)) return undefined;
|
|
679
|
+
const rawType = (indentNode.attributes["w:type"] ?? indentNode.attributes.type ?? "dxa").toLowerCase();
|
|
680
|
+
const type: ParsedTableIndent["type"] =
|
|
681
|
+
rawType === "auto" ? "auto" : rawType === "pct" ? "pct" : rawType === "nil" ? "nil" : "dxa";
|
|
682
|
+
return { value, type };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export function readTableLayoutMode(propertiesNode: XmlElementNode): "fixed" | "autofit" | undefined {
|
|
686
|
+
const layoutNode = findFirstChild(propertiesNode, "tblLayout");
|
|
687
|
+
if (!layoutNode) return undefined;
|
|
688
|
+
const raw = (layoutNode.attributes["w:type"] ?? layoutNode.attributes.type ?? "").toLowerCase();
|
|
689
|
+
if (raw === "fixed") return "fixed";
|
|
690
|
+
if (raw === "autofit") return "autofit";
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function readTableCellSpacing(propertiesNode: XmlElementNode): ParsedTableWidth | undefined {
|
|
695
|
+
const spacingNode = findFirstChild(propertiesNode, "tblCellSpacing");
|
|
696
|
+
if (!spacingNode) return undefined;
|
|
697
|
+
const valueRaw = spacingNode.attributes["w:w"] ?? spacingNode.attributes.w;
|
|
698
|
+
const value = valueRaw !== undefined ? Number.parseInt(valueRaw, 10) : 0;
|
|
699
|
+
if (!Number.isFinite(value)) return undefined;
|
|
700
|
+
const rawType = (spacingNode.attributes["w:type"] ?? spacingNode.attributes.type ?? "dxa").toLowerCase();
|
|
701
|
+
const type: ParsedTableWidth["type"] =
|
|
702
|
+
rawType === "auto" ? "auto" : rawType === "pct" ? "pct" : rawType === "nil" ? "nil" : "dxa";
|
|
703
|
+
return { value, type };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export function readTableCaption(propertiesNode: XmlElementNode): string | undefined {
|
|
707
|
+
const captionNode = findFirstChild(propertiesNode, "tblCaption");
|
|
708
|
+
if (!captionNode) return undefined;
|
|
709
|
+
return captionNode.attributes["w:val"] ?? captionNode.attributes.val;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function readTableDescription(propertiesNode: XmlElementNode): string | undefined {
|
|
713
|
+
const descriptionNode = findFirstChild(propertiesNode, "tblDescription");
|
|
714
|
+
if (!descriptionNode) return undefined;
|
|
715
|
+
return descriptionNode.attributes["w:val"] ?? descriptionNode.attributes.val;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function readTableBidiVisual(propertiesNode: XmlElementNode): boolean | undefined {
|
|
719
|
+
const bidiNode = findFirstChild(propertiesNode, "bidiVisual");
|
|
720
|
+
if (!bidiNode) return undefined;
|
|
721
|
+
const val = bidiNode.attributes["w:val"] ?? bidiNode.attributes.val;
|
|
722
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export function readTableFloating(propertiesNode: XmlElementNode): ParsedTableFloating | undefined {
|
|
726
|
+
const tblpPrNode = findFirstChild(propertiesNode, "tblpPr");
|
|
727
|
+
const overlapNode = findFirstChild(propertiesNode, "tblOverlap");
|
|
728
|
+
if (!tblpPrNode && !overlapNode) return undefined;
|
|
729
|
+
|
|
730
|
+
const floating: ParsedTableFloating = {};
|
|
731
|
+
|
|
732
|
+
if (tblpPrNode) {
|
|
733
|
+
const attr = (name: string): string | undefined =>
|
|
734
|
+
tblpPrNode.attributes[`w:${name}`] ?? tblpPrNode.attributes[name];
|
|
735
|
+
const num = (raw: string | undefined): number | undefined => {
|
|
736
|
+
if (raw === undefined) return undefined;
|
|
737
|
+
const parsed = Number.parseInt(raw, 10);
|
|
738
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
739
|
+
};
|
|
740
|
+
const anchor = (raw: string | undefined): ParsedTableFloating["horizontalAnchor"] | undefined => {
|
|
741
|
+
if (raw === "margin" || raw === "page" || raw === "text") return raw;
|
|
742
|
+
return undefined;
|
|
743
|
+
};
|
|
744
|
+
const hAlign = (raw: string | undefined): ParsedTableFloating["horizontalAlign"] | undefined => {
|
|
745
|
+
if (raw === "left" || raw === "center" || raw === "right" || raw === "inside" || raw === "outside") return raw;
|
|
746
|
+
return undefined;
|
|
747
|
+
};
|
|
748
|
+
const vAlign = (raw: string | undefined): ParsedTableFloating["verticalAlign"] | undefined => {
|
|
749
|
+
if (raw === "top" || raw === "center" || raw === "bottom" || raw === "inside" || raw === "outside") return raw;
|
|
750
|
+
return undefined;
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const hAnc = anchor(attr("horzAnchor"));
|
|
754
|
+
if (hAnc) floating.horizontalAnchor = hAnc;
|
|
755
|
+
const vAnc = anchor(attr("vertAnchor"));
|
|
756
|
+
if (vAnc) floating.verticalAnchor = vAnc;
|
|
757
|
+
const hA = hAlign(attr("tblpXSpec"));
|
|
758
|
+
if (hA) floating.horizontalAlign = hA;
|
|
759
|
+
const hOff = num(attr("tblpX"));
|
|
760
|
+
if (hOff !== undefined) floating.horizontalOffset = hOff;
|
|
761
|
+
const vA = vAlign(attr("tblpYSpec"));
|
|
762
|
+
if (vA) floating.verticalAlign = vA;
|
|
763
|
+
const vOff = num(attr("tblpY"));
|
|
764
|
+
if (vOff !== undefined) floating.verticalOffset = vOff;
|
|
765
|
+
const left = num(attr("leftFromText"));
|
|
766
|
+
if (left !== undefined) floating.leftFromText = left;
|
|
767
|
+
const right = num(attr("rightFromText"));
|
|
768
|
+
if (right !== undefined) floating.rightFromText = right;
|
|
769
|
+
const top = num(attr("topFromText"));
|
|
770
|
+
if (top !== undefined) floating.topFromText = top;
|
|
771
|
+
const bottom = num(attr("bottomFromText"));
|
|
772
|
+
if (bottom !== undefined) floating.bottomFromText = bottom;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (overlapNode) {
|
|
776
|
+
const val = (overlapNode.attributes["w:val"] ?? overlapNode.attributes.val ?? "overlap").toLowerCase();
|
|
777
|
+
floating.overlap = val === "overlap";
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return Object.keys(floating).length > 0 ? floating : undefined;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export function readRowCantSplit(propertiesNode: XmlElementNode): boolean | undefined {
|
|
784
|
+
const cantSplitNode = findFirstChild(propertiesNode, "cantSplit");
|
|
785
|
+
if (!cantSplitNode) return undefined;
|
|
786
|
+
const val = cantSplitNode.attributes["w:val"] ?? cantSplitNode.attributes.val;
|
|
787
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export function readRowHorizontalAlignment(propertiesNode: XmlElementNode): "left" | "center" | "right" | undefined {
|
|
791
|
+
const jcNode = findFirstChild(propertiesNode, "jc");
|
|
792
|
+
if (!jcNode) return undefined;
|
|
793
|
+
const val = jcNode.attributes["w:val"] ?? jcNode.attributes.val;
|
|
794
|
+
if (val === "left" || val === "center" || val === "right") return val;
|
|
795
|
+
return undefined;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export function readRowCnfStyle(propertiesNode: XmlElementNode): string | undefined {
|
|
799
|
+
const cnfNode = findFirstChild(propertiesNode, "cnfStyle");
|
|
800
|
+
if (!cnfNode) return undefined;
|
|
801
|
+
return cnfNode.attributes["w:val"] ?? cnfNode.attributes.val;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
export function readCellTextDirection(propertiesNode: XmlElementNode): "lrTb" | "tbRl" | "btLr" | undefined {
|
|
805
|
+
const dirNode = findFirstChild(propertiesNode, "textDirection");
|
|
806
|
+
if (!dirNode) return undefined;
|
|
807
|
+
const val = dirNode.attributes["w:val"] ?? dirNode.attributes.val;
|
|
808
|
+
if (val === "lrTb" || val === "tbRl" || val === "btLr") return val;
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
export function readCellNoWrap(propertiesNode: XmlElementNode): boolean | undefined {
|
|
813
|
+
const noWrapNode = findFirstChild(propertiesNode, "noWrap");
|
|
814
|
+
if (!noWrapNode) return undefined;
|
|
815
|
+
const val = noWrapNode.attributes["w:val"] ?? noWrapNode.attributes.val;
|
|
816
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export function readCellFitText(propertiesNode: XmlElementNode): boolean | undefined {
|
|
820
|
+
const fitNode = findFirstChild(propertiesNode, "tcFitText");
|
|
821
|
+
if (!fitNode) return undefined;
|
|
822
|
+
const val = fitNode.attributes["w:val"] ?? fitNode.attributes.val;
|
|
823
|
+
return val !== "false" && val !== "0" && val !== "off";
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
export function readCellMargins(propertiesNode: XmlElementNode): ParsedCellMargins | undefined {
|
|
827
|
+
const marginsNode = findFirstChild(propertiesNode, "tcMar");
|
|
828
|
+
if (!marginsNode) return undefined;
|
|
829
|
+
const readSide = (name: string): number | undefined => {
|
|
830
|
+
const sideNode = findFirstChild(marginsNode, name);
|
|
831
|
+
if (!sideNode) return undefined;
|
|
832
|
+
const raw = sideNode.attributes["w:w"] ?? sideNode.attributes.w;
|
|
833
|
+
if (raw === undefined) return undefined;
|
|
834
|
+
const parsed = Number.parseInt(raw, 10);
|
|
835
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
836
|
+
};
|
|
837
|
+
const top = readSide("top");
|
|
838
|
+
const bottom = readSide("bottom");
|
|
839
|
+
const left = readSide("start") ?? readSide("left");
|
|
840
|
+
const right = readSide("end") ?? readSide("right");
|
|
841
|
+
if (top === undefined && bottom === undefined && left === undefined && right === undefined) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
...(top !== undefined ? { top } : {}),
|
|
846
|
+
...(bottom !== undefined ? { bottom } : {}),
|
|
847
|
+
...(left !== undefined ? { left } : {}),
|
|
848
|
+
...(right !== undefined ? { right } : {}),
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export function readCellCnfStyle(propertiesNode: XmlElementNode): string | undefined {
|
|
853
|
+
const cnfNode = findFirstChild(propertiesNode, "cnfStyle");
|
|
854
|
+
if (!cnfNode) return undefined;
|
|
855
|
+
return cnfNode.attributes["w:val"] ?? cnfNode.attributes.val;
|
|
856
|
+
}
|
|
857
|
+
|
|
609
858
|
function parseBorderSpec(child: XmlElementNode): ParsedBorderSpec | undefined {
|
|
610
859
|
const value = child.attributes["w:val"] ?? child.attributes.val;
|
|
611
860
|
const sizeRaw = child.attributes["w:sz"] ?? child.attributes.sz;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared XML helpers for OOXML parsers.
|
|
3
|
+
*
|
|
4
|
+
* These functions factor out the common pattern of reading namespaced-prefixed
|
|
5
|
+
* attributes (e.g., `w:val`) with fallback to the unprefixed form, handling
|
|
6
|
+
* the OOXML `ST_OnOff` toggle semantics (missing child = undefined, present
|
|
7
|
+
* with no val = true, `val="0"|"false"|"off"` = false), and reading integers.
|
|
8
|
+
*
|
|
9
|
+
* Used by parse-run-formatting.ts, parse-paragraph-formatting.ts, and (later)
|
|
10
|
+
* parse-styles.ts, parse-numbering.ts.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { XmlElementNode } from "./xml-element.ts";
|
|
14
|
+
|
|
15
|
+
export function localName(name: string): string {
|
|
16
|
+
const sep = name.indexOf(":");
|
|
17
|
+
return sep >= 0 ? name.slice(sep + 1) : name;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function findChildOptional(
|
|
21
|
+
node: XmlElementNode,
|
|
22
|
+
local: string,
|
|
23
|
+
): XmlElementNode | undefined {
|
|
24
|
+
return node.children.find(
|
|
25
|
+
(c): c is XmlElementNode => c.type === "element" && localName(c.name) === local,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** ST_OnOff: missing child → undefined; present bare or w:val="1|true|on" → true; w:val="0|false|off" → false. */
|
|
30
|
+
export function readOnOff(node: XmlElementNode | undefined): boolean | undefined {
|
|
31
|
+
if (!node) return undefined;
|
|
32
|
+
const raw = node.attributes["w:val"] ?? node.attributes.val;
|
|
33
|
+
if (raw === undefined) return true;
|
|
34
|
+
const n = raw.toLowerCase();
|
|
35
|
+
if (n === "0" || n === "false" || n === "off") return false;
|
|
36
|
+
if (n === "1" || n === "true" || n === "on") return true;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Read the child's `w:val` attribute as an int. Returns undefined if missing or not a finite integer. */
|
|
41
|
+
export function readIntVal(node: XmlElementNode | undefined): number | undefined {
|
|
42
|
+
if (!node) return undefined;
|
|
43
|
+
const raw = node.attributes["w:val"] ?? node.attributes.val;
|
|
44
|
+
if (raw === undefined) return undefined;
|
|
45
|
+
const v = Number.parseInt(raw, 10);
|
|
46
|
+
return Number.isFinite(v) ? v : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Read an arbitrary attribute from a node as an int, with namespace fallback. */
|
|
50
|
+
export function readIntAttr(node: XmlElementNode, attr: string): number | undefined {
|
|
51
|
+
const raw = node.attributes[attr] ?? node.attributes[attr.replace(/^w:/, "")];
|
|
52
|
+
if (raw === undefined) return undefined;
|
|
53
|
+
const v = Number.parseInt(raw, 10);
|
|
54
|
+
return Number.isFinite(v) ? v : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Read an arbitrary attribute from a node as a string, with namespace fallback. */
|
|
58
|
+
export function readStringAttr(node: XmlElementNode, attr: string): string | undefined {
|
|
59
|
+
return node.attributes[attr] ?? node.attributes[attr.replace(/^w:/, "")];
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface XmlElementNode {
|
|
2
|
+
type: "element";
|
|
3
|
+
name: string;
|
|
4
|
+
attributes: Record<string, string>;
|
|
5
|
+
children: Array<XmlElementNode | XmlTextNode>;
|
|
6
|
+
/** Optional source offset (start) — parsers that track offsets may populate. */
|
|
7
|
+
start?: number;
|
|
8
|
+
/** Optional source offset (end) — parsers that track offsets may populate. */
|
|
9
|
+
end?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface XmlTextNode {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
start?: number;
|
|
16
|
+
end?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type XmlNode = XmlElementNode | XmlTextNode;
|
|
@@ -83,6 +83,7 @@ export interface StylesCatalog {
|
|
|
83
83
|
tables: Record<string, TableStyleDefinition>;
|
|
84
84
|
latentStyles?: Record<string, LatentStyleDefinition>;
|
|
85
85
|
fromPackage?: boolean;
|
|
86
|
+
docDefaults?: DocumentDefaults;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
export interface ParagraphStyleDefinition {
|
|
@@ -94,6 +95,8 @@ export interface ParagraphStyleDefinition {
|
|
|
94
95
|
displayName: string;
|
|
95
96
|
kind: "paragraph";
|
|
96
97
|
isDefault: boolean;
|
|
98
|
+
paragraphProperties?: CanonicalParagraphFormatting;
|
|
99
|
+
runProperties?: CanonicalRunFormatting;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
export interface ParagraphStyleNumberingReference {
|
|
@@ -107,6 +110,7 @@ export interface CharacterStyleDefinition {
|
|
|
107
110
|
displayName: string;
|
|
108
111
|
kind: "character";
|
|
109
112
|
isDefault: boolean;
|
|
113
|
+
runProperties?: CanonicalRunFormatting;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export interface TableStyleDefinition {
|
|
@@ -136,6 +140,12 @@ export interface NumberingCatalog {
|
|
|
136
140
|
export interface AbstractNumberingDefinition {
|
|
137
141
|
abstractNumberingId: string;
|
|
138
142
|
levels: NumberingLevelDefinition[];
|
|
143
|
+
nsid?: string;
|
|
144
|
+
multiLevelType?: "singleLevel" | "multilevel" | "hybridMultilevel";
|
|
145
|
+
/** ECMA-376 17.9.26 `<w:tmpl>` — template code identifying the abstractNum's origin template (ST_LongHexNumber). Preserved for round-trip. */
|
|
146
|
+
tplc?: string;
|
|
147
|
+
styleLink?: string;
|
|
148
|
+
numStyleLink?: string;
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
export interface NumberingLevelParagraphGeometry {
|
|
@@ -154,6 +164,8 @@ export interface NumberingLevelDefinition {
|
|
|
154
164
|
isLegalNumbering?: boolean;
|
|
155
165
|
suffix?: "tab" | "space" | "nothing";
|
|
156
166
|
paragraphGeometry?: NumberingLevelParagraphGeometry;
|
|
167
|
+
runProperties?: CanonicalRunFormatting;
|
|
168
|
+
restartAfterLevel?: number;
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
export interface NumberingLevelOverrideDefinition {
|
|
@@ -165,6 +177,8 @@ export interface NumberingLevelOverrideDefinition {
|
|
|
165
177
|
isLegalNumbering?: boolean;
|
|
166
178
|
suffix?: "tab" | "space" | "nothing";
|
|
167
179
|
paragraphGeometry?: NumberingLevelParagraphGeometry;
|
|
180
|
+
runProperties?: CanonicalRunFormatting;
|
|
181
|
+
restartAfterLevel?: number;
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
export interface NumberingInstance {
|
|
@@ -350,6 +364,68 @@ export interface ParagraphShading {
|
|
|
350
364
|
val?: string;
|
|
351
365
|
}
|
|
352
366
|
|
|
367
|
+
/** Body of an OOXML `<w:rPr>` (run properties). All fields optional; absence = "not specified at this level". */
|
|
368
|
+
export interface CanonicalRunFormatting {
|
|
369
|
+
bold?: boolean;
|
|
370
|
+
italic?: boolean;
|
|
371
|
+
underline?: "single" | "double" | "thick" | "dotted" | "dash" | "wave" | "none";
|
|
372
|
+
strikethrough?: boolean;
|
|
373
|
+
doubleStrikethrough?: boolean;
|
|
374
|
+
vanish?: boolean;
|
|
375
|
+
allCaps?: boolean;
|
|
376
|
+
smallCaps?: boolean;
|
|
377
|
+
verticalAlign?: "baseline" | "superscript" | "subscript";
|
|
378
|
+
/**
|
|
379
|
+
* Convenience alias for the primary font family — the first non-empty of
|
|
380
|
+
* `fontFamilyAscii` → `fontFamilyHAnsi` → `fontFamilyEastAsia` → `fontFamilyCs`.
|
|
381
|
+
* Script-aware consumers should read the specific `fontFamily{Ascii,HAnsi,EastAsia,Cs}`
|
|
382
|
+
* fields directly.
|
|
383
|
+
*/
|
|
384
|
+
fontFamily?: string;
|
|
385
|
+
fontFamilyAscii?: string;
|
|
386
|
+
fontFamilyHAnsi?: string;
|
|
387
|
+
fontFamilyEastAsia?: string;
|
|
388
|
+
fontFamilyCs?: string;
|
|
389
|
+
fontSizeHalfPoints?: number;
|
|
390
|
+
fontSizeCsHalfPoints?: number;
|
|
391
|
+
/**
|
|
392
|
+
* Color value from `<w:color w:val>`. Either an OOXML hex (e.g., `"2E74B5"`)
|
|
393
|
+
* or the sentinel `"auto"` (which serializers must round-trip verbatim).
|
|
394
|
+
*/
|
|
395
|
+
colorHex?: string;
|
|
396
|
+
colorThemeSlot?: string;
|
|
397
|
+
highlight?: string;
|
|
398
|
+
characterSpacingTwips?: number;
|
|
399
|
+
characterStyleId?: string;
|
|
400
|
+
languageCode?: string;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Body of an OOXML `<w:pPr>` (paragraph properties). All fields optional; absence = "not specified at this level". */
|
|
404
|
+
export interface CanonicalParagraphFormatting {
|
|
405
|
+
spacing?: ParagraphSpacing;
|
|
406
|
+
indentation?: ParagraphIndentation;
|
|
407
|
+
alignment?: "left" | "center" | "right" | "both" | "distribute" | "start" | "end";
|
|
408
|
+
borders?: ParagraphBorders;
|
|
409
|
+
shading?: ParagraphShading;
|
|
410
|
+
tabStops?: TabStop[];
|
|
411
|
+
contextualSpacing?: boolean;
|
|
412
|
+
keepNext?: boolean;
|
|
413
|
+
keepLines?: boolean;
|
|
414
|
+
widowControl?: boolean;
|
|
415
|
+
pageBreakBefore?: boolean;
|
|
416
|
+
outlineLevel?: number;
|
|
417
|
+
bidi?: boolean;
|
|
418
|
+
suppressLineNumbers?: boolean;
|
|
419
|
+
suppressAutoHyphens?: boolean;
|
|
420
|
+
paragraphMarkRunProperties?: CanonicalRunFormatting;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Body of an OOXML `<w:docDefaults>` — baseline formatting applied before style chain. */
|
|
424
|
+
export interface DocumentDefaults {
|
|
425
|
+
paragraph?: CanonicalParagraphFormatting;
|
|
426
|
+
run?: CanonicalRunFormatting;
|
|
427
|
+
}
|
|
428
|
+
|
|
353
429
|
export interface ParagraphNode {
|
|
354
430
|
type: "paragraph";
|
|
355
431
|
styleId?: string;
|
|
@@ -416,6 +492,25 @@ export interface TableWidth {
|
|
|
416
492
|
type: "dxa" | "auto" | "pct" | "nil";
|
|
417
493
|
}
|
|
418
494
|
|
|
495
|
+
export interface TableIndent {
|
|
496
|
+
value: number;
|
|
497
|
+
type: "dxa" | "auto" | "pct" | "nil";
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export interface TableFloatingProperties {
|
|
501
|
+
horizontalAnchor?: "margin" | "page" | "text";
|
|
502
|
+
verticalAnchor?: "margin" | "page" | "text";
|
|
503
|
+
horizontalAlign?: "left" | "center" | "right" | "inside" | "outside";
|
|
504
|
+
horizontalOffset?: number;
|
|
505
|
+
verticalAlign?: "top" | "center" | "bottom" | "inside" | "outside";
|
|
506
|
+
verticalOffset?: number;
|
|
507
|
+
leftFromText?: number;
|
|
508
|
+
rightFromText?: number;
|
|
509
|
+
topFromText?: number;
|
|
510
|
+
bottomFromText?: number;
|
|
511
|
+
overlap?: boolean;
|
|
512
|
+
}
|
|
513
|
+
|
|
419
514
|
export interface CellShading {
|
|
420
515
|
fill?: string;
|
|
421
516
|
color?: string;
|
|
@@ -481,6 +576,13 @@ export interface TableNode {
|
|
|
481
576
|
borders?: TableBorders;
|
|
482
577
|
cellMargins?: TableCellMargins;
|
|
483
578
|
tblLook?: TableLook;
|
|
579
|
+
indent?: TableIndent;
|
|
580
|
+
layoutMode?: "fixed" | "autofit";
|
|
581
|
+
cellSpacing?: TableWidth;
|
|
582
|
+
caption?: string;
|
|
583
|
+
description?: string;
|
|
584
|
+
bidiVisual?: boolean;
|
|
585
|
+
floating?: TableFloatingProperties;
|
|
484
586
|
}
|
|
485
587
|
|
|
486
588
|
export interface TableRowNode {
|
|
@@ -494,6 +596,9 @@ export interface TableRowNode {
|
|
|
494
596
|
height?: number;
|
|
495
597
|
heightRule?: "auto" | "atLeast" | "exact";
|
|
496
598
|
isHeader?: boolean;
|
|
599
|
+
cantSplit?: boolean;
|
|
600
|
+
horizontalAlignment?: "left" | "center" | "right";
|
|
601
|
+
cnfStyle?: string;
|
|
497
602
|
}
|
|
498
603
|
|
|
499
604
|
export interface TableCellNode {
|
|
@@ -506,6 +611,11 @@ export interface TableCellNode {
|
|
|
506
611
|
borders?: TableCellBorders;
|
|
507
612
|
shading?: CellShading;
|
|
508
613
|
verticalAlign?: "top" | "center" | "bottom";
|
|
614
|
+
textDirection?: "lrTb" | "tbRl" | "btLr";
|
|
615
|
+
noWrap?: boolean;
|
|
616
|
+
fitText?: boolean;
|
|
617
|
+
margins?: TableCellMargins;
|
|
618
|
+
cnfStyle?: string;
|
|
509
619
|
}
|
|
510
620
|
|
|
511
621
|
export interface SdtCheckboxState {
|
|
@@ -560,15 +670,19 @@ export interface AltChunkNode {
|
|
|
560
670
|
* These families have stable registry IDs, dependency metadata, and
|
|
561
671
|
* runtime-owned refresh behavior.
|
|
562
672
|
*/
|
|
563
|
-
export type SupportedFieldFamily =
|
|
673
|
+
export type SupportedFieldFamily =
|
|
674
|
+
| "REF"
|
|
675
|
+
| "PAGEREF"
|
|
676
|
+
| "NOTEREF"
|
|
677
|
+
| "TOC"
|
|
678
|
+
| "PAGE"
|
|
679
|
+
| "NUMPAGES";
|
|
564
680
|
|
|
565
681
|
/**
|
|
566
682
|
* Unsupported field families that remain preserve-only.
|
|
567
683
|
* They survive round-trip but do not participate in runtime refresh.
|
|
568
684
|
*/
|
|
569
685
|
export type PreserveOnlyFieldFamily =
|
|
570
|
-
| "PAGE"
|
|
571
|
-
| "NUMPAGES"
|
|
572
686
|
| "DATE"
|
|
573
687
|
| "TIME"
|
|
574
688
|
| "AUTHOR"
|