@shadow-garden/bapbong-docx 0.1.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/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/index.cjs +1837 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1802 -0
- package/dist/lib/docx.d.ts +63 -0
- package/dist/lib/docx.d.ts.map +1 -0
- package/dist/lib/export.d.ts +15 -0
- package/dist/lib/export.d.ts.map +1 -0
- package/dist/lib/numbering.d.ts +13 -0
- package/dist/lib/numbering.d.ts.map +1 -0
- package/dist/lib/ooxml.d.ts +43 -0
- package/dist/lib/ooxml.d.ts.map +1 -0
- package/dist/lib/rels.d.ts +8 -0
- package/dist/lib/rels.d.ts.map +1 -0
- package/dist/lib/styles.d.ts +18 -0
- package/dist/lib/styles.d.ts.map +1 -0
- package/dist/lib/theme.d.ts +5 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/package.json +66 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1802 @@
|
|
|
1
|
+
// packages/docx/src/lib/docx.ts
|
|
2
|
+
import JSZip from "jszip";
|
|
3
|
+
import {
|
|
4
|
+
commentSchema,
|
|
5
|
+
schema
|
|
6
|
+
} from "@shadow-garden/bapbong-model";
|
|
7
|
+
|
|
8
|
+
// packages/docx/src/lib/ooxml.ts
|
|
9
|
+
import { XMLParser } from "fast-xml-parser";
|
|
10
|
+
var parser = new XMLParser({
|
|
11
|
+
ignoreAttributes: false,
|
|
12
|
+
attributeNamePrefix: "@_",
|
|
13
|
+
preserveOrder: true,
|
|
14
|
+
// Keep significant whitespace in <w:t> (e.g. xml:space="preserve" "Hello ").
|
|
15
|
+
trimValues: false,
|
|
16
|
+
// Text is text — never strnum it. The default (true) mangles document
|
|
17
|
+
// content: "100.000" → 100, "00" → 0, "1." → 1 — Word splits numbers like
|
|
18
|
+
// "1.500.000" across runs (rsid), and each fragment got destroyed.
|
|
19
|
+
parseTagValue: false
|
|
20
|
+
});
|
|
21
|
+
function buildNode(entry) {
|
|
22
|
+
const name = Object.keys(entry).find((k) => k !== ":@") ?? "";
|
|
23
|
+
const attrs = {};
|
|
24
|
+
const rawAttrs = entry[":@"];
|
|
25
|
+
if (rawAttrs) {
|
|
26
|
+
for (const [k, v] of Object.entries(rawAttrs)) {
|
|
27
|
+
attrs[k.startsWith("@_") ? k.slice(2) : k] = v == null ? "" : String(v);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const raw = entry[name];
|
|
31
|
+
const rawChildren = Array.isArray(raw) ? raw : [];
|
|
32
|
+
const children2 = [];
|
|
33
|
+
let text = "";
|
|
34
|
+
for (const c of rawChildren) {
|
|
35
|
+
if (Object.prototype.hasOwnProperty.call(c, "#text")) {
|
|
36
|
+
text += String(c["#text"] ?? "");
|
|
37
|
+
} else {
|
|
38
|
+
children2.push(buildNode(c));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { name, attrs, children: children2, text };
|
|
42
|
+
}
|
|
43
|
+
function parseXml(xml) {
|
|
44
|
+
const top = parser.parse(xml);
|
|
45
|
+
const children2 = top.filter((e) => {
|
|
46
|
+
const key = Object.keys(e).find((k) => k !== ":@");
|
|
47
|
+
return !!key && key !== "#text" && !key.startsWith("?");
|
|
48
|
+
}).map(buildNode);
|
|
49
|
+
return { name: "#root", attrs: {}, children: children2, text: "" };
|
|
50
|
+
}
|
|
51
|
+
function child(node, name) {
|
|
52
|
+
return node?.children.find((c) => c.name === name);
|
|
53
|
+
}
|
|
54
|
+
function children(node, name) {
|
|
55
|
+
return node ? node.children.filter((c) => c.name === name) : [];
|
|
56
|
+
}
|
|
57
|
+
function attrOf(node, name) {
|
|
58
|
+
return node?.attrs[name];
|
|
59
|
+
}
|
|
60
|
+
function findDescendant(node, name) {
|
|
61
|
+
if (!node) return void 0;
|
|
62
|
+
for (const c of node.children) {
|
|
63
|
+
if (c.name === name) return c;
|
|
64
|
+
const found = findDescendant(c, name);
|
|
65
|
+
if (found) return found;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
var OFF = /* @__PURE__ */ new Set(["false", "0", "off"]);
|
|
70
|
+
function toggle(el) {
|
|
71
|
+
if (!el) return void 0;
|
|
72
|
+
const v = attrOf(el, "w:val");
|
|
73
|
+
return v === void 0 ? true : !OFF.has(v.toLowerCase());
|
|
74
|
+
}
|
|
75
|
+
function underlineToggle(el) {
|
|
76
|
+
if (!el) return void 0;
|
|
77
|
+
const v = attrOf(el, "w:val");
|
|
78
|
+
if (v === void 0) return true;
|
|
79
|
+
const lower = v.toLowerCase();
|
|
80
|
+
return lower !== "none" && !OFF.has(lower);
|
|
81
|
+
}
|
|
82
|
+
var HIGHLIGHT_COLORS = {
|
|
83
|
+
yellow: "#FFFF00",
|
|
84
|
+
green: "#00FF00",
|
|
85
|
+
cyan: "#00FFFF",
|
|
86
|
+
magenta: "#FF00FF",
|
|
87
|
+
blue: "#0000FF",
|
|
88
|
+
red: "#FF0000",
|
|
89
|
+
darkBlue: "#000080",
|
|
90
|
+
darkCyan: "#008080",
|
|
91
|
+
darkGreen: "#008000",
|
|
92
|
+
darkMagenta: "#800080",
|
|
93
|
+
darkRed: "#800000",
|
|
94
|
+
darkYellow: "#808000",
|
|
95
|
+
darkGray: "#808080",
|
|
96
|
+
lightGray: "#C0C0C0",
|
|
97
|
+
black: "#000000",
|
|
98
|
+
white: "#FFFFFF"
|
|
99
|
+
};
|
|
100
|
+
function normalizeHex(v) {
|
|
101
|
+
if (!v || v.toLowerCase() === "auto") return void 0;
|
|
102
|
+
return v.startsWith("#") ? v.toUpperCase() : `#${v.toUpperCase()}`;
|
|
103
|
+
}
|
|
104
|
+
function parseRunProps(rPr, resolveTheme) {
|
|
105
|
+
if (!rPr) return {};
|
|
106
|
+
const props = {};
|
|
107
|
+
const b = toggle(child(rPr, "w:b"));
|
|
108
|
+
if (b !== void 0) props.bold = b;
|
|
109
|
+
const i = toggle(child(rPr, "w:i"));
|
|
110
|
+
if (i !== void 0) props.italic = i;
|
|
111
|
+
const u = underlineToggle(child(rPr, "w:u"));
|
|
112
|
+
if (u !== void 0) props.underline = u;
|
|
113
|
+
const s = toggle(child(rPr, "w:strike"));
|
|
114
|
+
if (s !== void 0) props.strike = s;
|
|
115
|
+
const colorEl = child(rPr, "w:color");
|
|
116
|
+
const colorVal = attrOf(colorEl, "w:val");
|
|
117
|
+
if (colorVal && colorVal.toLowerCase() !== "auto") {
|
|
118
|
+
props.color = colorVal.startsWith("#") ? colorVal.toUpperCase() : `#${colorVal.toUpperCase()}`;
|
|
119
|
+
} else if (resolveTheme) {
|
|
120
|
+
const themeColor = attrOf(colorEl, "w:themeColor");
|
|
121
|
+
if (themeColor) {
|
|
122
|
+
const hex = resolveTheme(
|
|
123
|
+
themeColor,
|
|
124
|
+
attrOf(colorEl, "w:themeTint"),
|
|
125
|
+
attrOf(colorEl, "w:themeShade")
|
|
126
|
+
);
|
|
127
|
+
if (hex) props.color = hex;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const sz = attrOf(child(rPr, "w:sz"), "w:val");
|
|
131
|
+
if (sz !== void 0) {
|
|
132
|
+
const halfPoints = Number(sz);
|
|
133
|
+
if (!Number.isNaN(halfPoints)) props.sizePt = halfPoints / 2;
|
|
134
|
+
}
|
|
135
|
+
const rFonts = child(rPr, "w:rFonts");
|
|
136
|
+
const family = attrOf(rFonts, "w:ascii") ?? attrOf(rFonts, "w:hAnsi");
|
|
137
|
+
if (family) props.fontFamily = family;
|
|
138
|
+
const va = attrOf(child(rPr, "w:vertAlign"), "w:val");
|
|
139
|
+
if (va === "superscript") props.vertAlign = "super";
|
|
140
|
+
else if (va === "subscript") props.vertAlign = "sub";
|
|
141
|
+
const hl = attrOf(child(rPr, "w:highlight"), "w:val");
|
|
142
|
+
if (hl && hl !== "none") {
|
|
143
|
+
props.highlight = HIGHLIGHT_COLORS[hl] ?? normalizeHex(hl);
|
|
144
|
+
} else {
|
|
145
|
+
const hex = normalizeHex(attrOf(child(rPr, "w:shd"), "w:fill"));
|
|
146
|
+
if (hex) props.highlight = hex;
|
|
147
|
+
}
|
|
148
|
+
return props;
|
|
149
|
+
}
|
|
150
|
+
function mergeRunProps(base, over) {
|
|
151
|
+
return { ...base, ...over };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// packages/docx/src/lib/styles.ts
|
|
155
|
+
var EMPTY = {};
|
|
156
|
+
function buildStyleRegistry(stylesRoot, resolveTheme) {
|
|
157
|
+
const stylesEl = child(stylesRoot, "w:styles");
|
|
158
|
+
const rPrDefault = child(child(child(stylesEl, "w:docDefaults"), "w:rPrDefault"), "w:rPr");
|
|
159
|
+
const docDefaults = parseRunProps(rPrDefault, resolveTheme);
|
|
160
|
+
const docDefaultsPPr = child(child(child(stylesEl, "w:docDefaults"), "w:pPrDefault"), "w:pPr");
|
|
161
|
+
const defs = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const style of children(stylesEl, "w:style")) {
|
|
163
|
+
const id = attrOf(style, "w:styleId");
|
|
164
|
+
if (id === void 0) continue;
|
|
165
|
+
defs.set(id, {
|
|
166
|
+
basedOn: attrOf(child(style, "w:basedOn"), "w:val"),
|
|
167
|
+
rPr: parseRunProps(child(style, "w:rPr"), resolveTheme),
|
|
168
|
+
pPr: child(style, "w:pPr"),
|
|
169
|
+
tblBorders: child(child(style, "w:tblPr"), "w:tblBorders")
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function resolve(styleId, seen) {
|
|
173
|
+
if (!styleId || seen.has(styleId)) return EMPTY;
|
|
174
|
+
const def = defs.get(styleId);
|
|
175
|
+
if (!def) return EMPTY;
|
|
176
|
+
seen.add(styleId);
|
|
177
|
+
const base = def.basedOn ? resolve(def.basedOn, seen) : EMPTY;
|
|
178
|
+
return mergeRunProps(base, def.rPr);
|
|
179
|
+
}
|
|
180
|
+
function resolvePPr(styleId, seen) {
|
|
181
|
+
if (!styleId || seen.has(styleId)) return [];
|
|
182
|
+
const def = defs.get(styleId);
|
|
183
|
+
if (!def) return [];
|
|
184
|
+
seen.add(styleId);
|
|
185
|
+
const base = def.basedOn ? resolvePPr(def.basedOn, seen) : [];
|
|
186
|
+
return def.pPr ? [...base, def.pPr] : base;
|
|
187
|
+
}
|
|
188
|
+
function resolveTblBorders(styleId, seen) {
|
|
189
|
+
if (!styleId || seen.has(styleId)) return void 0;
|
|
190
|
+
const def = defs.get(styleId);
|
|
191
|
+
if (!def) return void 0;
|
|
192
|
+
seen.add(styleId);
|
|
193
|
+
return def.tblBorders ?? resolveTblBorders(def.basedOn, seen);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
docDefaults,
|
|
197
|
+
docDefaultsPPr,
|
|
198
|
+
resolveStyle: (styleId) => resolve(styleId, /* @__PURE__ */ new Set()),
|
|
199
|
+
resolveStylePPr: (styleId) => resolvePPr(styleId, /* @__PURE__ */ new Set()),
|
|
200
|
+
resolveTableBorders: (styleId) => resolveTblBorders(styleId, /* @__PURE__ */ new Set())
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// packages/docx/src/lib/numbering.ts
|
|
205
|
+
function buildNumbering(numberingRoot) {
|
|
206
|
+
const numberingEl = child(numberingRoot, "w:numbering");
|
|
207
|
+
const abstract = /* @__PURE__ */ new Map();
|
|
208
|
+
for (const abstractNum of children(numberingEl, "w:abstractNum")) {
|
|
209
|
+
const id = attrOf(abstractNum, "w:abstractNumId");
|
|
210
|
+
if (id === void 0) continue;
|
|
211
|
+
const levels = /* @__PURE__ */ new Map();
|
|
212
|
+
for (const lvl of children(abstractNum, "w:lvl")) {
|
|
213
|
+
const ilvl = Number(attrOf(lvl, "w:ilvl") ?? "0");
|
|
214
|
+
levels.set(Number.isNaN(ilvl) ? 0 : ilvl, {
|
|
215
|
+
numFmt: attrOf(child(lvl, "w:numFmt"), "w:val") ?? "decimal",
|
|
216
|
+
lvlText: attrOf(child(lvl, "w:lvlText"), "w:val") ?? "",
|
|
217
|
+
start: Number(attrOf(child(lvl, "w:start"), "w:val") ?? "1") || 1,
|
|
218
|
+
pPr: child(lvl, "w:pPr")
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
abstract.set(id, levels);
|
|
222
|
+
}
|
|
223
|
+
const numToAbstract = /* @__PURE__ */ new Map();
|
|
224
|
+
for (const num of children(numberingEl, "w:num")) {
|
|
225
|
+
const numId = attrOf(num, "w:numId");
|
|
226
|
+
const absId = attrOf(child(num, "w:abstractNumId"), "w:val");
|
|
227
|
+
if (numId !== void 0 && absId !== void 0) numToAbstract.set(numId, absId);
|
|
228
|
+
}
|
|
229
|
+
let defs = null;
|
|
230
|
+
for (const [numId, absId] of numToAbstract) {
|
|
231
|
+
const levels = abstract.get(absId);
|
|
232
|
+
if (!levels) continue;
|
|
233
|
+
const plain = {};
|
|
234
|
+
for (const [ilvl, def] of levels) {
|
|
235
|
+
plain[ilvl] = { numFmt: def.numFmt, lvlText: def.lvlText, start: def.start };
|
|
236
|
+
}
|
|
237
|
+
(defs ??= {})[numId] = { key: absId, levels: plain };
|
|
238
|
+
}
|
|
239
|
+
function levelPPr(numId, level) {
|
|
240
|
+
const absId = numToAbstract.get(numId);
|
|
241
|
+
return absId === void 0 ? void 0 : abstract.get(absId)?.get(level)?.pPr;
|
|
242
|
+
}
|
|
243
|
+
return { defs, levelPPr };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// packages/docx/src/lib/rels.ts
|
|
247
|
+
function buildRels(root) {
|
|
248
|
+
const map = /* @__PURE__ */ new Map();
|
|
249
|
+
for (const rel of children(child(root, "Relationships"), "Relationship")) {
|
|
250
|
+
const id = attrOf(rel, "Id");
|
|
251
|
+
const target = attrOf(rel, "Target");
|
|
252
|
+
if (id && target) {
|
|
253
|
+
map.set(id, { target, external: attrOf(rel, "TargetMode") === "External" });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return map;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// packages/docx/src/lib/theme.ts
|
|
260
|
+
var ALIASES = {
|
|
261
|
+
dk1: ["dark1", "text1"],
|
|
262
|
+
lt1: ["light1", "background1"],
|
|
263
|
+
dk2: ["dark2", "text2"],
|
|
264
|
+
lt2: ["light2", "background2"],
|
|
265
|
+
accent1: ["accent1"],
|
|
266
|
+
accent2: ["accent2"],
|
|
267
|
+
accent3: ["accent3"],
|
|
268
|
+
accent4: ["accent4"],
|
|
269
|
+
accent5: ["accent5"],
|
|
270
|
+
accent6: ["accent6"],
|
|
271
|
+
hlink: ["hyperlink"],
|
|
272
|
+
folHlink: ["followedHyperlink"]
|
|
273
|
+
};
|
|
274
|
+
function schemeColor(el) {
|
|
275
|
+
const srgb = child(el, "a:srgbClr");
|
|
276
|
+
const srgbVal = attrOf(srgb, "val");
|
|
277
|
+
if (srgbVal) return `#${srgbVal.toUpperCase()}`;
|
|
278
|
+
const sys = child(el, "a:sysClr");
|
|
279
|
+
const sysVal = attrOf(sys, "lastClr") ?? attrOf(sys, "val");
|
|
280
|
+
if (sysVal) return `#${sysVal.toUpperCase()}`;
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
var clamp = (n) => Math.max(0, Math.min(255, Math.round(n)));
|
|
284
|
+
var hex2 = (n) => clamp(n).toString(16).padStart(2, "0").toUpperCase();
|
|
285
|
+
function applyTintShade(hex, tint, shade) {
|
|
286
|
+
let r = parseInt(hex.slice(1, 3), 16);
|
|
287
|
+
let g = parseInt(hex.slice(3, 5), 16);
|
|
288
|
+
let b = parseInt(hex.slice(5, 7), 16);
|
|
289
|
+
if (shade !== void 0) {
|
|
290
|
+
const f = parseInt(shade, 16) / 255;
|
|
291
|
+
r *= f;
|
|
292
|
+
g *= f;
|
|
293
|
+
b *= f;
|
|
294
|
+
}
|
|
295
|
+
if (tint !== void 0) {
|
|
296
|
+
const f = parseInt(tint, 16) / 255;
|
|
297
|
+
r = r * f + 255 * (1 - f);
|
|
298
|
+
g = g * f + 255 * (1 - f);
|
|
299
|
+
b = b * f + 255 * (1 - f);
|
|
300
|
+
}
|
|
301
|
+
return `#${hex2(r)}${hex2(g)}${hex2(b)}`;
|
|
302
|
+
}
|
|
303
|
+
function buildThemeResolver(themeRoot) {
|
|
304
|
+
const map = /* @__PURE__ */ new Map();
|
|
305
|
+
const scheme = findDescendant(themeRoot, "a:clrScheme");
|
|
306
|
+
for (const el of scheme?.children ?? []) {
|
|
307
|
+
const key = el.name.startsWith("a:") ? el.name.slice(2) : el.name;
|
|
308
|
+
const color = schemeColor(el);
|
|
309
|
+
if (!color) continue;
|
|
310
|
+
map.set(key, color);
|
|
311
|
+
for (const alias of ALIASES[key] ?? []) map.set(alias, color);
|
|
312
|
+
}
|
|
313
|
+
return (themeColor, tint, shade) => {
|
|
314
|
+
const base = map.get(themeColor);
|
|
315
|
+
if (!base) return void 0;
|
|
316
|
+
return tint === void 0 && shade === void 0 ? base : applyTintShade(base, tint, shade);
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// packages/docx/src/lib/docx.ts
|
|
321
|
+
var twipsToPx = (twips) => Math.round(twips / 15);
|
|
322
|
+
function emuToPx(emu) {
|
|
323
|
+
const n = Number(emu ?? "0");
|
|
324
|
+
return Number.isNaN(n) || n === 0 ? null : Math.round(n / 9525);
|
|
325
|
+
}
|
|
326
|
+
function mimeOf(path) {
|
|
327
|
+
switch (path.slice(path.lastIndexOf(".") + 1).toLowerCase()) {
|
|
328
|
+
case "png":
|
|
329
|
+
return "image/png";
|
|
330
|
+
case "jpg":
|
|
331
|
+
case "jpeg":
|
|
332
|
+
return "image/jpeg";
|
|
333
|
+
case "gif":
|
|
334
|
+
return "image/gif";
|
|
335
|
+
case "bmp":
|
|
336
|
+
return "image/bmp";
|
|
337
|
+
case "svg":
|
|
338
|
+
return "image/svg+xml";
|
|
339
|
+
case "webp":
|
|
340
|
+
return "image/webp";
|
|
341
|
+
case "tif":
|
|
342
|
+
case "tiff":
|
|
343
|
+
return "image/tiff";
|
|
344
|
+
default:
|
|
345
|
+
return "application/octet-stream";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function propsToMarks(p, ctx) {
|
|
349
|
+
const marks = [];
|
|
350
|
+
if (p.bold) marks.push(ctx.schema.marks["strong"].create());
|
|
351
|
+
if (p.italic) marks.push(ctx.schema.marks["em"].create());
|
|
352
|
+
if (p.underline) marks.push(ctx.schema.marks["underline"].create());
|
|
353
|
+
if (p.strike) marks.push(ctx.schema.marks["strike"].create());
|
|
354
|
+
if (p.color) marks.push(ctx.schema.marks["textColor"].create({ color: p.color }));
|
|
355
|
+
if (p.sizePt !== void 0) marks.push(ctx.schema.marks["fontSize"].create({ size: p.sizePt }));
|
|
356
|
+
if (p.fontFamily) marks.push(ctx.schema.marks["fontFamily"].create({ family: p.fontFamily }));
|
|
357
|
+
if (p.highlight) marks.push(ctx.schema.marks["highlight"].create({ color: p.highlight }));
|
|
358
|
+
if (p.vertAlign) marks.push(ctx.schema.marks["vertAlign"].create({ value: p.vertAlign }));
|
|
359
|
+
return marks;
|
|
360
|
+
}
|
|
361
|
+
var SYMBOL_MAP = {
|
|
362
|
+
F0B7: "\u2022",
|
|
363
|
+
F06C: "\u25CF",
|
|
364
|
+
F0A7: "\u25AA",
|
|
365
|
+
F0A8: "\u25AB",
|
|
366
|
+
F0FC: "\u2714",
|
|
367
|
+
F0FB: "\u2717",
|
|
368
|
+
F0E0: "\u2192"
|
|
369
|
+
};
|
|
370
|
+
function symbolChar(code) {
|
|
371
|
+
if (!code) return "";
|
|
372
|
+
const upper = code.toUpperCase();
|
|
373
|
+
if (SYMBOL_MAP[upper]) return SYMBOL_MAP[upper];
|
|
374
|
+
const n = parseInt(code, 16);
|
|
375
|
+
return Number.isNaN(n) ? "" : String.fromCodePoint(n >= 61440 ? n - 61440 + 32 : n);
|
|
376
|
+
}
|
|
377
|
+
function hasPageBreak(run) {
|
|
378
|
+
return run.children.some((n) => n.name === "w:br" && attrOf(n, "w:type") === "page");
|
|
379
|
+
}
|
|
380
|
+
function effectiveChildren(nodes) {
|
|
381
|
+
const out = [];
|
|
382
|
+
for (const node of nodes) {
|
|
383
|
+
if (node.name === "w:del" || node.name === "w:moveFrom") continue;
|
|
384
|
+
if (node.name === "w:ins" || node.name === "w:moveTo") {
|
|
385
|
+
out.push(...effectiveChildren(node.children));
|
|
386
|
+
} else if (node.name === "w:sdt") {
|
|
387
|
+
out.push(...effectiveChildren(unwrapSdt([node])));
|
|
388
|
+
} else {
|
|
389
|
+
out.push(node);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
function unwrapSdt(nodes) {
|
|
395
|
+
const out = [];
|
|
396
|
+
for (const node of nodes) {
|
|
397
|
+
if (node.name === "w:sdt") {
|
|
398
|
+
const content = child(node, "w:sdtContent");
|
|
399
|
+
if (content) out.push(...unwrapSdt(content.children));
|
|
400
|
+
} else {
|
|
401
|
+
out.push(node);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return out;
|
|
405
|
+
}
|
|
406
|
+
function runInlineNodes(run, marks, ctx) {
|
|
407
|
+
const group = parseGroup(run, ctx);
|
|
408
|
+
if (group) return group;
|
|
409
|
+
const image = parseImage(run, ctx) ?? parseShape(run, ctx) ?? parseVmlImage(run, ctx);
|
|
410
|
+
if (image) return [image];
|
|
411
|
+
const out = [];
|
|
412
|
+
let buf = "";
|
|
413
|
+
const flush = () => {
|
|
414
|
+
if (buf.length > 0) out.push(ctx.schema.text(buf, marks));
|
|
415
|
+
buf = "";
|
|
416
|
+
};
|
|
417
|
+
for (const node of run.children) {
|
|
418
|
+
if (node.name === "w:t") buf += node.text;
|
|
419
|
+
else if (node.name === "w:tab") buf += " ";
|
|
420
|
+
else if (node.name === "w:sym") buf += symbolChar(attrOf(node, "w:char"));
|
|
421
|
+
else if (node.name === "w:footnoteReference" || node.name === "w:endnoteReference") {
|
|
422
|
+
const kind = node.name === "w:footnoteReference" ? "footnote" : "endnote";
|
|
423
|
+
const id = attrOf(node, "w:id");
|
|
424
|
+
if (id && ctx.notes.bodies[kind].has(id)) {
|
|
425
|
+
flush();
|
|
426
|
+
const num = ctx.notes.ref(kind, id);
|
|
427
|
+
const refMarks = [...marks, ctx.schema.marks["vertAlign"].create({ value: "super" })];
|
|
428
|
+
if (kind === "footnote") refMarks.push(ctx.schema.marks["footnote"].create({ num }));
|
|
429
|
+
out.push(ctx.schema.text(String(num), refMarks));
|
|
430
|
+
}
|
|
431
|
+
} else if (node.name === "w:br" && attrOf(node, "w:type") !== "page") {
|
|
432
|
+
flush();
|
|
433
|
+
out.push(ctx.schema.nodes["hard_break"].create());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
flush();
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
function emuToPxZero(emu) {
|
|
440
|
+
if (emu === void 0) return void 0;
|
|
441
|
+
const n = Number(emu);
|
|
442
|
+
return Number.isNaN(n) ? void 0 : Math.round(n / 9525);
|
|
443
|
+
}
|
|
444
|
+
function parseAnchorFloat(drawing) {
|
|
445
|
+
const anchor = child(drawing, "wp:anchor");
|
|
446
|
+
if (!anchor) return null;
|
|
447
|
+
const wrap = child(anchor, "wp:wrapTopAndBottom") ? "topAndBottom" : child(anchor, "wp:wrapSquare") || child(anchor, "wp:wrapTight") || child(anchor, "wp:wrapThrough") ? "square" : "none";
|
|
448
|
+
const float = { wrap };
|
|
449
|
+
const posH = child(anchor, "wp:positionH");
|
|
450
|
+
if (posH) {
|
|
451
|
+
const align = child(posH, "wp:align")?.text.trim();
|
|
452
|
+
if (align === "left" || align === "right" || align === "center") float["hAlign"] = align;
|
|
453
|
+
const off = emuToPxZero(child(posH, "wp:posOffset")?.text);
|
|
454
|
+
if (off !== void 0 && float["hAlign"] === void 0) float["hOffset"] = off;
|
|
455
|
+
const rel = attrOf(posH, "relativeFrom");
|
|
456
|
+
float["hRel"] = rel === "page" ? "page" : "margin";
|
|
457
|
+
}
|
|
458
|
+
const posV = child(anchor, "wp:positionV");
|
|
459
|
+
if (posV) {
|
|
460
|
+
const off = emuToPxZero(child(posV, "wp:posOffset")?.text);
|
|
461
|
+
if (off !== void 0) float["vOffset"] = off;
|
|
462
|
+
const rel = attrOf(posV, "relativeFrom");
|
|
463
|
+
float["vRel"] = rel === "page" ? "page" : rel === "margin" ? "margin" : "paragraph";
|
|
464
|
+
}
|
|
465
|
+
for (const side of ["distL", "distR", "distT", "distB"]) {
|
|
466
|
+
const v = emuToPxZero(attrOf(anchor, side));
|
|
467
|
+
if (v !== void 0) float[side] = v;
|
|
468
|
+
}
|
|
469
|
+
return float;
|
|
470
|
+
}
|
|
471
|
+
function runDrawing(run) {
|
|
472
|
+
const direct = child(run, "w:drawing");
|
|
473
|
+
if (direct) return direct;
|
|
474
|
+
const alt = child(run, "mc:AlternateContent");
|
|
475
|
+
if (!alt) return void 0;
|
|
476
|
+
for (const branch of ["mc:Choice", "mc:Fallback"]) {
|
|
477
|
+
for (const b of children(alt, branch)) {
|
|
478
|
+
const d = child(b, "w:drawing");
|
|
479
|
+
if (d) return d;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
484
|
+
function parseGroup(run, ctx) {
|
|
485
|
+
const drawing = runDrawing(run);
|
|
486
|
+
if (!drawing) return null;
|
|
487
|
+
const wgp = findDescendant(drawing, "wpg:wgp");
|
|
488
|
+
if (!wgp) return null;
|
|
489
|
+
const baseFloat = parseAnchorFloat(drawing);
|
|
490
|
+
if (!baseFloat) return null;
|
|
491
|
+
const num = (n, a) => Number(attrOf(n, a) ?? "0");
|
|
492
|
+
const xfrm = child(child(wgp, "wpg:grpSpPr"), "a:xfrm");
|
|
493
|
+
const ext = child(xfrm, "a:ext");
|
|
494
|
+
const chOff = child(xfrm, "a:chOff");
|
|
495
|
+
const chExt = child(xfrm, "a:chExt");
|
|
496
|
+
const chW = num(chExt, "cx") || num(ext, "cx") || 1;
|
|
497
|
+
const chH = num(chExt, "cy") || num(ext, "cy") || 1;
|
|
498
|
+
const sx = (num(ext, "cx") || chW) / chW;
|
|
499
|
+
const sy = (num(ext, "cy") || chH) / chH;
|
|
500
|
+
const out = [];
|
|
501
|
+
for (const pic of children(wgp, "pic:pic")) {
|
|
502
|
+
const blip = findDescendant(pic, "a:blip");
|
|
503
|
+
const embed = attrOf(blip, "r:embed") ?? attrOf(blip, "r:link");
|
|
504
|
+
const rel = embed ? ctx.rels.get(embed) : void 0;
|
|
505
|
+
if (!rel) continue;
|
|
506
|
+
const target = rel.target.replace(/^\/+/, "");
|
|
507
|
+
const src = ctx.media.get(`word/${target}`) ?? ctx.media.get(target);
|
|
508
|
+
if (!src) continue;
|
|
509
|
+
const picXfrm = child(child(pic, "pic:spPr"), "a:xfrm");
|
|
510
|
+
const off = child(picXfrm, "a:off");
|
|
511
|
+
const cext = child(picXfrm, "a:ext");
|
|
512
|
+
const emuPx = (emu) => Math.round(emu / 9525);
|
|
513
|
+
out.push(
|
|
514
|
+
ctx.schema.nodes["image"].create({
|
|
515
|
+
src,
|
|
516
|
+
width: emuPx(num(cext, "cx") * sx),
|
|
517
|
+
height: emuPx(num(cext, "cy") * sy),
|
|
518
|
+
alt: attrOf(findDescendant(pic, "pic:cNvPr"), "descr") ?? "",
|
|
519
|
+
float: {
|
|
520
|
+
...baseFloat,
|
|
521
|
+
hOffset: (baseFloat["hOffset"] ?? 0) + emuPx((num(off, "x") - num(chOff, "x")) * sx),
|
|
522
|
+
vOffset: (baseFloat["vOffset"] ?? 0) + emuPx((num(off, "y") - num(chOff, "y")) * sy)
|
|
523
|
+
}
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
return out.length > 0 ? out : null;
|
|
528
|
+
}
|
|
529
|
+
function xfrmRotation(root) {
|
|
530
|
+
const rot = Number(attrOf(findDescendant(root, "a:xfrm"), "rot"));
|
|
531
|
+
return rot ? Math.round(rot / 6e4 * 100) / 100 : 0;
|
|
532
|
+
}
|
|
533
|
+
function parseImage(run, ctx) {
|
|
534
|
+
const drawing = runDrawing(run);
|
|
535
|
+
if (!drawing) return null;
|
|
536
|
+
const blip = findDescendant(drawing, "a:blip");
|
|
537
|
+
const embed = attrOf(blip, "r:embed") ?? attrOf(blip, "r:link");
|
|
538
|
+
const rel = embed ? ctx.rels.get(embed) : void 0;
|
|
539
|
+
if (!rel) return null;
|
|
540
|
+
const target = rel.target.replace(/^\/+/, "");
|
|
541
|
+
const src = ctx.media.get(`word/${target}`) ?? ctx.media.get(target);
|
|
542
|
+
if (!src) return null;
|
|
543
|
+
const extent = findDescendant(drawing, "wp:extent");
|
|
544
|
+
const docPr = findDescendant(drawing, "wp:docPr");
|
|
545
|
+
const float = parseAnchorFloat(drawing);
|
|
546
|
+
return ctx.schema.nodes["image"].create({
|
|
547
|
+
src,
|
|
548
|
+
width: emuToPx(attrOf(extent, "cx")),
|
|
549
|
+
height: emuToPx(attrOf(extent, "cy")),
|
|
550
|
+
alt: attrOf(docPr, "descr") ?? attrOf(docPr, "title") ?? "",
|
|
551
|
+
float,
|
|
552
|
+
rotation: xfrmRotation(drawing)
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function parseVmlImage(run, ctx) {
|
|
556
|
+
const holder = child(run, "w:object") ?? child(run, "w:pict");
|
|
557
|
+
if (!holder) return null;
|
|
558
|
+
const imagedata = findDescendant(holder, "v:imagedata");
|
|
559
|
+
const rid = attrOf(imagedata, "r:id");
|
|
560
|
+
const rel = rid ? ctx.rels.get(rid) : void 0;
|
|
561
|
+
if (!rel) return null;
|
|
562
|
+
const target = rel.target.replace(/^\/+/, "");
|
|
563
|
+
const src = ctx.media.get(`word/${target}`) ?? ctx.media.get(target);
|
|
564
|
+
if (!src) return null;
|
|
565
|
+
const style = attrOf(findDescendant(holder, "v:shape"), "style") ?? "";
|
|
566
|
+
const ptToPx = (m) => m ? Math.round(parseFloat(m[1]) * 96 / 72) : null;
|
|
567
|
+
const width = ptToPx(/(?:^|;)width:([\d.]+)pt/.exec(style)) ?? (Number(attrOf(holder, "w:dxaOrig")) ? twipsToPx(Number(attrOf(holder, "w:dxaOrig"))) : null);
|
|
568
|
+
const height = ptToPx(/(?:^|;)height:([\d.]+)pt/.exec(style)) ?? (Number(attrOf(holder, "w:dyaOrig")) ? twipsToPx(Number(attrOf(holder, "w:dyaOrig"))) : null);
|
|
569
|
+
return ctx.schema.nodes["image"].create({
|
|
570
|
+
src,
|
|
571
|
+
width,
|
|
572
|
+
height,
|
|
573
|
+
alt: attrOf(imagedata, "o:title") ?? "",
|
|
574
|
+
float: null
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
function solidFillColor(node, ctx) {
|
|
578
|
+
const fill = child(node, "a:solidFill");
|
|
579
|
+
if (!fill) return void 0;
|
|
580
|
+
const srgb = attrOf(child(fill, "a:srgbClr"), "val");
|
|
581
|
+
if (srgb) return `#${srgb}`;
|
|
582
|
+
const scheme = attrOf(child(fill, "a:schemeClr"), "val");
|
|
583
|
+
return scheme ? ctx.resolveTheme(scheme) : void 0;
|
|
584
|
+
}
|
|
585
|
+
function parseShape(run, ctx) {
|
|
586
|
+
const drawing = runDrawing(run);
|
|
587
|
+
if (!drawing) return null;
|
|
588
|
+
const wsp = findDescendant(drawing, "wps:wsp");
|
|
589
|
+
const spPr = child(wsp, "wps:spPr");
|
|
590
|
+
const prst = attrOf(child(spPr, "a:prstGeom"), "prst");
|
|
591
|
+
const textbox = parseTextbox(wsp, ctx);
|
|
592
|
+
const KIND = {
|
|
593
|
+
rect: "rect",
|
|
594
|
+
line: "line",
|
|
595
|
+
straightConnector1: "line",
|
|
596
|
+
ellipse: "ellipse",
|
|
597
|
+
roundRect: "roundRect",
|
|
598
|
+
rightArrow: "rightArrow",
|
|
599
|
+
horizontalScroll: "horizontalScroll"
|
|
600
|
+
};
|
|
601
|
+
const kind = prst && KIND[prst] || (textbox ? "rect" : null);
|
|
602
|
+
if (!kind) return null;
|
|
603
|
+
const shape = { kind };
|
|
604
|
+
const ln = child(spPr, "a:ln");
|
|
605
|
+
if (!child(ln, "a:noFill")) {
|
|
606
|
+
const w = attrOf(ln, "w");
|
|
607
|
+
shape["strokeWidth"] = w ? Math.max(1, Math.round(Number(w) / 9525)) : 1;
|
|
608
|
+
const lnRef = findDescendant(child(wsp, "wps:style"), "a:lnRef");
|
|
609
|
+
const refScheme = attrOf(child(lnRef, "a:schemeClr"), "val");
|
|
610
|
+
shape["stroke"] = solidFillColor(ln, ctx) ?? (refScheme ? ctx.resolveTheme(refScheme) : void 0) ?? "#000000";
|
|
611
|
+
}
|
|
612
|
+
const fill = solidFillColor(spPr, ctx);
|
|
613
|
+
if (fill) shape["fill"] = fill;
|
|
614
|
+
if (attrOf(child(spPr, "a:xfrm"), "flipV") === "1") shape["flipV"] = true;
|
|
615
|
+
const extent = findDescendant(drawing, "wp:extent");
|
|
616
|
+
const docPr = findDescendant(drawing, "wp:docPr");
|
|
617
|
+
return ctx.schema.nodes["image"].create({
|
|
618
|
+
src: "",
|
|
619
|
+
width: emuToPxZero(attrOf(extent, "cx")) ?? 0,
|
|
620
|
+
height: emuToPxZero(attrOf(extent, "cy")) ?? 0,
|
|
621
|
+
alt: attrOf(docPr, "name") ?? kind,
|
|
622
|
+
float: parseAnchorFloat(drawing),
|
|
623
|
+
shape,
|
|
624
|
+
textbox,
|
|
625
|
+
rotation: xfrmRotation(spPr)
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
function parseTextbox(wsp, ctx) {
|
|
629
|
+
const content = child(child(wsp, "wps:txbx"), "w:txbxContent");
|
|
630
|
+
if (!content) return null;
|
|
631
|
+
const paragraphs = children(content, "w:p").map((p) => parseParagraph(p, ctx).toJSON());
|
|
632
|
+
if (paragraphs.length === 0) return null;
|
|
633
|
+
const bodyPr = child(wsp, "wps:bodyPr");
|
|
634
|
+
const ins = (name) => emuToPxZero(attrOf(bodyPr, name));
|
|
635
|
+
const l = ins("lIns"), t = ins("tIns"), r = ins("rIns"), b = ins("bIns");
|
|
636
|
+
const inset = l !== void 0 || t !== void 0 || r !== void 0 || b !== void 0 ? { l: l ?? 10, t: t ?? 5, r: r ?? 10, b: b ?? 5 } : void 0;
|
|
637
|
+
return inset ? { paragraphs, inset } : { paragraphs };
|
|
638
|
+
}
|
|
639
|
+
function runMarks(run, paraBase, ctx, href) {
|
|
640
|
+
const rPr = child(run, "w:rPr");
|
|
641
|
+
const rStyleId = attrOf(child(rPr, "w:rStyle"), "w:val");
|
|
642
|
+
const effective = [
|
|
643
|
+
paraBase,
|
|
644
|
+
ctx.styles.resolveStyle(rStyleId),
|
|
645
|
+
parseRunProps(rPr, ctx.resolveTheme)
|
|
646
|
+
].reduce(mergeRunProps, {});
|
|
647
|
+
const marks = propsToMarks(effective, ctx);
|
|
648
|
+
if (href) marks.push(ctx.schema.marks["link"].create({ href }));
|
|
649
|
+
if (ctx.comments.active.size > 0 && ctx.schema.marks["comment"]) {
|
|
650
|
+
const ids = [...ctx.comments.active].sort((a, b) => a - b);
|
|
651
|
+
marks.push(ctx.schema.marks["comment"].create({ ids }));
|
|
652
|
+
}
|
|
653
|
+
return marks;
|
|
654
|
+
}
|
|
655
|
+
function runToInline(run, paraBase, ctx, href) {
|
|
656
|
+
const marks = runMarks(run, paraBase, ctx, href);
|
|
657
|
+
const nodes = runInlineNodes(run, marks, ctx);
|
|
658
|
+
return href ? nodes.map((n) => n.type.name === "image" ? n.mark(marks) : n) : nodes;
|
|
659
|
+
}
|
|
660
|
+
function fieldKind(instr) {
|
|
661
|
+
if (/\bNUMPAGES\b/.test(instr)) return "pages";
|
|
662
|
+
if (/\bPAGE\b/.test(instr)) return "page";
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
function pageFieldNode(kind, formatRun, paraBase, ctx) {
|
|
666
|
+
return ctx.schema.nodes["page_field"].create({ kind }).mark(runMarks(formatRun, paraBase, ctx, null));
|
|
667
|
+
}
|
|
668
|
+
function headingLevel(pStyleId, pPrChain) {
|
|
669
|
+
if (pStyleId) {
|
|
670
|
+
const m = /^heading\s*([1-9])$/i.exec(pStyleId);
|
|
671
|
+
if (m) return Math.min(6, Number(m[1]));
|
|
672
|
+
}
|
|
673
|
+
const ol = lastWith(pPrChain, "w:outlineLvl");
|
|
674
|
+
if (ol) {
|
|
675
|
+
const v = Number(attrOf(ol, "w:val"));
|
|
676
|
+
if (!Number.isNaN(v) && v >= 0 && v <= 8) return Math.min(6, v + 1);
|
|
677
|
+
}
|
|
678
|
+
return void 0;
|
|
679
|
+
}
|
|
680
|
+
var SUB_DIGITS = "\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089";
|
|
681
|
+
var SUP_DIGITS = "\u2070\xB9\xB2\xB3\u2074\u2075\u2076\u2077\u2078\u2079";
|
|
682
|
+
function scriptDigits(text, alphabet) {
|
|
683
|
+
if (!/^[0-9]+$/.test(text)) return null;
|
|
684
|
+
return [...text].map((d) => alphabet[Number(d)]).join("");
|
|
685
|
+
}
|
|
686
|
+
function flattenOmml(node) {
|
|
687
|
+
const flat = (n) => n ? flattenOmml(n) : "";
|
|
688
|
+
switch (node.name) {
|
|
689
|
+
case "m:t":
|
|
690
|
+
return node.text;
|
|
691
|
+
case "m:f": {
|
|
692
|
+
const side = (s) => /[+\-±×÷/ ]/.test(s) ? `(${s})` : s;
|
|
693
|
+
return `${side(flat(child(node, "m:num")))}/${side(flat(child(node, "m:den")))}`;
|
|
694
|
+
}
|
|
695
|
+
case "m:sSub": {
|
|
696
|
+
const sub = flat(child(node, "m:sub"));
|
|
697
|
+
return flat(child(node, "m:e")) + (scriptDigits(sub, SUB_DIGITS) ?? `_(${sub})`);
|
|
698
|
+
}
|
|
699
|
+
case "m:sSup": {
|
|
700
|
+
const sup = flat(child(node, "m:sup"));
|
|
701
|
+
return flat(child(node, "m:e")) + (scriptDigits(sup, SUP_DIGITS) ?? `^(${sup})`);
|
|
702
|
+
}
|
|
703
|
+
case "m:sSubSup": {
|
|
704
|
+
const sub = flat(child(node, "m:sub"));
|
|
705
|
+
const sup = flat(child(node, "m:sup"));
|
|
706
|
+
return flat(child(node, "m:e")) + (scriptDigits(sub, SUB_DIGITS) ?? `_(${sub})`) + (scriptDigits(sup, SUP_DIGITS) ?? `^(${sup})`);
|
|
707
|
+
}
|
|
708
|
+
case "m:rad": {
|
|
709
|
+
const deg = flat(child(node, "m:deg"));
|
|
710
|
+
return `${deg}\u221A(${flat(child(node, "m:e"))})`;
|
|
711
|
+
}
|
|
712
|
+
case "m:d": {
|
|
713
|
+
const pr = child(node, "m:dPr");
|
|
714
|
+
const chr = (name, dflt) => attrOf(child(pr, name), "m:val") ?? dflt;
|
|
715
|
+
const args = children(node, "m:e").map(flat);
|
|
716
|
+
return chr("m:begChr", "(") + args.join(chr("m:sepChr", ",")) + chr("m:endChr", ")");
|
|
717
|
+
}
|
|
718
|
+
default:
|
|
719
|
+
if (node.name.startsWith("m:") && node.name.endsWith("Pr")) return "";
|
|
720
|
+
return node.children.map(flat).join("");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function parseParagraph(p, ctx) {
|
|
724
|
+
const pPr = child(p, "w:pPr");
|
|
725
|
+
const pStyleId = attrOf(child(pPr, "w:pStyle"), "w:val");
|
|
726
|
+
const paraBase = mergeRunProps(ctx.styles.docDefaults, ctx.styles.resolveStyle(pStyleId));
|
|
727
|
+
const pPrChain = [
|
|
728
|
+
ctx.styles.docDefaultsPPr,
|
|
729
|
+
...ctx.styles.resolveStylePPr(pStyleId),
|
|
730
|
+
pPr
|
|
731
|
+
];
|
|
732
|
+
const list = parseList(lastWith(pPrChain, "w:numPr"));
|
|
733
|
+
if (list) {
|
|
734
|
+
const lvlPPr = ctx.numbering.levelPPr(list.numId, list.level);
|
|
735
|
+
if (lvlPPr) pPrChain.splice(pPrChain.length - 1, 0, lvlPPr);
|
|
736
|
+
}
|
|
737
|
+
const align = resolveAlign(pPrChain);
|
|
738
|
+
const indent = resolveIndent(pPrChain);
|
|
739
|
+
const spacing = resolveSpacing(pPrChain);
|
|
740
|
+
const tabs = resolveTabs(pPrChain);
|
|
741
|
+
const heading = headingLevel(pStyleId, pPrChain);
|
|
742
|
+
const inline = [];
|
|
743
|
+
let field = null;
|
|
744
|
+
let pageBreak = lastWith(pPrChain, "w:pageBreakBefore") !== void 0;
|
|
745
|
+
for (const node of effectiveChildren(p.children)) {
|
|
746
|
+
if (node.name === "w:r") {
|
|
747
|
+
if (hasPageBreak(node)) pageBreak = true;
|
|
748
|
+
const fldType = attrOf(child(node, "w:fldChar"), "w:fldCharType");
|
|
749
|
+
if (fldType === "begin") {
|
|
750
|
+
field = { instr: "", resultRuns: [], phase: "instr" };
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
if (field) {
|
|
754
|
+
if (fldType === "separate") {
|
|
755
|
+
field.phase = "result";
|
|
756
|
+
} else if (fldType === "end") {
|
|
757
|
+
const kind = fieldKind(field.instr);
|
|
758
|
+
if (kind) {
|
|
759
|
+
inline.push(pageFieldNode(kind, field.resultRuns[0], paraBase, ctx));
|
|
760
|
+
} else {
|
|
761
|
+
for (const r of field.resultRuns) inline.push(...runToInline(r, paraBase, ctx, null));
|
|
762
|
+
}
|
|
763
|
+
field = null;
|
|
764
|
+
} else if (field.phase === "instr") {
|
|
765
|
+
field.instr += children(node, "w:instrText").map((t) => t.text).join("");
|
|
766
|
+
} else {
|
|
767
|
+
field.resultRuns.push(node);
|
|
768
|
+
}
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
inline.push(...runToInline(node, paraBase, ctx, null));
|
|
772
|
+
} else if (node.name === "w:fldSimple") {
|
|
773
|
+
const kind = fieldKind(attrOf(node, "w:instr") ?? "");
|
|
774
|
+
const resultRuns = children(node, "w:r");
|
|
775
|
+
if (kind) {
|
|
776
|
+
inline.push(pageFieldNode(kind, resultRuns[0], paraBase, ctx));
|
|
777
|
+
} else {
|
|
778
|
+
for (const r of resultRuns) inline.push(...runToInline(r, paraBase, ctx, null));
|
|
779
|
+
}
|
|
780
|
+
} else if (node.name === "w:hyperlink") {
|
|
781
|
+
const rel = attrOf(node, "r:id") ? ctx.rels.get(attrOf(node, "r:id")) : void 0;
|
|
782
|
+
const anchor = attrOf(node, "w:anchor");
|
|
783
|
+
const href = rel?.target ?? (anchor ? `#${anchor}` : null);
|
|
784
|
+
for (const run of children(node, "w:r")) {
|
|
785
|
+
inline.push(...runToInline(run, paraBase, ctx, href));
|
|
786
|
+
}
|
|
787
|
+
} else if (node.name === "m:oMath" || node.name === "m:oMathPara") {
|
|
788
|
+
const text = flattenOmml(node);
|
|
789
|
+
if (text.length > 0) {
|
|
790
|
+
const first = findDescendant(node, "m:r");
|
|
791
|
+
inline.push(ctx.schema.text(text, runMarks(first, paraBase, ctx, null)));
|
|
792
|
+
}
|
|
793
|
+
} else if (node.name === "w:commentRangeStart") {
|
|
794
|
+
const id = Number(attrOf(node, "w:id"));
|
|
795
|
+
if (!Number.isNaN(id) && ctx.comments.defs.has(id)) {
|
|
796
|
+
ctx.comments.active.add(id);
|
|
797
|
+
if (!ctx.comments.used.includes(id)) ctx.comments.used.push(id);
|
|
798
|
+
}
|
|
799
|
+
} else if (node.name === "w:commentRangeEnd") {
|
|
800
|
+
const id = Number(attrOf(node, "w:id"));
|
|
801
|
+
if (!Number.isNaN(id)) ctx.comments.active.delete(id);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const attrs = {};
|
|
805
|
+
if (list) attrs.list = list;
|
|
806
|
+
if (align) attrs.align = align;
|
|
807
|
+
if (heading) attrs.heading = heading;
|
|
808
|
+
if (indent) attrs.indent = indent;
|
|
809
|
+
if (spacing) attrs.spacing = spacing;
|
|
810
|
+
if (tabs) attrs.tabs = tabs;
|
|
811
|
+
if (pageBreak) attrs.pageBreakBefore = true;
|
|
812
|
+
return ctx.schema.nodes["paragraph"].create(attrs, inline);
|
|
813
|
+
}
|
|
814
|
+
function resolveTabs(chain) {
|
|
815
|
+
const layer = lastWith(chain, "w:tabs");
|
|
816
|
+
if (!layer) return null;
|
|
817
|
+
const stops = [];
|
|
818
|
+
for (const tab of children(child(layer, "w:tabs"), "w:tab")) {
|
|
819
|
+
const val = attrOf(tab, "w:val") ?? "left";
|
|
820
|
+
if (val === "clear" || val === "bar") continue;
|
|
821
|
+
const pos = attrOf(tab, "w:pos");
|
|
822
|
+
if (pos === void 0) continue;
|
|
823
|
+
const stop = {
|
|
824
|
+
pos: twipsToPx(Number(pos)),
|
|
825
|
+
val: val === "right" || val === "center" || val === "decimal" ? val : "left"
|
|
826
|
+
};
|
|
827
|
+
const leader = attrOf(tab, "w:leader");
|
|
828
|
+
if (leader && leader !== "none") {
|
|
829
|
+
stop.leader = leader === "hyphen" ? "hyphen" : leader === "underscore" ? "underscore" : leader === "middleDot" ? "middleDot" : "dot";
|
|
830
|
+
}
|
|
831
|
+
stops.push(stop);
|
|
832
|
+
}
|
|
833
|
+
return stops.length > 0 ? stops.sort((a, b) => a.pos - b.pos) : null;
|
|
834
|
+
}
|
|
835
|
+
function lastWith(chain, childName) {
|
|
836
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
837
|
+
if (child(chain[i], childName)) return chain[i];
|
|
838
|
+
}
|
|
839
|
+
return void 0;
|
|
840
|
+
}
|
|
841
|
+
function resolveAlign(chain) {
|
|
842
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
843
|
+
const align = parseAlign(chain[i]);
|
|
844
|
+
if (align) return align;
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
function resolveIndent(chain) {
|
|
849
|
+
let left;
|
|
850
|
+
let right;
|
|
851
|
+
let firstLine;
|
|
852
|
+
let hanging;
|
|
853
|
+
for (const pPr of chain) {
|
|
854
|
+
const ind = child(pPr, "w:ind");
|
|
855
|
+
if (!ind) continue;
|
|
856
|
+
const px = (attr) => {
|
|
857
|
+
const v = attrOf(ind, attr);
|
|
858
|
+
return v === void 0 ? void 0 : twipsToPx(Number(v));
|
|
859
|
+
};
|
|
860
|
+
left = px("w:left") ?? px("w:start") ?? left;
|
|
861
|
+
right = px("w:right") ?? px("w:end") ?? right;
|
|
862
|
+
const fl = px("w:firstLine");
|
|
863
|
+
const hg = px("w:hanging");
|
|
864
|
+
if (fl !== void 0 || hg !== void 0) {
|
|
865
|
+
firstLine = fl;
|
|
866
|
+
hanging = hg;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const out = {};
|
|
870
|
+
if (left !== void 0) out.left = left;
|
|
871
|
+
if (right !== void 0) out.right = right;
|
|
872
|
+
if (hanging !== void 0) out.hanging = hanging;
|
|
873
|
+
else if (firstLine !== void 0) out.firstLine = firstLine;
|
|
874
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
875
|
+
}
|
|
876
|
+
function resolveSpacing(chain) {
|
|
877
|
+
const out = {};
|
|
878
|
+
for (const pPr of chain) {
|
|
879
|
+
const sp = child(pPr, "w:spacing");
|
|
880
|
+
if (!sp) continue;
|
|
881
|
+
const before = attrOf(sp, "w:before");
|
|
882
|
+
const after = attrOf(sp, "w:after");
|
|
883
|
+
const line = attrOf(sp, "w:line");
|
|
884
|
+
const rule = attrOf(sp, "w:lineRule");
|
|
885
|
+
if (before !== void 0) out.before = twipsToPx(Number(before));
|
|
886
|
+
if (after !== void 0) out.after = twipsToPx(Number(after));
|
|
887
|
+
if (line !== void 0) {
|
|
888
|
+
const lineRule = rule === "exact" || rule === "atLeast" ? rule : "auto";
|
|
889
|
+
out.lineRule = lineRule;
|
|
890
|
+
out.line = lineRule === "auto" ? Number(line) / 240 : twipsToPx(Number(line));
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
894
|
+
}
|
|
895
|
+
function parseAlign(pPr) {
|
|
896
|
+
switch (attrOf(child(pPr, "w:jc"), "w:val")) {
|
|
897
|
+
case "center":
|
|
898
|
+
return "center";
|
|
899
|
+
case "right":
|
|
900
|
+
case "end":
|
|
901
|
+
return "right";
|
|
902
|
+
case "both":
|
|
903
|
+
case "distribute":
|
|
904
|
+
return "justify";
|
|
905
|
+
case "left":
|
|
906
|
+
case "start":
|
|
907
|
+
return "left";
|
|
908
|
+
default:
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function parseList(pPr) {
|
|
913
|
+
const numPr = child(pPr, "w:numPr");
|
|
914
|
+
const numId = attrOf(child(numPr, "w:numId"), "w:val");
|
|
915
|
+
if (numId === void 0 || numId === "0") return null;
|
|
916
|
+
const ilvl = Number(attrOf(child(numPr, "w:ilvl"), "w:val") ?? "0");
|
|
917
|
+
return { numId, level: Number.isNaN(ilvl) ? 0 : ilvl };
|
|
918
|
+
}
|
|
919
|
+
function emptyCell(ctx) {
|
|
920
|
+
return ctx.schema.nodes["table_cell"].create(null, [ctx.schema.nodes["paragraph"].create()]);
|
|
921
|
+
}
|
|
922
|
+
function parseCellMargins(tbl) {
|
|
923
|
+
const mar = child(child(tbl, "w:tblPr"), "w:tblCellMar");
|
|
924
|
+
if (!mar) return null;
|
|
925
|
+
const side = (name) => {
|
|
926
|
+
const el = child(mar, name);
|
|
927
|
+
if (!el) return void 0;
|
|
928
|
+
if (attrOf(el, "w:type") === "nil") return 0;
|
|
929
|
+
const w = attrOf(el, "w:w");
|
|
930
|
+
return w === void 0 ? void 0 : twipsToPx(Number(w));
|
|
931
|
+
};
|
|
932
|
+
const out = {};
|
|
933
|
+
const left = side("w:left") ?? side("w:start");
|
|
934
|
+
const right = side("w:right") ?? side("w:end");
|
|
935
|
+
const top = side("w:top");
|
|
936
|
+
const bottom = side("w:bottom");
|
|
937
|
+
if (left !== void 0) out.left = left;
|
|
938
|
+
if (right !== void 0) out.right = right;
|
|
939
|
+
if (top !== void 0) out.top = top;
|
|
940
|
+
if (bottom !== void 0) out.bottom = bottom;
|
|
941
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
942
|
+
}
|
|
943
|
+
var BORDER_STYLE_IN = {
|
|
944
|
+
single: "solid",
|
|
945
|
+
thick: "solid",
|
|
946
|
+
dashed: "dashed",
|
|
947
|
+
dashSmallGap: "dashed",
|
|
948
|
+
dotted: "dotted",
|
|
949
|
+
dotDash: "dashed",
|
|
950
|
+
dotDotDash: "dashed",
|
|
951
|
+
double: "double"
|
|
952
|
+
};
|
|
953
|
+
function parseBorderSide(el) {
|
|
954
|
+
const val = attrOf(el, "w:val");
|
|
955
|
+
if (val === "none" || val === "nil") return false;
|
|
956
|
+
const sz = Number(attrOf(el, "w:sz") ?? "4");
|
|
957
|
+
const width = Math.max(0.75, sz / 8 * (96 / 72));
|
|
958
|
+
const style = BORDER_STYLE_IN[val ?? "single"] ?? "solid";
|
|
959
|
+
const colorAttr = attrOf(el, "w:color");
|
|
960
|
+
const color = colorAttr && colorAttr !== "auto" ? normalizeHex(colorAttr) ?? "#b0b0b0" : "#b0b0b0";
|
|
961
|
+
return { width, style, color };
|
|
962
|
+
}
|
|
963
|
+
function parseBordersEl(bordersEl, sides) {
|
|
964
|
+
if (!bordersEl) return null;
|
|
965
|
+
const out = {};
|
|
966
|
+
for (const side of sides) {
|
|
967
|
+
const el = child(bordersEl, `w:${side}`);
|
|
968
|
+
if (!el) continue;
|
|
969
|
+
out[side] = parseBorderSide(el);
|
|
970
|
+
}
|
|
971
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
972
|
+
}
|
|
973
|
+
var TABLE_SIDES = ["top", "bottom", "left", "right", "insideH", "insideV"];
|
|
974
|
+
var CELL_SIDES = ["top", "bottom", "left", "right"];
|
|
975
|
+
function parseTableBorders(tbl, ctx) {
|
|
976
|
+
const tblPr = child(tbl, "w:tblPr");
|
|
977
|
+
const styleId = attrOf(child(tblPr, "w:tblStyle"), "w:val");
|
|
978
|
+
const bordersEl = child(tblPr, "w:tblBorders") ?? ctx.styles.resolveTableBorders(styleId);
|
|
979
|
+
const out = parseBordersEl(bordersEl, TABLE_SIDES);
|
|
980
|
+
return out && Object.values(out).some(Boolean) ? out : null;
|
|
981
|
+
}
|
|
982
|
+
function parseTable(tbl, ctx) {
|
|
983
|
+
const grid = children(child(tbl, "w:tblGrid"), "w:gridCol").map(
|
|
984
|
+
(c) => Number(attrOf(c, "w:w") ?? "0")
|
|
985
|
+
);
|
|
986
|
+
const logicalRows = children(tbl, "w:tr").map((tr) => {
|
|
987
|
+
const cells = [];
|
|
988
|
+
let col = 0;
|
|
989
|
+
for (const tc of children(tr, "w:tc")) {
|
|
990
|
+
const tcPr = child(tc, "w:tcPr");
|
|
991
|
+
const colspan = Number(attrOf(child(tcPr, "w:gridSpan"), "w:val") ?? "1") || 1;
|
|
992
|
+
const vMergeEl = child(tcPr, "w:vMerge");
|
|
993
|
+
const vMerge = !vMergeEl ? null : attrOf(vMergeEl, "w:val") === "restart" ? "restart" : "continue";
|
|
994
|
+
const widths = grid.length ? grid.slice(col, col + colspan).map(twipsToPx) : [];
|
|
995
|
+
const background = normalizeHex(attrOf(child(tcPr, "w:shd"), "w:fill")) ?? null;
|
|
996
|
+
const vAlignVal = attrOf(child(tcPr, "w:vAlign"), "w:val");
|
|
997
|
+
const vAlign = vAlignVal === "center" || vAlignVal === "bottom" ? vAlignVal : null;
|
|
998
|
+
const borders2 = parseBordersEl(child(tcPr, "w:tcBorders"), CELL_SIDES);
|
|
999
|
+
const content = parseBlocks(tc, ctx);
|
|
1000
|
+
if (content.length === 0) content.push(ctx.schema.nodes["paragraph"].create());
|
|
1001
|
+
cells.push({ startCol: col, colspan, vMerge, colwidth: widths.length ? widths : null, background, vAlign, borders: borders2, content });
|
|
1002
|
+
col += colspan;
|
|
1003
|
+
}
|
|
1004
|
+
return cells;
|
|
1005
|
+
});
|
|
1006
|
+
const rowProps = children(tbl, "w:tr").map((tr) => {
|
|
1007
|
+
const trPr = child(tr, "w:trPr");
|
|
1008
|
+
const hdr = child(trPr, "w:tblHeader");
|
|
1009
|
+
const header = hdr ? attrOf(hdr, "w:val") !== "false" && attrOf(hdr, "w:val") !== "0" : false;
|
|
1010
|
+
const trH = child(trPr, "w:trHeight");
|
|
1011
|
+
const hv = attrOf(trH, "w:val");
|
|
1012
|
+
const height = hv !== void 0 ? { value: twipsToPx(Number(hv)), exact: attrOf(trH, "w:hRule") === "exact" } : null;
|
|
1013
|
+
const cs = child(trPr, "w:cantSplit");
|
|
1014
|
+
const cantSplit = cs ? attrOf(cs, "w:val") !== "false" && attrOf(cs, "w:val") !== "0" : false;
|
|
1015
|
+
return { header, height, cantSplit };
|
|
1016
|
+
});
|
|
1017
|
+
const colIndex = logicalRows.map((cells) => new Map(cells.map((c) => [c.startCol, c])));
|
|
1018
|
+
const rows = logicalRows.map((cells, r) => {
|
|
1019
|
+
const emitted = [];
|
|
1020
|
+
for (const cell of cells) {
|
|
1021
|
+
if (cell.vMerge === "continue") continue;
|
|
1022
|
+
let rowspan = 1;
|
|
1023
|
+
for (let r2 = r + 1; r2 < logicalRows.length; r2++) {
|
|
1024
|
+
const below = colIndex[r2].get(cell.startCol);
|
|
1025
|
+
if (below && below.vMerge === "continue") rowspan++;
|
|
1026
|
+
else break;
|
|
1027
|
+
}
|
|
1028
|
+
emitted.push(
|
|
1029
|
+
ctx.schema.nodes["table_cell"].create(
|
|
1030
|
+
{
|
|
1031
|
+
colspan: cell.colspan,
|
|
1032
|
+
rowspan,
|
|
1033
|
+
colwidth: cell.colwidth,
|
|
1034
|
+
background: cell.background,
|
|
1035
|
+
vAlign: cell.vAlign,
|
|
1036
|
+
borders: cell.borders
|
|
1037
|
+
},
|
|
1038
|
+
cell.content
|
|
1039
|
+
)
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
const rp = rowProps[r];
|
|
1043
|
+
const rowAttrs = {};
|
|
1044
|
+
if (rp.header) rowAttrs["header"] = true;
|
|
1045
|
+
if (rp.height) rowAttrs["height"] = rp.height;
|
|
1046
|
+
if (rp.cantSplit) rowAttrs["cantSplit"] = true;
|
|
1047
|
+
return ctx.schema.nodes["table_row"].create(
|
|
1048
|
+
Object.keys(rowAttrs).length > 0 ? rowAttrs : null,
|
|
1049
|
+
emitted.length > 0 ? emitted : [emptyCell(ctx)]
|
|
1050
|
+
);
|
|
1051
|
+
});
|
|
1052
|
+
const cellPadding = parseCellMargins(tbl);
|
|
1053
|
+
const borders = parseTableBorders(tbl, ctx);
|
|
1054
|
+
const jc = attrOf(child(child(tbl, "w:tblPr"), "w:jc"), "w:val");
|
|
1055
|
+
const attrs = {};
|
|
1056
|
+
if (cellPadding) attrs["cellPadding"] = cellPadding;
|
|
1057
|
+
if (borders) attrs["borders"] = borders;
|
|
1058
|
+
if (jc === "center" || jc === "right" || jc === "end") attrs["align"] = jc === "end" ? "right" : jc;
|
|
1059
|
+
return ctx.schema.nodes["table"].create(
|
|
1060
|
+
Object.keys(attrs).length > 0 ? attrs : null,
|
|
1061
|
+
rows.length > 0 ? rows : [ctx.schema.nodes["table_row"].create(null, [emptyCell(ctx)])]
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
function parseBlocks(parent, ctx) {
|
|
1065
|
+
const blocks = [];
|
|
1066
|
+
for (const node of unwrapSdt(parent.children)) {
|
|
1067
|
+
if (node.name === "w:p") blocks.push(parseParagraph(node, ctx));
|
|
1068
|
+
else if (node.name === "w:tbl") blocks.push(parseTable(node, ctx));
|
|
1069
|
+
}
|
|
1070
|
+
return blocks;
|
|
1071
|
+
}
|
|
1072
|
+
function parseColumns(sectPr) {
|
|
1073
|
+
const cols = sectPr && child(sectPr, "w:cols");
|
|
1074
|
+
const num = Number(attrOf(cols, "w:num") ?? "1");
|
|
1075
|
+
const explicit = cols ? children(cols, "w:col").length : 0;
|
|
1076
|
+
const count = Math.max(Number.isNaN(num) ? 1 : num, explicit, 1);
|
|
1077
|
+
const spaceTw = Number(attrOf(cols, "w:space") ?? "720");
|
|
1078
|
+
return { count, gap: twipsToPx(Number.isNaN(spaceTw) ? 720 : spaceTw) };
|
|
1079
|
+
}
|
|
1080
|
+
function sectionStartsNewPage(sectPr) {
|
|
1081
|
+
return attrOf(child(sectPr, "w:type"), "w:val") !== "continuous";
|
|
1082
|
+
}
|
|
1083
|
+
function parseBodyBlocks(body, ctx) {
|
|
1084
|
+
const blocks = [];
|
|
1085
|
+
const sections = [];
|
|
1086
|
+
let start = 0;
|
|
1087
|
+
for (const node of unwrapSdt(body.children)) {
|
|
1088
|
+
if (node.name === "w:p") {
|
|
1089
|
+
blocks.push(parseParagraph(node, ctx));
|
|
1090
|
+
const sectPr = child(child(node, "w:pPr"), "w:sectPr");
|
|
1091
|
+
if (sectPr) {
|
|
1092
|
+
sections.push({
|
|
1093
|
+
blockCount: blocks.length - start,
|
|
1094
|
+
columns: parseColumns(sectPr),
|
|
1095
|
+
newPage: sectionStartsNewPage(sectPr)
|
|
1096
|
+
});
|
|
1097
|
+
start = blocks.length;
|
|
1098
|
+
}
|
|
1099
|
+
} else if (node.name === "w:tbl") {
|
|
1100
|
+
blocks.push(parseTable(node, ctx));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const bodySectPr = child(body, "w:sectPr");
|
|
1104
|
+
if (blocks.length > start || sections.length === 0) {
|
|
1105
|
+
sections.push({
|
|
1106
|
+
blockCount: blocks.length - start,
|
|
1107
|
+
columns: parseColumns(bodySectPr),
|
|
1108
|
+
newPage: sectionStartsNewPage(bodySectPr)
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
return { blocks, sections };
|
|
1112
|
+
}
|
|
1113
|
+
async function readPart(zip, path) {
|
|
1114
|
+
const entry = zip.file(path);
|
|
1115
|
+
return entry ? entry.async("string") : void 0;
|
|
1116
|
+
}
|
|
1117
|
+
async function readPartRels(zip, partPath) {
|
|
1118
|
+
const slash = partPath.lastIndexOf("/");
|
|
1119
|
+
const relsPath = `${partPath.slice(0, slash + 1)}_rels/${partPath.slice(slash + 1)}.rels`;
|
|
1120
|
+
const xml = await readPart(zip, relsPath);
|
|
1121
|
+
return xml ? parseXml(xml) : void 0;
|
|
1122
|
+
}
|
|
1123
|
+
async function buildNotesRegistry(zip) {
|
|
1124
|
+
const bodies = { footnote: /* @__PURE__ */ new Map(), endnote: /* @__PURE__ */ new Map() };
|
|
1125
|
+
const load = async (path, root, tag, into) => {
|
|
1126
|
+
const xml = await readPart(zip, path);
|
|
1127
|
+
if (!xml) return;
|
|
1128
|
+
for (const note of children(child(parseXml(xml), root), tag)) {
|
|
1129
|
+
const id = attrOf(note, "w:id");
|
|
1130
|
+
const type = attrOf(note, "w:type");
|
|
1131
|
+
if (id === void 0 || Number(id) < 1 || type && type !== "normal") continue;
|
|
1132
|
+
into.set(id, note);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
await load("word/footnotes.xml", "w:footnotes", "w:footnote", bodies.footnote);
|
|
1136
|
+
await load("word/endnotes.xml", "w:endnotes", "w:endnote", bodies.endnote);
|
|
1137
|
+
const reg = {
|
|
1138
|
+
bodies,
|
|
1139
|
+
refs: [],
|
|
1140
|
+
counter: { footnote: 0, endnote: 0 },
|
|
1141
|
+
ref(kind, id) {
|
|
1142
|
+
const num = ++reg.counter[kind];
|
|
1143
|
+
reg.refs.push({ kind, id, num });
|
|
1144
|
+
return num;
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
return reg;
|
|
1148
|
+
}
|
|
1149
|
+
async function buildCommentsRegistry(zip) {
|
|
1150
|
+
const defs = /* @__PURE__ */ new Map();
|
|
1151
|
+
const paraToId = /* @__PURE__ */ new Map();
|
|
1152
|
+
const xml = await readPart(zip, "word/comments.xml");
|
|
1153
|
+
if (xml) {
|
|
1154
|
+
for (const c of children(child(parseXml(xml), "w:comments"), "w:comment")) {
|
|
1155
|
+
const id = Number(attrOf(c, "w:id"));
|
|
1156
|
+
if (Number.isNaN(id)) continue;
|
|
1157
|
+
const paraIds = children(c, "w:p").map((p) => attrOf(p, "w14:paraId")).filter((v) => !!v);
|
|
1158
|
+
for (const pid of paraIds) paraToId.set(pid, id);
|
|
1159
|
+
defs.set(id, {
|
|
1160
|
+
author: attrOf(c, "w:author") ?? "",
|
|
1161
|
+
date: attrOf(c, "w:date") ?? "",
|
|
1162
|
+
body: c,
|
|
1163
|
+
paraIds
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
const ext = /* @__PURE__ */ new Map();
|
|
1168
|
+
const extXml = await readPart(zip, "word/commentsExtended.xml");
|
|
1169
|
+
if (extXml) {
|
|
1170
|
+
for (const ex of children(child(parseXml(extXml), "w15:commentsEx"), "w15:commentEx")) {
|
|
1171
|
+
const paraId2 = attrOf(ex, "w15:paraId");
|
|
1172
|
+
if (!paraId2) continue;
|
|
1173
|
+
const done = attrOf(ex, "w15:done");
|
|
1174
|
+
ext.set(paraId2, {
|
|
1175
|
+
parentParaId: attrOf(ex, "w15:paraIdParent") ?? null,
|
|
1176
|
+
done: done === "1" || done === "true"
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return { defs, paraToId, ext, active: /* @__PURE__ */ new Set(), used: [] };
|
|
1181
|
+
}
|
|
1182
|
+
function collectText(node) {
|
|
1183
|
+
if (node.name === "w:t") return node.text ?? "";
|
|
1184
|
+
return node.children.map(collectText).join("");
|
|
1185
|
+
}
|
|
1186
|
+
function buildCommentsList(ctx) {
|
|
1187
|
+
const out = [];
|
|
1188
|
+
for (const id of ctx.comments.used) {
|
|
1189
|
+
const def = ctx.comments.defs.get(id);
|
|
1190
|
+
if (def) out.push({ id, author: def.author, date: def.date, text: collectText(def.body).trim() });
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1194
|
+
function commentBodyJSON(comment) {
|
|
1195
|
+
const paras = children(comment, "w:p").map((p) => {
|
|
1196
|
+
const text = collectText(p);
|
|
1197
|
+
return commentSchema.node("paragraph", null, text ? [commentSchema.text(text)] : []);
|
|
1198
|
+
});
|
|
1199
|
+
return commentSchema.node("doc", null, paras.length ? paras : [commentSchema.node("paragraph")]).toJSON();
|
|
1200
|
+
}
|
|
1201
|
+
function buildCommentNodes(ctx) {
|
|
1202
|
+
const { defs, paraToId, ext, used } = ctx.comments;
|
|
1203
|
+
const usedSet = new Set(used);
|
|
1204
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
1205
|
+
const resolvedOf = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const [id, def] of defs) {
|
|
1207
|
+
const exEntry = def.paraIds.map((p) => ext.get(p)).find(Boolean);
|
|
1208
|
+
const pid = exEntry?.parentParaId != null ? paraToId.get(exEntry.parentParaId) ?? null : null;
|
|
1209
|
+
parentOf.set(id, pid === id ? null : pid);
|
|
1210
|
+
resolvedOf.set(id, exEntry?.done ?? false);
|
|
1211
|
+
}
|
|
1212
|
+
const referenced = (id) => {
|
|
1213
|
+
for (let cur = id, n = 0; cur != null && n < 100; cur = parentOf.get(cur) ?? null, n++)
|
|
1214
|
+
if (usedSet.has(cur)) return true;
|
|
1215
|
+
return false;
|
|
1216
|
+
};
|
|
1217
|
+
const ids = [...used.filter((id) => defs.has(id)), ...[...defs.keys()].filter((id) => !usedSet.has(id))];
|
|
1218
|
+
const out = [];
|
|
1219
|
+
for (const id of ids) {
|
|
1220
|
+
const def = defs.get(id);
|
|
1221
|
+
if (!def || !referenced(id)) continue;
|
|
1222
|
+
const user = { id: def.author || "unknown", name: def.author || "Unknown" };
|
|
1223
|
+
out.push({
|
|
1224
|
+
id,
|
|
1225
|
+
parentId: parentOf.get(id) ?? null,
|
|
1226
|
+
user,
|
|
1227
|
+
date: def.date,
|
|
1228
|
+
body: commentBodyJSON(def.body),
|
|
1229
|
+
resolved: resolvedOf.get(id) ?? false
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1234
|
+
function noteBlocks(note, num, ctx) {
|
|
1235
|
+
const blocks = parseBlocks(note, ctx);
|
|
1236
|
+
const marker = ctx.schema.text(`${num}. `, [ctx.schema.marks["vertAlign"].create({ value: "super" })]);
|
|
1237
|
+
const first = blocks[0];
|
|
1238
|
+
if (first && first.type.name === "paragraph") {
|
|
1239
|
+
const kids = [marker];
|
|
1240
|
+
first.forEach((k) => kids.push(k));
|
|
1241
|
+
blocks[0] = ctx.schema.nodes["paragraph"].create(first.attrs, kids);
|
|
1242
|
+
} else {
|
|
1243
|
+
blocks.unshift(ctx.schema.nodes["paragraph"].create(null, [marker]));
|
|
1244
|
+
}
|
|
1245
|
+
return blocks;
|
|
1246
|
+
}
|
|
1247
|
+
function buildFootnotesMap(ctx) {
|
|
1248
|
+
const out = {};
|
|
1249
|
+
for (const { kind, id, num } of ctx.notes.refs) {
|
|
1250
|
+
if (kind !== "footnote") continue;
|
|
1251
|
+
const note = ctx.notes.bodies.footnote.get(id);
|
|
1252
|
+
if (note) out[num] = storyDoc(ctx, noteBlocks(note, num, ctx), null);
|
|
1253
|
+
}
|
|
1254
|
+
return out;
|
|
1255
|
+
}
|
|
1256
|
+
function buildNotesSection(ctx) {
|
|
1257
|
+
const endnotes = ctx.notes.refs.filter((r) => r.kind === "endnote");
|
|
1258
|
+
if (endnotes.length === 0) return [];
|
|
1259
|
+
const out = [
|
|
1260
|
+
ctx.schema.nodes["paragraph"].create({ spacing: { before: 12 } }, [
|
|
1261
|
+
ctx.schema.text("Ghi ch\xFA cu\u1ED1i", [ctx.schema.marks["strong"].create()])
|
|
1262
|
+
])
|
|
1263
|
+
];
|
|
1264
|
+
for (const { id, num } of endnotes) {
|
|
1265
|
+
const note = ctx.notes.bodies.endnote.get(id);
|
|
1266
|
+
if (note) out.push(...noteBlocks(note, num, ctx));
|
|
1267
|
+
}
|
|
1268
|
+
return out;
|
|
1269
|
+
}
|
|
1270
|
+
function storyDoc(ctx, blocks, numbering, sections = null, comments = null) {
|
|
1271
|
+
const attrs = {};
|
|
1272
|
+
if (numbering) attrs["numbering"] = numbering;
|
|
1273
|
+
if (sections) attrs["sections"] = sections;
|
|
1274
|
+
if (comments && comments.length > 0) attrs["comments"] = comments;
|
|
1275
|
+
return ctx.schema.nodes["doc"].create(
|
|
1276
|
+
Object.keys(attrs).length > 0 ? attrs : null,
|
|
1277
|
+
blocks.length > 0 ? blocks : [ctx.schema.nodes["paragraph"].create()]
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
async function extractMedia(zip) {
|
|
1281
|
+
const media = /* @__PURE__ */ new Map();
|
|
1282
|
+
for (const path of Object.keys(zip.files)) {
|
|
1283
|
+
if (!path.startsWith("word/media/")) continue;
|
|
1284
|
+
const entry = zip.file(path);
|
|
1285
|
+
if (!entry || entry.dir) continue;
|
|
1286
|
+
media.set(path, `data:${mimeOf(path)};base64,${await entry.async("base64")}`);
|
|
1287
|
+
}
|
|
1288
|
+
return media;
|
|
1289
|
+
}
|
|
1290
|
+
async function importDocx(input, opts) {
|
|
1291
|
+
const zip = await JSZip.loadAsync(input);
|
|
1292
|
+
const rawDocumentXml = await readPart(zip, "word/document.xml");
|
|
1293
|
+
if (rawDocumentXml === void 0) {
|
|
1294
|
+
throw new Error("bapbong-docx: word/document.xml not found in archive");
|
|
1295
|
+
}
|
|
1296
|
+
const stylesXml = await readPart(zip, "word/styles.xml");
|
|
1297
|
+
const numberingXml = await readPart(zip, "word/numbering.xml");
|
|
1298
|
+
const themeXml = await readPart(zip, "word/theme/theme1.xml");
|
|
1299
|
+
const resolveTheme = buildThemeResolver(themeXml ? parseXml(themeXml) : void 0);
|
|
1300
|
+
const styles = buildStyleRegistry(stylesXml ? parseXml(stylesXml) : void 0, resolveTheme);
|
|
1301
|
+
const numberingRoot = numberingXml ? parseXml(numberingXml) : void 0;
|
|
1302
|
+
const media = await extractMedia(zip);
|
|
1303
|
+
const notes = await buildNotesRegistry(zip);
|
|
1304
|
+
const comments = await buildCommentsRegistry(zip);
|
|
1305
|
+
const makeCtx = (rels) => ({
|
|
1306
|
+
styles,
|
|
1307
|
+
numbering: buildNumbering(numberingRoot),
|
|
1308
|
+
rels,
|
|
1309
|
+
media,
|
|
1310
|
+
resolveTheme,
|
|
1311
|
+
notes,
|
|
1312
|
+
comments,
|
|
1313
|
+
schema: opts?.schema ?? schema
|
|
1314
|
+
});
|
|
1315
|
+
const docRels = await readPart(zip, "word/_rels/document.xml.rels");
|
|
1316
|
+
const ctx = makeCtx(buildRels(docRels ? parseXml(docRels) : void 0));
|
|
1317
|
+
const body = child(child(parseXml(rawDocumentXml), "w:document"), "w:body");
|
|
1318
|
+
const parsed = body ? parseBodyBlocks(body, ctx) : { blocks: [], sections: [] };
|
|
1319
|
+
const footnotes = buildFootnotesMap(ctx);
|
|
1320
|
+
const endnoteBlocks = buildNotesSection(ctx);
|
|
1321
|
+
const { sections } = parsed;
|
|
1322
|
+
if (endnoteBlocks.length > 0 && sections.length > 0) {
|
|
1323
|
+
sections[sections.length - 1].blockCount += endnoteBlocks.length;
|
|
1324
|
+
}
|
|
1325
|
+
const multiSection = sections.length > 1 || sections.some((s) => s.columns.count > 1);
|
|
1326
|
+
const hasComments = !!ctx.schema.marks["comment"];
|
|
1327
|
+
const doc = storyDoc(
|
|
1328
|
+
ctx,
|
|
1329
|
+
[...parsed.blocks, ...endnoteBlocks],
|
|
1330
|
+
ctx.numbering.defs,
|
|
1331
|
+
multiSection ? sections : null,
|
|
1332
|
+
hasComments ? buildCommentNodes(ctx) : null
|
|
1333
|
+
);
|
|
1334
|
+
const headers = {};
|
|
1335
|
+
const footers = {};
|
|
1336
|
+
const sectPr = body ? child(body, "w:sectPr") : void 0;
|
|
1337
|
+
if (sectPr) {
|
|
1338
|
+
const collect = async (refName, store, root) => {
|
|
1339
|
+
for (const ref of children(sectPr, refName)) {
|
|
1340
|
+
const type = attrOf(ref, "w:type") ?? "default";
|
|
1341
|
+
const rId = attrOf(ref, "r:id");
|
|
1342
|
+
const target = rId ? ctx.rels.get(rId)?.target : void 0;
|
|
1343
|
+
if (!target || store[type]) continue;
|
|
1344
|
+
const partPath = `word/${target.replace(/^\/+/, "")}`;
|
|
1345
|
+
const xml = await readPart(zip, partPath);
|
|
1346
|
+
if (!xml) continue;
|
|
1347
|
+
const partCtx = makeCtx(buildRels(await readPartRels(zip, partPath)));
|
|
1348
|
+
const el = child(parseXml(xml), root);
|
|
1349
|
+
store[type] = storyDoc(partCtx, el ? parseBlocks(el, partCtx) : [], ctx.numbering.defs);
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1352
|
+
await collect("w:headerReference", headers, "w:hdr");
|
|
1353
|
+
await collect("w:footerReference", footers, "w:ftr");
|
|
1354
|
+
}
|
|
1355
|
+
const titlePg = sectPr ? isToggleOn(child(sectPr, "w:titlePg")) : false;
|
|
1356
|
+
const settingsXml = await readPart(zip, "word/settings.xml");
|
|
1357
|
+
const settings = settingsXml ? child(parseXml(settingsXml), "w:settings") : void 0;
|
|
1358
|
+
const evenAndOdd = settings ? isToggleOn(child(settings, "w:evenAndOddHeaders")) : false;
|
|
1359
|
+
return {
|
|
1360
|
+
doc,
|
|
1361
|
+
rawDocumentXml,
|
|
1362
|
+
headers,
|
|
1363
|
+
footers,
|
|
1364
|
+
footnotes,
|
|
1365
|
+
titlePg,
|
|
1366
|
+
evenAndOdd,
|
|
1367
|
+
comments: hasComments ? buildCommentsList(ctx) : [],
|
|
1368
|
+
page: parsePageGeometry(sectPr),
|
|
1369
|
+
raw: zip
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function isToggleOn(el) {
|
|
1373
|
+
if (!el) return false;
|
|
1374
|
+
const val = attrOf(el, "w:val");
|
|
1375
|
+
return val === void 0 || !["false", "0", "off"].includes(val);
|
|
1376
|
+
}
|
|
1377
|
+
function parsePageGeometry(sectPr) {
|
|
1378
|
+
const A4 = {
|
|
1379
|
+
width: 794,
|
|
1380
|
+
height: 1123,
|
|
1381
|
+
margin: { top: 96, right: 96, bottom: 96, left: 96 }
|
|
1382
|
+
};
|
|
1383
|
+
if (!sectPr) return A4;
|
|
1384
|
+
const pgSz = child(sectPr, "w:pgSz");
|
|
1385
|
+
const pgMar = child(sectPr, "w:pgMar");
|
|
1386
|
+
const px = (el, attr, fallback) => {
|
|
1387
|
+
const v = el && attrOf(el, attr);
|
|
1388
|
+
return v === void 0 || v === null ? fallback : twipsToPx(Number(v));
|
|
1389
|
+
};
|
|
1390
|
+
let width = px(pgSz, "w:w", A4.width);
|
|
1391
|
+
let height = px(pgSz, "w:h", A4.height);
|
|
1392
|
+
if (attrOf(pgSz, "w:orient") === "landscape" && height > width) {
|
|
1393
|
+
[width, height] = [height, width];
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
width,
|
|
1397
|
+
height,
|
|
1398
|
+
margin: {
|
|
1399
|
+
top: px(pgMar, "w:top", 96),
|
|
1400
|
+
right: px(pgMar, "w:right", 96),
|
|
1401
|
+
bottom: px(pgMar, "w:bottom", 96),
|
|
1402
|
+
left: px(pgMar, "w:left", 96)
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// packages/docx/src/lib/export.ts
|
|
1408
|
+
import JSZip2 from "jszip";
|
|
1409
|
+
import { commentSchema as commentSchema2 } from "@shadow-garden/bapbong-model";
|
|
1410
|
+
var W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
1411
|
+
var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
1412
|
+
var WP_NS = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
|
|
1413
|
+
var A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
1414
|
+
var PIC_NS = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
1415
|
+
var WPS_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
|
|
1416
|
+
var W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
1417
|
+
var W15_NS = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
1418
|
+
var CT_NS = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
1419
|
+
var PR_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
1420
|
+
var paraId = (id) => (268435456 + id).toString(16).toUpperCase();
|
|
1421
|
+
var pxToTwips = (px) => Math.round(px * 15);
|
|
1422
|
+
var pxToEmu = (px) => Math.round(px * 9525);
|
|
1423
|
+
var commentIdsOf = (node) => node.marks.find((m) => m.type.name === "comment")?.attrs["ids"] ?? [];
|
|
1424
|
+
var isInlineLeaf = (node) => node.isText || node.type.name === "image" || node.type.name === "hard_break";
|
|
1425
|
+
function esc(s) {
|
|
1426
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1427
|
+
}
|
|
1428
|
+
var MIME_EXT = {
|
|
1429
|
+
"image/png": "png",
|
|
1430
|
+
"image/jpeg": "jpeg",
|
|
1431
|
+
"image/jpg": "jpeg",
|
|
1432
|
+
"image/gif": "gif",
|
|
1433
|
+
"image/bmp": "bmp"
|
|
1434
|
+
};
|
|
1435
|
+
function runProps(marks) {
|
|
1436
|
+
const byName = new Map(marks.map((m) => [m.type.name, m]));
|
|
1437
|
+
const out = [];
|
|
1438
|
+
const fam = byName.get("fontFamily")?.attrs["family"];
|
|
1439
|
+
if (fam) out.push(`<w:rFonts w:ascii="${esc(fam)}" w:hAnsi="${esc(fam)}"/>`);
|
|
1440
|
+
if (byName.has("strong")) out.push("<w:b/>");
|
|
1441
|
+
if (byName.has("em")) out.push("<w:i/>");
|
|
1442
|
+
if (byName.has("underline")) out.push('<w:u w:val="single"/>');
|
|
1443
|
+
if (byName.has("strike")) out.push("<w:strike/>");
|
|
1444
|
+
const color = byName.get("textColor")?.attrs["color"];
|
|
1445
|
+
if (color) out.push(`<w:color w:val="${color.replace(/^#/, "")}"/>`);
|
|
1446
|
+
const size = byName.get("fontSize")?.attrs["size"];
|
|
1447
|
+
if (size != null) out.push(`<w:sz w:val="${Math.round(size * 2)}"/>`);
|
|
1448
|
+
const hl = byName.get("highlight")?.attrs["color"];
|
|
1449
|
+
if (hl) out.push(`<w:shd w:val="clear" w:color="auto" w:fill="${hl.replace(/^#/, "")}"/>`);
|
|
1450
|
+
const va = byName.get("vertAlign")?.attrs["value"];
|
|
1451
|
+
if (va) out.push(`<w:vertAlign w:val="${va === "sub" ? "subscript" : "superscript"}"/>`);
|
|
1452
|
+
return out.length ? `<w:rPr>${out.join("")}</w:rPr>` : "";
|
|
1453
|
+
}
|
|
1454
|
+
function anchorXml(float, cx, cy, n, graphic) {
|
|
1455
|
+
const dist = (k) => pxToEmu(float[k] ?? 0);
|
|
1456
|
+
const hRel = float["hRel"] === "page" ? "page" : "column";
|
|
1457
|
+
const vRel = float["vRel"] === "page" ? "page" : float["vRel"] === "margin" ? "margin" : "paragraph";
|
|
1458
|
+
const posH = float["hAlign"] ? `<wp:align>${float["hAlign"]}</wp:align>` : `<wp:posOffset>${pxToEmu(float["hOffset"] ?? 0)}</wp:posOffset>`;
|
|
1459
|
+
const posV = `<wp:posOffset>${pxToEmu(float["vOffset"] ?? 0)}</wp:posOffset>`;
|
|
1460
|
+
const wrap = float["wrap"] === "topAndBottom" ? "<wp:wrapTopAndBottom/>" : float["wrap"] === "none" ? "<wp:wrapNone/>" : '<wp:wrapSquare wrapText="bothSides"/>';
|
|
1461
|
+
return `<wp:anchor distT="${dist("distT")}" distB="${dist("distB")}" distL="${dist("distL")}" distR="${dist("distR")}" simplePos="0" relativeHeight="251658240" behindDoc="0" locked="0" layoutInCell="1" allowOverlap="1"><wp:simplePos x="0" y="0"/><wp:positionH relativeFrom="${hRel}">${posH}</wp:positionH><wp:positionV relativeFrom="${vRel}">${posV}</wp:positionV><wp:extent cx="${cx}" cy="${cy}"/><wp:effectExtent l="0" t="0" r="0" b="0"/>` + wrap + `<wp:docPr id="${n}" name="Shape ${n}"/><wp:cNvGraphicFramePr/>` + graphic + `</wp:anchor>`;
|
|
1462
|
+
}
|
|
1463
|
+
function rotAttr(node) {
|
|
1464
|
+
const deg = Number(node.attrs["rotation"]) || 0;
|
|
1465
|
+
return deg ? ` rot="${Math.round(deg * 6e4)}"` : "";
|
|
1466
|
+
}
|
|
1467
|
+
function shapeXml(node, ctx) {
|
|
1468
|
+
const s = node.attrs["shape"];
|
|
1469
|
+
const rot = rotAttr(node);
|
|
1470
|
+
const n = ctx.nextId++;
|
|
1471
|
+
const cx = pxToEmu(node.attrs["width"] ?? 0);
|
|
1472
|
+
const cy = pxToEmu(node.attrs["height"] ?? 0);
|
|
1473
|
+
const fill = s.fill ? `<a:solidFill><a:srgbClr val="${s.fill.replace(/^#/, "")}"/></a:solidFill>` : "<a:noFill/>";
|
|
1474
|
+
const ln = s.stroke ? `<a:ln w="${pxToEmu(s.strokeWidth ?? 1)}"><a:solidFill><a:srgbClr val="${s.stroke.replace(/^#/, "")}"/></a:solidFill></a:ln>` : "<a:ln><a:noFill/></a:ln>";
|
|
1475
|
+
const tb = node.attrs["textbox"];
|
|
1476
|
+
const txbx = tb ? `<wps:txbx><w:txbxContent>${tb.paragraphs.map((json) => paragraphXml(node.type.schema.nodeFromJSON(json), ctx)).join("")}</w:txbxContent></wps:txbx>` : "";
|
|
1477
|
+
const bodyPr = tb?.inset ? `<wps:bodyPr lIns="${pxToEmu(tb.inset.l)}" tIns="${pxToEmu(tb.inset.t)}" rIns="${pxToEmu(tb.inset.r)}" bIns="${pxToEmu(tb.inset.b)}"/>` : "<wps:bodyPr/>";
|
|
1478
|
+
const graphic = `<a:graphic><a:graphicData uri="${WPS_NS}"><wps:wsp><wps:cNvSpPr${tb ? ' txBox="1"' : ""}/><wps:spPr><a:xfrm${rot}${s.flipV ? ' flipV="1"' : ""}><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="${s.kind}"><a:avLst/></a:prstGeom>${fill}${ln}</wps:spPr>${txbx}${bodyPr}</wps:wsp></a:graphicData></a:graphic>`;
|
|
1479
|
+
const float = node.attrs["float"];
|
|
1480
|
+
const body = float ? anchorXml(float, cx, cy, n, graphic) : `<wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${n}" name="Shape ${n}"/>${graphic}</wp:inline>`;
|
|
1481
|
+
return `<w:r><w:drawing>${body}</w:drawing></w:r>`;
|
|
1482
|
+
}
|
|
1483
|
+
function imageXml(node, ctx) {
|
|
1484
|
+
if (node.attrs["shape"]) return shapeXml(node, ctx);
|
|
1485
|
+
const src = String(node.attrs["src"] ?? "");
|
|
1486
|
+
const m = /^data:([^;]+);base64,(.+)$/.exec(src);
|
|
1487
|
+
if (!m) return "";
|
|
1488
|
+
const ext = MIME_EXT[m[1].toLowerCase()] ?? "png";
|
|
1489
|
+
ctx.exts.add(ext);
|
|
1490
|
+
const n = ctx.nextId++;
|
|
1491
|
+
const rid = `rId${n}`;
|
|
1492
|
+
ctx.media.push({ path: `word/media/image${n}.${ext}`, base64: m[2] });
|
|
1493
|
+
ctx.rels.push(`<Relationship Id="${rid}" Type="${R_NS}/image" Target="media/image${n}.${ext}"/>`);
|
|
1494
|
+
const cx = pxToEmu(node.attrs["width"] ?? 96);
|
|
1495
|
+
const cy = pxToEmu(node.attrs["height"] ?? 96);
|
|
1496
|
+
return `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${n}" name="Picture ${n}"/><a:graphic><a:graphicData uri="${PIC_NS}"><pic:pic><pic:nvPicPr><pic:cNvPr id="${n}" name="image${n}.${ext}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${rid}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm${rotAttr(node)}><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
1497
|
+
}
|
|
1498
|
+
function inlineXml(node, ctx) {
|
|
1499
|
+
if (node.type.name === "hard_break") return "<w:r><w:br/></w:r>";
|
|
1500
|
+
if (node.type.name === "image") return imageXml(node, ctx);
|
|
1501
|
+
if (node.isText) {
|
|
1502
|
+
const fn = node.marks.find((m) => m.type.name === "footnote");
|
|
1503
|
+
if (fn) return `<w:r>${runProps(node.marks)}<w:footnoteReference w:id="${fn.attrs["num"]}"/></w:r>`;
|
|
1504
|
+
return `<w:r>${runProps(node.marks)}<w:t xml:space="preserve">${esc(node.text ?? "")}</w:t></w:r>`;
|
|
1505
|
+
}
|
|
1506
|
+
return "";
|
|
1507
|
+
}
|
|
1508
|
+
var linkHref = (node) => node.marks.find((m) => m.type.name === "link")?.attrs["href"] ?? null;
|
|
1509
|
+
function inlineUnit(node, ctx) {
|
|
1510
|
+
const inner = inlineXml(node, ctx);
|
|
1511
|
+
const href = linkHref(node);
|
|
1512
|
+
if (!href || !inner) return inner;
|
|
1513
|
+
if (href.startsWith("#")) return `<w:hyperlink w:anchor="${esc(href.slice(1))}">${inner}</w:hyperlink>`;
|
|
1514
|
+
const n = ctx.nextId++;
|
|
1515
|
+
ctx.rels.push(`<Relationship Id="rId${n}" Type="${R_NS}/hyperlink" Target="${esc(href)}" TargetMode="External"/>`);
|
|
1516
|
+
return `<w:hyperlink r:id="rId${n}">${inner}</w:hyperlink>`;
|
|
1517
|
+
}
|
|
1518
|
+
function inlineContent(node, ctx) {
|
|
1519
|
+
let out = "";
|
|
1520
|
+
node.forEach((child2) => {
|
|
1521
|
+
if (!isInlineLeaf(child2)) return;
|
|
1522
|
+
const ids = commentIdsOf(child2).filter((id) => ctx.knownComments.has(id));
|
|
1523
|
+
for (const id of ids) {
|
|
1524
|
+
if (!ctx.openComments.has(id)) {
|
|
1525
|
+
out += `<w:commentRangeStart w:id="${id}"/>`;
|
|
1526
|
+
ctx.openComments.add(id);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
out += inlineUnit(child2, ctx);
|
|
1530
|
+
const here = ctx.runIdx++;
|
|
1531
|
+
for (const id of [...ctx.openComments]) {
|
|
1532
|
+
if (ctx.lastRun.get(id) === here) {
|
|
1533
|
+
out += `<w:commentRangeEnd w:id="${id}"/><w:r><w:commentReference w:id="${id}"/></w:r>`;
|
|
1534
|
+
ctx.openComments.delete(id);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
return out;
|
|
1539
|
+
}
|
|
1540
|
+
function sectionSectPr(s) {
|
|
1541
|
+
const type = `<w:type w:val="${s.newPage ? "nextPage" : "continuous"}"/>`;
|
|
1542
|
+
const cols = s.columns.count > 1 ? `<w:cols w:num="${s.columns.count}" w:space="${pxToTwips(s.columns.gap)}"/>` : `<w:cols w:space="${pxToTwips(s.columns.gap)}"/>`;
|
|
1543
|
+
return `<w:sectPr>${type}${cols}</w:sectPr>`;
|
|
1544
|
+
}
|
|
1545
|
+
function paraProps(node) {
|
|
1546
|
+
const a = node.attrs;
|
|
1547
|
+
const out = [];
|
|
1548
|
+
const heading = a["heading"];
|
|
1549
|
+
if (heading) out.push(`<w:pStyle w:val="Heading${heading}"/>`);
|
|
1550
|
+
if (a["pageBreakBefore"]) out.push("<w:pageBreakBefore/>");
|
|
1551
|
+
const list = a["list"];
|
|
1552
|
+
if (list) out.push(`<w:numPr><w:ilvl w:val="${list.level}"/><w:numId w:val="${esc(list.numId)}"/></w:numPr>`);
|
|
1553
|
+
const sp = a["spacing"];
|
|
1554
|
+
if (sp) {
|
|
1555
|
+
const at = [];
|
|
1556
|
+
if (sp.before != null) at.push(`w:before="${pxToTwips(sp.before)}"`);
|
|
1557
|
+
if (sp.after != null) at.push(`w:after="${pxToTwips(sp.after)}"`);
|
|
1558
|
+
if (sp.line != null) {
|
|
1559
|
+
const auto = sp.lineRule === "auto" || sp.lineRule == null;
|
|
1560
|
+
at.push(`w:line="${auto ? Math.round(sp.line * 240) : pxToTwips(sp.line)}"`);
|
|
1561
|
+
at.push(`w:lineRule="${sp.lineRule ?? "auto"}"`);
|
|
1562
|
+
}
|
|
1563
|
+
if (at.length) out.push(`<w:spacing ${at.join(" ")}/>`);
|
|
1564
|
+
}
|
|
1565
|
+
const ind = a["indent"];
|
|
1566
|
+
if (ind) {
|
|
1567
|
+
const at = [];
|
|
1568
|
+
if (ind.left != null) at.push(`w:left="${pxToTwips(ind.left)}"`);
|
|
1569
|
+
if (ind.right != null) at.push(`w:right="${pxToTwips(ind.right)}"`);
|
|
1570
|
+
if (ind.hanging != null) at.push(`w:hanging="${pxToTwips(ind.hanging)}"`);
|
|
1571
|
+
else if (ind.firstLine != null) at.push(`w:firstLine="${pxToTwips(ind.firstLine)}"`);
|
|
1572
|
+
if (at.length) out.push(`<w:ind ${at.join(" ")}/>`);
|
|
1573
|
+
}
|
|
1574
|
+
const align = a["align"];
|
|
1575
|
+
if (align) out.push(`<w:jc w:val="${align === "justify" ? "both" : align}"/>`);
|
|
1576
|
+
return out.join("");
|
|
1577
|
+
}
|
|
1578
|
+
function paragraphXml(node, ctx, sectPr = "") {
|
|
1579
|
+
const props = paraProps(node) + sectPr;
|
|
1580
|
+
const pPr = props ? `<w:pPr>${props}</w:pPr>` : "";
|
|
1581
|
+
return `<w:p>${pPr}${inlineContent(node, ctx)}</w:p>`;
|
|
1582
|
+
}
|
|
1583
|
+
var TABLE_SIDES2 = ["top", "bottom", "left", "right", "insideH", "insideV"];
|
|
1584
|
+
var CELL_SIDES2 = ["top", "bottom", "left", "right"];
|
|
1585
|
+
var BORDER_STYLE_OUT = {
|
|
1586
|
+
solid: "single",
|
|
1587
|
+
dashed: "dashed",
|
|
1588
|
+
dotted: "dotted",
|
|
1589
|
+
double: "double"
|
|
1590
|
+
};
|
|
1591
|
+
function bordersXml(tag, borders, sides) {
|
|
1592
|
+
const inner = sides.filter((s) => s in borders).map((s) => {
|
|
1593
|
+
const side = borders[s];
|
|
1594
|
+
if (!side) return `<w:${s} w:val="nil"/>`;
|
|
1595
|
+
const sz = Math.max(2, Math.round(side.width * 6));
|
|
1596
|
+
const color = side.color === "#b0b0b0" ? "auto" : side.color.replace(/^#/, "");
|
|
1597
|
+
return `<w:${s} w:val="${BORDER_STYLE_OUT[side.style] ?? "single"}" w:sz="${sz}" w:space="0" w:color="${color}"/>`;
|
|
1598
|
+
}).join("");
|
|
1599
|
+
return inner ? `<${tag}>${inner}</${tag}>` : "";
|
|
1600
|
+
}
|
|
1601
|
+
function cellXml(cell, ctx) {
|
|
1602
|
+
const a = cell.attrs;
|
|
1603
|
+
const pr = [];
|
|
1604
|
+
const colwidth = a["colwidth"];
|
|
1605
|
+
if (colwidth?.length) pr.push(`<w:tcW w:w="${pxToTwips(colwidth.reduce((x, y) => x + y, 0))}" w:type="dxa"/>`);
|
|
1606
|
+
if (a["colspan"] > 1) pr.push(`<w:gridSpan w:val="${a["colspan"]}"/>`);
|
|
1607
|
+
const borders = a["borders"];
|
|
1608
|
+
if (borders) pr.push(bordersXml("w:tcBorders", borders, CELL_SIDES2));
|
|
1609
|
+
const bg = a["background"];
|
|
1610
|
+
if (bg) pr.push(`<w:shd w:val="clear" w:color="auto" w:fill="${bg.replace(/^#/, "")}"/>`);
|
|
1611
|
+
const vAlign = a["vAlign"];
|
|
1612
|
+
if (vAlign) pr.push(`<w:vAlign w:val="${vAlign}"/>`);
|
|
1613
|
+
let content = "";
|
|
1614
|
+
cell.forEach((b) => content += blockXml(b, ctx));
|
|
1615
|
+
if (!content) content = "<w:p/>";
|
|
1616
|
+
return `<w:tc><w:tcPr>${pr.join("")}</w:tcPr>${content}</w:tc>`;
|
|
1617
|
+
}
|
|
1618
|
+
function rowXml(row, ctx) {
|
|
1619
|
+
const pr = [];
|
|
1620
|
+
if (row.attrs["header"]) pr.push("<w:tblHeader/>");
|
|
1621
|
+
if (row.attrs["cantSplit"]) pr.push("<w:cantSplit/>");
|
|
1622
|
+
const h = row.attrs["height"];
|
|
1623
|
+
if (h) pr.push(`<w:trHeight w:val="${pxToTwips(h.value)}" w:hRule="${h.exact ? "exact" : "atLeast"}"/>`);
|
|
1624
|
+
const trPr = pr.length ? `<w:trPr>${pr.join("")}</w:trPr>` : "";
|
|
1625
|
+
let cells = "";
|
|
1626
|
+
row.forEach((c) => cells += cellXml(c, ctx));
|
|
1627
|
+
return `<w:tr>${trPr}${cells}</w:tr>`;
|
|
1628
|
+
}
|
|
1629
|
+
function tableXml(node, ctx) {
|
|
1630
|
+
const a = node.attrs;
|
|
1631
|
+
const pr = [];
|
|
1632
|
+
if (a["align"]) pr.push(`<w:jc w:val="${a["align"]}"/>`);
|
|
1633
|
+
const borders = a["borders"];
|
|
1634
|
+
if (borders) pr.push(bordersXml("w:tblBorders", borders, TABLE_SIDES2));
|
|
1635
|
+
const pad = a["cellPadding"];
|
|
1636
|
+
if (pad) {
|
|
1637
|
+
const m = ["top", "left", "bottom", "right"].filter((s) => pad[s] != null).map((s) => `<w:${s} w:w="${pxToTwips(pad[s])}" w:type="dxa"/>`).join("");
|
|
1638
|
+
if (m) pr.push(`<w:tblCellMar>${m}</w:tblCellMar>`);
|
|
1639
|
+
}
|
|
1640
|
+
const firstRow = node.firstChild;
|
|
1641
|
+
const grid = [];
|
|
1642
|
+
firstRow?.forEach((c) => c.attrs["colwidth"]?.forEach((w) => grid.push(w)));
|
|
1643
|
+
const gridXml = grid.length ? `<w:tblGrid>${grid.map((w) => `<w:gridCol w:w="${pxToTwips(w)}"/>`).join("")}</w:tblGrid>` : "";
|
|
1644
|
+
let rows = "";
|
|
1645
|
+
node.forEach((r) => rows += rowXml(r, ctx));
|
|
1646
|
+
return `<w:tbl><w:tblPr>${pr.join("")}</w:tblPr>${gridXml}${rows}</w:tbl>`;
|
|
1647
|
+
}
|
|
1648
|
+
function blockXml(node, ctx, sectPr = "") {
|
|
1649
|
+
if (node.type.name === "paragraph") return paragraphXml(node, ctx, sectPr);
|
|
1650
|
+
let out = node.type.name === "table" ? tableXml(node, ctx) : "";
|
|
1651
|
+
if (sectPr) out += `<w:p><w:pPr>${sectPr}</w:pPr></w:p>`;
|
|
1652
|
+
return out;
|
|
1653
|
+
}
|
|
1654
|
+
function sectionBoundaries(doc) {
|
|
1655
|
+
const sections = doc.attrs["sections"];
|
|
1656
|
+
const out = /* @__PURE__ */ new Map();
|
|
1657
|
+
if (!sections || sections.length < 2) return out;
|
|
1658
|
+
let acc = 0;
|
|
1659
|
+
for (let i = 0; i < sections.length - 1; i++) {
|
|
1660
|
+
acc += sections[i].blockCount;
|
|
1661
|
+
out.set(acc - 1, sectionSectPr(sections[i]));
|
|
1662
|
+
}
|
|
1663
|
+
return out;
|
|
1664
|
+
}
|
|
1665
|
+
function commentBodyXml(body, firstParaId) {
|
|
1666
|
+
let doc = null;
|
|
1667
|
+
try {
|
|
1668
|
+
doc = commentSchema2.nodeFromJSON(body);
|
|
1669
|
+
} catch {
|
|
1670
|
+
doc = null;
|
|
1671
|
+
}
|
|
1672
|
+
if (!doc) return `<w:p w14:paraId="${firstParaId}"/>`;
|
|
1673
|
+
const ps = [];
|
|
1674
|
+
doc.forEach((p, _o, i) => {
|
|
1675
|
+
let runs = "";
|
|
1676
|
+
p.forEach((inline) => {
|
|
1677
|
+
if (inline.isText) runs += `<w:r><w:t xml:space="preserve">${esc(inline.text ?? "")}</w:t></w:r>`;
|
|
1678
|
+
else if (inline.type.name === "mention") runs += `<w:r><w:t xml:space="preserve">@${esc(String(inline.attrs["label"] ?? ""))}</w:t></w:r>`;
|
|
1679
|
+
});
|
|
1680
|
+
ps.push(`<w:p${i === 0 ? ` w14:paraId="${firstParaId}"` : ""}>${runs}</w:p>`);
|
|
1681
|
+
});
|
|
1682
|
+
return ps.join("") || `<w:p w14:paraId="${firstParaId}"/>`;
|
|
1683
|
+
}
|
|
1684
|
+
var initialsOf = (name) => name.trim().split(/\s+/).map((w) => w[0] ?? "").join("").slice(0, 3).toUpperCase();
|
|
1685
|
+
function commentsXml(comments) {
|
|
1686
|
+
const body = comments.map(
|
|
1687
|
+
(c) => `<w:comment w:id="${c.id}" w:author="${esc(c.user.name)}" w:date="${esc(c.date)}" w:initials="${esc(initialsOf(c.user.name))}">${commentBodyXml(c.body, paraId(c.id))}</w:comment>`
|
|
1688
|
+
).join("");
|
|
1689
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1690
|
+
<w:comments xmlns:w="${W_NS}" xmlns:w14="${W14_NS}">${body}</w:comments>`;
|
|
1691
|
+
}
|
|
1692
|
+
function commentsExtendedXml(comments) {
|
|
1693
|
+
const body = comments.map((c) => {
|
|
1694
|
+
const parent = c.parentId != null ? ` w15:paraIdParent="${paraId(c.parentId)}"` : "";
|
|
1695
|
+
return `<w15:commentEx w15:paraId="${paraId(c.id)}"${parent} w15:done="${c.resolved ? 1 : 0}"/>`;
|
|
1696
|
+
}).join("");
|
|
1697
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1698
|
+
<w15:commentsEx xmlns:w15="${W15_NS}">${body}</w15:commentsEx>`;
|
|
1699
|
+
}
|
|
1700
|
+
var ROOT_RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1701
|
+
<Relationships xmlns="${PR_NS}"><Relationship Id="rId1" Type="${R_NS}/officeDocument" Target="word/document.xml"/></Relationships>`;
|
|
1702
|
+
function contentTypes(exts, hasComments) {
|
|
1703
|
+
const parts = [
|
|
1704
|
+
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
|
|
1705
|
+
'<Default Extension="xml" ContentType="application/xml"/>'
|
|
1706
|
+
];
|
|
1707
|
+
for (const ext of exts) parts.push(`<Default Extension="${ext}" ContentType="image/${ext === "jpg" ? "jpeg" : ext}"/>`);
|
|
1708
|
+
parts.push('<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>');
|
|
1709
|
+
if (hasComments) {
|
|
1710
|
+
parts.push('<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>');
|
|
1711
|
+
parts.push('<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>');
|
|
1712
|
+
}
|
|
1713
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1714
|
+
<Types xmlns="${CT_NS}">${parts.join("")}</Types>`;
|
|
1715
|
+
}
|
|
1716
|
+
function mergeContentTypes(xml, exts, hasComments) {
|
|
1717
|
+
let out = xml;
|
|
1718
|
+
const add = (frag, key) => {
|
|
1719
|
+
if (!out.includes(`"${key}"`)) out = out.replace("</Types>", `${frag}</Types>`);
|
|
1720
|
+
};
|
|
1721
|
+
for (const ext of exts) add(`<Default Extension="${ext}" ContentType="image/${ext === "jpg" ? "jpeg" : ext}"/>`, ext);
|
|
1722
|
+
if (hasComments) {
|
|
1723
|
+
add('<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>', "/word/comments.xml");
|
|
1724
|
+
add('<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>', "/word/commentsExtended.xml");
|
|
1725
|
+
}
|
|
1726
|
+
return out;
|
|
1727
|
+
}
|
|
1728
|
+
function extractBodySectPr(xml) {
|
|
1729
|
+
const all = xml.match(/<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>|<w:sectPr\b[^>]*\/>/g);
|
|
1730
|
+
return all ? all[all.length - 1] : "";
|
|
1731
|
+
}
|
|
1732
|
+
function mergeRels(xml, newRels) {
|
|
1733
|
+
const base = (xml ?? `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1734
|
+
<Relationships xmlns="${PR_NS}"></Relationships>`).replace(
|
|
1735
|
+
/<Relationship\b[^>]*Target="comments(?:Extended)?\.xml"[^>]*\/>/g,
|
|
1736
|
+
""
|
|
1737
|
+
);
|
|
1738
|
+
return base.replace("</Relationships>", `${newRels.join("")}</Relationships>`);
|
|
1739
|
+
}
|
|
1740
|
+
async function exportDocx(doc, opts) {
|
|
1741
|
+
const comments = doc.attrs["comments"] ?? [];
|
|
1742
|
+
const knownComments = new Set(comments.map((c) => c.id));
|
|
1743
|
+
const lastRun = /* @__PURE__ */ new Map();
|
|
1744
|
+
let idx = 0;
|
|
1745
|
+
doc.descendants((n) => {
|
|
1746
|
+
if (!isInlineLeaf(n)) return;
|
|
1747
|
+
for (const id of commentIdsOf(n)) if (knownComments.has(id)) lastRun.set(id, idx);
|
|
1748
|
+
idx++;
|
|
1749
|
+
});
|
|
1750
|
+
const ctx = {
|
|
1751
|
+
rels: [],
|
|
1752
|
+
media: [],
|
|
1753
|
+
exts: /* @__PURE__ */ new Set(),
|
|
1754
|
+
nextId: 100,
|
|
1755
|
+
knownComments,
|
|
1756
|
+
lastRun,
|
|
1757
|
+
openComments: /* @__PURE__ */ new Set(),
|
|
1758
|
+
runIdx: 0
|
|
1759
|
+
};
|
|
1760
|
+
const boundaries = sectionBoundaries(doc);
|
|
1761
|
+
let body = "";
|
|
1762
|
+
doc.forEach((block, _offset, i) => body += blockXml(block, ctx, boundaries.get(i)));
|
|
1763
|
+
const hasComments = comments.length > 0;
|
|
1764
|
+
if (hasComments) {
|
|
1765
|
+
ctx.rels.push(`<Relationship Id="rIdComments" Type="${R_NS}/comments" Target="comments.xml"/>`);
|
|
1766
|
+
ctx.rels.push(`<Relationship Id="rIdCommentsExt" Type="${R_NS}/commentsExtended" Target="commentsExtended.xml"/>`);
|
|
1767
|
+
}
|
|
1768
|
+
const zip = new JSZip2();
|
|
1769
|
+
let sectPr = "";
|
|
1770
|
+
if (opts?.carry) {
|
|
1771
|
+
const carry = opts.carry;
|
|
1772
|
+
for (const [path, f] of Object.entries(carry.files)) {
|
|
1773
|
+
if (!f.dir) zip.file(path, await f.async("uint8array"));
|
|
1774
|
+
}
|
|
1775
|
+
const ct = await carry.file("[Content_Types].xml")?.async("string");
|
|
1776
|
+
zip.file("[Content_Types].xml", ct ? mergeContentTypes(ct, ctx.exts, hasComments) : contentTypes(ctx.exts, hasComments));
|
|
1777
|
+
const rels = await carry.file("word/_rels/document.xml.rels")?.async("string");
|
|
1778
|
+
zip.file("word/_rels/document.xml.rels", mergeRels(rels, ctx.rels));
|
|
1779
|
+
const origDoc = await carry.file("word/document.xml")?.async("string");
|
|
1780
|
+
if (origDoc) sectPr = extractBodySectPr(origDoc);
|
|
1781
|
+
} else {
|
|
1782
|
+
zip.file("[Content_Types].xml", contentTypes(ctx.exts, hasComments));
|
|
1783
|
+
zip.file("_rels/.rels", ROOT_RELS);
|
|
1784
|
+
if (ctx.rels.length) {
|
|
1785
|
+
zip.file("word/_rels/document.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1786
|
+
<Relationships xmlns="${PR_NS}">${ctx.rels.join("")}</Relationships>`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1790
|
+
<w:document xmlns:w="${W_NS}" xmlns:r="${R_NS}" xmlns:wp="${WP_NS}" xmlns:a="${A_NS}" xmlns:pic="${PIC_NS}" xmlns:wps="${WPS_NS}"><w:body>${body}${sectPr}</w:body></w:document>`;
|
|
1791
|
+
zip.file("word/document.xml", documentXml);
|
|
1792
|
+
if (hasComments) {
|
|
1793
|
+
zip.file("word/comments.xml", commentsXml(comments));
|
|
1794
|
+
zip.file("word/commentsExtended.xml", commentsExtendedXml(comments));
|
|
1795
|
+
}
|
|
1796
|
+
for (const { path, base64 } of ctx.media) zip.file(path, base64, { base64: true });
|
|
1797
|
+
return zip.generateAsync({ type: "uint8array" });
|
|
1798
|
+
}
|
|
1799
|
+
export {
|
|
1800
|
+
exportDocx,
|
|
1801
|
+
importDocx
|
|
1802
|
+
};
|