web-doc 0.6.2 → 0.8.0
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/THIRD_PARTY_NOTICES.md +33 -1
- package/dist/adapters/docx-images.d.ts +13 -4
- package/dist/adapters/docx-images.js +3 -0
- package/dist/adapters/docx-paragraphs.d.ts +65 -0
- package/dist/adapters/docx-paragraphs.js +165 -0
- package/dist/adapters/docx-prepass.d.ts +17 -0
- package/dist/adapters/docx-prepass.js +230 -0
- package/dist/adapters/office.d.ts +31 -5
- package/dist/adapters/office.js +60 -29
- package/dist/adapters/pdf.d.ts +9 -0
- package/dist/adapters/pdf.js +10 -0
- package/dist/assets/pdfium/pdfium.wasm +0 -0
- package/dist/contracts.d.ts +53 -2
- package/dist/edit/ai/outline.d.ts +47 -0
- package/dist/edit/ai/outline.js +338 -0
- package/dist/edit/ai/targets.d.ts +16 -0
- package/dist/edit/ai/targets.js +309 -0
- package/dist/edit/ai/tools.d.ts +28 -0
- package/dist/edit/ai/tools.js +605 -0
- package/dist/edit/ai/types.d.ts +175 -0
- package/dist/edit/ai/types.js +1 -0
- package/dist/edit/assets.d.ts +23 -0
- package/dist/edit/assets.js +75 -0
- package/dist/edit/docx/elements.d.ts +6 -0
- package/dist/edit/docx/elements.js +90 -0
- package/dist/edit/docx/engine.d.ts +57 -0
- package/dist/edit/docx/engine.js +547 -0
- package/dist/edit/docx/handlers.d.ts +3 -0
- package/dist/edit/docx/handlers.js +15 -0
- package/dist/edit/docx/ids.d.ts +45 -0
- package/dist/edit/docx/ids.js +101 -0
- package/dist/edit/docx/model.d.ts +94 -0
- package/dist/edit/docx/model.js +350 -0
- package/dist/edit/docx/operations.d.ts +47 -0
- package/dist/edit/docx/operations.js +3 -0
- package/dist/edit/docx/provider.d.ts +16 -0
- package/dist/edit/docx/provider.js +37 -0
- package/dist/edit/docx/schemas.d.ts +6 -0
- package/dist/edit/docx/schemas.js +192 -0
- package/dist/edit/docx/session.d.ts +48 -0
- package/dist/edit/docx/session.js +467 -0
- package/dist/edit/docx/structure-ops.d.ts +6 -0
- package/dist/edit/docx/structure-ops.js +529 -0
- package/dist/edit/docx/style.d.ts +45 -0
- package/dist/edit/docx/style.js +375 -0
- package/dist/edit/docx/table-ops.d.ts +4 -0
- package/dist/edit/docx/table-ops.js +241 -0
- package/dist/edit/docx/text-ops.d.ts +40 -0
- package/dist/edit/docx/text-ops.js +491 -0
- package/dist/edit/docx/text.d.ts +34 -0
- package/dist/edit/docx/text.js +284 -0
- package/dist/edit/docx/tracked.d.ts +52 -0
- package/dist/edit/docx/tracked.js +347 -0
- package/dist/edit/docx/types.d.ts +206 -0
- package/dist/edit/docx/types.js +1 -0
- package/dist/edit/docx/write.d.ts +64 -0
- package/dist/edit/docx/write.js +375 -0
- package/dist/edit/engine.d.ts +128 -0
- package/dist/edit/engine.js +1 -0
- package/dist/edit/history.d.ts +77 -0
- package/dist/edit/history.js +123 -0
- package/dist/edit/ooxml/names.d.ts +20 -0
- package/dist/edit/ooxml/names.js +61 -0
- package/dist/edit/ooxml/opc.d.ts +50 -0
- package/dist/edit/ooxml/opc.js +150 -0
- package/dist/edit/ooxml/package.d.ts +82 -0
- package/dist/edit/ooxml/package.js +233 -0
- package/dist/edit/ooxml/patch.d.ts +51 -0
- package/dist/edit/ooxml/patch.js +250 -0
- package/dist/edit/ooxml/transaction.d.ts +39 -0
- package/dist/edit/ooxml/transaction.js +316 -0
- package/dist/edit/ooxml/worker.d.ts +8 -0
- package/dist/edit/ooxml/worker.js +12 -0
- package/dist/edit/ooxml/writer.d.ts +21 -0
- package/dist/edit/ooxml/writer.js +187 -0
- package/dist/edit/ooxml/xml.d.ts +74 -0
- package/dist/edit/ooxml/xml.js +451 -0
- package/dist/edit/ooxml/zip.d.ts +54 -0
- package/dist/edit/ooxml/zip.js +280 -0
- package/dist/edit/operations.d.ts +19 -0
- package/dist/edit/operations.js +137 -0
- package/dist/edit/pdf/engine/compact.d.ts +6 -0
- package/dist/edit/pdf/engine/compact.js +442 -0
- package/dist/edit/pdf/engine/document.d.ts +95 -0
- package/dist/edit/pdf/engine/document.js +868 -0
- package/dist/edit/pdf/engine/elements.d.ts +45 -0
- package/dist/edit/pdf/engine/elements.js +313 -0
- package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
- package/dist/edit/pdf/engine/existing-text.js +424 -0
- package/dist/edit/pdf/engine/fonts.d.ts +78 -0
- package/dist/edit/pdf/engine/fonts.js +466 -0
- package/dist/edit/pdf/engine/geometry.d.ts +36 -0
- package/dist/edit/pdf/engine/geometry.js +97 -0
- package/dist/edit/pdf/engine/handler.d.ts +21 -0
- package/dist/edit/pdf/engine/handler.js +108 -0
- package/dist/edit/pdf/engine/images.d.ts +33 -0
- package/dist/edit/pdf/engine/images.js +188 -0
- package/dist/edit/pdf/engine/layout.d.ts +23 -0
- package/dist/edit/pdf/engine/layout.js +177 -0
- package/dist/edit/pdf/engine/operations.d.ts +67 -0
- package/dist/edit/pdf/engine/operations.js +3 -0
- package/dist/edit/pdf/engine/pages.d.ts +6 -0
- package/dist/edit/pdf/engine/pages.js +98 -0
- package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
- package/dist/edit/pdf/engine/pdfium.js +249 -0
- package/dist/edit/pdf/engine/shapes.d.ts +4 -0
- package/dist/edit/pdf/engine/shapes.js +183 -0
- package/dist/edit/pdf/engine/tables.d.ts +44 -0
- package/dist/edit/pdf/engine/tables.js +315 -0
- package/dist/edit/pdf/engine/text-box.d.ts +71 -0
- package/dist/edit/pdf/engine/text-box.js +317 -0
- package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
- package/dist/edit/pdf/engine/text-layout.js +67 -0
- package/dist/edit/pdf/engine/transform.d.ts +5 -0
- package/dist/edit/pdf/engine/transform.js +137 -0
- package/dist/edit/pdf/provider.d.ts +35 -0
- package/dist/edit/pdf/provider.js +92 -0
- package/dist/edit/pdf/range-map.d.ts +16 -0
- package/dist/edit/pdf/range-map.js +107 -0
- package/dist/edit/pdf/schemas.d.ts +11 -0
- package/dist/edit/pdf/schemas.js +291 -0
- package/dist/edit/pdf/selection.d.ts +5 -0
- package/dist/edit/pdf/selection.js +152 -0
- package/dist/edit/pdf/session.d.ts +63 -0
- package/dist/edit/pdf/session.js +312 -0
- package/dist/edit/pdf/types.d.ts +336 -0
- package/dist/edit/pdf/types.js +1 -0
- package/dist/edit/pptx/elements.d.ts +74 -0
- package/dist/edit/pptx/elements.js +301 -0
- package/dist/edit/pptx/engine.d.ts +37 -0
- package/dist/edit/pptx/engine.js +466 -0
- package/dist/edit/pptx/geometry.d.ts +72 -0
- package/dist/edit/pptx/geometry.js +193 -0
- package/dist/edit/pptx/handler.d.ts +7 -0
- package/dist/edit/pptx/handler.js +99 -0
- package/dist/edit/pptx/handlers.d.ts +3 -0
- package/dist/edit/pptx/handlers.js +21 -0
- package/dist/edit/pptx/image-table-ops.d.ts +5 -0
- package/dist/edit/pptx/image-table-ops.js +233 -0
- package/dist/edit/pptx/model.d.ts +69 -0
- package/dist/edit/pptx/model.js +170 -0
- package/dist/edit/pptx/operations.d.ts +50 -0
- package/dist/edit/pptx/operations.js +11 -0
- package/dist/edit/pptx/provider.d.ts +17 -0
- package/dist/edit/pptx/provider.js +40 -0
- package/dist/edit/pptx/schemas.d.ts +6 -0
- package/dist/edit/pptx/schemas.js +220 -0
- package/dist/edit/pptx/session.d.ts +54 -0
- package/dist/edit/pptx/session.js +145 -0
- package/dist/edit/pptx/shape-ops.d.ts +10 -0
- package/dist/edit/pptx/shape-ops.js +486 -0
- package/dist/edit/pptx/slide-ops.d.ts +6 -0
- package/dist/edit/pptx/slide-ops.js +353 -0
- package/dist/edit/pptx/style.d.ts +31 -0
- package/dist/edit/pptx/style.js +181 -0
- package/dist/edit/pptx/text-ops.d.ts +13 -0
- package/dist/edit/pptx/text-ops.js +267 -0
- package/dist/edit/pptx/text-write.d.ts +67 -0
- package/dist/edit/pptx/text-write.js +293 -0
- package/dist/edit/pptx/text.d.ts +41 -0
- package/dist/edit/pptx/text.js +99 -0
- package/dist/edit/pptx/types.d.ts +232 -0
- package/dist/edit/pptx/types.js +1 -0
- package/dist/edit/schema.d.ts +11 -0
- package/dist/edit/schema.js +275 -0
- package/dist/edit/session.d.ts +77 -0
- package/dist/edit/session.js +943 -0
- package/dist/edit/sessions.d.ts +8 -0
- package/dist/edit/sessions.js +1 -0
- package/dist/edit/types.d.ts +290 -0
- package/dist/edit/types.js +1 -0
- package/dist/edit/worker-engine.d.ts +31 -0
- package/dist/edit/worker-engine.js +91 -0
- package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/fonts/manifest.json +6 -1
- package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +4 -0
- package/dist/fuzzy-alignment.d.ts +11 -4
- package/dist/fuzzy-alignment.js +3 -9
- package/dist/headless.d.ts +5 -1
- package/dist/headless.js +8 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +16 -2
- package/dist/limits.js +6 -0
- package/dist/ooxml-edit-worker.d.ts +1 -0
- package/dist/ooxml-edit-worker.js +8 -0
- package/dist/pdf-edit-worker.d.ts +1 -0
- package/dist/pdf-edit-worker.js +35 -0
- package/dist/spreadsheet-viewport.d.ts +5 -0
- package/dist/spreadsheet-viewport.js +11 -0
- package/dist/ui.js +7 -0
- package/dist/viewer.d.ts +6 -0
- package/dist/viewer.js +293 -19
- package/dist/viewport.d.ts +13 -0
- package/dist/viewport.js +126 -1
- package/dist/worker-protocol.d.ts +39 -2
- package/dist/workers/fuzzy-search-worker.js +1 -1
- package/dist/workers/ooxml-edit-worker.js +9212 -0
- package/dist/workers/pdf-edit-worker.js +10847 -0
- package/package.json +3 -3
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { W_NS } from "./ids.js";
|
|
3
|
+
/*
|
|
4
|
+
* The text model of a paragraph: `w:t` text, a tab, a line break (`\v`), a
|
|
5
|
+
* page or column break (`\f`), an inline picture or an embedded object as
|
|
6
|
+
* one object character, a field (`w:fldSimple`, or a complex field from
|
|
7
|
+
* `fldChar begin` to `end`) as its cached result text, hyperlinks and
|
|
8
|
+
* inline `w:sdt` as their runs, hidden runs as text, deleted text (`w:del`)
|
|
9
|
+
* left out. Offsets are UTF-16 code units of the paragraph's text. Every
|
|
10
|
+
* item remembers the XML it came from, so a later patch can keep the bytes
|
|
11
|
+
* of what an edit does not touch.
|
|
12
|
+
*/
|
|
13
|
+
export const TAB = "\t";
|
|
14
|
+
export const LINE_BREAK = "\u000b";
|
|
15
|
+
export const PAGE_BREAK = "\f";
|
|
16
|
+
/** The object replacement character: an inline picture or an embedded object. */
|
|
17
|
+
export const OBJECT = "";
|
|
18
|
+
/** Wrappers whose children are read as the paragraph's own runs. */
|
|
19
|
+
const TRANSPARENT = new Set([
|
|
20
|
+
"hyperlink",
|
|
21
|
+
"smartTag",
|
|
22
|
+
"customXml",
|
|
23
|
+
"dir",
|
|
24
|
+
"bdo",
|
|
25
|
+
"ins",
|
|
26
|
+
"moveTo",
|
|
27
|
+
"sdtContent",
|
|
28
|
+
"fldSimple",
|
|
29
|
+
]);
|
|
30
|
+
/** Wrappers whose content is deleted text: left out, the paragraph is tracked. */
|
|
31
|
+
const DELETED = new Set(["del", "moveFrom"]);
|
|
32
|
+
class Reader {
|
|
33
|
+
part;
|
|
34
|
+
items = [];
|
|
35
|
+
text = "";
|
|
36
|
+
tracked = false;
|
|
37
|
+
#field;
|
|
38
|
+
constructor(part) {
|
|
39
|
+
this.part = part;
|
|
40
|
+
}
|
|
41
|
+
paragraph(paragraph) {
|
|
42
|
+
for (const child of paragraph.children)
|
|
43
|
+
this.#child(child, undefined);
|
|
44
|
+
if (this.#field)
|
|
45
|
+
this.#closeField();
|
|
46
|
+
// A paragraph mark inserted, deleted or moved under revision marks the
|
|
47
|
+
// paragraph as well.
|
|
48
|
+
const pPr = paragraph.children.find((child) => child.local === "pPr" && child.namespace === W_NS);
|
|
49
|
+
if (pPr && paragraphMarkTracked(pPr))
|
|
50
|
+
this.#tracked();
|
|
51
|
+
}
|
|
52
|
+
#child(node, wrapper) {
|
|
53
|
+
if (node.namespace !== W_NS) {
|
|
54
|
+
// DrawingML, math and markup compatibility live outside w:; a math
|
|
55
|
+
// zone or an alternate-content block counts as one object.
|
|
56
|
+
if (node.local === "oMath" ||
|
|
57
|
+
node.local === "oMathPara" ||
|
|
58
|
+
node.local === "AlternateContent")
|
|
59
|
+
this.#push("object", OBJECT, node, node, wrapper);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
switch (node.local) {
|
|
63
|
+
case "r":
|
|
64
|
+
this.#run(node, wrapper);
|
|
65
|
+
return;
|
|
66
|
+
case "sdt": {
|
|
67
|
+
const content = node.children.find((child) => child.local === "sdtContent" && child.namespace === W_NS);
|
|
68
|
+
if (content)
|
|
69
|
+
for (const child of content.children)
|
|
70
|
+
this.#child(child, node);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
case "ins":
|
|
74
|
+
case "moveTo":
|
|
75
|
+
this.#tracked();
|
|
76
|
+
for (const child of node.children)
|
|
77
|
+
this.#child(child, node);
|
|
78
|
+
return;
|
|
79
|
+
default:
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
if (DELETED.has(node.local)) {
|
|
83
|
+
this.#tracked();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (TRANSPARENT.has(node.local)) {
|
|
87
|
+
if (node.local === "fldSimple") {
|
|
88
|
+
this.#simpleField(node, wrapper);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const child of node.children)
|
|
92
|
+
this.#child(child, node);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
#tracked() {
|
|
96
|
+
this.tracked = true;
|
|
97
|
+
}
|
|
98
|
+
/** A simple field: one item carrying the text of its result runs. */
|
|
99
|
+
#simpleField(node, wrapper) {
|
|
100
|
+
const inner = new _a(this.part);
|
|
101
|
+
for (const child of node.children)
|
|
102
|
+
inner.#child(child, node);
|
|
103
|
+
this.tracked ||= inner.tracked;
|
|
104
|
+
this.#push("field", inner.text, node, node, wrapper);
|
|
105
|
+
}
|
|
106
|
+
#run(run, wrapper) {
|
|
107
|
+
const rPr = run.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
|
|
108
|
+
if (rPr) {
|
|
109
|
+
const marked = rPr.children.some((child) => child.namespace === W_NS &&
|
|
110
|
+
(child.local === "ins" || child.local === "del"));
|
|
111
|
+
if (marked)
|
|
112
|
+
this.#tracked();
|
|
113
|
+
}
|
|
114
|
+
if (this.#field)
|
|
115
|
+
this.#field.runs.push(run);
|
|
116
|
+
for (const child of run.children) {
|
|
117
|
+
if (child.namespace !== W_NS) {
|
|
118
|
+
if (child.local === "AlternateContent")
|
|
119
|
+
this.#emit("object", OBJECT, run, child, rPr, wrapper);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
switch (child.local) {
|
|
123
|
+
case "rPr":
|
|
124
|
+
case "lastRenderedPageBreak":
|
|
125
|
+
case "instrText":
|
|
126
|
+
case "delInstrText":
|
|
127
|
+
case "delText":
|
|
128
|
+
break;
|
|
129
|
+
case "fldChar":
|
|
130
|
+
this.#fieldChar(child, run);
|
|
131
|
+
break;
|
|
132
|
+
case "t":
|
|
133
|
+
this.#emit("text", this.part.textOf(child), run, child, rPr, wrapper);
|
|
134
|
+
break;
|
|
135
|
+
case "tab":
|
|
136
|
+
case "ptab":
|
|
137
|
+
this.#emit("tab", TAB, run, child, rPr, wrapper);
|
|
138
|
+
break;
|
|
139
|
+
case "br": {
|
|
140
|
+
const type = this.part.attribute(child, "w:type");
|
|
141
|
+
if (type === "page" || type === "column")
|
|
142
|
+
this.#emit("pageBreak", PAGE_BREAK, run, child, rPr, wrapper);
|
|
143
|
+
else
|
|
144
|
+
this.#emit("break", LINE_BREAK, run, child, rPr, wrapper);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "cr":
|
|
148
|
+
this.#emit("break", LINE_BREAK, run, child, rPr, wrapper);
|
|
149
|
+
break;
|
|
150
|
+
case "sym": {
|
|
151
|
+
const code = Number.parseInt(this.part.attribute(child, "w:char") ?? "", 16);
|
|
152
|
+
this.#emit("text", Number.isFinite(code) && code > 0 && code <= 0x10ffff
|
|
153
|
+
? String.fromCodePoint(code)
|
|
154
|
+
: "", run, child, rPr, wrapper);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "noBreakHyphen":
|
|
158
|
+
this.#emit("text", "‑", run, child, rPr, wrapper);
|
|
159
|
+
break;
|
|
160
|
+
case "softHyphen":
|
|
161
|
+
this.#emit("text", "", run, child, rPr, wrapper);
|
|
162
|
+
break;
|
|
163
|
+
case "drawing": {
|
|
164
|
+
const inline = child.children.some((node) => node.local === "inline");
|
|
165
|
+
if (inline)
|
|
166
|
+
this.#emit("picture", OBJECT, run, child, rPr, wrapper);
|
|
167
|
+
else
|
|
168
|
+
this.#emit("anchor", "", run, child, rPr, wrapper);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "object":
|
|
172
|
+
case "pict":
|
|
173
|
+
this.#emit("object", OBJECT, run, child, rPr, wrapper);
|
|
174
|
+
break;
|
|
175
|
+
case "footnoteReference":
|
|
176
|
+
case "endnoteReference":
|
|
177
|
+
this.#emit("note", "", run, child, rPr, wrapper);
|
|
178
|
+
break;
|
|
179
|
+
case "ruby": {
|
|
180
|
+
const base = child.children.find((node) => node.local === "rubyBase");
|
|
181
|
+
if (base)
|
|
182
|
+
for (const node of base.children)
|
|
183
|
+
this.#child(node, wrapper);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
#fieldChar(node, run) {
|
|
192
|
+
const type = this.part.attribute(node, "w:fldCharType");
|
|
193
|
+
if (type === "begin") {
|
|
194
|
+
if (this.#field)
|
|
195
|
+
this.#field.inResult.push(false);
|
|
196
|
+
else
|
|
197
|
+
this.#field = { begin: run, runs: [run], text: "", inResult: [false] };
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!this.#field)
|
|
201
|
+
return;
|
|
202
|
+
const levels = this.#field.inResult;
|
|
203
|
+
if (type === "separate") {
|
|
204
|
+
levels[levels.length - 1] = true;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (type === "end") {
|
|
208
|
+
if (levels.length > 1)
|
|
209
|
+
levels.pop();
|
|
210
|
+
else
|
|
211
|
+
this.#closeField();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
#closeField() {
|
|
215
|
+
const field = this.#field;
|
|
216
|
+
this.#field = undefined;
|
|
217
|
+
const rPr = field.begin.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
|
|
218
|
+
this.#append({
|
|
219
|
+
kind: "field",
|
|
220
|
+
start: this.text.length,
|
|
221
|
+
end: this.text.length + field.text.length,
|
|
222
|
+
text: field.text,
|
|
223
|
+
run: field.begin,
|
|
224
|
+
child: field.begin,
|
|
225
|
+
...(rPr ? { rPr } : {}),
|
|
226
|
+
fieldRuns: field.runs,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/** Text inside an open field goes to its result; outside it is an item. */
|
|
230
|
+
#emit(kind, text, run, child, rPr, wrapper) {
|
|
231
|
+
const field = this.#field;
|
|
232
|
+
if (field) {
|
|
233
|
+
// What Word shows: the result of the outer field, which holds the
|
|
234
|
+
// results of the fields nested in it and none of their instructions.
|
|
235
|
+
if (field.inResult.every(Boolean))
|
|
236
|
+
field.text += text;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.#push(kind, text, run, child, wrapper, rPr);
|
|
240
|
+
}
|
|
241
|
+
#push(kind, text, run, child, wrapper, rPr) {
|
|
242
|
+
this.#append({
|
|
243
|
+
kind,
|
|
244
|
+
start: this.text.length,
|
|
245
|
+
end: this.text.length + text.length,
|
|
246
|
+
text,
|
|
247
|
+
run,
|
|
248
|
+
child,
|
|
249
|
+
...(rPr ? { rPr } : {}),
|
|
250
|
+
...(wrapper ? { wrapper } : {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
#append(item) {
|
|
254
|
+
this.items.push(item);
|
|
255
|
+
this.text += item.text;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
_a = Reader;
|
|
259
|
+
const MARK_REVISIONS = new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
260
|
+
/**
|
|
261
|
+
* Whether the paragraph mark's `w:rPr` carries an insertion, deletion or
|
|
262
|
+
* move. Changed properties alone (`w:rPrChange`, `w:pPrChange`) leave a
|
|
263
|
+
* paragraph editable, as module 06 decided.
|
|
264
|
+
*/
|
|
265
|
+
function paragraphMarkTracked(pPr) {
|
|
266
|
+
for (const child of pPr.children) {
|
|
267
|
+
if (child.namespace !== W_NS)
|
|
268
|
+
continue;
|
|
269
|
+
if (child.local === "rPr" &&
|
|
270
|
+
child.children.some((mark) => mark.namespace === W_NS && MARK_REVISIONS.has(mark.local)))
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
/** Reads the text of a `w:p`. */
|
|
276
|
+
export function readParagraphText(part, paragraph) {
|
|
277
|
+
const reader = new Reader(part);
|
|
278
|
+
reader.paragraph(paragraph);
|
|
279
|
+
return { text: reader.text, items: reader.items, tracked: reader.tracked };
|
|
280
|
+
}
|
|
281
|
+
/** The first item that draws text, for the resolved style of a paragraph. */
|
|
282
|
+
export function firstTextItem(model) {
|
|
283
|
+
return model.items.find((item) => (item.kind === "text" || item.kind === "field") && item.text.length > 0);
|
|
284
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { XmlElement, XmlPart } from "../ooxml/xml.js";
|
|
2
|
+
import type { ParagraphRecord } from "./model.js";
|
|
3
|
+
import type { DocxOperationContext, Issue } from "./operations.js";
|
|
4
|
+
import type { DocxRevision } from "./types.js";
|
|
5
|
+
/** The attributes of one revision element, with a fresh `w:id`. */
|
|
6
|
+
export declare function revisionAttributes(context: DocxOperationContext): string;
|
|
7
|
+
/** A refusal for an operation, or a part of one, that has no tracked form. */
|
|
8
|
+
export declare function unsupportedTracked(issue: Issue, path: string, what: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a tracked change of `[start, end)` would touch a hyperlink, a
|
|
11
|
+
* content control, a field or a run already under revision, which the
|
|
12
|
+
* tracked rebuild cannot express; reports it as an issue at `path`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function trackedRangeProblem(record: ParagraphRecord, start: number, end: number, issue: Issue, path: string): boolean;
|
|
15
|
+
/** A deleted run: its properties and its content, text as `w:delText`. */
|
|
16
|
+
export declare function deletedRunXml(rPr: string, content: string): string;
|
|
17
|
+
export interface TrackedSplit {
|
|
18
|
+
/** Everything before the change, bytes kept. */
|
|
19
|
+
readonly before: string;
|
|
20
|
+
/** The removed runs inside one `w:del`, or "". */
|
|
21
|
+
readonly deleted: string;
|
|
22
|
+
/** The `w:rPr` bytes new text takes. */
|
|
23
|
+
readonly rPr: string;
|
|
24
|
+
/** Everything after the change, bytes kept. */
|
|
25
|
+
readonly after: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The paragraph's content around `[start, end)`, unit by unit as the direct
|
|
29
|
+
* edit walks it: the runs the range covers re-serialized as deleted, a cut
|
|
30
|
+
* run split into a kept head, a deleted middle and a kept tail, everything
|
|
31
|
+
* else as its bytes. New text takes the style of the first replaced run,
|
|
32
|
+
* else of the run before a caret, else of the run after it. The caller
|
|
33
|
+
* places the insertion between `deleted` and `after`. A wrapper or a field
|
|
34
|
+
* inside the range never reaches here: validation refuses it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function trackedSplit(context: DocxOperationContext, record: ParagraphRecord, start: number, end: number, fallbackRPr: string): TrackedSplit;
|
|
37
|
+
/** `text` as an inserted run under revision; nothing for empty text. */
|
|
38
|
+
export declare function insertedRunXml(context: DocxOperationContext, rPr: string, text: string): string;
|
|
39
|
+
/** `w:pPr` bytes with the paragraph mark under revision (`w:ins` or `w:del` first in its `w:rPr`), section properties left out. */
|
|
40
|
+
export declare function markedParagraphProperties(context: DocxOperationContext, pPr: XmlElement | undefined, mark: "ins" | "del"): string;
|
|
41
|
+
/**
|
|
42
|
+
* A whole paragraph marked deleted: its mark under `w:del`, every run
|
|
43
|
+
* inside `w:del` with `w:delText`, other children kept. Word removes the
|
|
44
|
+
* paragraph when the deletion is accepted.
|
|
45
|
+
*/
|
|
46
|
+
export declare function deletedParagraphXml(context: DocxOperationContext, record: ParagraphRecord): string;
|
|
47
|
+
/**
|
|
48
|
+
* The revisions a paragraph holds, in document order: run-level
|
|
49
|
+
* insertions, deletions and moves with their text, the paragraph mark's
|
|
50
|
+
* own revision, and changed run or paragraph properties.
|
|
51
|
+
*/
|
|
52
|
+
export declare function revisionsOf(part: XmlPart, paragraph: XmlElement): DocxRevision[];
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { W_NS } from "./ids.js";
|
|
2
|
+
import { LINE_BREAK, TAB } from "./text.js";
|
|
3
|
+
import { contentOf, rPrOf, unitsOf } from "./text-ops.js";
|
|
4
|
+
import { escapeAttributeValue, innerPropertiesXml, mergedProperties, paragraphXml, PPR_ORDER, runContentXml, runXml, sliceOf, } from "./write.js";
|
|
5
|
+
/*
|
|
6
|
+
* Tracked changes: the same paragraph rebuild as a direct edit, but what a
|
|
7
|
+
* direct edit drops is kept inside `w:del` (text as `w:delText`) and what
|
|
8
|
+
* it adds goes inside `w:ins`, each with an id, the author and the date of
|
|
9
|
+
* the batch, so Word and Pages show the edit as a suggestion to accept or
|
|
10
|
+
* reject. A paragraph rebuilt this way holds revisions and is read-only
|
|
11
|
+
* for the next edit (decision 6 of the ai-edit module). Hyperlinks,
|
|
12
|
+
* content controls and fields have no tracked form here: a change that
|
|
13
|
+
* touches one is refused.
|
|
14
|
+
*/
|
|
15
|
+
/** The attributes of one revision element, with a fresh `w:id`. */
|
|
16
|
+
export function revisionAttributes(context) {
|
|
17
|
+
const tracked = context.tracked;
|
|
18
|
+
return ` w:id="${context.nextRevisionId()}" w:author="${escapeAttributeValue(tracked.author)}"${tracked.date === undefined
|
|
19
|
+
? ""
|
|
20
|
+
: ` w:date="${escapeAttributeValue(tracked.date)}"`}`;
|
|
21
|
+
}
|
|
22
|
+
/** A refusal for an operation, or a part of one, that has no tracked form. */
|
|
23
|
+
export function unsupportedTracked(issue, path, what) {
|
|
24
|
+
issue(path, "unsupported-change-mode", `${what} has no tracked form; apply it directly and review with checkpoints`);
|
|
25
|
+
}
|
|
26
|
+
/** The child of `container` an item sits under. */
|
|
27
|
+
function topOf(node, container) {
|
|
28
|
+
let current = node;
|
|
29
|
+
while (current.parent && current.parent !== container)
|
|
30
|
+
current = current.parent;
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether a tracked change of `[start, end)` would touch a hyperlink, a
|
|
35
|
+
* content control, a field or a run already under revision, which the
|
|
36
|
+
* tracked rebuild cannot express; reports it as an issue at `path`.
|
|
37
|
+
*/
|
|
38
|
+
export function trackedRangeProblem(record, start, end, issue, path) {
|
|
39
|
+
for (const item of record.text.items) {
|
|
40
|
+
const overlaps = item.start === item.end
|
|
41
|
+
? start < item.start && item.start < end
|
|
42
|
+
: item.start < end && start < item.end;
|
|
43
|
+
if (!overlaps)
|
|
44
|
+
continue;
|
|
45
|
+
const top = topOf(item.run, record.node);
|
|
46
|
+
// An equation or an alternate-content block sits in the paragraph
|
|
47
|
+
// without a run; a revision cannot wrap it either.
|
|
48
|
+
const plainRun = item.run.local === "r" && item.run.namespace === W_NS;
|
|
49
|
+
if (!plainRun ||
|
|
50
|
+
item.fieldRuns ||
|
|
51
|
+
item.kind === "field" ||
|
|
52
|
+
top !== item.run ||
|
|
53
|
+
item.wrapper !== undefined) {
|
|
54
|
+
unsupportedTracked(issue, path, "A change touching a hyperlink, content control, field or equation");
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
/** Run content with `w:t` as `w:delText`, for a deleted run. */
|
|
61
|
+
function deletedContent(content) {
|
|
62
|
+
return content
|
|
63
|
+
.replaceAll("<w:t>", "<w:delText>")
|
|
64
|
+
.replaceAll("<w:t ", "<w:delText ")
|
|
65
|
+
.replaceAll("</w:t>", "</w:delText>")
|
|
66
|
+
.replaceAll("<w:instrText", "<w:delInstrText")
|
|
67
|
+
.replaceAll("</w:instrText>", "</w:delInstrText>");
|
|
68
|
+
}
|
|
69
|
+
/** A deleted run: its properties and its content, text as `w:delText`. */
|
|
70
|
+
export function deletedRunXml(rPr, content) {
|
|
71
|
+
return content.length === 0
|
|
72
|
+
? ""
|
|
73
|
+
: `<w:r>${rPr}${deletedContent(content)}</w:r>`;
|
|
74
|
+
}
|
|
75
|
+
/** The whole content of a run (every child but `w:rPr`) as bytes. */
|
|
76
|
+
function runContentOf(part, run) {
|
|
77
|
+
return run.children
|
|
78
|
+
.filter((child) => !(child.local === "rPr" && child.namespace === W_NS))
|
|
79
|
+
.map((child) => sliceOf(part, child))
|
|
80
|
+
.join("");
|
|
81
|
+
}
|
|
82
|
+
/** `w:rPr` bytes of a run element. */
|
|
83
|
+
function runPropertiesOf(part, run) {
|
|
84
|
+
const rPr = run.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
|
|
85
|
+
return sliceOf(part, rPr);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The deleted part of a cut run: like `contentOf`, but a zero-width item
|
|
89
|
+
* (a note reference, an anchored drawing) belongs to the deletion only
|
|
90
|
+
* strictly inside `(from, to)`, so the kept head or tail keeps it instead
|
|
91
|
+
* and nothing is written twice.
|
|
92
|
+
*/
|
|
93
|
+
function deletedContentOf(part, items, from, to) {
|
|
94
|
+
let out = "";
|
|
95
|
+
for (const item of items) {
|
|
96
|
+
if (item.start === item.end) {
|
|
97
|
+
if (from < item.start && item.start < to)
|
|
98
|
+
out += sliceOf(part, item.child);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const begin = Math.max(item.start, from);
|
|
102
|
+
const stop = Math.min(item.end, to);
|
|
103
|
+
if (stop <= begin)
|
|
104
|
+
continue;
|
|
105
|
+
if (item.kind === "text" && item.child.local === "t")
|
|
106
|
+
out += runContentXml(item.text.slice(begin - item.start, stop - item.start));
|
|
107
|
+
else if (begin === item.start && stop === item.end)
|
|
108
|
+
out += sliceOf(part, item.child);
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The paragraph's content around `[start, end)`, unit by unit as the direct
|
|
114
|
+
* edit walks it: the runs the range covers re-serialized as deleted, a cut
|
|
115
|
+
* run split into a kept head, a deleted middle and a kept tail, everything
|
|
116
|
+
* else as its bytes. New text takes the style of the first replaced run,
|
|
117
|
+
* else of the run before a caret, else of the run after it. The caller
|
|
118
|
+
* places the insertion between `deleted` and `after`. A wrapper or a field
|
|
119
|
+
* inside the range never reaches here: validation refuses it.
|
|
120
|
+
*/
|
|
121
|
+
export function trackedSplit(context, record, start, end, fallbackRPr) {
|
|
122
|
+
const part = context.model.document;
|
|
123
|
+
let before = "";
|
|
124
|
+
let after = "";
|
|
125
|
+
const deletedRuns = [];
|
|
126
|
+
let rPr;
|
|
127
|
+
let lastRPr;
|
|
128
|
+
/** The change has been passed: zero-width children now follow it. */
|
|
129
|
+
let passed = false;
|
|
130
|
+
const raw = (unit) => unit.nodes.map((node) => sliceOf(part, node)).join("");
|
|
131
|
+
for (const unit of unitsOf(record.node, record.text.items)) {
|
|
132
|
+
if (unit.kind === "zero") {
|
|
133
|
+
if (passed)
|
|
134
|
+
after += raw(unit);
|
|
135
|
+
else
|
|
136
|
+
before += raw(unit);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (unit.end <= start) {
|
|
140
|
+
before += raw(unit);
|
|
141
|
+
lastRPr = rPrOf(part, unit.items.at(-1));
|
|
142
|
+
// A caret right after this unit: the new text follows its style.
|
|
143
|
+
if (start === end && unit.end === start) {
|
|
144
|
+
rPr ??= lastRPr;
|
|
145
|
+
passed = true;
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (unit.start >= end) {
|
|
150
|
+
rPr ??= lastRPr ?? rPrOf(part, unit.items[0]);
|
|
151
|
+
passed = true;
|
|
152
|
+
after += raw(unit);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (unit.kind !== "run")
|
|
156
|
+
throw new Error("A tracked change cannot cut a field or a wrapper");
|
|
157
|
+
const run = unit.nodes[0];
|
|
158
|
+
const style = runPropertiesOf(part, run);
|
|
159
|
+
if (unit.start >= start && unit.end <= end) {
|
|
160
|
+
// Covered whole: the run goes into the deletion as it is.
|
|
161
|
+
deletedRuns.push(deletedRunXml(style, runContentOf(part, run)));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const head = contentOf(part, unit.items, unit.start, start, true);
|
|
165
|
+
const middle = deletedContentOf(part, unit.items, Math.max(unit.start, start), Math.min(unit.end, end));
|
|
166
|
+
const tail = contentOf(part, unit.items, end, unit.end, start < end);
|
|
167
|
+
if (head)
|
|
168
|
+
before += runXml(style, head);
|
|
169
|
+
if (middle)
|
|
170
|
+
deletedRuns.push(deletedRunXml(style, middle));
|
|
171
|
+
if (tail)
|
|
172
|
+
after += runXml(style, tail);
|
|
173
|
+
}
|
|
174
|
+
rPr ??= style;
|
|
175
|
+
if (unit.end >= end)
|
|
176
|
+
passed = true;
|
|
177
|
+
}
|
|
178
|
+
const deleted = deletedRuns.length > 0
|
|
179
|
+
? `<w:del${revisionAttributes(context)}>${deletedRuns.join("")}</w:del>`
|
|
180
|
+
: "";
|
|
181
|
+
return { before, deleted, rPr: rPr ?? lastRPr ?? fallbackRPr, after };
|
|
182
|
+
}
|
|
183
|
+
/** `text` as an inserted run under revision; nothing for empty text. */
|
|
184
|
+
export function insertedRunXml(context, rPr, text) {
|
|
185
|
+
const content = runContentXml(text);
|
|
186
|
+
return content.length === 0
|
|
187
|
+
? ""
|
|
188
|
+
: `<w:ins${revisionAttributes(context)}>${runXml(rPr, content)}</w:ins>`;
|
|
189
|
+
}
|
|
190
|
+
/** `w:pPr` bytes with the paragraph mark under revision (`w:ins` or `w:del` first in its `w:rPr`), section properties left out. */
|
|
191
|
+
export function markedParagraphProperties(context, pPr, mark) {
|
|
192
|
+
const part = context.model.document;
|
|
193
|
+
const rPr = pPr?.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
|
|
194
|
+
const inner = innerPropertiesXml(part, rPr, ["ins", "del"]);
|
|
195
|
+
const marked = `<w:rPr><w:${mark}${revisionAttributes(context)}/>${inner}</w:rPr>`;
|
|
196
|
+
return mergedProperties(part, pPr, "w:pPr", PPR_ORDER, new Map([
|
|
197
|
+
["rPr", marked],
|
|
198
|
+
["sectPr", null],
|
|
199
|
+
]));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* A whole paragraph marked deleted: its mark under `w:del`, every run
|
|
203
|
+
* inside `w:del` with `w:delText`, other children kept. Word removes the
|
|
204
|
+
* paragraph when the deletion is accepted.
|
|
205
|
+
*/
|
|
206
|
+
export function deletedParagraphXml(context, record) {
|
|
207
|
+
const part = context.model.document;
|
|
208
|
+
const pPr = markedParagraphProperties(context, record.pPr, "del");
|
|
209
|
+
let content = "";
|
|
210
|
+
let open = [];
|
|
211
|
+
const flush = () => {
|
|
212
|
+
if (open.length > 0)
|
|
213
|
+
content += `<w:del${revisionAttributes(context)}>${open.join("")}</w:del>`;
|
|
214
|
+
open = [];
|
|
215
|
+
};
|
|
216
|
+
for (const child of record.node.children) {
|
|
217
|
+
if (child.local === "pPr" && child.namespace === W_NS)
|
|
218
|
+
continue;
|
|
219
|
+
if (child.local === "r" && child.namespace === W_NS) {
|
|
220
|
+
const run = deletedRunXml(runPropertiesOf(part, child), runContentOf(part, child));
|
|
221
|
+
if (run)
|
|
222
|
+
open.push(run);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
flush();
|
|
226
|
+
content += sliceOf(part, child);
|
|
227
|
+
}
|
|
228
|
+
flush();
|
|
229
|
+
return paragraphXml(record.node, record.id, pPr, content);
|
|
230
|
+
}
|
|
231
|
+
const RUN_REVISIONS = new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
232
|
+
/**
|
|
233
|
+
* The revisions a paragraph holds, in document order: run-level
|
|
234
|
+
* insertions, deletions and moves with their text, the paragraph mark's
|
|
235
|
+
* own revision, and changed run or paragraph properties.
|
|
236
|
+
*/
|
|
237
|
+
export function revisionsOf(part, paragraph) {
|
|
238
|
+
const out = [];
|
|
239
|
+
const attributes = (node) => {
|
|
240
|
+
const author = part.attribute(node, "w:author");
|
|
241
|
+
const date = part.attribute(node, "w:date");
|
|
242
|
+
const raw = part.attribute(node, "w:id");
|
|
243
|
+
return {
|
|
244
|
+
id: raw !== undefined && /^\d+$/.test(raw) ? Number(raw) : -1,
|
|
245
|
+
...(author === undefined ? {} : { author }),
|
|
246
|
+
...(date === undefined ? {} : { date }),
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
const visit = (node, inRPr, inPPr) => {
|
|
250
|
+
for (const child of node.children) {
|
|
251
|
+
if (child.namespace !== W_NS) {
|
|
252
|
+
visit(child, inRPr, inPPr);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (child.local === "pPr") {
|
|
256
|
+
visit(child, false, true);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (child.local === "rPr") {
|
|
260
|
+
visit(child, true, inPPr);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (inRPr && RUN_REVISIONS.has(child.local)) {
|
|
264
|
+
out.push({
|
|
265
|
+
start: child.start,
|
|
266
|
+
revision: {
|
|
267
|
+
kind: child.local,
|
|
268
|
+
scope: inPPr ? "mark" : "runs",
|
|
269
|
+
...attributes(child),
|
|
270
|
+
...(inPPr ? { text: "\n" } : {}),
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (inRPr && child.local === "rPrChange") {
|
|
276
|
+
out.push({
|
|
277
|
+
start: child.start,
|
|
278
|
+
revision: {
|
|
279
|
+
kind: "rPrChange",
|
|
280
|
+
scope: inPPr ? "mark" : "runs",
|
|
281
|
+
...attributes(child),
|
|
282
|
+
...(inPPr || !node.parent
|
|
283
|
+
? {}
|
|
284
|
+
: { text: textOf(part, node.parent) }),
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (inPPr && child.local === "pPrChange") {
|
|
290
|
+
out.push({
|
|
291
|
+
start: child.start,
|
|
292
|
+
revision: {
|
|
293
|
+
kind: "pPrChange",
|
|
294
|
+
scope: "paragraph",
|
|
295
|
+
...attributes(child),
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (!inRPr && !inPPr && RUN_REVISIONS.has(child.local)) {
|
|
301
|
+
out.push({
|
|
302
|
+
start: child.start,
|
|
303
|
+
revision: {
|
|
304
|
+
kind: child.local,
|
|
305
|
+
scope: "runs",
|
|
306
|
+
...attributes(child),
|
|
307
|
+
text: textOf(part, child),
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
visit(child, false, false);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
visit(child, inRPr, inPPr);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
visit(paragraph, false, false);
|
|
317
|
+
return out
|
|
318
|
+
.sort((a, b) => a.start - b.start)
|
|
319
|
+
.map((entry) => Object.freeze(entry.revision));
|
|
320
|
+
}
|
|
321
|
+
/** The text of every run under `node`: `w:t` and `w:delText`, tabs and breaks. */
|
|
322
|
+
function textOf(part, node) {
|
|
323
|
+
let text = "";
|
|
324
|
+
const visit = (current) => {
|
|
325
|
+
for (const child of current.children) {
|
|
326
|
+
if (child.namespace === W_NS) {
|
|
327
|
+
if (child.local === "t" || child.local === "delText") {
|
|
328
|
+
text += part.textOf(child);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (child.local === "tab") {
|
|
332
|
+
text += TAB;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (child.local === "br" || child.local === "cr") {
|
|
336
|
+
text += LINE_BREAK;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (child.local === "rPr" || child.local === "pPr")
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
visit(child);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
visit(node);
|
|
346
|
+
return text;
|
|
347
|
+
}
|