@beyondwork/docx-react-component 1.0.11 → 1.0.13
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 +8 -2
- package/package.json +35 -21
- package/src/api/public-types.ts +103 -1
- package/src/core/commands/formatting-commands.ts +742 -0
- package/src/core/commands/image-commands.ts +84 -2
- package/src/core/commands/structural-helpers.ts +309 -0
- package/src/core/commands/table-structure-commands.ts +721 -0
- package/src/core/commands/text-commands.ts +166 -1
- package/src/core/state/editor-state.ts +318 -9
- package/src/formats/xlsx/io/parse-sheet.ts +177 -7
- package/src/formats/xlsx/io/parse-styles.ts +2 -0
- package/src/formats/xlsx/io/xlsx-session.ts +18 -12
- package/src/formats/xlsx/model/sheet.ts +81 -1
- package/src/formats/xlsx/model/workbook.ts +10 -6
- package/src/io/docx-session.ts +392 -22
- package/src/io/export/export-session.ts +55 -0
- package/src/io/export/serialize-footnotes.ts +5 -20
- package/src/io/export/serialize-headers-footers.ts +5 -31
- package/src/io/export/serialize-main-document.ts +78 -5
- package/src/io/normalize/normalize-text.ts +90 -1
- package/src/io/ooxml/parse-footnotes.ts +68 -5
- package/src/io/ooxml/parse-headers-footers.ts +67 -9
- package/src/io/ooxml/parse-main-document.ts +169 -6
- package/src/io/opc/package-reader.ts +3 -3
- package/src/io/source-package-provenance.ts +241 -0
- package/src/model/canonical-document.ts +450 -2
- package/src/model/cds-1.0.0.ts +5 -2
- package/src/model/snapshot.ts +190 -19
- package/src/preservation/package-preservation.ts +0 -7
- package/src/runtime/document-runtime.ts +7 -1
- package/src/runtime/read-only-diagnostics-runtime.ts +1 -1
- package/src/runtime/surface-projection.ts +200 -17
- package/src/runtime/table-commands.ts +79 -0
- package/src/runtime/table-schema.ts +9 -0
- package/src/ui/WordReviewEditor.tsx +708 -16
- package/src/ui-tailwind/editor-surface/pm-schema.ts +121 -5
- package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +73 -7
- package/src/ui-tailwind/editor-surface/search-plugin.ts +76 -16
- package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +162 -14
- package/src/validation/compatibility-engine.ts +208 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
comparePartPaths,
|
|
8
8
|
getRelationshipsPartPath,
|
|
9
9
|
normalizePartPath,
|
|
10
|
+
resolveRelationshipTarget,
|
|
10
11
|
} from "../ooxml/part-manifest.ts";
|
|
11
12
|
import type { OpcPackage } from "../opc/package-reader.ts";
|
|
12
13
|
import { writeOpcPackage } from "../opc/package-writer.ts";
|
|
@@ -71,6 +72,43 @@ export class ExportSession {
|
|
|
71
72
|
return this;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
public ensurePackageRelationship(input: {
|
|
76
|
+
type: string;
|
|
77
|
+
target: string;
|
|
78
|
+
targetMode?: OpcRelationship["targetMode"];
|
|
79
|
+
preferredId?: string;
|
|
80
|
+
}): void {
|
|
81
|
+
const targetMode = input.targetMode ?? "internal";
|
|
82
|
+
const normalizedTarget =
|
|
83
|
+
targetMode === "internal" ? normalizePartPath(input.target) : input.target;
|
|
84
|
+
const existing = this.packageRelationships.find((relationship) => {
|
|
85
|
+
if (relationship.type !== input.type || relationship.targetMode !== targetMode) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return targetMode === "internal"
|
|
90
|
+
? resolveRelationshipTarget(null, relationship) === normalizedTarget
|
|
91
|
+
: relationship.target === normalizedTarget;
|
|
92
|
+
});
|
|
93
|
+
if (existing) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const relationshipId = createUniqueRelationshipId(
|
|
98
|
+
new Set(this.packageRelationships.map((relationship) => relationship.id)),
|
|
99
|
+
input.preferredId ?? "rIdPackage1",
|
|
100
|
+
);
|
|
101
|
+
this.packageRelationships.push({
|
|
102
|
+
id: relationshipId,
|
|
103
|
+
type: input.type,
|
|
104
|
+
target:
|
|
105
|
+
targetMode === "internal"
|
|
106
|
+
? toPackageRelationshipTarget(normalizedTarget)
|
|
107
|
+
: normalizedTarget,
|
|
108
|
+
targetMode,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
74
112
|
public toPackage(): OpcPackage {
|
|
75
113
|
reattachPreservedParts(
|
|
76
114
|
this.sourcePackage,
|
|
@@ -162,4 +200,21 @@ function cloneRelationship(relationship: OpcRelationship): OpcRelationship {
|
|
|
162
200
|
return { ...relationship };
|
|
163
201
|
}
|
|
164
202
|
|
|
203
|
+
function createUniqueRelationshipId(existingIds: ReadonlySet<string>, preferredId: string): string {
|
|
204
|
+
if (!existingIds.has(preferredId)) {
|
|
205
|
+
return preferredId;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let nextIndex = 2;
|
|
209
|
+
while (existingIds.has(`${preferredId}-${nextIndex}`)) {
|
|
210
|
+
nextIndex += 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return `${preferredId}-${nextIndex}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function toPackageRelationshipTarget(partPath: string): string {
|
|
217
|
+
return normalizePartPath(partPath).slice(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
165
220
|
export { buildManifest };
|
|
@@ -71,8 +71,7 @@ function serializeNoteDefinition(
|
|
|
71
71
|
if (block.type === "paragraph") {
|
|
72
72
|
return serializeParagraph(block);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
return `<w:p/>`;
|
|
74
|
+
throw new Error(`Cannot safely serialize ${block.type} content in note sub-parts.`);
|
|
76
75
|
})
|
|
77
76
|
.join("");
|
|
78
77
|
|
|
@@ -135,24 +134,10 @@ function serializeInlineNode(node: InlineNode): string {
|
|
|
135
134
|
return `<w:r><w:rPr><w:rStyle w:val="${styleVal}"/></w:rPr>${refElement}</w:r>`;
|
|
136
135
|
}
|
|
137
136
|
case "opaque_inline":
|
|
138
|
-
|
|
139
|
-
case "hyperlink":
|
|
140
|
-
return node.children
|
|
141
|
-
.map((child) => {
|
|
142
|
-
if (child.type === "text") {
|
|
143
|
-
const preserve = requiresPreservedSpace(child.text)
|
|
144
|
-
? ` xml:space="preserve"`
|
|
145
|
-
: "";
|
|
146
|
-
return `<w:r><w:t${preserve}>${escapeXml(child.text)}</w:t></w:r>`;
|
|
147
|
-
}
|
|
148
|
-
if (child.type === "tab") return "<w:r><w:tab/></w:r>";
|
|
149
|
-
if (child.type === "hard_break") return "<w:r><w:br/></w:r>";
|
|
150
|
-
return "";
|
|
151
|
-
})
|
|
152
|
-
.join("");
|
|
153
|
-
}
|
|
137
|
+
throw new Error(`Cannot safely serialize ${node.type} content in note sub-parts.`);
|
|
138
|
+
case "hyperlink":
|
|
154
139
|
default:
|
|
155
|
-
|
|
140
|
+
throw new Error(`Cannot safely serialize ${node.type} content in note sub-parts.`);
|
|
156
141
|
}
|
|
157
142
|
}
|
|
158
143
|
|
|
@@ -180,7 +165,7 @@ function buildRunPropertiesXml(marks: TextMark[] | undefined): string {
|
|
|
180
165
|
parts.push("<w:dstrike/>");
|
|
181
166
|
break;
|
|
182
167
|
default:
|
|
183
|
-
|
|
168
|
+
throw new Error(`Cannot safely serialize ${mark.type} marks in note sub-parts.`);
|
|
184
169
|
}
|
|
185
170
|
}
|
|
186
171
|
|
|
@@ -51,8 +51,7 @@ function serializeBlocks(
|
|
|
51
51
|
if (block.type === "paragraph") {
|
|
52
52
|
return serializeParagraph(block);
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
return `<w:p/>`;
|
|
54
|
+
throw new Error(`Cannot safely serialize ${block.type} content in header/footer sub-parts.`);
|
|
56
55
|
})
|
|
57
56
|
.join("");
|
|
58
57
|
}
|
|
@@ -108,32 +107,8 @@ function serializeInlineNode(node: InlineNode): string {
|
|
|
108
107
|
return `<w:r><w:rPr><w:rStyle w:val="${node.noteKind === "footnote" ? "FootnoteReference" : "EndnoteReference"}"/></w:rPr>${refElement}</w:r>`;
|
|
109
108
|
}
|
|
110
109
|
case "opaque_inline":
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
case "hyperlink": {
|
|
114
|
-
const childrenXml = node.children
|
|
115
|
-
.map((child) => {
|
|
116
|
-
switch (child.type) {
|
|
117
|
-
case "text": {
|
|
118
|
-
const properties = buildRunPropertiesXml(undefined);
|
|
119
|
-
const preserve = requiresPreservedSpace(child.text)
|
|
120
|
-
? ` xml:space="preserve"`
|
|
121
|
-
: "";
|
|
122
|
-
return `<w:r>${properties}<w:t${preserve}>${escapeXml(child.text)}</w:t></w:r>`;
|
|
123
|
-
}
|
|
124
|
-
case "tab":
|
|
125
|
-
return "<w:r><w:tab/></w:r>";
|
|
126
|
-
case "hard_break":
|
|
127
|
-
return "<w:r><w:br/></w:r>";
|
|
128
|
-
default:
|
|
129
|
-
return "";
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
.join("");
|
|
133
|
-
// Hyperlinks in headers/footers typically use bookmark anchors or external URLs.
|
|
134
|
-
// Emit as a plain run since we don't retain the relationship ID here.
|
|
135
|
-
return childrenXml;
|
|
136
|
-
}
|
|
110
|
+
throw new Error(`Cannot safely serialize ${node.type} content in header/footer sub-parts.`);
|
|
111
|
+
case "hyperlink":
|
|
137
112
|
case "image":
|
|
138
113
|
case "field":
|
|
139
114
|
case "bookmark_start":
|
|
@@ -146,7 +121,7 @@ function serializeInlineNode(node: InlineNode): string {
|
|
|
146
121
|
case "wordart":
|
|
147
122
|
case "vml_shape":
|
|
148
123
|
default:
|
|
149
|
-
|
|
124
|
+
throw new Error(`Cannot safely serialize ${node.type} content in header/footer sub-parts.`);
|
|
150
125
|
}
|
|
151
126
|
}
|
|
152
127
|
|
|
@@ -174,8 +149,7 @@ function buildRunPropertiesXml(marks: TextMark[] | undefined): string {
|
|
|
174
149
|
parts.push("<w:dstrike/>");
|
|
175
150
|
break;
|
|
176
151
|
default:
|
|
177
|
-
|
|
178
|
-
break;
|
|
152
|
+
throw new Error(`Cannot safely serialize ${mark.type} marks in header/footer sub-parts.`);
|
|
179
153
|
}
|
|
180
154
|
}
|
|
181
155
|
|
|
@@ -375,9 +375,25 @@ function serializeTableInlineNode(
|
|
|
375
375
|
return `${hyperlinkOpen}${childrenXml}</w:hyperlink>`;
|
|
376
376
|
}
|
|
377
377
|
case "field":
|
|
378
|
+
return `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}"/>`;
|
|
378
379
|
case "bookmark_start":
|
|
380
|
+
return (
|
|
381
|
+
`<w:bookmarkStart w:id="${escapeAttribute(node.bookmarkId)}"` +
|
|
382
|
+
` w:name="${escapeAttribute(node.name)}"/>`
|
|
383
|
+
);
|
|
379
384
|
case "bookmark_end":
|
|
380
|
-
|
|
385
|
+
return `<w:bookmarkEnd w:id="${escapeAttribute(node.bookmarkId)}"/>`;
|
|
386
|
+
case "footnote_ref": {
|
|
387
|
+
const refElement =
|
|
388
|
+
node.noteKind === "footnote"
|
|
389
|
+
? `<w:footnoteReference w:id="${escapeAttribute(node.noteId)}"/>`
|
|
390
|
+
: `<w:endnoteReference w:id="${escapeAttribute(node.noteId)}"/>`;
|
|
391
|
+
const styleVal =
|
|
392
|
+
node.noteKind === "footnote"
|
|
393
|
+
? "FootnoteReference"
|
|
394
|
+
: "EndnoteReference";
|
|
395
|
+
return `<w:r><w:rPr><w:rStyle w:val="${styleVal}"/></w:rPr>${refElement}</w:r>`;
|
|
396
|
+
}
|
|
381
397
|
default:
|
|
382
398
|
return "";
|
|
383
399
|
}
|
|
@@ -787,10 +803,52 @@ function serializeInlineNode(
|
|
|
787
803
|
boundaries,
|
|
788
804
|
};
|
|
789
805
|
}
|
|
790
|
-
case "field":
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
806
|
+
case "field": {
|
|
807
|
+
const xml = `<w:fldSimple w:instr="${escapeAttribute(node.instruction)}"/>`;
|
|
808
|
+
const boundaries = new Map<number, number>();
|
|
809
|
+
boundaries.set(cursor, xmlOffset);
|
|
810
|
+
boundaries.set(cursor + 1, xmlOffset + xml.length);
|
|
811
|
+
return {
|
|
812
|
+
xml,
|
|
813
|
+
cursor: cursor + 1,
|
|
814
|
+
boundaries,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
case "bookmark_start": {
|
|
818
|
+
const xml =
|
|
819
|
+
`<w:bookmarkStart w:id="${escapeAttribute(node.bookmarkId)}"` +
|
|
820
|
+
` w:name="${escapeAttribute(node.name)}"/>`;
|
|
821
|
+
const boundaries = new Map<number, number>();
|
|
822
|
+
boundaries.set(cursor, xmlOffset);
|
|
823
|
+
boundaries.set(cursor, xmlOffset + xml.length);
|
|
824
|
+
return { xml, cursor, boundaries };
|
|
825
|
+
}
|
|
826
|
+
case "bookmark_end": {
|
|
827
|
+
const xml = `<w:bookmarkEnd w:id="${escapeAttribute(node.bookmarkId)}"/>`;
|
|
828
|
+
const boundaries = new Map<number, number>();
|
|
829
|
+
boundaries.set(cursor, xmlOffset);
|
|
830
|
+
boundaries.set(cursor, xmlOffset + xml.length);
|
|
831
|
+
return { xml, cursor, boundaries };
|
|
832
|
+
}
|
|
833
|
+
case "footnote_ref": {
|
|
834
|
+
const refElement =
|
|
835
|
+
node.noteKind === "footnote"
|
|
836
|
+
? `<w:footnoteReference w:id="${escapeAttribute(node.noteId)}"/>`
|
|
837
|
+
: `<w:endnoteReference w:id="${escapeAttribute(node.noteId)}"/>`;
|
|
838
|
+
const styleVal =
|
|
839
|
+
node.noteKind === "footnote"
|
|
840
|
+
? "FootnoteReference"
|
|
841
|
+
: "EndnoteReference";
|
|
842
|
+
const xml = `<w:r><w:rPr><w:rStyle w:val="${styleVal}"/></w:rPr>${refElement}</w:r>`;
|
|
843
|
+
const boundaries = new Map<number, number>();
|
|
844
|
+
boundaries.set(cursor, xmlOffset);
|
|
845
|
+
boundaries.set(cursor + 1, xmlOffset + xml.length);
|
|
846
|
+
return {
|
|
847
|
+
xml,
|
|
848
|
+
cursor: cursor + 1,
|
|
849
|
+
boundaries,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
794
852
|
default: {
|
|
795
853
|
const boundaries = new Map<number, number>();
|
|
796
854
|
boundaries.set(cursor, xmlOffset);
|
|
@@ -882,6 +940,21 @@ function serializeRunProperties(marks: TextMark[] | undefined): string {
|
|
|
882
940
|
case "textFill":
|
|
883
941
|
markParts.push(mark.xml);
|
|
884
942
|
break;
|
|
943
|
+
case "fontFamily":
|
|
944
|
+
markParts.push(`<w:rFonts w:ascii="${escapeAttribute(mark.val)}" w:hAnsi="${escapeAttribute(mark.val)}"/>`);
|
|
945
|
+
break;
|
|
946
|
+
case "fontSize":
|
|
947
|
+
markParts.push(`<w:sz w:val="${mark.val}"/>`);
|
|
948
|
+
break;
|
|
949
|
+
case "textColor":
|
|
950
|
+
markParts.push(`<w:color w:val="${escapeAttribute(mark.color)}"/>`);
|
|
951
|
+
break;
|
|
952
|
+
case "smallCaps":
|
|
953
|
+
markParts.push("<w:smallCaps/>");
|
|
954
|
+
break;
|
|
955
|
+
case "allCaps":
|
|
956
|
+
markParts.push("<w:caps/>");
|
|
957
|
+
break;
|
|
885
958
|
}
|
|
886
959
|
}
|
|
887
960
|
|
|
@@ -147,6 +147,9 @@ function normalizeParagraph(
|
|
|
147
147
|
...(paragraph.keepLines ? { keepLines: paragraph.keepLines } : {}),
|
|
148
148
|
...(paragraph.outlineLevel !== undefined ? { outlineLevel: paragraph.outlineLevel } : {}),
|
|
149
149
|
...(paragraph.pageBreakBefore ? { pageBreakBefore: paragraph.pageBreakBefore } : {}),
|
|
150
|
+
...(paragraph.bidi ? { bidi: paragraph.bidi } : {}),
|
|
151
|
+
...(paragraph.borders ? { borders: paragraph.borders } : {}),
|
|
152
|
+
...(paragraph.shading ? { shading: paragraph.shading } : {}),
|
|
150
153
|
children,
|
|
151
154
|
};
|
|
152
155
|
}
|
|
@@ -287,6 +290,84 @@ function normalizeInlineChildren(
|
|
|
287
290
|
state.cursor += 1;
|
|
288
291
|
break;
|
|
289
292
|
}
|
|
293
|
+
case "symbol":
|
|
294
|
+
normalized.push({
|
|
295
|
+
type: "symbol",
|
|
296
|
+
char: node.char,
|
|
297
|
+
font: node.font,
|
|
298
|
+
...(node.marks && node.marks.length > 0 ? { marks: node.marks } : {}),
|
|
299
|
+
});
|
|
300
|
+
state.cursor += 1;
|
|
301
|
+
break;
|
|
302
|
+
case "column_break":
|
|
303
|
+
normalized.push({ type: "column_break" });
|
|
304
|
+
state.cursor += 1;
|
|
305
|
+
break;
|
|
306
|
+
case "chart_preview":
|
|
307
|
+
normalized.push({
|
|
308
|
+
type: "chart_preview",
|
|
309
|
+
...(node.previewMediaId ? { previewMediaId: node.previewMediaId } : {}),
|
|
310
|
+
rawXml: node.rawXml,
|
|
311
|
+
});
|
|
312
|
+
state.cursor += 1;
|
|
313
|
+
break;
|
|
314
|
+
case "smartart_preview":
|
|
315
|
+
normalized.push({
|
|
316
|
+
type: "smartart_preview",
|
|
317
|
+
...(node.previewMediaId ? { previewMediaId: node.previewMediaId } : {}),
|
|
318
|
+
rawXml: node.rawXml,
|
|
319
|
+
});
|
|
320
|
+
state.cursor += 1;
|
|
321
|
+
break;
|
|
322
|
+
case "shape":
|
|
323
|
+
normalized.push({
|
|
324
|
+
type: "shape",
|
|
325
|
+
...(node.text ? { text: node.text } : {}),
|
|
326
|
+
...(node.geometry ? { geometry: node.geometry } : {}),
|
|
327
|
+
rawXml: node.rawXml,
|
|
328
|
+
});
|
|
329
|
+
state.cursor += 1;
|
|
330
|
+
break;
|
|
331
|
+
case "wordart":
|
|
332
|
+
normalized.push({
|
|
333
|
+
type: "wordart",
|
|
334
|
+
text: node.text,
|
|
335
|
+
...(node.geometry ? { geometry: node.geometry } : {}),
|
|
336
|
+
rawXml: node.rawXml,
|
|
337
|
+
});
|
|
338
|
+
state.cursor += 1;
|
|
339
|
+
break;
|
|
340
|
+
case "vml_shape":
|
|
341
|
+
normalized.push({
|
|
342
|
+
type: "vml_shape",
|
|
343
|
+
...(node.text ? { text: node.text } : {}),
|
|
344
|
+
...(node.shapeType ? { shapeType: node.shapeType } : {}),
|
|
345
|
+
rawXml: node.rawXml,
|
|
346
|
+
});
|
|
347
|
+
state.cursor += 1;
|
|
348
|
+
break;
|
|
349
|
+
case "bookmark_start":
|
|
350
|
+
normalized.push({
|
|
351
|
+
type: "bookmark_start",
|
|
352
|
+
bookmarkId: node.bookmarkId,
|
|
353
|
+
name: node.name,
|
|
354
|
+
});
|
|
355
|
+
break;
|
|
356
|
+
case "bookmark_end":
|
|
357
|
+
normalized.push({
|
|
358
|
+
type: "bookmark_end",
|
|
359
|
+
bookmarkId: node.bookmarkId,
|
|
360
|
+
});
|
|
361
|
+
break;
|
|
362
|
+
case "field":
|
|
363
|
+
normalized.push({
|
|
364
|
+
type: "field",
|
|
365
|
+
fieldType: node.fieldType,
|
|
366
|
+
instruction: node.instruction,
|
|
367
|
+
children: [],
|
|
368
|
+
});
|
|
369
|
+
state.cursor += 1;
|
|
370
|
+
break;
|
|
290
371
|
}
|
|
291
372
|
}
|
|
292
373
|
|
|
@@ -383,7 +464,15 @@ function sameMarks(left: TextMark[] | undefined, right: TextMark[] | undefined):
|
|
|
383
464
|
}
|
|
384
465
|
|
|
385
466
|
function normalizeMarks(marks: TextMark[] | undefined): string[] {
|
|
386
|
-
return [...(marks ?? []).map(
|
|
467
|
+
return [...(marks ?? []).map(serializeMark)].sort();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function serializeMark(mark: TextMark): string {
|
|
471
|
+
return JSON.stringify(
|
|
472
|
+
Object.fromEntries(
|
|
473
|
+
Object.entries(mark).sort(([left], [right]) => left.localeCompare(right)),
|
|
474
|
+
),
|
|
475
|
+
);
|
|
387
476
|
}
|
|
388
477
|
|
|
389
478
|
function recordOpaqueFragment(
|
|
@@ -171,11 +171,11 @@ function parseParagraphElement(pElement: XmlElementNode): ParagraphNode {
|
|
|
171
171
|
} else if (name === "r") {
|
|
172
172
|
children.push(...parseRunElement(child));
|
|
173
173
|
} else if (name === "hyperlink") {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
children.push(parseHyperlinkElement(child));
|
|
175
|
+
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
176
|
+
children.push(parseBookmarkElement(child));
|
|
177
|
+
} else if (name === "fldSimple") {
|
|
178
|
+
children.push(parseFieldElement(child));
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -224,12 +224,75 @@ function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
|
224
224
|
if (noteId) {
|
|
225
225
|
nodes.push({ type: "footnote_ref", noteId, noteKind: "endnote" });
|
|
226
226
|
}
|
|
227
|
+
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
228
|
+
nodes.push(parseBookmarkElement(child));
|
|
229
|
+
} else if (name === "fldChar" || name === "instrText") {
|
|
230
|
+
nodes.push(parseFieldElement(child));
|
|
227
231
|
}
|
|
228
232
|
}
|
|
229
233
|
|
|
230
234
|
return nodes;
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
function parseHyperlinkElement(element: XmlElementNode): Extract<InlineNode, { type: "hyperlink" }> {
|
|
238
|
+
const href = element.attributes["w:anchor"]
|
|
239
|
+
? `#${element.attributes["w:anchor"]}`
|
|
240
|
+
: element.attributes["r:id"] ?? "relationship:unknown";
|
|
241
|
+
const children: Array<Extract<InlineNode, { type: "text" | "hard_break" | "tab" }>> = [];
|
|
242
|
+
|
|
243
|
+
for (const child of element.children) {
|
|
244
|
+
if (child.type === "element" && localName(child.name) === "r") {
|
|
245
|
+
for (const runChild of parseRunElement(child)) {
|
|
246
|
+
if (runChild.type === "text" || runChild.type === "hard_break" || runChild.type === "tab") {
|
|
247
|
+
children.push(runChild);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
type: "hyperlink",
|
|
255
|
+
href,
|
|
256
|
+
children,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function parseBookmarkElement(
|
|
261
|
+
element: XmlElementNode,
|
|
262
|
+
): Extract<InlineNode, { type: "bookmark_start" | "bookmark_end" }> {
|
|
263
|
+
const bookmarkId = element.attributes["w:id"] ?? element.attributes.id ?? "0";
|
|
264
|
+
if (localName(element.name) === "bookmarkStart") {
|
|
265
|
+
return {
|
|
266
|
+
type: "bookmark_start",
|
|
267
|
+
bookmarkId,
|
|
268
|
+
name: element.attributes["w:name"] ?? element.attributes.name ?? "",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
type: "bookmark_end",
|
|
274
|
+
bookmarkId,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function parseFieldElement(element: XmlElementNode): Extract<InlineNode, { type: "field" }> {
|
|
279
|
+
const rawFieldType =
|
|
280
|
+
element.attributes["w:fldCharType"] ??
|
|
281
|
+
element.attributes.fldCharType ??
|
|
282
|
+
localName(element.name);
|
|
283
|
+
const fieldType: "simple" | "complex" = rawFieldType === "complex" ? "complex" : "simple";
|
|
284
|
+
const instruction =
|
|
285
|
+
element.attributes["w:instr"] ??
|
|
286
|
+
element.attributes.instr ??
|
|
287
|
+
extractTextContent(element);
|
|
288
|
+
return {
|
|
289
|
+
type: "field",
|
|
290
|
+
fieldType,
|
|
291
|
+
instruction,
|
|
292
|
+
children: [],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
233
296
|
function parseRunProperties(rElement: XmlElementNode): TextMark[] {
|
|
234
297
|
const rPr = findChildElementOptional(rElement, "rPr");
|
|
235
298
|
if (!rPr) {
|
|
@@ -214,16 +214,11 @@ function parseParagraphElement(pElement: XmlElementNode): ParagraphNode {
|
|
|
214
214
|
} else if (name === "r") {
|
|
215
215
|
children.push(...parseRunElement(child));
|
|
216
216
|
} else if (name === "hyperlink") {
|
|
217
|
-
|
|
218
|
-
for (const hChild of child.children) {
|
|
219
|
-
if (hChild.type === "element" && localName(hChild.name) === "r") {
|
|
220
|
-
children.push(...parseRunElement(hChild));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
217
|
+
children.push(parseHyperlinkElement(child));
|
|
223
218
|
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
224
|
-
|
|
225
|
-
} else if (name === "
|
|
226
|
-
|
|
219
|
+
children.push(parseBookmarkElement(child));
|
|
220
|
+
} else if (name === "fldSimple") {
|
|
221
|
+
children.push(parseFieldElement(child));
|
|
227
222
|
}
|
|
228
223
|
}
|
|
229
224
|
|
|
@@ -281,12 +276,75 @@ function parseRunElement(rElement: XmlElementNode): InlineNode[] {
|
|
|
281
276
|
};
|
|
282
277
|
nodes.push(ref);
|
|
283
278
|
}
|
|
279
|
+
} else if (name === "bookmarkStart" || name === "bookmarkEnd") {
|
|
280
|
+
nodes.push(parseBookmarkElement(child));
|
|
281
|
+
} else if (name === "fldChar" || name === "instrText") {
|
|
282
|
+
nodes.push(parseFieldElement(child));
|
|
284
283
|
}
|
|
285
284
|
}
|
|
286
285
|
|
|
287
286
|
return nodes;
|
|
288
287
|
}
|
|
289
288
|
|
|
289
|
+
function parseHyperlinkElement(element: XmlElementNode): Extract<InlineNode, { type: "hyperlink" }> {
|
|
290
|
+
const href = element.attributes["w:anchor"]
|
|
291
|
+
? `#${element.attributes["w:anchor"]}`
|
|
292
|
+
: element.attributes["r:id"] ?? "relationship:unknown";
|
|
293
|
+
const children: Array<Extract<InlineNode, { type: "text" | "hard_break" | "tab" }>> = [];
|
|
294
|
+
|
|
295
|
+
for (const child of element.children) {
|
|
296
|
+
if (child.type === "element" && localName(child.name) === "r") {
|
|
297
|
+
for (const runChild of parseRunElement(child)) {
|
|
298
|
+
if (runChild.type === "text" || runChild.type === "hard_break" || runChild.type === "tab") {
|
|
299
|
+
children.push(runChild);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
type: "hyperlink",
|
|
307
|
+
href,
|
|
308
|
+
children,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function parseBookmarkElement(
|
|
313
|
+
element: XmlElementNode,
|
|
314
|
+
): Extract<InlineNode, { type: "bookmark_start" | "bookmark_end" }> {
|
|
315
|
+
const bookmarkId = element.attributes["w:id"] ?? element.attributes.id ?? "0";
|
|
316
|
+
if (localName(element.name) === "bookmarkStart") {
|
|
317
|
+
return {
|
|
318
|
+
type: "bookmark_start",
|
|
319
|
+
bookmarkId,
|
|
320
|
+
name: element.attributes["w:name"] ?? element.attributes.name ?? "",
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
type: "bookmark_end",
|
|
326
|
+
bookmarkId,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function parseFieldElement(element: XmlElementNode): Extract<InlineNode, { type: "field" }> {
|
|
331
|
+
const rawFieldType =
|
|
332
|
+
element.attributes["w:fldCharType"] ??
|
|
333
|
+
element.attributes.fldCharType ??
|
|
334
|
+
localName(element.name);
|
|
335
|
+
const fieldType: "simple" | "complex" = rawFieldType === "complex" ? "complex" : "simple";
|
|
336
|
+
const instruction =
|
|
337
|
+
element.attributes["w:instr"] ??
|
|
338
|
+
element.attributes.instr ??
|
|
339
|
+
extractTextContent(element);
|
|
340
|
+
return {
|
|
341
|
+
type: "field",
|
|
342
|
+
fieldType,
|
|
343
|
+
instruction,
|
|
344
|
+
children: [],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
290
348
|
function parseRunProperties(rElement: XmlElementNode): TextMark[] {
|
|
291
349
|
const rPr = findChildElementOptional(rElement, "rPr");
|
|
292
350
|
if (!rPr) {
|