@coding01/docsjs 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -5
- package/README.zh-CN.md +30 -5
- package/dist/{chunk-PRPDJOB7.js → chunk-IBVWD4UO.js} +359 -87
- package/dist/chunk-IBVWD4UO.js.map +1 -0
- package/dist/index.cjs +362 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +358 -86
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/dist/vue.cjs +358 -86
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-PRPDJOB7.js.map +0 -1
package/dist/react.cjs
CHANGED
|
@@ -79,6 +79,9 @@ function getAttr(node, name) {
|
|
|
79
79
|
function emuToPx(emu) {
|
|
80
80
|
return emu * 96 / 914400;
|
|
81
81
|
}
|
|
82
|
+
function twipToPx(twip) {
|
|
83
|
+
return twip * 96 / 1440;
|
|
84
|
+
}
|
|
82
85
|
function parseDrawingSizePx(drawing) {
|
|
83
86
|
const extentNode = queryAllByLocalName(drawing, "extent").find((node) => {
|
|
84
87
|
const parent = node.parentElement;
|
|
@@ -107,9 +110,7 @@ function imageDimensionAttributes(sizePx) {
|
|
|
107
110
|
}
|
|
108
111
|
return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
109
112
|
}
|
|
110
|
-
function parseAnchorPositionPx(
|
|
111
|
-
const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
|
|
112
|
-
if (!anchor) return { leftPx: null, topPx: null };
|
|
113
|
+
function parseAnchorPositionPx(anchor) {
|
|
113
114
|
let leftPx = null;
|
|
114
115
|
let topPx = null;
|
|
115
116
|
const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
|
|
@@ -124,34 +125,80 @@ function parseAnchorPositionPx(drawing) {
|
|
|
124
125
|
if (Number.isFinite(top)) topPx = emuToPx(top);
|
|
125
126
|
return { leftPx, topPx };
|
|
126
127
|
}
|
|
127
|
-
function parseAnchorWrapMode(
|
|
128
|
-
const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
|
|
129
|
-
if (!anchor) return null;
|
|
128
|
+
function parseAnchorWrapMode(anchor) {
|
|
130
129
|
if (directChildrenByLocalName(anchor, "wrapSquare")[0]) return "square";
|
|
131
130
|
if (directChildrenByLocalName(anchor, "wrapTight")[0]) return "tight";
|
|
132
131
|
if (directChildrenByLocalName(anchor, "wrapTopAndBottom")[0]) return "topAndBottom";
|
|
133
132
|
if (directChildrenByLocalName(anchor, "wrapNone")[0]) return "none";
|
|
134
133
|
return null;
|
|
135
134
|
}
|
|
136
|
-
function
|
|
137
|
-
|
|
135
|
+
function parseAnchorMeta(drawing) {
|
|
136
|
+
const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
|
|
137
|
+
if (!anchor) return null;
|
|
138
|
+
const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
|
|
139
|
+
const positionV = directChildrenByLocalName(anchor, "positionV")[0] ?? null;
|
|
140
|
+
const relativeFromH = getAttr(positionH, "relativeFrom");
|
|
141
|
+
const relativeFromV = getAttr(positionV, "relativeFrom");
|
|
142
|
+
const parseDistPx = (name) => {
|
|
143
|
+
const raw = getAttr(anchor, name);
|
|
144
|
+
const emu = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
145
|
+
return Number.isFinite(emu) && emu >= 0 ? emuToPx(emu) : null;
|
|
146
|
+
};
|
|
147
|
+
const rawHeight = getAttr(anchor, "relativeHeight");
|
|
148
|
+
const parsedHeight = rawHeight ? Number.parseInt(rawHeight, 10) : Number.NaN;
|
|
149
|
+
const boolAttr = (name, fallback) => {
|
|
150
|
+
const raw = (getAttr(anchor, name) ?? "").toLowerCase();
|
|
151
|
+
if (raw === "1" || raw === "true" || raw === "on") return true;
|
|
152
|
+
if (raw === "0" || raw === "false" || raw === "off") return false;
|
|
153
|
+
return fallback;
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
position: parseAnchorPositionPx(anchor),
|
|
157
|
+
wrapMode: parseAnchorWrapMode(anchor),
|
|
158
|
+
distTPx: parseDistPx("distT"),
|
|
159
|
+
distBPx: parseDistPx("distB"),
|
|
160
|
+
distLPx: parseDistPx("distL"),
|
|
161
|
+
distRPx: parseDistPx("distR"),
|
|
162
|
+
relativeFromH,
|
|
163
|
+
relativeFromV,
|
|
164
|
+
behindDoc: boolAttr("behindDoc", false),
|
|
165
|
+
allowOverlap: boolAttr("allowOverlap", true),
|
|
166
|
+
layoutInCell: boolAttr("layoutInCell", true),
|
|
167
|
+
relativeHeight: Number.isFinite(parsedHeight) ? parsedHeight : null
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function mergeImageStyle(baseAttrs, anchorMeta) {
|
|
171
|
+
if (!anchorMeta) return baseAttrs;
|
|
172
|
+
const { position, wrapMode } = anchorMeta;
|
|
173
|
+
if (position.leftPx === null && position.topPx === null) return baseAttrs;
|
|
138
174
|
const styleParts = [
|
|
139
175
|
"position:absolute",
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
176
|
+
position.leftPx !== null ? `left:${position.leftPx.toFixed(2)}px` : "",
|
|
177
|
+
position.topPx !== null ? `top:${position.topPx.toFixed(2)}px` : "",
|
|
178
|
+
`z-index:${anchorMeta.behindDoc ? 0 : anchorMeta.relativeHeight ?? 3}`,
|
|
179
|
+
anchorMeta.distTPx !== null ? `margin-top:${anchorMeta.distTPx.toFixed(2)}px` : "",
|
|
180
|
+
anchorMeta.distBPx !== null ? `margin-bottom:${anchorMeta.distBPx.toFixed(2)}px` : "",
|
|
181
|
+
anchorMeta.distLPx !== null ? `margin-left:${anchorMeta.distLPx.toFixed(2)}px` : "",
|
|
182
|
+
anchorMeta.distRPx !== null ? `margin-right:${anchorMeta.distRPx.toFixed(2)}px` : ""
|
|
143
183
|
].filter((x) => x.length > 0);
|
|
144
184
|
if (wrapMode === "topAndBottom") {
|
|
145
|
-
styleParts.push("display:block");
|
|
146
|
-
}
|
|
185
|
+
styleParts.push("display:block", "clear:both");
|
|
186
|
+
}
|
|
187
|
+
const anchorAttrs = [
|
|
188
|
+
`data-word-anchor="1"`,
|
|
189
|
+
wrapMode ? `data-word-wrap="${wrapMode}"` : "",
|
|
190
|
+
anchorMeta.relativeFromH ? `data-word-anchor-relh="${escapeHtml(anchorMeta.relativeFromH)}"` : "",
|
|
191
|
+
anchorMeta.relativeFromV ? `data-word-anchor-relv="${escapeHtml(anchorMeta.relativeFromV)}"` : "",
|
|
192
|
+
anchorMeta.behindDoc ? `data-word-anchor-behind="1"` : `data-word-anchor-behind="0"`,
|
|
193
|
+
anchorMeta.allowOverlap ? `data-word-anchor-overlap="1"` : `data-word-anchor-overlap="0"`,
|
|
194
|
+
anchorMeta.layoutInCell ? `data-word-anchor-layout-cell="1"` : `data-word-anchor-layout-cell="0"`
|
|
195
|
+
].filter((x) => x.length > 0).join(" ");
|
|
147
196
|
if (!baseAttrs.includes("style=")) {
|
|
148
|
-
|
|
149
|
-
return `${baseAttrs} style="${styleParts.join(";")}" data-word-anchor="1"${wrapAttr}`;
|
|
197
|
+
return `${baseAttrs} style="${styleParts.join(";")}" ${anchorAttrs}`;
|
|
150
198
|
}
|
|
151
199
|
return baseAttrs.replace(/style="([^"]*)"/, (_m, styleText) => {
|
|
152
200
|
const merged = [styleText, ...styleParts].filter((x) => x.length > 0).join(";");
|
|
153
|
-
|
|
154
|
-
return `style="${merged}" data-word-anchor="1"${wrapAttr}`;
|
|
201
|
+
return `style="${merged}" ${anchorAttrs}`;
|
|
155
202
|
});
|
|
156
203
|
}
|
|
157
204
|
function parseDocRelsMap(relsXmlText) {
|
|
@@ -177,11 +224,16 @@ function extToMime(ext) {
|
|
|
177
224
|
if (lower === "svg") return "image/svg+xml";
|
|
178
225
|
return "application/octet-stream";
|
|
179
226
|
}
|
|
227
|
+
function normalizeWordPath(relTarget) {
|
|
228
|
+
const normalized = relTarget.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
229
|
+
if (normalized.startsWith("word/")) return normalized;
|
|
230
|
+
if (normalized.startsWith("../")) return `word/${normalized.replace(/^(\.\.\/)+/, "")}`;
|
|
231
|
+
return `word/${normalized}`;
|
|
232
|
+
}
|
|
180
233
|
async function imageRidToDataUrl(zip, relMap, rid) {
|
|
181
234
|
const relTarget = relMap[rid];
|
|
182
235
|
if (!relTarget) return null;
|
|
183
|
-
const
|
|
184
|
-
const path = normalized.startsWith("word/") ? normalized : `word/${normalized}`;
|
|
236
|
+
const path = normalizeWordPath(relTarget);
|
|
185
237
|
const file = zip.file(path);
|
|
186
238
|
if (!file) return null;
|
|
187
239
|
const base64 = await file.async("base64");
|
|
@@ -189,6 +241,55 @@ async function imageRidToDataUrl(zip, relMap, rid) {
|
|
|
189
241
|
const mime = extToMime(ext);
|
|
190
242
|
return `data:${mime};base64,${base64}`;
|
|
191
243
|
}
|
|
244
|
+
async function readXmlByRid(zip, relMap, rid) {
|
|
245
|
+
const relTarget = relMap[rid];
|
|
246
|
+
if (!relTarget) return null;
|
|
247
|
+
const path = normalizeWordPath(relTarget);
|
|
248
|
+
const file = zip.file(path);
|
|
249
|
+
return file ? file.async("string") : null;
|
|
250
|
+
}
|
|
251
|
+
function parseChartType(chartDoc) {
|
|
252
|
+
const known = ["barChart", "lineChart", "pieChart", "areaChart", "scatterChart", "radarChart", "doughnutChart"];
|
|
253
|
+
for (const type of known) {
|
|
254
|
+
if (queryByLocalName(chartDoc, type)) return type.replace(/Chart$/, "");
|
|
255
|
+
}
|
|
256
|
+
return "unknown";
|
|
257
|
+
}
|
|
258
|
+
function parseChartSummary(chartXmlText) {
|
|
259
|
+
const chartDoc = parseXml(chartXmlText);
|
|
260
|
+
const title = queryAllByLocalName(chartDoc, "t").map((n) => (n.textContent ?? "").trim()).find((v) => v.length > 0) ?? "Chart";
|
|
261
|
+
const seriesCount = queryAllByLocalName(chartDoc, "ser").length;
|
|
262
|
+
const pointCount = queryAllByLocalName(chartDoc, "pt").length;
|
|
263
|
+
const type = parseChartType(chartDoc);
|
|
264
|
+
return { title, type, seriesCount, pointCount };
|
|
265
|
+
}
|
|
266
|
+
function extractSmartArtText(diagramXmlText) {
|
|
267
|
+
const diagramDoc = parseXml(diagramXmlText);
|
|
268
|
+
return queryAllByLocalName(diagramDoc, "t").map((n) => (n.textContent ?? "").trim()).filter((v) => v.length > 0).slice(0, 12);
|
|
269
|
+
}
|
|
270
|
+
function ommlNodeToText(node) {
|
|
271
|
+
if (node.localName === "t") return node.textContent ?? "";
|
|
272
|
+
if (node.localName === "f") {
|
|
273
|
+
const num = queryByLocalName(node, "num");
|
|
274
|
+
const den = queryByLocalName(node, "den");
|
|
275
|
+
return `(${num ? ommlNodeToText(num) : "?"})/(${den ? ommlNodeToText(den) : "?"})`;
|
|
276
|
+
}
|
|
277
|
+
if (node.localName === "sSup") {
|
|
278
|
+
const e = queryByLocalName(node, "e");
|
|
279
|
+
const sup = queryByLocalName(node, "sup");
|
|
280
|
+
return `${e ? ommlNodeToText(e) : ""}^(${sup ? ommlNodeToText(sup) : ""})`;
|
|
281
|
+
}
|
|
282
|
+
if (node.localName === "sSub") {
|
|
283
|
+
const e = queryByLocalName(node, "e");
|
|
284
|
+
const sub = queryByLocalName(node, "sub");
|
|
285
|
+
return `${e ? ommlNodeToText(e) : ""}_(${sub ? ommlNodeToText(sub) : ""})`;
|
|
286
|
+
}
|
|
287
|
+
if (node.localName === "rad") {
|
|
288
|
+
const e = queryByLocalName(node, "e");
|
|
289
|
+
return `sqrt(${e ? ommlNodeToText(e) : ""})`;
|
|
290
|
+
}
|
|
291
|
+
return Array.from(node.children).map((child) => ommlNodeToText(child)).join("");
|
|
292
|
+
}
|
|
192
293
|
function runStyleToCss(rPr) {
|
|
193
294
|
if (!rPr) return "";
|
|
194
295
|
const declarations = [];
|
|
@@ -307,44 +408,66 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
|
|
|
307
408
|
const tag = paragraphTag(paragraph);
|
|
308
409
|
const alignStyle = paragraphAlignStyle(paragraph);
|
|
309
410
|
const dataAttr = paragraphDataAttr(paragraphIndex);
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
411
|
+
const hasRenderableNode = queryAllByLocalName(paragraph, "r").length > 0 || queryAllByLocalName(paragraph, "oMath").length > 0 || queryAllByLocalName(paragraph, "oMathPara").length > 0;
|
|
412
|
+
if (!hasRenderableNode) {
|
|
312
413
|
return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}><br/></${tag}>`;
|
|
313
414
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
415
|
+
function parseRevisionMeta(node, type) {
|
|
416
|
+
return {
|
|
417
|
+
type,
|
|
418
|
+
id: getAttr(node, "w:id") ?? getAttr(node, "id"),
|
|
419
|
+
author: getAttr(node, "w:author") ?? getAttr(node, "author"),
|
|
420
|
+
date: getAttr(node, "w:date") ?? getAttr(node, "date")
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function inferRevisionMeta(run, fallback) {
|
|
424
|
+
if (fallback) return fallback;
|
|
425
|
+
let cursor = run;
|
|
426
|
+
while (cursor) {
|
|
427
|
+
if (cursor.localName === "ins") return parseRevisionMeta(cursor, "ins");
|
|
428
|
+
if (cursor.localName === "del") return parseRevisionMeta(cursor, "del");
|
|
429
|
+
if (cursor.localName === "p") break;
|
|
430
|
+
cursor = cursor.parentElement;
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
318
433
|
}
|
|
319
|
-
|
|
434
|
+
function revisionMetaAttrs(meta) {
|
|
435
|
+
const attrs = [`data-word-revision="${meta.type}"`];
|
|
436
|
+
if (meta.id) attrs.push(`data-word-revision-id="${escapeHtml(meta.id)}"`);
|
|
437
|
+
if (meta.author) attrs.push(`data-word-revision-author="${escapeHtml(meta.author)}"`);
|
|
438
|
+
if (meta.date) attrs.push(`data-word-revision-date="${escapeHtml(meta.date)}"`);
|
|
439
|
+
return attrs.join(" ");
|
|
440
|
+
}
|
|
441
|
+
async function runToHtml(run, revisionFallback) {
|
|
442
|
+
const result = [];
|
|
320
443
|
const rPr = queryByLocalName(run, "rPr");
|
|
321
444
|
const css = runStyleToCss(rPr);
|
|
322
445
|
const footnoteRef = queryByLocalName(run, "footnoteReference");
|
|
323
446
|
const footnoteId = getAttr(footnoteRef, "w:id") ?? getAttr(footnoteRef, "id");
|
|
324
447
|
if (footnoteId && footnotesMap[footnoteId]) {
|
|
325
448
|
usedFootnoteIds.push(footnoteId);
|
|
326
|
-
|
|
449
|
+
result.push(
|
|
327
450
|
`<sup data-word-footnote-ref="${footnoteId}"><a href="#word-footnote-${footnoteId}">[${footnoteId}]</a></sup>`
|
|
328
451
|
);
|
|
329
|
-
|
|
452
|
+
return result;
|
|
330
453
|
}
|
|
331
454
|
const endnoteRef = queryByLocalName(run, "endnoteReference");
|
|
332
455
|
const endnoteId = getAttr(endnoteRef, "w:id") ?? getAttr(endnoteRef, "id");
|
|
333
456
|
if (endnoteId && endnotesMap[endnoteId]) {
|
|
334
457
|
usedEndnoteIds.push(endnoteId);
|
|
335
|
-
|
|
458
|
+
result.push(
|
|
336
459
|
`<sup data-word-endnote-ref="${endnoteId}"><a href="#word-endnote-${endnoteId}">[${endnoteId}]</a></sup>`
|
|
337
460
|
);
|
|
338
|
-
|
|
461
|
+
return result;
|
|
339
462
|
}
|
|
340
463
|
const commentRef = queryByLocalName(run, "commentReference");
|
|
341
464
|
const commentId = getAttr(commentRef, "w:id") ?? getAttr(commentRef, "id");
|
|
342
465
|
if (commentId && commentsMap[commentId]) {
|
|
343
466
|
usedCommentIds.push(commentId);
|
|
344
|
-
|
|
467
|
+
result.push(
|
|
345
468
|
`<sup data-word-comment-ref="${commentId}"><a href="#word-comment-${commentId}">[c${commentId}]</a></sup>`
|
|
346
469
|
);
|
|
347
|
-
|
|
470
|
+
return result;
|
|
348
471
|
}
|
|
349
472
|
const drawing = queryByLocalName(run, "drawing");
|
|
350
473
|
if (drawing) {
|
|
@@ -355,13 +478,35 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
|
|
|
355
478
|
if (src) {
|
|
356
479
|
const imageSize = parseDrawingSizePx(drawing);
|
|
357
480
|
const dimensionAttrs = imageDimensionAttributes(imageSize);
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
continue;
|
|
481
|
+
const anchorMeta = parseAnchorMeta(drawing);
|
|
482
|
+
const attrs = mergeImageStyle(dimensionAttrs, anchorMeta);
|
|
483
|
+
result.push(`<img src="${src}" alt="word-image"${attrs}/>`);
|
|
484
|
+
return result;
|
|
363
485
|
}
|
|
364
486
|
}
|
|
487
|
+
const chartRef = queryByLocalName(drawing, "chart");
|
|
488
|
+
const chartRid = getAttr(chartRef, "r:id") ?? getAttr(chartRef, "id");
|
|
489
|
+
if (chartRid) {
|
|
490
|
+
const chartXmlText = await readXmlByRid(zip, relMap, chartRid);
|
|
491
|
+
if (chartXmlText) {
|
|
492
|
+
const summary = parseChartSummary(chartXmlText);
|
|
493
|
+
result.push(
|
|
494
|
+
`<figure data-word-chart="1" data-word-chart-type="${summary.type}" data-word-chart-series="${summary.seriesCount}" data-word-chart-points="${summary.pointCount}"><figcaption>${escapeHtml(summary.title)}</figcaption><div>Chart(${escapeHtml(summary.type)}): series=${summary.seriesCount}, points=${summary.pointCount}</div></figure>`
|
|
495
|
+
);
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const smartArtRef = queryByLocalName(drawing, "relIds");
|
|
500
|
+
const smartArtRid = getAttr(smartArtRef, "r:dm") ?? getAttr(smartArtRef, "dm");
|
|
501
|
+
if (smartArtRid) {
|
|
502
|
+
const diagramXmlText = await readXmlByRid(zip, relMap, smartArtRid);
|
|
503
|
+
const textItems = diagramXmlText ? extractSmartArtText(diagramXmlText) : [];
|
|
504
|
+
const preview = textItems.length > 0 ? `: ${escapeHtml(textItems.join(" / "))}` : "";
|
|
505
|
+
result.push(
|
|
506
|
+
`<figure data-word-smartart="1" data-word-smartart-items="${textItems.length}"><figcaption>SmartArt fallback${preview}</figcaption></figure>`
|
|
507
|
+
);
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
365
510
|
}
|
|
366
511
|
const texts = queryAllByLocalName(run, "t").map((t) => t.textContent ?? "").join("");
|
|
367
512
|
const delTexts = queryAllByLocalName(run, "delText").map((t) => t.textContent ?? "").join("");
|
|
@@ -372,40 +517,66 @@ async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotes
|
|
|
372
517
|
}).length;
|
|
373
518
|
const lineBreakCount = Math.max(0, brNodes.length - pageBreakCount);
|
|
374
519
|
const runText2 = `${escapeHtml(texts || delTexts)}${"<br/>".repeat(lineBreakCount)}`;
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
cursor = cursor.parentElement;
|
|
389
|
-
}
|
|
390
|
-
if (css) {
|
|
391
|
-
const span = `<span style="${css}">${runText2}</span>`;
|
|
392
|
-
if (revisionType) {
|
|
393
|
-
const tag2 = revisionType === "ins" ? "ins" : "del";
|
|
394
|
-
parts.push(`<${tag2} data-word-revision="${revisionType}">${span}</${tag2}>`);
|
|
395
|
-
} else {
|
|
396
|
-
parts.push(span);
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
if (revisionType) {
|
|
400
|
-
const tag2 = revisionType === "ins" ? "ins" : "del";
|
|
401
|
-
parts.push(`<${tag2} data-word-revision="${revisionType}">${runText2}</${tag2}>`);
|
|
520
|
+
if (runText2) {
|
|
521
|
+
const revisionMeta = inferRevisionMeta(run, revisionFallback);
|
|
522
|
+
if (css) {
|
|
523
|
+
const span = `<span style="${css}">${runText2}</span>`;
|
|
524
|
+
if (revisionMeta) {
|
|
525
|
+
const tagName = revisionMeta.type === "ins" ? "ins" : "del";
|
|
526
|
+
result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${span}</${tagName}>`);
|
|
527
|
+
} else {
|
|
528
|
+
result.push(span);
|
|
529
|
+
}
|
|
530
|
+
} else if (revisionMeta) {
|
|
531
|
+
const tagName = revisionMeta.type === "ins" ? "ins" : "del";
|
|
532
|
+
result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${runText2}</${tagName}>`);
|
|
402
533
|
} else {
|
|
403
|
-
|
|
534
|
+
result.push(runText2);
|
|
404
535
|
}
|
|
405
536
|
}
|
|
406
537
|
for (let i = 0; i < pageBreakCount; i += 1) {
|
|
407
|
-
|
|
538
|
+
result.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
541
|
+
}
|
|
542
|
+
async function nodeToHtml(node, revisionFallback) {
|
|
543
|
+
if (node.localName === "commentRangeStart") {
|
|
544
|
+
const id = getAttr(node, "w:id") ?? getAttr(node, "id");
|
|
545
|
+
return id ? [`<span data-word-comment-range-start="${id}"></span>`] : [];
|
|
546
|
+
}
|
|
547
|
+
if (node.localName === "commentRangeEnd") {
|
|
548
|
+
const id = getAttr(node, "w:id") ?? getAttr(node, "id");
|
|
549
|
+
return id ? [`<span data-word-comment-range-end="${id}"></span>`] : [];
|
|
550
|
+
}
|
|
551
|
+
if (node.localName === "r") {
|
|
552
|
+
return runToHtml(node, revisionFallback);
|
|
408
553
|
}
|
|
554
|
+
if (node.localName === "oMath" || node.localName === "oMathPara") {
|
|
555
|
+
const linear = ommlNodeToText(node).trim();
|
|
556
|
+
if (!linear) return [];
|
|
557
|
+
return [`<span data-word-omml="1">${escapeHtml(linear)}</span>`];
|
|
558
|
+
}
|
|
559
|
+
if (node.localName === "ins" || node.localName === "del") {
|
|
560
|
+
const scopedMeta = parseRevisionMeta(node, node.localName === "ins" ? "ins" : "del");
|
|
561
|
+
const nested2 = [];
|
|
562
|
+
for (const child of Array.from(node.children)) {
|
|
563
|
+
nested2.push(...await nodeToHtml(child, scopedMeta));
|
|
564
|
+
}
|
|
565
|
+
return nested2;
|
|
566
|
+
}
|
|
567
|
+
const nested = [];
|
|
568
|
+
for (const child of Array.from(node.children)) {
|
|
569
|
+
nested.push(...await nodeToHtml(child, revisionFallback));
|
|
570
|
+
}
|
|
571
|
+
return nested;
|
|
572
|
+
}
|
|
573
|
+
const parts = [];
|
|
574
|
+
const renderedPageBreakCount = queryAllByLocalName(paragraph, "lastRenderedPageBreak").length;
|
|
575
|
+
for (let i = 0; i < renderedPageBreakCount; i += 1) {
|
|
576
|
+
parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
|
|
577
|
+
}
|
|
578
|
+
for (const child of Array.from(paragraph.children)) {
|
|
579
|
+
parts.push(...await nodeToHtml(child, null));
|
|
409
580
|
}
|
|
410
581
|
const content = parts.join("") || "<br/>";
|
|
411
582
|
return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}>${content}</${tag}>`;
|
|
@@ -435,6 +606,101 @@ function parseTcVMerge(tc) {
|
|
|
435
606
|
const rawVal = (getAttr(vMerge, "w:val") ?? getAttr(vMerge, "val") ?? "continue").toLowerCase();
|
|
436
607
|
return rawVal === "restart" ? "restart" : "continue";
|
|
437
608
|
}
|
|
609
|
+
function parseTblGridWidthsPx(table) {
|
|
610
|
+
const grid = directChildrenByLocalName(table, "tblGrid")[0] ?? null;
|
|
611
|
+
if (!grid) return [];
|
|
612
|
+
return directChildrenByLocalName(grid, "gridCol").map((col) => {
|
|
613
|
+
const raw = getAttr(col, "w:w") ?? getAttr(col, "w");
|
|
614
|
+
const twip = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
615
|
+
return Number.isFinite(twip) && twip > 0 ? twipToPx(twip) : 0;
|
|
616
|
+
}).filter((px) => px > 0);
|
|
617
|
+
}
|
|
618
|
+
function borderSizeToPx(size) {
|
|
619
|
+
return size / 6;
|
|
620
|
+
}
|
|
621
|
+
function parseBorderCss(borderNode) {
|
|
622
|
+
if (!borderNode) return null;
|
|
623
|
+
const val = (getAttr(borderNode, "w:val") ?? getAttr(borderNode, "val") ?? "").toLowerCase();
|
|
624
|
+
if (!val || val === "nil" || val === "none") return "none";
|
|
625
|
+
const color = (getAttr(borderNode, "w:color") ?? getAttr(borderNode, "color") ?? "222222").replace(/^#/, "");
|
|
626
|
+
const rawSize = getAttr(borderNode, "w:sz") ?? getAttr(borderNode, "sz");
|
|
627
|
+
const size = rawSize ? Number.parseInt(rawSize, 10) : Number.NaN;
|
|
628
|
+
const px = Number.isFinite(size) && size > 0 ? borderSizeToPx(size) : 1;
|
|
629
|
+
const style = val === "single" ? "solid" : val;
|
|
630
|
+
return `${px.toFixed(2)}px ${style} #${color}`;
|
|
631
|
+
}
|
|
632
|
+
function parseTableStyleProfile(table) {
|
|
633
|
+
const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
|
|
634
|
+
const tblBorders = tblPr ? directChildrenByLocalName(tblPr, "tblBorders")[0] ?? null : null;
|
|
635
|
+
const layout = tblPr ? directChildrenByLocalName(tblPr, "tblLayout")[0] ?? null : null;
|
|
636
|
+
const spacing = tblPr ? directChildrenByLocalName(tblPr, "tblCellSpacing")[0] ?? null : null;
|
|
637
|
+
const spacingType = (getAttr(spacing, "w:type") ?? getAttr(spacing, "type") ?? "dxa").toLowerCase();
|
|
638
|
+
const spacingRaw = getAttr(spacing, "w:w") ?? getAttr(spacing, "w");
|
|
639
|
+
const spacingVal = spacingRaw ? Number.parseFloat(spacingRaw) : Number.NaN;
|
|
640
|
+
const borderSpacingPx = spacingType === "dxa" && Number.isFinite(spacingVal) && spacingVal > 0 ? twipToPx(spacingVal) : 0;
|
|
641
|
+
const borderCollapse = borderSpacingPx > 0 ? "separate" : "collapse";
|
|
642
|
+
const tableLayout = (getAttr(layout, "w:type") ?? getAttr(layout, "type") ?? "").toLowerCase() === "autofit" ? "auto" : "fixed";
|
|
643
|
+
const top = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "top")[0] ?? null : null);
|
|
644
|
+
const bottom = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "bottom")[0] ?? null : null);
|
|
645
|
+
const left = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "left")[0] ?? null : null);
|
|
646
|
+
const right = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "right")[0] ?? null : null);
|
|
647
|
+
const insideH = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideH")[0] ?? null : null);
|
|
648
|
+
const insideV = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideV")[0] ?? null : null);
|
|
649
|
+
const borderCss = top ?? right ?? bottom ?? left ?? "1px solid #222";
|
|
650
|
+
return {
|
|
651
|
+
tableLayout,
|
|
652
|
+
borderCollapse,
|
|
653
|
+
borderSpacingPx,
|
|
654
|
+
borderCss,
|
|
655
|
+
insideHCss: insideH,
|
|
656
|
+
insideVCss: insideV
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function parseTableWidthStyle(table, gridWidthsPx) {
|
|
660
|
+
const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
|
|
661
|
+
const tblW = tblPr ? directChildrenByLocalName(tblPr, "tblW")[0] ?? null : null;
|
|
662
|
+
const type = (getAttr(tblW, "w:type") ?? getAttr(tblW, "type") ?? "").toLowerCase();
|
|
663
|
+
const rawVal = getAttr(tblW, "w:w") ?? getAttr(tblW, "w");
|
|
664
|
+
const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
|
|
665
|
+
if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
|
|
666
|
+
return `width:${twipToPx(numericVal).toFixed(2)}px`;
|
|
667
|
+
}
|
|
668
|
+
if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
|
|
669
|
+
return `width:${(numericVal / 50).toFixed(2)}%`;
|
|
670
|
+
}
|
|
671
|
+
const gridTotal = gridWidthsPx.reduce((sum, item) => sum + item, 0);
|
|
672
|
+
if (gridTotal > 0) return `width:${gridTotal.toFixed(2)}px;max-width:100%`;
|
|
673
|
+
return "width:100%";
|
|
674
|
+
}
|
|
675
|
+
function parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx) {
|
|
676
|
+
const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
|
|
677
|
+
const tcW = tcPr ? directChildrenByLocalName(tcPr, "tcW")[0] ?? null : null;
|
|
678
|
+
const type = (getAttr(tcW, "w:type") ?? getAttr(tcW, "type") ?? "").toLowerCase();
|
|
679
|
+
const rawVal = getAttr(tcW, "w:w") ?? getAttr(tcW, "w");
|
|
680
|
+
const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
|
|
681
|
+
if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
|
|
682
|
+
return `width:${twipToPx(numericVal).toFixed(2)}px`;
|
|
683
|
+
}
|
|
684
|
+
if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
|
|
685
|
+
return `width:${(numericVal / 50).toFixed(2)}%`;
|
|
686
|
+
}
|
|
687
|
+
const width = gridWidthsPx.slice(colCursor, colCursor + colSpan).reduce((sum, item) => sum + item, 0);
|
|
688
|
+
if (width > 0) return `width:${width.toFixed(2)}px`;
|
|
689
|
+
return "";
|
|
690
|
+
}
|
|
691
|
+
function parseCellBorderStyle(cell, tableStyle) {
|
|
692
|
+
const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
|
|
693
|
+
const tcBorders = tcPr ? directChildrenByLocalName(tcPr, "tcBorders")[0] ?? null : null;
|
|
694
|
+
if (!tcBorders) {
|
|
695
|
+
const fallback = tableStyle.insideHCss ?? tableStyle.insideVCss ?? tableStyle.borderCss;
|
|
696
|
+
return `border:${fallback}`;
|
|
697
|
+
}
|
|
698
|
+
const top = parseBorderCss(directChildrenByLocalName(tcBorders, "top")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
|
|
699
|
+
const right = parseBorderCss(directChildrenByLocalName(tcBorders, "right")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
|
|
700
|
+
const bottom = parseBorderCss(directChildrenByLocalName(tcBorders, "bottom")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
|
|
701
|
+
const left = parseBorderCss(directChildrenByLocalName(tcBorders, "left")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
|
|
702
|
+
return `border-top:${top};border-right:${right};border-bottom:${bottom};border-left:${left}`;
|
|
703
|
+
}
|
|
438
704
|
function tableCellHtml(cell, paragraphIndexMap) {
|
|
439
705
|
const blocks = [];
|
|
440
706
|
for (const child of Array.from(cell.children)) {
|
|
@@ -455,6 +721,8 @@ function tableCellHtml(cell, paragraphIndexMap) {
|
|
|
455
721
|
}
|
|
456
722
|
function tableToHtml(table, paragraphIndexMap) {
|
|
457
723
|
const rows = directChildrenByLocalName(table, "tr");
|
|
724
|
+
const gridWidthsPx = parseTblGridWidthsPx(table);
|
|
725
|
+
const tableStyle = parseTableStyleProfile(table);
|
|
458
726
|
const activeByCol = /* @__PURE__ */ new Map();
|
|
459
727
|
const allOrigins = [];
|
|
460
728
|
let nextOriginId = 1;
|
|
@@ -481,6 +749,8 @@ function tableToHtml(table, paragraphIndexMap) {
|
|
|
481
749
|
}
|
|
482
750
|
const html = tableCellHtml(cell, paragraphIndexMap);
|
|
483
751
|
const attrs = [];
|
|
752
|
+
const widthStyle = parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx);
|
|
753
|
+
const borderStyle = parseCellBorderStyle(cell, tableStyle);
|
|
484
754
|
if (vMerge === "restart") {
|
|
485
755
|
const origin = {
|
|
486
756
|
id: `m${nextOriginId}`,
|
|
@@ -498,7 +768,7 @@ function tableToHtml(table, paragraphIndexMap) {
|
|
|
498
768
|
}
|
|
499
769
|
if (colSpan > 1) attrs.push(`colspan="${colSpan}"`);
|
|
500
770
|
emittedCells.push(
|
|
501
|
-
`<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="
|
|
771
|
+
`<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${borderStyle};vertical-align:top;${widthStyle}">${html}</td>`
|
|
502
772
|
);
|
|
503
773
|
colCursor += colSpan;
|
|
504
774
|
}
|
|
@@ -517,7 +787,9 @@ function tableToHtml(table, paragraphIndexMap) {
|
|
|
517
787
|
const replacement = origin.rowSpan > 1 ? `rowspan="${origin.rowSpan}"` : "";
|
|
518
788
|
merged = merged.replace(marker, replacement).replace(/\s{2,}/g, " ");
|
|
519
789
|
}
|
|
520
|
-
|
|
790
|
+
const tableWidthStyle = parseTableWidthStyle(table, gridWidthsPx);
|
|
791
|
+
const spacing = tableStyle.borderSpacingPx > 0 ? `border-spacing:${tableStyle.borderSpacingPx.toFixed(2)}px;` : "";
|
|
792
|
+
return `<table style="border-collapse:${tableStyle.borderCollapse};${spacing}table-layout:${tableStyle.tableLayout};${tableWidthStyle};border:${tableStyle.borderCss};">${merged}</table>`;
|
|
521
793
|
}
|
|
522
794
|
async function parseDocxToHtmlSnapshot(file) {
|
|
523
795
|
const maybeArrayBuffer = file.arrayBuffer;
|
|
@@ -807,7 +1079,7 @@ function createFallbackWordStyleProfile(sourceFileName = "snapshot") {
|
|
|
807
1079
|
paragraphProfiles: []
|
|
808
1080
|
};
|
|
809
1081
|
}
|
|
810
|
-
function
|
|
1082
|
+
function twipToPx2(twip) {
|
|
811
1083
|
return twip / 15;
|
|
812
1084
|
}
|
|
813
1085
|
function getAttr2(node, attr) {
|
|
@@ -851,10 +1123,10 @@ function parsePageGeometry(documentXml) {
|
|
|
851
1123
|
const top = getTwipAttr(pgMar, "w:top") ?? getTwipAttr(pgMar, "top") ?? null;
|
|
852
1124
|
const bottom = getTwipAttr(pgMar, "w:bottom") ?? getTwipAttr(pgMar, "bottom") ?? null;
|
|
853
1125
|
return {
|
|
854
|
-
contentWidthPx: pageW === null ? null :
|
|
855
|
-
pageHeightPx: pageH === null ? null :
|
|
856
|
-
marginTopPx: top === null ? null :
|
|
857
|
-
marginBottomPx: bottom === null ? null :
|
|
1126
|
+
contentWidthPx: pageW === null ? null : twipToPx2(pageW - left - right),
|
|
1127
|
+
pageHeightPx: pageH === null ? null : twipToPx2(pageH),
|
|
1128
|
+
marginTopPx: top === null ? null : twipToPx2(top),
|
|
1129
|
+
marginBottomPx: bottom === null ? null : twipToPx2(bottom)
|
|
858
1130
|
};
|
|
859
1131
|
}
|
|
860
1132
|
function parseHeadingAlignFromDocument(documentXml) {
|
|
@@ -1027,15 +1299,15 @@ function parseParagraphProfiles(documentXml, numberingMap) {
|
|
|
1027
1299
|
text,
|
|
1028
1300
|
isEmpty: text.length === 0,
|
|
1029
1301
|
align: parseParagraphAlign(paragraph),
|
|
1030
|
-
beforePx: before === null ? null :
|
|
1031
|
-
afterPx: after === null ? null :
|
|
1302
|
+
beforePx: before === null ? null : twipToPx2(before),
|
|
1303
|
+
afterPx: after === null ? null : twipToPx2(after),
|
|
1032
1304
|
lineHeightRatio: line === null || lineHeightRule !== "auto" ? null : line / 240,
|
|
1033
|
-
lineHeightPx: line === null || lineHeightRule === "auto" ? null :
|
|
1305
|
+
lineHeightPx: line === null || lineHeightRule === "auto" ? null : twipToPx2(line),
|
|
1034
1306
|
lineHeightRule,
|
|
1035
|
-
indentLeftPx: left === null ? null :
|
|
1036
|
-
indentRightPx: right === null ? null :
|
|
1037
|
-
firstLinePx: firstLine === null ? null :
|
|
1038
|
-
hangingPx: hanging === null ? null :
|
|
1307
|
+
indentLeftPx: left === null ? null : twipToPx2(left),
|
|
1308
|
+
indentRightPx: right === null ? null : twipToPx2(right),
|
|
1309
|
+
firstLinePx: firstLine === null ? null : twipToPx2(firstLine),
|
|
1310
|
+
hangingPx: hanging === null ? null : twipToPx2(hanging),
|
|
1039
1311
|
listNumId,
|
|
1040
1312
|
listLevel,
|
|
1041
1313
|
listFormat: listSpec?.numFmt ?? null,
|
|
@@ -1070,19 +1342,19 @@ function parseTableDefaults(stylesXml) {
|
|
|
1070
1342
|
return {
|
|
1071
1343
|
topPx: (() => {
|
|
1072
1344
|
const v = getTwipAttr(top, "w:w") ?? getTwipAttr(top, "w") ?? null;
|
|
1073
|
-
return v === null ? null :
|
|
1345
|
+
return v === null ? null : twipToPx2(v);
|
|
1074
1346
|
})(),
|
|
1075
1347
|
leftPx: (() => {
|
|
1076
1348
|
const v = getTwipAttr(left, "w:w") ?? getTwipAttr(left, "w") ?? null;
|
|
1077
|
-
return v === null ? null :
|
|
1349
|
+
return v === null ? null : twipToPx2(v);
|
|
1078
1350
|
})(),
|
|
1079
1351
|
bottomPx: (() => {
|
|
1080
1352
|
const v = getTwipAttr(bottom, "w:w") ?? getTwipAttr(bottom, "w") ?? null;
|
|
1081
|
-
return v === null ? null :
|
|
1353
|
+
return v === null ? null : twipToPx2(v);
|
|
1082
1354
|
})(),
|
|
1083
1355
|
rightPx: (() => {
|
|
1084
1356
|
const v = getTwipAttr(right, "w:w") ?? getTwipAttr(right, "w") ?? null;
|
|
1085
|
-
return v === null ? null :
|
|
1357
|
+
return v === null ? null : twipToPx2(v);
|
|
1086
1358
|
})()
|
|
1087
1359
|
};
|
|
1088
1360
|
}
|
|
@@ -1180,9 +1452,9 @@ function parseDefaults(stylesXml) {
|
|
|
1180
1452
|
const rawLineRule = (getAttr2(spacing, "w:lineRule") ?? getAttr2(spacing, "lineRule") ?? "auto").toLowerCase();
|
|
1181
1453
|
const bodyLineHeightRule = rawLineRule === "exact" ? "exact" : rawLineRule === "atleast" ? "atLeast" : "auto";
|
|
1182
1454
|
const bodyLineHeightRatio = line === null || bodyLineHeightRule !== "auto" ? null : line / 240;
|
|
1183
|
-
const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null :
|
|
1455
|
+
const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null : twipToPx2(line);
|
|
1184
1456
|
const after = getTwipAttr(spacing, "w:after") ?? getTwipAttr(spacing, "after") ?? null;
|
|
1185
|
-
const paragraphAfterPx = after === null ? null :
|
|
1457
|
+
const paragraphAfterPx = after === null ? null : twipToPx2(after);
|
|
1186
1458
|
return { bodyFontPx, bodyLineHeightRatio, bodyLineHeightPx, bodyLineHeightRule, paragraphAfterPx };
|
|
1187
1459
|
}
|
|
1188
1460
|
function parseHeading1Style(stylesXml) {
|