@yinyoudexing/xml2word 0.1.1 → 0.1.3
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/dist/createDocxZip-BWHSZ7VQ.js +500 -0
- package/dist/createDocxZip-BWHSZ7VQ.js.map +1 -0
- package/dist/{htmlToWordBodyXml-AG3GTZEZ.js → htmlToWordBodyXml-SIVUZ7K7.js} +381 -49
- package/dist/htmlToWordBodyXml-SIVUZ7K7.js.map +1 -0
- package/dist/index.cjs +736 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/dist/createDocxZip-WGQSPGIF.js +0 -152
- package/dist/createDocxZip-WGQSPGIF.js.map +0 -1
- package/dist/htmlToWordBodyXml-AG3GTZEZ.js.map +0 -1
|
@@ -7,6 +7,12 @@ function shouldPreserveSpace(text) {
|
|
|
7
7
|
if (!text) return false;
|
|
8
8
|
return /^\s/.test(text) || /\s$/.test(text) || /\s{2,}/.test(text);
|
|
9
9
|
}
|
|
10
|
+
function shouldKeepWhitespaceOnlyRun(text) {
|
|
11
|
+
if (!text) return false;
|
|
12
|
+
if (/\r|\n/.test(text)) return false;
|
|
13
|
+
if (text.includes("\xA0")) return true;
|
|
14
|
+
return /\s{2,}/.test(text);
|
|
15
|
+
}
|
|
10
16
|
function parseStyleAttribute(style) {
|
|
11
17
|
if (!style) return {};
|
|
12
18
|
const normalized = style.replace(/\r/g, "\n");
|
|
@@ -36,6 +42,17 @@ function parseCssColorToHex(value) {
|
|
|
36
42
|
if (hex) return hex.toUpperCase();
|
|
37
43
|
return parseRgbToHex(v);
|
|
38
44
|
}
|
|
45
|
+
function extractBackgroundFillHex(css) {
|
|
46
|
+
const raw = (css["background-color"] ?? css.background)?.trim();
|
|
47
|
+
if (!raw) return void 0;
|
|
48
|
+
const direct = parseCssColorToHex(raw);
|
|
49
|
+
if (direct) return direct;
|
|
50
|
+
const hex = raw.match(/#([0-9a-fA-F]{6})/)?.[0];
|
|
51
|
+
if (hex) return parseCssColorToHex(hex);
|
|
52
|
+
const rgb = raw.match(/rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)/)?.[0];
|
|
53
|
+
if (rgb) return parseCssColorToHex(rgb);
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
39
56
|
function parseFontSizeToHalfPoints(value) {
|
|
40
57
|
if (!value) return void 0;
|
|
41
58
|
const v = value.trim().toLowerCase();
|
|
@@ -106,6 +123,7 @@ function getTextContent(node) {
|
|
|
106
123
|
for (const c of children) out += getTextContent(c);
|
|
107
124
|
return out;
|
|
108
125
|
}
|
|
126
|
+
var IMAGE_RELATIONSHIP_ID_OFFSET = 7;
|
|
109
127
|
function decodeBase64ToUint8Array(base64) {
|
|
110
128
|
const BufferCtor = globalThis.Buffer;
|
|
111
129
|
if (BufferCtor) {
|
|
@@ -226,7 +244,7 @@ function collectInlineRuns(node, inherited, out, result) {
|
|
|
226
244
|
const intrinsic = parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
|
|
227
245
|
const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
|
|
228
246
|
const id = result.images.length + 1;
|
|
229
|
-
const relationshipId = `rId${id}`;
|
|
247
|
+
const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
|
|
230
248
|
const target = `media/image${id}.${parsed.extension}`;
|
|
231
249
|
result.images.push({
|
|
232
250
|
relationshipId,
|
|
@@ -249,7 +267,7 @@ function collectInlineRuns(node, inherited, out, result) {
|
|
|
249
267
|
const intrinsic = Number.isFinite(bufferW) && bufferW && Number.isFinite(bufferH) && bufferH ? { widthPx: Math.max(1, Math.round(bufferW)), heightPx: Math.max(1, Math.round(bufferH)) } : parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
|
|
250
268
|
const { widthPx, heightPx } = computeImageSizePx(node, intrinsic);
|
|
251
269
|
const id = result.images.length + 1;
|
|
252
|
-
const relationshipId = `rId${id}`;
|
|
270
|
+
const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
|
|
253
271
|
const target = `media/image${id}.${parsed.extension}`;
|
|
254
272
|
result.images.push({
|
|
255
273
|
relationshipId,
|
|
@@ -350,9 +368,12 @@ function inferFirstFontSizeHalfPoints(node) {
|
|
|
350
368
|
}
|
|
351
369
|
return void 0;
|
|
352
370
|
}
|
|
353
|
-
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
|
|
371
|
+
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
354
372
|
const css = parseStyleAttribute(node.attribs?.style);
|
|
355
373
|
const parts = [];
|
|
374
|
+
if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
|
|
375
|
+
const shdHex = extractBackgroundFillHex(css);
|
|
376
|
+
if (shdHex) parts.push(`<w:shd w:val="clear" w:color="auto" w:fill="${shdHex}"/>`);
|
|
356
377
|
const align = css["text-align"]?.trim().toLowerCase();
|
|
357
378
|
const jcVal = align === "center" ? "center" : align === "right" ? "right" : align === "justify" ? "both" : void 0;
|
|
358
379
|
if (jcVal) parts.push(`<w:jc w:val="${jcVal}"/>`);
|
|
@@ -403,15 +424,16 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd) {
|
|
|
403
424
|
if (!parts.length) return "";
|
|
404
425
|
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
405
426
|
}
|
|
406
|
-
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, result) {
|
|
407
|
-
const
|
|
408
|
-
const
|
|
427
|
+
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
|
|
428
|
+
const containerStyle = mergeTextStyle(baseStyle, styleFromElement(node));
|
|
429
|
+
const baseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
|
|
430
|
+
const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId);
|
|
409
431
|
const runs = [];
|
|
410
432
|
const res = result ?? {
|
|
411
433
|
bodyXml: "",
|
|
412
434
|
images: []
|
|
413
435
|
};
|
|
414
|
-
for (const c of node.children ?? []) collectInlineRuns(c,
|
|
436
|
+
for (const c of node.children ?? []) collectInlineRuns(c, containerStyle, runs, res);
|
|
415
437
|
const rXml = [];
|
|
416
438
|
for (const token of runs) {
|
|
417
439
|
if (token.kind === "br") {
|
|
@@ -424,7 +446,7 @@ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, result) {
|
|
|
424
446
|
}
|
|
425
447
|
const text = token.text;
|
|
426
448
|
if (!text) continue;
|
|
427
|
-
if (!text.trim()) continue;
|
|
449
|
+
if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
|
|
428
450
|
rXml.push(buildRunXml(token.style, text));
|
|
429
451
|
}
|
|
430
452
|
if (!rXml.length) return "";
|
|
@@ -445,25 +467,30 @@ function isExplicitPageBreak(node) {
|
|
|
445
467
|
if (after?.includes("always") || before?.includes("always")) return true;
|
|
446
468
|
return false;
|
|
447
469
|
}
|
|
448
|
-
function
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const items = [];
|
|
454
|
-
const stack = [...listNode.children ?? []];
|
|
455
|
-
while (stack.length) {
|
|
456
|
-
const n = stack.shift();
|
|
457
|
-
if (n.type === "tag" && n.name?.toLowerCase() === "li") items.push(n);
|
|
458
|
-
}
|
|
470
|
+
function buildListBlocks(listNode, ordered, level, result) {
|
|
471
|
+
const liNodes = (listNode.children ?? []).filter(
|
|
472
|
+
(c) => c.type === "tag" && c.name?.toLowerCase() === "li"
|
|
473
|
+
);
|
|
474
|
+
if (!liNodes.length) return [];
|
|
459
475
|
const out = [];
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
476
|
+
const numId = ordered ? 2 : 1;
|
|
477
|
+
const ilvl = Math.max(0, Math.min(8, Math.floor(level)));
|
|
478
|
+
const leftTwips = 720 * (ilvl + 1);
|
|
479
|
+
const hangingTwips = 360;
|
|
480
|
+
for (const li of liNodes) {
|
|
481
|
+
const nestedLists = [];
|
|
482
|
+
const baseStyle = styleFromElement(li);
|
|
464
483
|
const runs = [];
|
|
465
|
-
|
|
466
|
-
|
|
484
|
+
for (const c of li.children ?? []) {
|
|
485
|
+
if (c.type === "tag") {
|
|
486
|
+
const tag = c.name?.toLowerCase();
|
|
487
|
+
if (tag === "ul" || tag === "ol") {
|
|
488
|
+
nestedLists.push(c);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
collectInlineRuns(c, baseStyle, runs, result);
|
|
493
|
+
}
|
|
467
494
|
const rXml = [];
|
|
468
495
|
for (const token of runs) {
|
|
469
496
|
if (token.kind === "br") {
|
|
@@ -476,18 +503,143 @@ function buildListBlocks(listNode, ordered, result) {
|
|
|
476
503
|
}
|
|
477
504
|
const text = token.text;
|
|
478
505
|
if (!text) continue;
|
|
479
|
-
if (!text.trim()) continue;
|
|
506
|
+
if (!text.trim() && !shouldKeepWhitespaceOnlyRun(text)) continue;
|
|
480
507
|
rXml.push(buildRunXml(token.style, text));
|
|
481
508
|
}
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
509
|
+
if (rXml.length) {
|
|
510
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
|
|
511
|
+
const pPrXml = buildParagraphPrXml(
|
|
512
|
+
li,
|
|
513
|
+
baseFontHalfPoints,
|
|
514
|
+
{ leftTwips, hangingTwips },
|
|
515
|
+
void 0
|
|
516
|
+
);
|
|
517
|
+
const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
|
|
518
|
+
const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
|
|
519
|
+
out.push(`<w:p>${mergedPPrXml}${rXml.join("")}</w:p>`);
|
|
520
|
+
}
|
|
521
|
+
for (const nested of nestedLists) {
|
|
522
|
+
const nestedOrdered = nested.name?.toLowerCase() === "ol";
|
|
523
|
+
out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
|
|
524
|
+
}
|
|
488
525
|
}
|
|
489
526
|
return out;
|
|
490
527
|
}
|
|
528
|
+
function parseCellWidthTwips(node) {
|
|
529
|
+
const css = parseStyleAttribute(node.attribs?.style);
|
|
530
|
+
const width = parseCssLengthToTwips(css.width, 28);
|
|
531
|
+
if (typeof width !== "number" || width <= 0) return void 0;
|
|
532
|
+
return width;
|
|
533
|
+
}
|
|
534
|
+
function estimateTextWidthTwips(text, baseFontHalfPoints) {
|
|
535
|
+
const basePt = baseFontHalfPoints / 2;
|
|
536
|
+
const cjkRegex = /[\u3400-\u4dbf\u4e00-\u9fff\u3000-\u303f\uff00-\uffef]/;
|
|
537
|
+
let cjk = 0;
|
|
538
|
+
let latin = 0;
|
|
539
|
+
let spaces = 0;
|
|
540
|
+
for (const ch of text) {
|
|
541
|
+
if (ch === " " || ch === " ") {
|
|
542
|
+
spaces++;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (cjkRegex.test(ch)) {
|
|
546
|
+
cjk++;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
latin++;
|
|
550
|
+
}
|
|
551
|
+
const cjkTwips = Math.round(basePt * 20);
|
|
552
|
+
const latinTwips = Math.round(basePt * 11);
|
|
553
|
+
const spaceTwips = Math.round(basePt * 6);
|
|
554
|
+
return cjk * cjkTwips + latin * latinTwips + spaces * spaceTwips;
|
|
555
|
+
}
|
|
556
|
+
function parseBorderShorthand(value, baseFontHalfPoints) {
|
|
557
|
+
if (!value) return void 0;
|
|
558
|
+
const raw = value.trim().toLowerCase();
|
|
559
|
+
if (!raw) return void 0;
|
|
560
|
+
if (raw === "none" || raw === "0") return { val: "nil", sz: 0 };
|
|
561
|
+
const tokens = raw.split(/\s+/).filter(Boolean);
|
|
562
|
+
if (!tokens.length) return void 0;
|
|
563
|
+
const css = Object.fromEntries(tokens.map((t, i) => [`${i}`, t]));
|
|
564
|
+
const widthToken = Object.values(css).find((t) => /^(?:\d+(?:\.\d+)?)(?:px|pt)?$/.test(t));
|
|
565
|
+
const styleToken = Object.values(css).find(
|
|
566
|
+
(t) => ["none", "solid", "dashed", "dotted", "double", "hidden"].includes(t)
|
|
567
|
+
);
|
|
568
|
+
const colorToken = Object.values(css).find((t) => t.startsWith("#") || t.startsWith("rgb("));
|
|
569
|
+
const widthTwips = parseCssLengthToTwips(widthToken, baseFontHalfPoints);
|
|
570
|
+
const sz = (() => {
|
|
571
|
+
if (typeof widthTwips !== "number") return 4;
|
|
572
|
+
if (widthTwips <= 0) return 0;
|
|
573
|
+
return Math.max(2, Math.round(widthTwips * 0.4));
|
|
574
|
+
})();
|
|
575
|
+
const val = (() => {
|
|
576
|
+
if (!styleToken) return "single";
|
|
577
|
+
if (styleToken === "none" || styleToken === "hidden") return "nil";
|
|
578
|
+
if (styleToken === "solid") return "single";
|
|
579
|
+
if (styleToken === "dashed") return "dashed";
|
|
580
|
+
if (styleToken === "dotted") return "dotted";
|
|
581
|
+
if (styleToken === "double") return "double";
|
|
582
|
+
return "single";
|
|
583
|
+
})();
|
|
584
|
+
const colorHex = parseCssColorToHex(colorToken);
|
|
585
|
+
return { val, sz, colorHex };
|
|
586
|
+
}
|
|
587
|
+
function buildBorderTag(tag, border, fallbackColorHex) {
|
|
588
|
+
const b = border ?? { val: "single", sz: 4, colorHex: fallbackColorHex };
|
|
589
|
+
const color = (b.colorHex ?? fallbackColorHex).toUpperCase();
|
|
590
|
+
return `<w:${tag} w:val="${b.val}" w:sz="${b.sz}" w:space="0" w:color="${color}"/>`;
|
|
591
|
+
}
|
|
592
|
+
function injectTableCellParagraphSpacing(pXml) {
|
|
593
|
+
if (!pXml.includes("<w:p")) return pXml;
|
|
594
|
+
if (!pXml.includes("<w:p>")) return pXml;
|
|
595
|
+
const spacingXml = '<w:spacing w:before="0" w:after="0" w:line="360" w:lineRule="auto"/><w:wordWrap w:val="1"/>';
|
|
596
|
+
if (pXml.includes("<w:pPr>")) {
|
|
597
|
+
if (pXml.includes("<w:spacing ")) return pXml;
|
|
598
|
+
return pXml.replace("<w:pPr>", `<w:pPr>${spacingXml}`);
|
|
599
|
+
}
|
|
600
|
+
return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
|
|
601
|
+
}
|
|
602
|
+
function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
603
|
+
const children = cell.children ?? [];
|
|
604
|
+
const hasBlocks = children.some((c) => {
|
|
605
|
+
if (c.type !== "tag") return false;
|
|
606
|
+
const tag = c.name?.toLowerCase();
|
|
607
|
+
return tag === "p" || tag === "ul" || tag === "ol" || tag === "img" || tag === "canvas" || /^h[1-6]$/.test(tag ?? "");
|
|
608
|
+
});
|
|
609
|
+
const out = [];
|
|
610
|
+
if (!hasBlocks) {
|
|
611
|
+
const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
|
|
612
|
+
if (p) out.push(p);
|
|
613
|
+
return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
|
|
614
|
+
}
|
|
615
|
+
for (const c of children) {
|
|
616
|
+
if (c.type === "tag") {
|
|
617
|
+
const tag = c.name?.toLowerCase();
|
|
618
|
+
if (tag === "p") {
|
|
619
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
|
|
620
|
+
if (p) out.push(p);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (tag && /^h[1-6]$/.test(tag)) {
|
|
624
|
+
const level = Number(tag.slice(1));
|
|
625
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
|
|
626
|
+
if (p) out.push(p);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
if (tag === "ul" || tag === "ol") {
|
|
630
|
+
out.push(...buildListBlocks(c, tag === "ol", 0, result));
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (tag === "img" || tag === "canvas") {
|
|
634
|
+
const p = buildParagraphXmlFromSingleInlineNode(c, baseStyle, result);
|
|
635
|
+
if (p) out.push(p);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (!out.length) return "<w:p/>";
|
|
641
|
+
return out.map(injectTableCellParagraphSpacing).join("");
|
|
642
|
+
}
|
|
491
643
|
function buildTableXml(tableNode, result) {
|
|
492
644
|
const rows = [];
|
|
493
645
|
const stack = [...tableNode.children ?? []];
|
|
@@ -496,25 +648,137 @@ function buildTableXml(tableNode, result) {
|
|
|
496
648
|
if (n.type === "tag" && n.name?.toLowerCase() === "tr") rows.push(n);
|
|
497
649
|
if (n.children?.length) stack.unshift(...n.children);
|
|
498
650
|
}
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
651
|
+
const rowCells = rows.map(
|
|
652
|
+
(tr) => (tr.children ?? []).filter((c) => c.type === "tag" && (c.name === "td" || c.name === "th"))
|
|
653
|
+
);
|
|
654
|
+
const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
|
|
655
|
+
const maxTableWidthTwips = 9360;
|
|
656
|
+
const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
|
|
657
|
+
let explicit;
|
|
658
|
+
let estimated = 0;
|
|
659
|
+
for (const cells of rowCells) {
|
|
660
|
+
const cell = cells[i];
|
|
661
|
+
if (!cell) continue;
|
|
662
|
+
const w = parseCellWidthTwips(cell);
|
|
663
|
+
if (typeof w === "number") explicit = explicit ?? w;
|
|
664
|
+
const text = getTextContent(cell).replace(/\s+/g, " ").trim();
|
|
665
|
+
if (!text) continue;
|
|
666
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(cell) ?? 28;
|
|
667
|
+
const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
|
|
668
|
+
estimated = Math.max(estimated, wTwips);
|
|
669
|
+
}
|
|
670
|
+
const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
|
|
671
|
+
return Math.max(720, Math.min(6e3, Math.round(base)));
|
|
672
|
+
});
|
|
673
|
+
const normalizedColWidths = (() => {
|
|
674
|
+
const sum = estimatedColWidths.reduce((a, b) => a + b, 0);
|
|
675
|
+
if (!sum) return estimatedColWidths;
|
|
676
|
+
if (sum <= maxTableWidthTwips) return estimatedColWidths;
|
|
677
|
+
const scaled = estimatedColWidths.map(
|
|
678
|
+
(w) => Math.max(720, Math.floor(w * maxTableWidthTwips / sum))
|
|
503
679
|
);
|
|
680
|
+
const scaledSum = scaled.reduce((a, b) => a + b, 0);
|
|
681
|
+
const diff = maxTableWidthTwips - scaledSum;
|
|
682
|
+
if (diff !== 0 && scaled.length) scaled[scaled.length - 1] = Math.max(720, scaled[scaled.length - 1] + diff);
|
|
683
|
+
return scaled;
|
|
684
|
+
})();
|
|
685
|
+
const tblGrid = `<w:tblGrid>${normalizedColWidths.map((w) => `<w:gridCol w:w="${w}"/>`).join("")}</w:tblGrid>`;
|
|
686
|
+
const rowXml = [];
|
|
687
|
+
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
688
|
+
const tr = rows[rowIdx];
|
|
689
|
+
const cells = rowCells[rowIdx] ?? [];
|
|
504
690
|
const cellXml = [];
|
|
505
|
-
for (
|
|
691
|
+
for (let i = 0; i < cells.length; i++) {
|
|
692
|
+
const cell = cells[i];
|
|
506
693
|
const isHeader = cell.name === "th";
|
|
507
694
|
const baseStyle = isHeader ? { bold: true } : {};
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
)
|
|
695
|
+
const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
|
|
696
|
+
const css = parseStyleAttribute(cell.attribs?.style);
|
|
697
|
+
const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
|
|
698
|
+
const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
|
|
699
|
+
const vAlign = (() => {
|
|
700
|
+
const v = css["vertical-align"]?.trim().toLowerCase();
|
|
701
|
+
if (!v) return "";
|
|
702
|
+
if (v === "middle" || v === "center") return '<w:vAlign w:val="center"/>';
|
|
703
|
+
if (v === "bottom") return '<w:vAlign w:val="bottom"/>';
|
|
704
|
+
if (v === "top") return '<w:vAlign w:val="top"/>';
|
|
705
|
+
return "";
|
|
706
|
+
})();
|
|
707
|
+
const shd = (() => {
|
|
708
|
+
const hex = parseCssColorToHex(css["background-color"]);
|
|
709
|
+
if (!hex) return "";
|
|
710
|
+
return `<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`;
|
|
711
|
+
})();
|
|
712
|
+
const noWrap = (() => {
|
|
713
|
+
const ws = css["white-space"]?.trim().toLowerCase();
|
|
714
|
+
if (ws?.includes("nowrap")) return "<w:noWrap/>";
|
|
715
|
+
return "";
|
|
716
|
+
})();
|
|
717
|
+
const cellBorder = (() => {
|
|
718
|
+
const bAll = parseBorderShorthand(css.border, 28);
|
|
719
|
+
const bTop = parseBorderShorthand(css["border-top"] ?? css.border, 28);
|
|
720
|
+
const bLeft = parseBorderShorthand(css["border-left"] ?? css.border, 28);
|
|
721
|
+
const bBottom = parseBorderShorthand(css["border-bottom"] ?? css.border, 28);
|
|
722
|
+
const bRight = parseBorderShorthand(css["border-right"] ?? css.border, 28);
|
|
723
|
+
const any = bAll || css.border || css["border-top"] || css["border-left"] || css["border-bottom"] || css["border-right"];
|
|
724
|
+
if (!any) return "";
|
|
725
|
+
const fallback = bAll?.colorHex ?? "D9D9D9";
|
|
726
|
+
return `<w:tcBorders>${buildBorderTag("top", bTop, fallback)}${buildBorderTag(
|
|
727
|
+
"left",
|
|
728
|
+
bLeft,
|
|
729
|
+
fallback
|
|
730
|
+
)}${buildBorderTag("bottom", bBottom, fallback)}${buildBorderTag(
|
|
731
|
+
"right",
|
|
732
|
+
bRight,
|
|
733
|
+
fallback
|
|
734
|
+
)}</w:tcBorders>`;
|
|
735
|
+
})();
|
|
736
|
+
cellXml.push(`<w:tc><w:tcPr>${tcW}${vAlign}${shd}${noWrap}${cellBorder}</w:tcPr>${paragraphs}</w:tc>`);
|
|
513
737
|
}
|
|
514
738
|
if (cellXml.length) rowXml.push(`<w:tr>${cellXml.join("")}</w:tr>`);
|
|
515
739
|
}
|
|
516
|
-
const
|
|
517
|
-
const
|
|
740
|
+
const tblCss = parseStyleAttribute(tableNode.attribs?.style);
|
|
741
|
+
const tblAlign = (() => {
|
|
742
|
+
const ml = tblCss["margin-left"]?.trim().toLowerCase();
|
|
743
|
+
const mr = tblCss["margin-right"]?.trim().toLowerCase();
|
|
744
|
+
const m = tblCss.margin?.trim().toLowerCase();
|
|
745
|
+
if (ml === "auto" && mr === "auto" || (m?.includes("auto") ?? false)) return '<w:tblJc w:val="center"/>';
|
|
746
|
+
const ta = tblCss["text-align"]?.trim().toLowerCase();
|
|
747
|
+
if (ta === "center") return '<w:tblJc w:val="center"/>';
|
|
748
|
+
if (ta === "right") return '<w:tblJc w:val="right"/>';
|
|
749
|
+
return "";
|
|
750
|
+
})();
|
|
751
|
+
const tblBorder = (() => {
|
|
752
|
+
const border = parseBorderShorthand(tblCss.border, 28);
|
|
753
|
+
if (tblCss.border) {
|
|
754
|
+
const fallback2 = border?.colorHex ?? "D9D9D9";
|
|
755
|
+
return `<w:tblBorders>${buildBorderTag("top", border, fallback2)}${buildBorderTag(
|
|
756
|
+
"left",
|
|
757
|
+
border,
|
|
758
|
+
fallback2
|
|
759
|
+
)}${buildBorderTag("bottom", border, fallback2)}${buildBorderTag(
|
|
760
|
+
"right",
|
|
761
|
+
border,
|
|
762
|
+
fallback2
|
|
763
|
+
)}${buildBorderTag("insideH", border, fallback2)}${buildBorderTag(
|
|
764
|
+
"insideV",
|
|
765
|
+
border,
|
|
766
|
+
fallback2
|
|
767
|
+
)}</w:tblBorders>`;
|
|
768
|
+
}
|
|
769
|
+
const fallback = "D9D9D9";
|
|
770
|
+
return `<w:tblBorders>${buildBorderTag("top", void 0, fallback)}${buildBorderTag(
|
|
771
|
+
"left",
|
|
772
|
+
void 0,
|
|
773
|
+
fallback
|
|
774
|
+
)}${buildBorderTag("bottom", void 0, fallback)}${buildBorderTag(
|
|
775
|
+
"right",
|
|
776
|
+
void 0,
|
|
777
|
+
fallback
|
|
778
|
+
)}${buildBorderTag("insideH", void 0, fallback)}${buildBorderTag("insideV", void 0, fallback)}</w:tblBorders>`;
|
|
779
|
+
})();
|
|
780
|
+
const tblW = `<w:tblW w:w="${normalizedColWidths.reduce((a, b) => a + b, 0)}" w:type="dxa"/>`;
|
|
781
|
+
const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
|
|
518
782
|
return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
|
|
519
783
|
}
|
|
520
784
|
function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
|
|
@@ -524,7 +788,71 @@ function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
|
|
|
524
788
|
attribs: { style: "text-align: center;" },
|
|
525
789
|
children: [node]
|
|
526
790
|
};
|
|
527
|
-
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, result);
|
|
791
|
+
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
|
|
792
|
+
}
|
|
793
|
+
function isRecognizedBlockTag(tag) {
|
|
794
|
+
if (!tag) return false;
|
|
795
|
+
if (tag === "p") return true;
|
|
796
|
+
if (tag === "table") return true;
|
|
797
|
+
if (tag === "ul" || tag === "ol") return true;
|
|
798
|
+
if (tag === "img" || tag === "canvas") return true;
|
|
799
|
+
if (/^h[1-6]$/.test(tag)) return true;
|
|
800
|
+
if (tag === "pre") return true;
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
function collectDivBlocks(node, out, result) {
|
|
804
|
+
const parentStyle = node.attribs?.style;
|
|
805
|
+
const inlineBuffer = [];
|
|
806
|
+
const flushInline = () => {
|
|
807
|
+
const wrapper = {
|
|
808
|
+
type: "tag",
|
|
809
|
+
name: "p",
|
|
810
|
+
attribs: { style: parentStyle },
|
|
811
|
+
children: inlineBuffer.splice(0)
|
|
812
|
+
};
|
|
813
|
+
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
|
|
814
|
+
if (pXml) out.push(pXml);
|
|
815
|
+
};
|
|
816
|
+
const children = node.children ?? [];
|
|
817
|
+
for (const child of children) {
|
|
818
|
+
if (child.type === "tag") {
|
|
819
|
+
const tag = child.name?.toLowerCase();
|
|
820
|
+
if (isExplicitPageBreak(child)) {
|
|
821
|
+
if (inlineBuffer.length) flushInline();
|
|
822
|
+
out.push(PAGE_BREAK_XML);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (isRecognizedBlockTag(tag)) {
|
|
826
|
+
if (inlineBuffer.length) flushInline();
|
|
827
|
+
collectBodyBlocks(child, out, result);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (tag === "div") {
|
|
831
|
+
const childHasRecognizedBlocks = (child.children ?? []).some((gc) => {
|
|
832
|
+
if (gc.type !== "tag") return false;
|
|
833
|
+
return isRecognizedBlockTag(gc.name?.toLowerCase());
|
|
834
|
+
});
|
|
835
|
+
if (childHasRecognizedBlocks) {
|
|
836
|
+
if (inlineBuffer.length) flushInline();
|
|
837
|
+
collectBodyBlocks(child, out, result);
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (inlineBuffer.length) flushInline();
|
|
841
|
+
const mergedStyle = [parentStyle, child.attribs?.style].filter(Boolean).join(";");
|
|
842
|
+
const wrapper = {
|
|
843
|
+
type: "tag",
|
|
844
|
+
name: "p",
|
|
845
|
+
attribs: { style: mergedStyle || void 0 },
|
|
846
|
+
children: child.children ?? []
|
|
847
|
+
};
|
|
848
|
+
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
|
|
849
|
+
if (pXml) out.push(pXml);
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
inlineBuffer.push(child);
|
|
854
|
+
}
|
|
855
|
+
if (inlineBuffer.length) flushInline();
|
|
528
856
|
}
|
|
529
857
|
function collectBodyBlocks(node, out, result) {
|
|
530
858
|
if (isSkippableSubtree(node)) return;
|
|
@@ -535,7 +863,7 @@ function collectBodyBlocks(node, out, result) {
|
|
|
535
863
|
return;
|
|
536
864
|
}
|
|
537
865
|
if (tag === "p") {
|
|
538
|
-
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, result);
|
|
866
|
+
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
|
|
539
867
|
if (pXml) out.push(pXml);
|
|
540
868
|
return;
|
|
541
869
|
}
|
|
@@ -546,7 +874,7 @@ function collectBodyBlocks(node, out, result) {
|
|
|
546
874
|
}
|
|
547
875
|
if (tag && /^h[1-6]$/.test(tag)) {
|
|
548
876
|
const level = Number(tag.slice(1));
|
|
549
|
-
const hXml = buildParagraphXmlFromContainer(node,
|
|
877
|
+
const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
|
|
550
878
|
if (hXml) out.push(hXml);
|
|
551
879
|
return;
|
|
552
880
|
}
|
|
@@ -556,7 +884,11 @@ function collectBodyBlocks(node, out, result) {
|
|
|
556
884
|
return;
|
|
557
885
|
}
|
|
558
886
|
if (tag === "ul" || tag === "ol") {
|
|
559
|
-
out.push(...buildListBlocks(node, tag === "ol", result));
|
|
887
|
+
out.push(...buildListBlocks(node, tag === "ol", 0, result));
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
if (tag === "div") {
|
|
891
|
+
collectDivBlocks(node, out, result);
|
|
560
892
|
return;
|
|
561
893
|
}
|
|
562
894
|
}
|
|
@@ -624,4 +956,4 @@ export {
|
|
|
624
956
|
htmlToWordBodyXml,
|
|
625
957
|
textToWordBodyXml
|
|
626
958
|
};
|
|
627
|
-
//# sourceMappingURL=htmlToWordBodyXml-
|
|
959
|
+
//# sourceMappingURL=htmlToWordBodyXml-SIVUZ7K7.js.map
|