@beyondwork/docx-react-component 1.0.1 → 1.0.3
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 +44 -104
- package/package.json +50 -30
- package/src/README.md +85 -0
- package/src/api/README.md +22 -0
- package/src/api/public-types.ts +525 -0
- package/src/compare/diff-engine.ts +530 -0
- package/src/compare/export-redlines.ts +162 -0
- package/src/compare/snapshot.ts +37 -0
- package/src/component-inventory.md +99 -0
- package/src/core/README.md +10 -0
- package/src/core/commands/README.md +3 -0
- package/src/core/commands/formatting-commands.ts +161 -0
- package/src/core/commands/image-commands.ts +144 -0
- package/src/core/commands/index.ts +1013 -0
- package/src/core/commands/list-commands.ts +370 -0
- package/src/core/commands/review-commands.ts +108 -0
- package/src/core/commands/text-commands.ts +119 -0
- package/src/core/schema/README.md +3 -0
- package/src/core/schema/text-schema.ts +512 -0
- package/src/core/selection/README.md +3 -0
- package/src/core/selection/mapping.ts +238 -0
- package/src/core/selection/review-anchors.ts +94 -0
- package/src/core/state/README.md +3 -0
- package/src/core/state/editor-state.ts +580 -0
- package/src/core/state/text-transaction.ts +276 -0
- package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
- package/src/formats/xlsx/io/parse-sheet.ts +289 -0
- package/src/formats/xlsx/io/parse-styles.ts +57 -0
- package/src/formats/xlsx/io/parse-workbook.ts +75 -0
- package/src/formats/xlsx/io/xlsx-session.ts +306 -0
- package/src/formats/xlsx/model/cell.ts +189 -0
- package/src/formats/xlsx/model/sheet.ts +244 -0
- package/src/formats/xlsx/model/styles.ts +118 -0
- package/src/formats/xlsx/model/workbook.ts +449 -0
- package/src/index.ts +45 -0
- package/src/io/README.md +10 -0
- package/src/io/docx-session.ts +1763 -0
- package/src/io/export/README.md +3 -0
- package/src/io/export/export-session.ts +165 -0
- package/src/io/export/minimal-docx.ts +115 -0
- package/src/io/export/reattach-preserved-parts.ts +54 -0
- package/src/io/export/serialize-comments.ts +876 -0
- package/src/io/export/serialize-footnotes.ts +217 -0
- package/src/io/export/serialize-headers-footers.ts +200 -0
- package/src/io/export/serialize-main-document.ts +982 -0
- package/src/io/export/serialize-numbering.ts +97 -0
- package/src/io/export/serialize-revisions.ts +389 -0
- package/src/io/export/serialize-runtime-revisions.ts +265 -0
- package/src/io/export/serialize-tables.ts +147 -0
- package/src/io/export/split-review-boundaries.ts +194 -0
- package/src/io/normalize/README.md +3 -0
- package/src/io/normalize/normalize-text.ts +437 -0
- package/src/io/ooxml/README.md +3 -0
- package/src/io/ooxml/parse-comments.ts +779 -0
- package/src/io/ooxml/parse-complex-content.ts +287 -0
- package/src/io/ooxml/parse-fields.ts +438 -0
- package/src/io/ooxml/parse-footnotes.ts +403 -0
- package/src/io/ooxml/parse-headers-footers.ts +483 -0
- package/src/io/ooxml/parse-inline-media.ts +431 -0
- package/src/io/ooxml/parse-main-document.ts +1846 -0
- package/src/io/ooxml/parse-numbering.ts +425 -0
- package/src/io/ooxml/parse-revisions.ts +658 -0
- package/src/io/ooxml/parse-shapes.ts +271 -0
- package/src/io/ooxml/parse-tables.ts +568 -0
- package/src/io/ooxml/parse-theme.ts +314 -0
- package/src/io/ooxml/part-manifest.ts +136 -0
- package/src/io/ooxml/revision-boundaries.ts +351 -0
- package/src/io/opc/README.md +3 -0
- package/src/io/opc/corrupt-package.ts +166 -0
- package/src/io/opc/docx-package.ts +74 -0
- package/src/io/opc/package-reader.ts +325 -0
- package/src/io/opc/package-writer.ts +273 -0
- package/src/legal/bookmarks.ts +196 -0
- package/src/legal/cross-references.ts +356 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/model/README.md +3 -0
- package/src/model/canonical-document.ts +1911 -0
- package/src/model/cds-1.0.0.ts +196 -0
- package/src/model/snapshot.ts +393 -0
- package/src/preservation/README.md +3 -0
- package/src/preservation/markup-compatibility.ts +48 -0
- package/src/preservation/opaque-fragment-store.ts +89 -0
- package/src/preservation/opaque-region.ts +233 -0
- package/src/preservation/package-preservation.ts +120 -0
- package/src/preservation/preserved-part-manifest.ts +56 -0
- package/src/preservation/relationship-retention.ts +57 -0
- package/src/preservation/store.ts +185 -0
- package/src/review/README.md +16 -0
- package/src/review/store/README.md +3 -0
- package/src/review/store/comment-anchors.ts +70 -0
- package/src/review/store/comment-remapping.ts +154 -0
- package/src/review/store/comment-store.ts +331 -0
- package/src/review/store/comment-thread.ts +109 -0
- package/src/review/store/revision-actions.ts +394 -0
- package/src/review/store/revision-store.ts +303 -0
- package/src/review/store/revision-types.ts +168 -0
- package/src/review/store/runtime-comment-store.ts +43 -0
- package/src/runtime/README.md +3 -0
- package/src/runtime/ai-action-policy.ts +764 -0
- package/src/runtime/document-runtime.ts +967 -0
- package/src/runtime/read-only-diagnostics-runtime.ts +232 -0
- package/src/runtime/review-runtime.ts +44 -0
- package/src/runtime/revision-runtime.ts +107 -0
- package/src/runtime/session-capabilities.ts +138 -0
- package/src/runtime/surface-projection.ts +570 -0
- package/src/runtime/table-commands.ts +87 -0
- package/src/runtime/table-schema.ts +140 -0
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/ui/README.md +30 -0
- package/src/ui/WordReviewEditor.tsx +1506 -0
- package/src/ui/comments/README.md +3 -0
- package/src/ui/compatibility/README.md +3 -0
- package/src/ui/editor-surface/README.md +3 -0
- package/src/ui/headless/comment-decoration-model.ts +124 -0
- package/src/ui/headless/revision-decoration-model.ts +128 -0
- package/src/ui/headless/selection-helpers.ts +34 -0
- package/src/ui/headless/use-editor-keyboard.ts +98 -0
- package/src/ui/review/README.md +3 -0
- package/src/ui/shared/revision-filters.ts +31 -0
- package/src/ui/status/README.md +3 -0
- package/src/ui/theme/README.md +3 -0
- package/src/ui/toolbar/README.md +3 -0
- package/src/ui-tailwind/chrome/tw-alert-banner.tsx +48 -0
- package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +44 -0
- package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
- package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
- package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +139 -0
- package/src/ui-tailwind/editor-surface/pm-decorations.ts +98 -0
- package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
- package/src/ui-tailwind/editor-surface/pm-schema.ts +452 -0
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +327 -0
- package/src/ui-tailwind/editor-surface/search-plugin.ts +157 -0
- package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
- package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +118 -0
- package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +52 -0
- package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +215 -0
- package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +122 -0
- package/src/ui-tailwind/index.ts +61 -0
- package/src/ui-tailwind/review/tw-comment-sidebar.tsx +276 -0
- package/src/ui-tailwind/review/tw-health-panel.tsx +120 -0
- package/src/ui-tailwind/review/tw-review-rail.tsx +120 -0
- package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
- package/src/ui-tailwind/status/tw-status-bar.tsx +58 -0
- package/src/ui-tailwind/theme/editor-theme.css +190 -0
- package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +48 -0
- package/src/ui-tailwind/toolbar/tw-toolbar.tsx +231 -0
- package/src/ui-tailwind/tw-review-workspace.tsx +140 -0
- package/src/validation/README.md +3 -0
- package/src/validation/compatibility-engine.ts +317 -0
- package/src/validation/compatibility-report.ts +160 -0
- package/src/validation/diagnostics.ts +203 -0
- package/src/validation/import-diagnostics.ts +128 -0
- package/src/validation/low-priority-word-surfaces.ts +373 -0
- package/dist/chunk-32W6IVQE.js +0 -7725
- package/dist/chunk-32W6IVQE.js.map +0 -1
- package/dist/index.cjs +0 -23722
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -16011
- package/dist/index.js.map +0 -1
- package/dist/public-types-DqCURAz8.d.cts +0 -1152
- package/dist/public-types-DqCURAz8.d.ts +0 -1152
- package/dist/tailwind.cjs +0 -8295
- package/dist/tailwind.cjs.map +0 -1
- package/dist/tailwind.d.cts +0 -323
- package/dist/tailwind.d.ts +0 -323
- package/dist/tailwind.js +0 -553
- package/dist/tailwind.js.map +0 -1
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
interface XmlElementNode {
|
|
2
|
+
type: "element";
|
|
3
|
+
name: string;
|
|
4
|
+
attributes: Record<string, string>;
|
|
5
|
+
children: XmlNode[];
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface XmlTextNode {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type XmlNode = XmlElementNode | XmlTextNode;
|
|
18
|
+
|
|
19
|
+
export interface ParsedBorderSpec {
|
|
20
|
+
value?: string;
|
|
21
|
+
size?: number;
|
|
22
|
+
space?: number;
|
|
23
|
+
color?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ParsedTableBorders {
|
|
27
|
+
top?: ParsedBorderSpec;
|
|
28
|
+
left?: ParsedBorderSpec;
|
|
29
|
+
bottom?: ParsedBorderSpec;
|
|
30
|
+
right?: ParsedBorderSpec;
|
|
31
|
+
insideH?: ParsedBorderSpec;
|
|
32
|
+
insideV?: ParsedBorderSpec;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ParsedTableCellBorders {
|
|
36
|
+
top?: ParsedBorderSpec;
|
|
37
|
+
left?: ParsedBorderSpec;
|
|
38
|
+
bottom?: ParsedBorderSpec;
|
|
39
|
+
right?: ParsedBorderSpec;
|
|
40
|
+
insideH?: ParsedBorderSpec;
|
|
41
|
+
insideV?: ParsedBorderSpec;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ParsedTableWidth {
|
|
45
|
+
value: number;
|
|
46
|
+
type: "dxa" | "auto" | "pct" | "nil";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ParsedCellShading {
|
|
50
|
+
fill?: string;
|
|
51
|
+
color?: string;
|
|
52
|
+
val?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ParsedCellMargins {
|
|
56
|
+
top?: number;
|
|
57
|
+
left?: number;
|
|
58
|
+
bottom?: number;
|
|
59
|
+
right?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ParsedTableDocument {
|
|
63
|
+
tables: ParsedTable[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ParsedTable {
|
|
67
|
+
type: "table";
|
|
68
|
+
propertiesXml?: string;
|
|
69
|
+
gridColumns: number[];
|
|
70
|
+
rows: ParsedTableRow[];
|
|
71
|
+
rawXml: string;
|
|
72
|
+
width?: ParsedTableWidth;
|
|
73
|
+
alignment?: "left" | "center" | "right";
|
|
74
|
+
borders?: ParsedTableBorders;
|
|
75
|
+
cellMargins?: ParsedCellMargins;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ParsedTableRow {
|
|
79
|
+
propertiesXml?: string;
|
|
80
|
+
cells: ParsedTableCell[];
|
|
81
|
+
rawXml: string;
|
|
82
|
+
height?: number;
|
|
83
|
+
heightRule?: "auto" | "atLeast" | "exact";
|
|
84
|
+
isHeader?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ParsedTableCell {
|
|
88
|
+
propertiesXml?: string;
|
|
89
|
+
gridSpan?: number;
|
|
90
|
+
verticalMerge?: "restart" | "continue";
|
|
91
|
+
blocksXml: string[];
|
|
92
|
+
rawXml: string;
|
|
93
|
+
width?: ParsedTableWidth;
|
|
94
|
+
borders?: ParsedTableCellBorders;
|
|
95
|
+
shading?: ParsedCellShading;
|
|
96
|
+
verticalAlign?: "top" | "center" | "bottom";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function parseTablesFromDocumentXml(xml: string): ParsedTableDocument {
|
|
100
|
+
const root = parseXml(xml);
|
|
101
|
+
const documentElement = findChildElement(root, "document");
|
|
102
|
+
const bodyElement = findChildElement(documentElement, "body");
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
tables: bodyElement.children
|
|
106
|
+
.filter((node): node is XmlElementNode => node.type === "element" && localName(node.name) === "tbl")
|
|
107
|
+
.map((node) => parseTable(node, xml)),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseTable(node: XmlElementNode, sourceXml: string): ParsedTable {
|
|
112
|
+
const propertiesNode = findFirstChild(node, "tblPr");
|
|
113
|
+
const gridNode = findFirstChild(node, "tblGrid");
|
|
114
|
+
const rows = node.children
|
|
115
|
+
.filter((child): child is XmlElementNode => child.type === "element" && localName(child.name) === "tr")
|
|
116
|
+
.map((rowNode) => parseRow(rowNode, sourceXml));
|
|
117
|
+
|
|
118
|
+
const width = propertiesNode ? readTableWidth(propertiesNode) : undefined;
|
|
119
|
+
const alignment = propertiesNode ? readTableAlignment(propertiesNode) : undefined;
|
|
120
|
+
const borders = propertiesNode ? readTableBorders(propertiesNode) : undefined;
|
|
121
|
+
const cellMargins = propertiesNode ? readTableCellMargins(propertiesNode) : undefined;
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
type: "table",
|
|
125
|
+
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
126
|
+
gridColumns: gridNode ? readGridColumns(gridNode) : [],
|
|
127
|
+
rows,
|
|
128
|
+
rawXml: sourceXml.slice(node.start, node.end),
|
|
129
|
+
...(width ? { width } : {}),
|
|
130
|
+
...(alignment ? { alignment } : {}),
|
|
131
|
+
...(borders ? { borders } : {}),
|
|
132
|
+
...(cellMargins ? { cellMargins } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseRow(node: XmlElementNode, sourceXml: string): ParsedTableRow {
|
|
137
|
+
const propertiesNode = findFirstChild(node, "trPr");
|
|
138
|
+
const height = propertiesNode ? readRowHeight(propertiesNode) : undefined;
|
|
139
|
+
const heightRule = propertiesNode ? readRowHeightRule(propertiesNode) : undefined;
|
|
140
|
+
const isHeader = propertiesNode ? readRowIsHeader(propertiesNode) : undefined;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
144
|
+
cells: node.children
|
|
145
|
+
.filter((child): child is XmlElementNode => child.type === "element" && localName(child.name) === "tc")
|
|
146
|
+
.map((cellNode) => parseCell(cellNode, sourceXml)),
|
|
147
|
+
rawXml: sourceXml.slice(node.start, node.end),
|
|
148
|
+
...(height !== undefined ? { height } : {}),
|
|
149
|
+
...(heightRule ? { heightRule } : {}),
|
|
150
|
+
...(isHeader ? { isHeader } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseCell(node: XmlElementNode, sourceXml: string): ParsedTableCell {
|
|
155
|
+
const propertiesNode = findFirstChild(node, "tcPr");
|
|
156
|
+
const gridSpanNode = propertiesNode ? findFirstChild(propertiesNode, "gridSpan") : undefined;
|
|
157
|
+
const verticalMergeNode = propertiesNode ? findFirstChild(propertiesNode, "vMerge") : undefined;
|
|
158
|
+
const blocksXml = node.children
|
|
159
|
+
.filter((child): child is XmlElementNode => child.type === "element" && localName(child.name) !== "tcPr")
|
|
160
|
+
.map((child) => sourceXml.slice(child.start, child.end));
|
|
161
|
+
|
|
162
|
+
const cellWidth = propertiesNode ? readCellWidth(propertiesNode) : undefined;
|
|
163
|
+
const borders = propertiesNode ? readCellBorders(propertiesNode) : undefined;
|
|
164
|
+
const shading = propertiesNode ? readCellShading(propertiesNode) : undefined;
|
|
165
|
+
const verticalAlign = propertiesNode ? readCellVerticalAlign(propertiesNode) : undefined;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
...(propertiesNode ? { propertiesXml: sourceXml.slice(propertiesNode.start, propertiesNode.end) } : {}),
|
|
169
|
+
...(gridSpanNode ? { gridSpan: parsePositiveInteger(gridSpanNode.attributes["w:val"] ?? gridSpanNode.attributes.val) } : {}),
|
|
170
|
+
...(verticalMergeNode ? { verticalMerge: readVerticalMerge(verticalMergeNode) } : {}),
|
|
171
|
+
blocksXml,
|
|
172
|
+
rawXml: sourceXml.slice(node.start, node.end),
|
|
173
|
+
...(cellWidth ? { width: cellWidth } : {}),
|
|
174
|
+
...(borders ? { borders } : {}),
|
|
175
|
+
...(shading ? { shading } : {}),
|
|
176
|
+
...(verticalAlign ? { verticalAlign } : {}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function readGridColumns(node: XmlElementNode): number[] {
|
|
181
|
+
return node.children
|
|
182
|
+
.filter((child): child is XmlElementNode => child.type === "element" && localName(child.name) === "gridCol")
|
|
183
|
+
.map((child) => parsePositiveInteger(child.attributes["w:w"] ?? child.attributes.w ?? "0"));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function readVerticalMerge(node: XmlElementNode): "restart" | "continue" {
|
|
187
|
+
const value = (node.attributes["w:val"] ?? node.attributes.val ?? "continue").toLowerCase();
|
|
188
|
+
return value === "restart" ? "restart" : "continue";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parsePositiveInteger(value: string | undefined): number {
|
|
192
|
+
const parsed = Number.parseInt(value ?? "0", 10);
|
|
193
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function findChildElement(node: XmlElementNode, childLocalName: string): XmlElementNode {
|
|
197
|
+
const child = findFirstChild(node, childLocalName);
|
|
198
|
+
if (!child) {
|
|
199
|
+
throw new Error(`Expected <${childLocalName}> element.`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return child;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function findFirstChild(node: XmlElementNode, childLocalName: string): XmlElementNode | undefined {
|
|
206
|
+
return node.children.find(
|
|
207
|
+
(entry): entry is XmlElementNode =>
|
|
208
|
+
entry.type === "element" && localName(entry.name) === childLocalName,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function localName(name: string): string {
|
|
213
|
+
const separatorIndex = name.indexOf(":");
|
|
214
|
+
return separatorIndex >= 0 ? name.slice(separatorIndex + 1) : name;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseXml(xml: string): XmlElementNode {
|
|
218
|
+
const root: XmlElementNode = {
|
|
219
|
+
type: "element",
|
|
220
|
+
name: "__root__",
|
|
221
|
+
attributes: {},
|
|
222
|
+
children: [],
|
|
223
|
+
start: 0,
|
|
224
|
+
end: xml.length,
|
|
225
|
+
};
|
|
226
|
+
const stack: XmlElementNode[] = [root];
|
|
227
|
+
let cursor = 0;
|
|
228
|
+
|
|
229
|
+
while (cursor < xml.length) {
|
|
230
|
+
if (xml.startsWith("<!--", cursor)) {
|
|
231
|
+
const end = xml.indexOf("-->", cursor);
|
|
232
|
+
cursor = end >= 0 ? end + 3 : xml.length;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (xml.startsWith("<?", cursor)) {
|
|
237
|
+
const end = xml.indexOf("?>", cursor);
|
|
238
|
+
cursor = end >= 0 ? end + 2 : xml.length;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (xml.startsWith("<![CDATA[", cursor)) {
|
|
243
|
+
const end = xml.indexOf("]]>", cursor);
|
|
244
|
+
const textEnd = end >= 0 ? end : xml.length;
|
|
245
|
+
stack[stack.length - 1]?.children.push({
|
|
246
|
+
type: "text",
|
|
247
|
+
text: xml.slice(cursor + 9, textEnd),
|
|
248
|
+
start: cursor,
|
|
249
|
+
end: end >= 0 ? end + 3 : xml.length,
|
|
250
|
+
});
|
|
251
|
+
cursor = end >= 0 ? end + 3 : xml.length;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (xml[cursor] !== "<") {
|
|
256
|
+
const nextTag = xml.indexOf("<", cursor);
|
|
257
|
+
const end = nextTag >= 0 ? nextTag : xml.length;
|
|
258
|
+
const text = decodeXmlEntities(xml.slice(cursor, end));
|
|
259
|
+
if (text.length > 0) {
|
|
260
|
+
stack[stack.length - 1]?.children.push({
|
|
261
|
+
type: "text",
|
|
262
|
+
text,
|
|
263
|
+
start: cursor,
|
|
264
|
+
end,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
cursor = end;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (xml[cursor + 1] === "/") {
|
|
272
|
+
const end = xml.indexOf(">", cursor);
|
|
273
|
+
if (end < 0) {
|
|
274
|
+
throw new Error("Malformed XML: missing closing >.");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const name = xml.slice(cursor + 2, end).trim();
|
|
278
|
+
const current = stack.pop();
|
|
279
|
+
if (!current || localName(current.name) !== localName(name)) {
|
|
280
|
+
throw new Error(`Malformed XML: unexpected closing tag </${name}>.`);
|
|
281
|
+
}
|
|
282
|
+
current.end = end + 1;
|
|
283
|
+
cursor = end + 1;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const tagEnd = findTagEnd(xml, cursor);
|
|
288
|
+
const tagBody = xml.slice(cursor + 1, tagEnd);
|
|
289
|
+
const selfClosing = /\/\s*$/.test(tagBody);
|
|
290
|
+
const { name, attributes } = parseTag(tagBody.replace(/\/\s*$/, "").trim());
|
|
291
|
+
const element: XmlElementNode = {
|
|
292
|
+
type: "element",
|
|
293
|
+
name,
|
|
294
|
+
attributes,
|
|
295
|
+
children: [],
|
|
296
|
+
start: cursor,
|
|
297
|
+
end: tagEnd + 1,
|
|
298
|
+
};
|
|
299
|
+
stack[stack.length - 1]?.children.push(element);
|
|
300
|
+
|
|
301
|
+
if (!selfClosing) {
|
|
302
|
+
stack.push(element);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
cursor = tagEnd + 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (stack.length !== 1) {
|
|
309
|
+
throw new Error("Malformed XML: unclosed element.");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return root;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function parseTag(tagBody: string): { name: string; attributes: Record<string, string> } {
|
|
316
|
+
let cursor = 0;
|
|
317
|
+
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
318
|
+
cursor += 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const nameStart = cursor;
|
|
322
|
+
while (cursor < tagBody.length && !/\s/.test(tagBody[cursor] ?? "")) {
|
|
323
|
+
cursor += 1;
|
|
324
|
+
}
|
|
325
|
+
const name = tagBody.slice(nameStart, cursor);
|
|
326
|
+
const attributes: Record<string, string> = {};
|
|
327
|
+
|
|
328
|
+
while (cursor < tagBody.length) {
|
|
329
|
+
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
330
|
+
cursor += 1;
|
|
331
|
+
}
|
|
332
|
+
if (cursor >= tagBody.length) {
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const keyStart = cursor;
|
|
337
|
+
while (cursor < tagBody.length && !/[\s=]/.test(tagBody[cursor] ?? "")) {
|
|
338
|
+
cursor += 1;
|
|
339
|
+
}
|
|
340
|
+
const key = tagBody.slice(keyStart, cursor);
|
|
341
|
+
|
|
342
|
+
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
343
|
+
cursor += 1;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (tagBody[cursor] !== "=") {
|
|
347
|
+
attributes[key] = "";
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
cursor += 1;
|
|
351
|
+
|
|
352
|
+
while (cursor < tagBody.length && /\s/.test(tagBody[cursor] ?? "")) {
|
|
353
|
+
cursor += 1;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const quote = tagBody[cursor];
|
|
357
|
+
if (quote !== `"` && quote !== `'`) {
|
|
358
|
+
throw new Error(`Malformed XML attribute ${key}.`);
|
|
359
|
+
}
|
|
360
|
+
cursor += 1;
|
|
361
|
+
|
|
362
|
+
const valueStart = cursor;
|
|
363
|
+
while (cursor < tagBody.length && tagBody[cursor] !== quote) {
|
|
364
|
+
cursor += 1;
|
|
365
|
+
}
|
|
366
|
+
attributes[key] = decodeXmlEntities(tagBody.slice(valueStart, cursor));
|
|
367
|
+
cursor += 1;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return { name, attributes };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function findTagEnd(xml: string, start: number): number {
|
|
374
|
+
let cursor = start + 1;
|
|
375
|
+
let quote: string | null = null;
|
|
376
|
+
|
|
377
|
+
while (cursor < xml.length) {
|
|
378
|
+
const current = xml[cursor];
|
|
379
|
+
if (quote) {
|
|
380
|
+
if (current === quote) {
|
|
381
|
+
quote = null;
|
|
382
|
+
}
|
|
383
|
+
cursor += 1;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (current === `"` || current === `'`) {
|
|
388
|
+
quote = current;
|
|
389
|
+
cursor += 1;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (current === ">") {
|
|
394
|
+
return cursor;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
cursor += 1;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
throw new Error("Malformed XML: missing >.");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function decodeXmlEntities(value: string): string {
|
|
404
|
+
return value.replace(/&(#x[0-9a-fA-F]+|#\d+|amp|lt|gt|quot|apos);/g, (match, entity) => {
|
|
405
|
+
switch (entity) {
|
|
406
|
+
case "amp":
|
|
407
|
+
return "&";
|
|
408
|
+
case "lt":
|
|
409
|
+
return "<";
|
|
410
|
+
case "gt":
|
|
411
|
+
return ">";
|
|
412
|
+
case "quot":
|
|
413
|
+
return `"`;
|
|
414
|
+
case "apos":
|
|
415
|
+
return "'";
|
|
416
|
+
default:
|
|
417
|
+
if (entity.startsWith("#x")) {
|
|
418
|
+
return String.fromCodePoint(Number.parseInt(entity.slice(2), 16));
|
|
419
|
+
}
|
|
420
|
+
if (entity.startsWith("#")) {
|
|
421
|
+
return String.fromCodePoint(Number.parseInt(entity.slice(1), 10));
|
|
422
|
+
}
|
|
423
|
+
return match;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Cell property readers
|
|
429
|
+
|
|
430
|
+
function readCellWidth(propertiesNode: XmlElementNode): ParsedTableWidth | undefined {
|
|
431
|
+
const widthNode = findFirstChild(propertiesNode, "tcW");
|
|
432
|
+
if (!widthNode) return undefined;
|
|
433
|
+
const value = parsePositiveInteger(widthNode.attributes["w:w"] ?? widthNode.attributes.w);
|
|
434
|
+
const rawType = (widthNode.attributes["w:type"] ?? widthNode.attributes.type ?? "dxa").toLowerCase();
|
|
435
|
+
const type: ParsedTableWidth["type"] =
|
|
436
|
+
rawType === "auto" ? "auto" : rawType === "pct" ? "pct" : rawType === "nil" ? "nil" : "dxa";
|
|
437
|
+
return { value, type };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function readCellBorders(propertiesNode: XmlElementNode): ParsedTableCellBorders | undefined {
|
|
441
|
+
const bordersNode = findFirstChild(propertiesNode, "tcBorders");
|
|
442
|
+
if (!bordersNode) return undefined;
|
|
443
|
+
const borders: ParsedTableCellBorders = {};
|
|
444
|
+
for (const child of bordersNode.children) {
|
|
445
|
+
if (child.type !== "element") continue;
|
|
446
|
+
const name = localName(child.name);
|
|
447
|
+
if (name !== "top" && name !== "left" && name !== "bottom" && name !== "right" && name !== "insideH" && name !== "insideV") continue;
|
|
448
|
+
const spec = parseBorderSpec(child);
|
|
449
|
+
if (spec) borders[name] = spec;
|
|
450
|
+
}
|
|
451
|
+
return Object.keys(borders).length > 0 ? borders : undefined;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function readCellShading(propertiesNode: XmlElementNode): ParsedCellShading | undefined {
|
|
455
|
+
const shdNode = findFirstChild(propertiesNode, "shd");
|
|
456
|
+
if (!shdNode) return undefined;
|
|
457
|
+
const fill = shdNode.attributes["w:fill"] ?? shdNode.attributes.fill;
|
|
458
|
+
const color = shdNode.attributes["w:color"] ?? shdNode.attributes.color;
|
|
459
|
+
const val = shdNode.attributes["w:val"] ?? shdNode.attributes.val;
|
|
460
|
+
if (!fill && !color && !val) return undefined;
|
|
461
|
+
const result: ParsedCellShading = {};
|
|
462
|
+
if (fill) result.fill = fill;
|
|
463
|
+
if (color) result.color = color;
|
|
464
|
+
if (val) result.val = val;
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function readCellVerticalAlign(propertiesNode: XmlElementNode): "top" | "center" | "bottom" | undefined {
|
|
469
|
+
const vAlignNode = findFirstChild(propertiesNode, "vAlign");
|
|
470
|
+
if (!vAlignNode) return undefined;
|
|
471
|
+
const val = vAlignNode.attributes["w:val"] ?? vAlignNode.attributes.val;
|
|
472
|
+
if (val === "center" || val === "top" || val === "bottom") return val;
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Table property readers
|
|
477
|
+
|
|
478
|
+
function readTableWidth(propertiesNode: XmlElementNode): ParsedTableWidth | undefined {
|
|
479
|
+
const widthNode = findFirstChild(propertiesNode, "tblW");
|
|
480
|
+
if (!widthNode) return undefined;
|
|
481
|
+
const value = parsePositiveInteger(widthNode.attributes["w:w"] ?? widthNode.attributes.w);
|
|
482
|
+
const rawType = (widthNode.attributes["w:type"] ?? widthNode.attributes.type ?? "dxa").toLowerCase();
|
|
483
|
+
const type: ParsedTableWidth["type"] =
|
|
484
|
+
rawType === "auto" ? "auto" : rawType === "pct" ? "pct" : rawType === "nil" ? "nil" : "dxa";
|
|
485
|
+
return { value, type };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function readTableAlignment(propertiesNode: XmlElementNode): "left" | "center" | "right" | undefined {
|
|
489
|
+
const jcNode = findFirstChild(propertiesNode, "jc");
|
|
490
|
+
if (!jcNode) return undefined;
|
|
491
|
+
const val = jcNode.attributes["w:val"] ?? jcNode.attributes.val;
|
|
492
|
+
if (val === "left" || val === "center" || val === "right") return val;
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function readTableBorders(propertiesNode: XmlElementNode): ParsedTableBorders | undefined {
|
|
497
|
+
const bordersNode = findFirstChild(propertiesNode, "tblBorders");
|
|
498
|
+
if (!bordersNode) return undefined;
|
|
499
|
+
const borders: ParsedTableBorders = {};
|
|
500
|
+
for (const child of bordersNode.children) {
|
|
501
|
+
if (child.type !== "element") continue;
|
|
502
|
+
const name = localName(child.name);
|
|
503
|
+
if (name !== "top" && name !== "left" && name !== "bottom" && name !== "right" && name !== "insideH" && name !== "insideV") continue;
|
|
504
|
+
const spec = parseBorderSpec(child);
|
|
505
|
+
if (spec) borders[name] = spec;
|
|
506
|
+
}
|
|
507
|
+
return Object.keys(borders).length > 0 ? borders : undefined;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function readTableCellMargins(propertiesNode: XmlElementNode): ParsedCellMargins | undefined {
|
|
511
|
+
const marginsNode = findFirstChild(propertiesNode, "tblCellMar");
|
|
512
|
+
if (!marginsNode) return undefined;
|
|
513
|
+
const readSide = (name: string): number => {
|
|
514
|
+
const n = findFirstChild(marginsNode, name);
|
|
515
|
+
return n ? parsePositiveInteger(n.attributes["w:w"] ?? n.attributes.w) : 0;
|
|
516
|
+
};
|
|
517
|
+
const top = readSide("top");
|
|
518
|
+
const bottom = readSide("bottom");
|
|
519
|
+
const left = readSide("start") || readSide("left");
|
|
520
|
+
const right = readSide("end") || readSide("right");
|
|
521
|
+
if (top === 0 && bottom === 0 && left === 0 && right === 0) return undefined;
|
|
522
|
+
return { top, bottom, left, right };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Row property readers
|
|
526
|
+
|
|
527
|
+
function readRowHeight(propertiesNode: XmlElementNode): number | undefined {
|
|
528
|
+
const heightNode = findFirstChild(propertiesNode, "trHeight");
|
|
529
|
+
if (!heightNode) return undefined;
|
|
530
|
+
return parsePositiveInteger(heightNode.attributes["w:val"] ?? heightNode.attributes.val);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function readRowHeightRule(propertiesNode: XmlElementNode): "auto" | "atLeast" | "exact" | undefined {
|
|
534
|
+
const heightNode = findFirstChild(propertiesNode, "trHeight");
|
|
535
|
+
if (!heightNode) return undefined;
|
|
536
|
+
const raw = (heightNode.attributes["w:hRule"] ?? heightNode.attributes.hRule ?? "").toLowerCase();
|
|
537
|
+
if (raw === "atleast") return "atLeast";
|
|
538
|
+
if (raw === "exact") return "exact";
|
|
539
|
+
if (raw === "auto") return "auto";
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function readRowIsHeader(propertiesNode: XmlElementNode): boolean | undefined {
|
|
544
|
+
const headerNode = findFirstChild(propertiesNode, "tblHeader");
|
|
545
|
+
if (!headerNode) return undefined;
|
|
546
|
+
const val = headerNode.attributes["w:val"] ?? headerNode.attributes.val;
|
|
547
|
+
return val !== "false" && val !== "0";
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function parseBorderSpec(child: XmlElementNode): ParsedBorderSpec | undefined {
|
|
551
|
+
const value = child.attributes["w:val"] ?? child.attributes.val;
|
|
552
|
+
const sizeRaw = child.attributes["w:sz"] ?? child.attributes.sz;
|
|
553
|
+
const spaceRaw = child.attributes["w:space"] ?? child.attributes.space;
|
|
554
|
+
const color = child.attributes["w:color"] ?? child.attributes.color;
|
|
555
|
+
if (!value && !sizeRaw && !spaceRaw && !color) return undefined;
|
|
556
|
+
const spec: ParsedBorderSpec = {};
|
|
557
|
+
if (value) spec.value = value;
|
|
558
|
+
if (sizeRaw !== undefined) {
|
|
559
|
+
const n = Number.parseInt(sizeRaw, 10);
|
|
560
|
+
if (Number.isFinite(n)) spec.size = n;
|
|
561
|
+
}
|
|
562
|
+
if (spaceRaw !== undefined) {
|
|
563
|
+
const n = Number.parseInt(spaceRaw, 10);
|
|
564
|
+
if (Number.isFinite(n)) spec.space = n;
|
|
565
|
+
}
|
|
566
|
+
if (color) spec.color = color;
|
|
567
|
+
return spec;
|
|
568
|
+
}
|