@beyondwork/docx-react-component 1.0.1 → 1.0.2
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 +76 -46
- 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 +320 -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 +1504 -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,483 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BlockNode,
|
|
3
|
+
FootnoteRefNode,
|
|
4
|
+
HeaderFooterVariant,
|
|
5
|
+
InlineNode,
|
|
6
|
+
ParagraphNode,
|
|
7
|
+
TextMark,
|
|
8
|
+
} from "../../model/canonical-document.ts";
|
|
9
|
+
|
|
10
|
+
// ---- Public types ----
|
|
11
|
+
|
|
12
|
+
export interface ParsedHeaderFooterReference {
|
|
13
|
+
variant: HeaderFooterVariant;
|
|
14
|
+
relationshipId: string;
|
|
15
|
+
kind: "header" | "footer";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ParsedHeaderFooterDocument {
|
|
19
|
+
blocks: BlockNode[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ---- XML node types (inline, no external dep) ----
|
|
23
|
+
|
|
24
|
+
interface XmlElementNode {
|
|
25
|
+
type: "element";
|
|
26
|
+
name: string;
|
|
27
|
+
attributes: Record<string, string>;
|
|
28
|
+
children: XmlNode[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface XmlTextNode {
|
|
32
|
+
type: "text";
|
|
33
|
+
text: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type XmlNode = XmlElementNode | XmlTextNode;
|
|
37
|
+
|
|
38
|
+
// ---- Public API ----
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Scan a document body XML for w:headerReference / w:footerReference elements
|
|
42
|
+
* inside w:sectPr and return the relationship references.
|
|
43
|
+
*/
|
|
44
|
+
export function parseHeaderFooterReferences(
|
|
45
|
+
documentXml: string,
|
|
46
|
+
): ParsedHeaderFooterReference[] {
|
|
47
|
+
const root = parseXml(documentXml);
|
|
48
|
+
const documentElement = findChildElementOptional(root, "document");
|
|
49
|
+
if (!documentElement) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const bodyElement = findChildElementOptional(documentElement, "body");
|
|
54
|
+
if (!bodyElement) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const refs: ParsedHeaderFooterReference[] = [];
|
|
59
|
+
|
|
60
|
+
// Collect all sectPr elements (can appear in paragraph pPr and at body level)
|
|
61
|
+
collectSectPrReferences(bodyElement, refs);
|
|
62
|
+
|
|
63
|
+
return refs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse a headerN.xml part (<w:hdr> root) into block nodes.
|
|
68
|
+
*/
|
|
69
|
+
export function parseHeaderXml(xml: string): ParsedHeaderFooterDocument {
|
|
70
|
+
return parseHdrFtrXml(xml, "hdr");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse a footerN.xml part (<w:ftr> root) into block nodes.
|
|
75
|
+
*/
|
|
76
|
+
export function parseFooterXml(xml: string): ParsedHeaderFooterDocument {
|
|
77
|
+
return parseHdrFtrXml(xml, "ftr");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---- Internal helpers ----
|
|
81
|
+
|
|
82
|
+
function collectSectPrReferences(
|
|
83
|
+
element: XmlElementNode,
|
|
84
|
+
refs: ParsedHeaderFooterReference[],
|
|
85
|
+
): void {
|
|
86
|
+
for (const child of element.children) {
|
|
87
|
+
if (child.type !== "element") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const name = localName(child.name);
|
|
92
|
+
|
|
93
|
+
if (name === "sectPr") {
|
|
94
|
+
extractSectPrRefs(child, refs);
|
|
95
|
+
} else if (name === "p") {
|
|
96
|
+
// Check paragraph properties for sectPr
|
|
97
|
+
const pPr = findChildElementOptional(child, "pPr");
|
|
98
|
+
if (pPr) {
|
|
99
|
+
const sectPr = findChildElementOptional(pPr, "sectPr");
|
|
100
|
+
if (sectPr) {
|
|
101
|
+
extractSectPrRefs(sectPr, refs);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function extractSectPrRefs(
|
|
109
|
+
sectPr: XmlElementNode,
|
|
110
|
+
refs: ParsedHeaderFooterReference[],
|
|
111
|
+
): void {
|
|
112
|
+
for (const child of sectPr.children) {
|
|
113
|
+
if (child.type !== "element") {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const name = localName(child.name);
|
|
118
|
+
if (name === "headerReference" || name === "footerReference") {
|
|
119
|
+
const kind: "header" | "footer" = name === "headerReference" ? "header" : "footer";
|
|
120
|
+
const rawType =
|
|
121
|
+
child.attributes["w:type"] ?? child.attributes.type ?? "default";
|
|
122
|
+
const variant = toHeaderFooterVariant(rawType);
|
|
123
|
+
const relationshipId =
|
|
124
|
+
child.attributes["r:id"] ??
|
|
125
|
+
child.attributes["r:Id"] ??
|
|
126
|
+
child.attributes.id ??
|
|
127
|
+
child.attributes.Id ??
|
|
128
|
+
"";
|
|
129
|
+
|
|
130
|
+
if (relationshipId) {
|
|
131
|
+
// Avoid duplicates (multiple sectPr may reference same header)
|
|
132
|
+
const alreadyAdded = refs.some(
|
|
133
|
+
(ref) => ref.relationshipId === relationshipId && ref.kind === kind,
|
|
134
|
+
);
|
|
135
|
+
if (!alreadyAdded) {
|
|
136
|
+
refs.push({ variant, relationshipId, kind });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function toHeaderFooterVariant(raw: string): HeaderFooterVariant {
|
|
144
|
+
if (raw === "first") {
|
|
145
|
+
return "first";
|
|
146
|
+
}
|
|
147
|
+
if (raw === "even") {
|
|
148
|
+
return "even";
|
|
149
|
+
}
|
|
150
|
+
return "default";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseHdrFtrXml(
|
|
154
|
+
xml: string,
|
|
155
|
+
rootLocalName: "hdr" | "ftr",
|
|
156
|
+
): ParsedHeaderFooterDocument {
|
|
157
|
+
const root = parseXml(xml);
|
|
158
|
+
const hdrFtrElement = findChildElementOptional(root, rootLocalName);
|
|
159
|
+
if (!hdrFtrElement) {
|
|
160
|
+
return { blocks: [] };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const blocks: BlockNode[] = [];
|
|
164
|
+
|
|
165
|
+
for (const child of hdrFtrElement.children) {
|
|
166
|
+
if (child.type !== "element") {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const name = localName(child.name);
|
|
171
|
+
|
|
172
|
+
if (name === "p") {
|
|
173
|
+
blocks.push(parseParagraphElement(child));
|
|
174
|
+
} else if (name === "tbl") {
|
|
175
|
+
// Table in header/footer: store as opaque to preserve fidelity
|
|
176
|
+
blocks.push({
|
|
177
|
+
type: "opaque_block",
|
|
178
|
+
fragmentId: "fragment:hdrftr-tbl",
|
|
179
|
+
warningId: "warning:hdrftr-opaque-table",
|
|
180
|
+
});
|
|
181
|
+
} else {
|
|
182
|
+
// Other block-level elements: treat as opaque
|
|
183
|
+
blocks.push({
|
|
184
|
+
type: "opaque_block",
|
|
185
|
+
fragmentId: "fragment:hdrftr-opaque",
|
|
186
|
+
warningId: "warning:hdrftr-opaque-block",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { blocks };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseParagraphElement(pElement: XmlElementNode): ParagraphNode {
|
|
195
|
+
let styleId: string | undefined;
|
|
196
|
+
let alignment: ParagraphNode["alignment"];
|
|
197
|
+
const children: InlineNode[] = [];
|
|
198
|
+
|
|
199
|
+
for (const child of pElement.children) {
|
|
200
|
+
if (child.type !== "element") {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const name = localName(child.name);
|
|
205
|
+
|
|
206
|
+
if (name === "pPr") {
|
|
207
|
+
const pStyle = findChildElementOptional(child, "pStyle");
|
|
208
|
+
styleId = pStyle?.attributes["w:val"] ?? pStyle?.attributes.val;
|
|
209
|
+
const jc = findChildElementOptional(child, "jc");
|
|
210
|
+
const jcVal = jc?.attributes["w:val"] ?? jc?.attributes.val;
|
|
211
|
+
if (jcVal === "left" || jcVal === "center" || jcVal === "right" || jcVal === "both" || jcVal === "distribute") {
|
|
212
|
+
alignment = jcVal;
|
|
213
|
+
}
|
|
214
|
+
} else if (name === "r") {
|
|
215
|
+
children.push(...parseRunElement(child));
|
|
216
|
+
} else if (name === "hyperlink") {
|
|
217
|
+
// Simplified: collect text children from hyperlink runs
|
|
218
|
+
for (const hChild of child.children) {
|
|
219
|
+
if (hChild.type === "element" && localName(hChild.name) === "r") {
|
|
220
|
+
children.push(...parseRunElement(hChild));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
224
|
+
// Skip bookmark nodes in headers/footers
|
|
225
|
+
} else if (name === "fldChar" || name === "instrText") {
|
|
226
|
+
// Skip field chars, handled via run parsing
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
type: "paragraph",
|
|
232
|
+
...(styleId ? { styleId } : {}),
|
|
233
|
+
...(alignment ? { alignment } : {}),
|
|
234
|
+
children,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
239
|
+
const nodes: InlineNode[] = [];
|
|
240
|
+
const marks: TextMark[] = parseRunProperties(rElement);
|
|
241
|
+
|
|
242
|
+
for (const child of rElement.children) {
|
|
243
|
+
if (child.type !== "element") {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const name = localName(child.name);
|
|
248
|
+
|
|
249
|
+
if (name === "t") {
|
|
250
|
+
const text = extractTextContent(child);
|
|
251
|
+
if (text.length > 0) {
|
|
252
|
+
nodes.push({
|
|
253
|
+
type: "text",
|
|
254
|
+
text,
|
|
255
|
+
...(marks.length > 0 ? { marks } : {}),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} else if (name === "br") {
|
|
259
|
+
nodes.push({ type: "hard_break" });
|
|
260
|
+
} else if (name === "tab") {
|
|
261
|
+
nodes.push({ type: "tab" });
|
|
262
|
+
} else if (name === "footnoteReference") {
|
|
263
|
+
const noteId =
|
|
264
|
+
child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
265
|
+
if (noteId) {
|
|
266
|
+
const ref: FootnoteRefNode = {
|
|
267
|
+
type: "footnote_ref",
|
|
268
|
+
noteId,
|
|
269
|
+
noteKind: "footnote",
|
|
270
|
+
};
|
|
271
|
+
nodes.push(ref);
|
|
272
|
+
}
|
|
273
|
+
} else if (name === "endnoteReference") {
|
|
274
|
+
const noteId =
|
|
275
|
+
child.attributes["w:id"] ?? child.attributes.id ?? "";
|
|
276
|
+
if (noteId) {
|
|
277
|
+
const ref: FootnoteRefNode = {
|
|
278
|
+
type: "footnote_ref",
|
|
279
|
+
noteId,
|
|
280
|
+
noteKind: "endnote",
|
|
281
|
+
};
|
|
282
|
+
nodes.push(ref);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return nodes;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function parseRunProperties(rElement: XmlElementNode): TextMark[] {
|
|
291
|
+
const rPr = findChildElementOptional(rElement, "rPr");
|
|
292
|
+
if (!rPr) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const marks: TextMark[] = [];
|
|
297
|
+
|
|
298
|
+
for (const child of rPr.children) {
|
|
299
|
+
if (child.type !== "element") {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const name = localName(child.name);
|
|
304
|
+
const val = child.attributes["w:val"] ?? child.attributes.val ?? "true";
|
|
305
|
+
|
|
306
|
+
switch (name) {
|
|
307
|
+
case "b":
|
|
308
|
+
if (val !== "0" && val !== "false") {
|
|
309
|
+
marks.push({ type: "bold" });
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
case "i":
|
|
313
|
+
if (val !== "0" && val !== "false") {
|
|
314
|
+
marks.push({ type: "italic" });
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
case "u":
|
|
318
|
+
if (val !== "none" && val !== "0") {
|
|
319
|
+
marks.push({ type: "underline" });
|
|
320
|
+
}
|
|
321
|
+
break;
|
|
322
|
+
case "strike":
|
|
323
|
+
if (val !== "0" && val !== "false") {
|
|
324
|
+
marks.push({ type: "strikethrough" });
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
case "dstrike":
|
|
328
|
+
if (val !== "0" && val !== "false") {
|
|
329
|
+
marks.push({ type: "doubleStrikethrough" });
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return marks;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function extractTextContent(tElement: XmlElementNode): string {
|
|
339
|
+
let text = "";
|
|
340
|
+
for (const child of tElement.children) {
|
|
341
|
+
if (child.type === "text") {
|
|
342
|
+
text += child.text;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return text;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function findChildElementOptional(
|
|
349
|
+
node: XmlElementNode,
|
|
350
|
+
childLocalName: string,
|
|
351
|
+
): XmlElementNode | undefined {
|
|
352
|
+
return node.children.find(
|
|
353
|
+
(entry): entry is XmlElementNode =>
|
|
354
|
+
entry.type === "element" && localName(entry.name) === childLocalName,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function localName(name: string): string {
|
|
359
|
+
const separatorIndex = name.indexOf(":");
|
|
360
|
+
return separatorIndex >= 0 ? name.slice(separatorIndex + 1) : name;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ---- Minimal XML parser (same pattern as parse-numbering.ts) ----
|
|
364
|
+
|
|
365
|
+
function parseXml(xml: string): XmlElementNode {
|
|
366
|
+
const root: XmlElementNode = {
|
|
367
|
+
type: "element",
|
|
368
|
+
name: "__root__",
|
|
369
|
+
attributes: {},
|
|
370
|
+
children: [],
|
|
371
|
+
};
|
|
372
|
+
const stack: XmlElementNode[] = [root];
|
|
373
|
+
let cursor = 0;
|
|
374
|
+
|
|
375
|
+
while (cursor < xml.length) {
|
|
376
|
+
if (xml.startsWith("<!--", cursor)) {
|
|
377
|
+
const end = xml.indexOf("-->", cursor);
|
|
378
|
+
cursor = end >= 0 ? end + 3 : xml.length;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (xml.startsWith("<?", cursor)) {
|
|
383
|
+
const end = xml.indexOf("?>", cursor);
|
|
384
|
+
cursor = end >= 0 ? end + 2 : xml.length;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (xml.startsWith("<![CDATA[", cursor)) {
|
|
389
|
+
const end = xml.indexOf("]]>", cursor);
|
|
390
|
+
const textEnd = end >= 0 ? end : xml.length;
|
|
391
|
+
stack[stack.length - 1]?.children.push({
|
|
392
|
+
type: "text",
|
|
393
|
+
text: xml.slice(cursor + 9, textEnd),
|
|
394
|
+
});
|
|
395
|
+
cursor = end >= 0 ? end + 3 : xml.length;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (xml[cursor] !== "<") {
|
|
400
|
+
const nextTag = xml.indexOf("<", cursor);
|
|
401
|
+
const end = nextTag >= 0 ? nextTag : xml.length;
|
|
402
|
+
const text = decodeXmlEntities(xml.slice(cursor, end));
|
|
403
|
+
if (text.trim().length > 0 || (text.length > 0 && stack.length > 1)) {
|
|
404
|
+
stack[stack.length - 1]?.children.push({ type: "text", text });
|
|
405
|
+
}
|
|
406
|
+
cursor = end;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Closing tag
|
|
411
|
+
if (xml[cursor + 1] === "/") {
|
|
412
|
+
const end = xml.indexOf(">", cursor);
|
|
413
|
+
if (end < 0) {
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
stack.pop();
|
|
417
|
+
cursor = end + 1;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Open or self-closing tag
|
|
422
|
+
const tagEnd = xml.indexOf(">", cursor);
|
|
423
|
+
if (tagEnd < 0) {
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const tagContent = xml.slice(cursor + 1, tagEnd);
|
|
428
|
+
const selfClosing = tagContent.endsWith("/");
|
|
429
|
+
const normalized = selfClosing ? tagContent.slice(0, -1).trimEnd() : tagContent;
|
|
430
|
+
|
|
431
|
+
const spaceIndex = normalized.search(/\s/);
|
|
432
|
+
const tagName =
|
|
433
|
+
spaceIndex >= 0 ? normalized.slice(0, spaceIndex) : normalized;
|
|
434
|
+
const attrString =
|
|
435
|
+
spaceIndex >= 0 ? normalized.slice(spaceIndex + 1) : "";
|
|
436
|
+
const attributes = parseAttributes(attrString);
|
|
437
|
+
|
|
438
|
+
const element: XmlElementNode = {
|
|
439
|
+
type: "element",
|
|
440
|
+
name: tagName,
|
|
441
|
+
attributes,
|
|
442
|
+
children: [],
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
stack[stack.length - 1]?.children.push(element);
|
|
446
|
+
|
|
447
|
+
if (!selfClosing) {
|
|
448
|
+
stack.push(element);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
cursor = tagEnd + 1;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return root;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function parseAttributes(attrString: string): Record<string, string> {
|
|
458
|
+
const attrs: Record<string, string> = {};
|
|
459
|
+
const pattern = /([A-Za-z_:][A-Za-z0-9:._-]*)\s*=\s*("([^"]*)"|'([^']*)')/gu;
|
|
460
|
+
|
|
461
|
+
for (const match of attrString.matchAll(pattern)) {
|
|
462
|
+
const name = match[1];
|
|
463
|
+
const value = match[3] ?? match[4] ?? "";
|
|
464
|
+
if (name) {
|
|
465
|
+
attrs[name] = decodeXmlEntities(value);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return attrs;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function decodeXmlEntities(text: string): string {
|
|
473
|
+
return text
|
|
474
|
+
.replace(/&/g, "&")
|
|
475
|
+
.replace(/</g, "<")
|
|
476
|
+
.replace(/>/g, ">")
|
|
477
|
+
.replace(/"/g, '"')
|
|
478
|
+
.replace(/'/g, "'")
|
|
479
|
+
.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(Number.parseInt(dec, 10)))
|
|
480
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) =>
|
|
481
|
+
String.fromCodePoint(Number.parseInt(hex, 16)),
|
|
482
|
+
);
|
|
483
|
+
}
|