@yinyoudexing/xml2word 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 +6 -3
- package/dist/{createDocxZip-BWHSZ7VQ.js → createDocxZip-5CCNW5LX.js} +2 -3
- package/dist/{createDocxZip-BWHSZ7VQ.js.map → createDocxZip-5CCNW5LX.js.map} +1 -1
- package/dist/{htmlToWordBodyXml-SIVUZ7K7.js → htmlToWordBodyXml-JSGDLGOS.js} +231 -48
- package/dist/htmlToWordBodyXml-JSGDLGOS.js.map +1 -0
- package/dist/index.cjs +231 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/package.json +2 -2
- package/dist/htmlToWordBodyXml-SIVUZ7K7.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -50,10 +50,9 @@ ${bodyXml}
|
|
|
50
50
|
<w:sectPr>
|
|
51
51
|
<w:headerReference w:type="default" r:id="rId6"/>
|
|
52
52
|
<w:footerReference w:type="default" r:id="rId7"/>
|
|
53
|
-
<w:pgSz w:w="
|
|
53
|
+
<w:pgSz w:w="11906" w:h="16838"/>
|
|
54
54
|
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/>
|
|
55
55
|
<w:cols w:space="708"/>
|
|
56
|
-
<w:docGrid w:linePitch="360"/>
|
|
57
56
|
</w:sectPr>
|
|
58
57
|
</w:body>
|
|
59
58
|
</w:document>
|
|
@@ -627,6 +626,92 @@ function normalizeFontFamily(value) {
|
|
|
627
626
|
if (!first) return void 0;
|
|
628
627
|
return first.replace(/^["']|["']$/g, "");
|
|
629
628
|
}
|
|
629
|
+
function cssSelectorsHitTarget(selectorsText, targetSelector) {
|
|
630
|
+
const target = targetSelector.trim().toLowerCase();
|
|
631
|
+
if (!target) return false;
|
|
632
|
+
const selectors = selectorsText.replace(/\/\*[\s\S]*?\*\//g, " ").split(",").map((s) => s.replace(/\s+/g, " ").trim().toLowerCase()).filter(Boolean);
|
|
633
|
+
return selectors.some((s) => {
|
|
634
|
+
if (s === target) return true;
|
|
635
|
+
if (!s.startsWith(target)) return false;
|
|
636
|
+
const rest = s.slice(target.length);
|
|
637
|
+
return rest.startsWith(".") || rest.startsWith(":") || rest.startsWith("#") || rest.startsWith("[");
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function parseCssRuleFromHtml(html, selector) {
|
|
641
|
+
const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
642
|
+
const merged = {};
|
|
643
|
+
let m;
|
|
644
|
+
while (m = styleTagRegex.exec(html)) {
|
|
645
|
+
const cssText = m[1] ?? "";
|
|
646
|
+
const target = selector.trim().toLowerCase();
|
|
647
|
+
if (!target) continue;
|
|
648
|
+
let depth = 0;
|
|
649
|
+
let lastRuleEnd = 0;
|
|
650
|
+
let ruleStart = -1;
|
|
651
|
+
let declStart = -1;
|
|
652
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
653
|
+
const ch = cssText[i];
|
|
654
|
+
if (ch === "{") {
|
|
655
|
+
if (depth === 0) {
|
|
656
|
+
ruleStart = lastRuleEnd;
|
|
657
|
+
declStart = i + 1;
|
|
658
|
+
}
|
|
659
|
+
depth++;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (ch === "}") {
|
|
663
|
+
if (depth > 0) depth--;
|
|
664
|
+
if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
|
|
665
|
+
const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
|
|
666
|
+
const decl = cssText.slice(declStart, i).trim();
|
|
667
|
+
lastRuleEnd = i + 1;
|
|
668
|
+
ruleStart = -1;
|
|
669
|
+
declStart = -1;
|
|
670
|
+
const hit = cssSelectorsHitTarget(selectorsText, target);
|
|
671
|
+
if (hit) Object.assign(merged, parseStyleAttribute(decl));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return merged;
|
|
677
|
+
}
|
|
678
|
+
function parseCssRuleFromHtmlFirst(html, selector) {
|
|
679
|
+
const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
680
|
+
let m;
|
|
681
|
+
while (m = styleTagRegex.exec(html)) {
|
|
682
|
+
const cssText = m[1] ?? "";
|
|
683
|
+
const target = selector.trim().toLowerCase();
|
|
684
|
+
if (!target) continue;
|
|
685
|
+
let depth = 0;
|
|
686
|
+
let lastRuleEnd = 0;
|
|
687
|
+
let ruleStart = -1;
|
|
688
|
+
let declStart = -1;
|
|
689
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
690
|
+
const ch = cssText[i];
|
|
691
|
+
if (ch === "{") {
|
|
692
|
+
if (depth === 0) {
|
|
693
|
+
ruleStart = lastRuleEnd;
|
|
694
|
+
declStart = i + 1;
|
|
695
|
+
}
|
|
696
|
+
depth++;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (ch === "}") {
|
|
700
|
+
if (depth > 0) depth--;
|
|
701
|
+
if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
|
|
702
|
+
const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
|
|
703
|
+
const decl = cssText.slice(declStart, i).trim();
|
|
704
|
+
lastRuleEnd = i + 1;
|
|
705
|
+
ruleStart = -1;
|
|
706
|
+
declStart = -1;
|
|
707
|
+
const hit = cssSelectorsHitTarget(selectorsText, target);
|
|
708
|
+
if (hit) return parseStyleAttribute(decl);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return {};
|
|
714
|
+
}
|
|
630
715
|
function mergeTextStyle(base, patch) {
|
|
631
716
|
return {
|
|
632
717
|
bold: patch.bold ?? base.bold,
|
|
@@ -903,10 +988,29 @@ function parseCssLengthToTwips(value, baseFontHalfPoints) {
|
|
|
903
988
|
const basePt = baseFontHalfPoints / 2;
|
|
904
989
|
return Math.round(Number(em[1]) * basePt * 20);
|
|
905
990
|
}
|
|
991
|
+
const rem = v.match(/^(-?\d+(?:\.\d+)?)rem$/);
|
|
992
|
+
if (rem) return Math.round(Number(rem[1]) * 16 * 72 * 20 / 96);
|
|
906
993
|
const num = v.match(/^(-?\d+(?:\.\d+)?)$/);
|
|
907
994
|
if (num) return Math.round(Number(num[1]));
|
|
908
995
|
return void 0;
|
|
909
996
|
}
|
|
997
|
+
function extractMarginBeforeAfterTwips(css, baseFontHalfPoints) {
|
|
998
|
+
const before = parseCssLengthToTwips(css["margin-top"], baseFontHalfPoints);
|
|
999
|
+
const after = parseCssLengthToTwips(css["margin-bottom"], baseFontHalfPoints);
|
|
1000
|
+
if (typeof before === "number" || typeof after === "number") {
|
|
1001
|
+
return { beforeTwips: before, afterTwips: after };
|
|
1002
|
+
}
|
|
1003
|
+
const m = css.margin?.trim().toLowerCase();
|
|
1004
|
+
if (!m) return {};
|
|
1005
|
+
const tokens = m.split(/\s+/).filter(Boolean);
|
|
1006
|
+
if (!tokens.length) return {};
|
|
1007
|
+
const topToken = tokens[0];
|
|
1008
|
+
const bottomToken = tokens.length === 1 ? tokens[0] : tokens.length === 2 ? tokens[0] : tokens[2] ?? tokens[0];
|
|
1009
|
+
return {
|
|
1010
|
+
beforeTwips: parseCssLengthToTwips(topToken, baseFontHalfPoints),
|
|
1011
|
+
afterTwips: parseCssLengthToTwips(bottomToken, baseFontHalfPoints)
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
910
1014
|
function inferFirstFontSizeHalfPoints(node) {
|
|
911
1015
|
const stack = [node];
|
|
912
1016
|
while (stack.length) {
|
|
@@ -923,8 +1027,10 @@ function inferFirstFontSizeHalfPoints(node) {
|
|
|
923
1027
|
}
|
|
924
1028
|
return void 0;
|
|
925
1029
|
}
|
|
926
|
-
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
927
|
-
const
|
|
1030
|
+
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId, defaultCss) {
|
|
1031
|
+
const tag = node.type === "tag" ? node.name?.toLowerCase() : void 0;
|
|
1032
|
+
const inlineCss = parseStyleAttribute(node.attribs?.style);
|
|
1033
|
+
const css = defaultCss ? { ...defaultCss, ...inlineCss } : inlineCss;
|
|
928
1034
|
const parts = [];
|
|
929
1035
|
if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
|
|
930
1036
|
const shdHex = extractBackgroundFillHex(css);
|
|
@@ -951,10 +1057,17 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
|
951
1057
|
if (typeof hangingTwips === "number") indAttrs.push(`w:hanging="${hangingTwips}"`);
|
|
952
1058
|
if (typeof firstLine === "number") indAttrs.push(`w:firstLine="${firstLine}"`);
|
|
953
1059
|
if (indAttrs.length) parts.push(`<w:ind ${indAttrs.join(" ")}/>`);
|
|
954
|
-
const
|
|
955
|
-
const
|
|
1060
|
+
const hasInlineBefore = inlineCss["margin-top"] != null;
|
|
1061
|
+
const hasInlineAfter = inlineCss["margin-bottom"] != null;
|
|
1062
|
+
const beforeToken = inlineCss["margin-top"] ?? (pStyleId ? void 0 : defaultCss?.["margin-top"]);
|
|
1063
|
+
const afterToken = inlineCss["margin-bottom"] ?? (pStyleId ? void 0 : defaultCss?.["margin-bottom"]);
|
|
1064
|
+
let before = parseCssLengthToTwips(beforeToken, baseFontHalfPoints);
|
|
1065
|
+
let after = parseCssLengthToTwips(afterToken, baseFontHalfPoints);
|
|
1066
|
+
if (tag === "p" && !hasInlineBefore && typeof before === "number") before = Math.min(before, 160);
|
|
1067
|
+
if (tag === "p" && !hasInlineAfter && typeof after === "number") after = Math.min(after, 160);
|
|
956
1068
|
const lineHeight = (() => {
|
|
957
|
-
const
|
|
1069
|
+
const lhToken = inlineCss["line-height"] ?? (pStyleId ? void 0 : defaultCss?.["line-height"]);
|
|
1070
|
+
const lh = lhToken?.trim().toLowerCase();
|
|
958
1071
|
if (!lh || lh === "normal") return void 0;
|
|
959
1072
|
const unitless = lh.match(/^(\d+(?:\.\d+)?)$/);
|
|
960
1073
|
if (unitless) {
|
|
@@ -969,8 +1082,16 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
|
969
1082
|
})();
|
|
970
1083
|
if (typeof before === "number" || typeof after === "number" || typeof lineHeight === "number") {
|
|
971
1084
|
const attrs = [];
|
|
972
|
-
if (typeof before === "number")
|
|
973
|
-
|
|
1085
|
+
if (typeof before === "number") {
|
|
1086
|
+
attrs.push(`w:before="${Math.max(0, before)}"`);
|
|
1087
|
+
} else if (typeof lineHeight === "number") {
|
|
1088
|
+
attrs.push('w:before="0"');
|
|
1089
|
+
}
|
|
1090
|
+
if (typeof after === "number") {
|
|
1091
|
+
attrs.push(`w:after="${Math.max(0, after)}"`);
|
|
1092
|
+
} else if (typeof lineHeight === "number") {
|
|
1093
|
+
attrs.push('w:after="160"');
|
|
1094
|
+
}
|
|
974
1095
|
if (typeof lineHeight === "number") {
|
|
975
1096
|
attrs.push(`w:line="${lineHeight}"`, 'w:lineRule="exact"');
|
|
976
1097
|
}
|
|
@@ -979,10 +1100,13 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
|
979
1100
|
if (!parts.length) return "";
|
|
980
1101
|
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
981
1102
|
}
|
|
982
|
-
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
|
|
983
|
-
const
|
|
1103
|
+
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result, ctx) {
|
|
1104
|
+
const seededBaseStyle = ctx ? { fontSizeHalfPoints: ctx.defaultBaseFontHalfPoints } : {};
|
|
1105
|
+
const containerStyle = mergeTextStyle(mergeTextStyle(seededBaseStyle, baseStyle), styleFromElement(node));
|
|
984
1106
|
const baseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
|
|
985
|
-
const
|
|
1107
|
+
const computedBaseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
|
|
1108
|
+
const defaultCss = ctx ? node.type === "tag" && node.name?.toLowerCase() === "p" ? { ...ctx.defaultBodyCss, ...ctx.defaultPCss } : ctx.defaultBodyCss : void 0;
|
|
1109
|
+
const pPrXml = buildParagraphPrXml(node, computedBaseFontHalfPoints, extraInd, pStyleId, defaultCss);
|
|
986
1110
|
const runs = [];
|
|
987
1111
|
const res = result ?? {
|
|
988
1112
|
bodyXml: "",
|
|
@@ -1007,6 +1131,11 @@ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, res
|
|
|
1007
1131
|
if (!rXml.length) return "";
|
|
1008
1132
|
return `<w:p>${pPrXml}${rXml.join("")}</w:p>`;
|
|
1009
1133
|
}
|
|
1134
|
+
function buildSpacerParagraphXml(afterTwips) {
|
|
1135
|
+
const after = Math.max(0, Math.round(afterTwips));
|
|
1136
|
+
if (!after) return "";
|
|
1137
|
+
return `<w:p><w:pPr><w:spacing w:before="0" w:after="${after}" w:line="1" w:lineRule="exact"/></w:pPr><w:r><w:rPr><w:sz w:val="1"/><w:szCs w:val="1"/></w:rPr><w:t></w:t></w:r></w:p>`;
|
|
1138
|
+
}
|
|
1010
1139
|
function isExplicitPageBreak(node) {
|
|
1011
1140
|
if (node.type !== "tag") return false;
|
|
1012
1141
|
const tag = node.name?.toLowerCase();
|
|
@@ -1015,13 +1144,14 @@ function isExplicitPageBreak(node) {
|
|
|
1015
1144
|
const classList = cls ? cls.split(/\s+/) : [];
|
|
1016
1145
|
if (tag === "hr" && classList.includes("page-break")) return true;
|
|
1017
1146
|
if (classList.includes("page-break")) return true;
|
|
1147
|
+
if (classList.includes("umo-page-break")) return true;
|
|
1018
1148
|
if (node.attribs?.["data-page-break"] === "true") return true;
|
|
1019
1149
|
const after = css["page-break-after"]?.toLowerCase() ?? css["break-after"]?.toLowerCase();
|
|
1020
1150
|
const before = css["page-break-before"]?.toLowerCase() ?? css["break-before"]?.toLowerCase();
|
|
1021
1151
|
if (after?.includes("always") || before?.includes("always")) return true;
|
|
1022
1152
|
return false;
|
|
1023
1153
|
}
|
|
1024
|
-
function buildListBlocks(listNode, ordered, level, result) {
|
|
1154
|
+
function buildListBlocks(listNode, ordered, level, result, ctx) {
|
|
1025
1155
|
const liNodes = (listNode.children ?? []).filter(
|
|
1026
1156
|
(c) => c.type === "tag" && c.name?.toLowerCase() === "li"
|
|
1027
1157
|
);
|
|
@@ -1061,12 +1191,13 @@ function buildListBlocks(listNode, ordered, level, result) {
|
|
|
1061
1191
|
rXml.push(buildRunXml(token.style, text));
|
|
1062
1192
|
}
|
|
1063
1193
|
if (rXml.length) {
|
|
1064
|
-
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
|
|
1194
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
|
|
1065
1195
|
const pPrXml = buildParagraphPrXml(
|
|
1066
1196
|
li,
|
|
1067
1197
|
baseFontHalfPoints,
|
|
1068
1198
|
{ leftTwips, hangingTwips },
|
|
1069
|
-
void 0
|
|
1199
|
+
void 0,
|
|
1200
|
+
ctx?.defaultBodyCss
|
|
1070
1201
|
);
|
|
1071
1202
|
const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
|
|
1072
1203
|
const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
|
|
@@ -1074,7 +1205,7 @@ function buildListBlocks(listNode, ordered, level, result) {
|
|
|
1074
1205
|
}
|
|
1075
1206
|
for (const nested of nestedLists) {
|
|
1076
1207
|
const nestedOrdered = nested.name?.toLowerCase() === "ol";
|
|
1077
|
-
out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
|
|
1208
|
+
out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result, ctx));
|
|
1078
1209
|
}
|
|
1079
1210
|
}
|
|
1080
1211
|
return out;
|
|
@@ -1153,7 +1284,7 @@ function injectTableCellParagraphSpacing(pXml) {
|
|
|
1153
1284
|
}
|
|
1154
1285
|
return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
|
|
1155
1286
|
}
|
|
1156
|
-
function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
1287
|
+
function buildTableCellBlocksXml(cell, baseStyle, result, ctx) {
|
|
1157
1288
|
const children = cell.children ?? [];
|
|
1158
1289
|
const hasBlocks = children.some((c) => {
|
|
1159
1290
|
if (c.type !== "tag") return false;
|
|
@@ -1162,7 +1293,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
|
1162
1293
|
});
|
|
1163
1294
|
const out = [];
|
|
1164
1295
|
if (!hasBlocks) {
|
|
1165
|
-
const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
|
|
1296
|
+
const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result, ctx);
|
|
1166
1297
|
if (p) out.push(p);
|
|
1167
1298
|
return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
|
|
1168
1299
|
}
|
|
@@ -1170,18 +1301,18 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
|
1170
1301
|
if (c.type === "tag") {
|
|
1171
1302
|
const tag = c.name?.toLowerCase();
|
|
1172
1303
|
if (tag === "p") {
|
|
1173
|
-
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
|
|
1304
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result, ctx);
|
|
1174
1305
|
if (p) out.push(p);
|
|
1175
1306
|
continue;
|
|
1176
1307
|
}
|
|
1177
1308
|
if (tag && /^h[1-6]$/.test(tag)) {
|
|
1178
1309
|
const level = Number(tag.slice(1));
|
|
1179
|
-
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
|
|
1310
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result, ctx);
|
|
1180
1311
|
if (p) out.push(p);
|
|
1181
1312
|
continue;
|
|
1182
1313
|
}
|
|
1183
1314
|
if (tag === "ul" || tag === "ol") {
|
|
1184
|
-
out.push(...buildListBlocks(c, tag === "ol", 0, result));
|
|
1315
|
+
out.push(...buildListBlocks(c, tag === "ol", 0, result, ctx));
|
|
1185
1316
|
continue;
|
|
1186
1317
|
}
|
|
1187
1318
|
if (tag === "img" || tag === "canvas") {
|
|
@@ -1194,7 +1325,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
|
1194
1325
|
if (!out.length) return "<w:p/>";
|
|
1195
1326
|
return out.map(injectTableCellParagraphSpacing).join("");
|
|
1196
1327
|
}
|
|
1197
|
-
function buildTableXml(tableNode, result) {
|
|
1328
|
+
function buildTableXml(tableNode, result, ctx) {
|
|
1198
1329
|
const rows = [];
|
|
1199
1330
|
const stack = [...tableNode.children ?? []];
|
|
1200
1331
|
while (stack.length) {
|
|
@@ -1207,8 +1338,18 @@ function buildTableXml(tableNode, result) {
|
|
|
1207
1338
|
);
|
|
1208
1339
|
const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
|
|
1209
1340
|
const maxTableWidthTwips = 9360;
|
|
1341
|
+
const colGroup = (tableNode.children ?? []).find((c) => c.type === "tag" && c.name === "colgroup");
|
|
1342
|
+
const colWidthsFromGroup = [];
|
|
1343
|
+
if (colGroup) {
|
|
1344
|
+
const cols = (colGroup.children ?? []).filter((c) => c.type === "tag" && c.name === "col");
|
|
1345
|
+
for (const col of cols) {
|
|
1346
|
+
const css = parseStyleAttribute(col.attribs?.style);
|
|
1347
|
+
const w = parseCssLengthToTwips(css.width ?? css["min-width"], 28);
|
|
1348
|
+
colWidthsFromGroup.push(w);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1210
1351
|
const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
|
|
1211
|
-
let explicit;
|
|
1352
|
+
let explicit = colWidthsFromGroup[i];
|
|
1212
1353
|
let estimated = 0;
|
|
1213
1354
|
for (const cells of rowCells) {
|
|
1214
1355
|
const cell = cells[i];
|
|
@@ -1221,7 +1362,7 @@ function buildTableXml(tableNode, result) {
|
|
|
1221
1362
|
const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
|
|
1222
1363
|
estimated = Math.max(estimated, wTwips);
|
|
1223
1364
|
}
|
|
1224
|
-
const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
|
|
1365
|
+
const base = typeof explicit === "number" ? Math.max(explicit, estimated) : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
|
|
1225
1366
|
return Math.max(720, Math.min(6e3, Math.round(base)));
|
|
1226
1367
|
});
|
|
1227
1368
|
const normalizedColWidths = (() => {
|
|
@@ -1246,7 +1387,7 @@ function buildTableXml(tableNode, result) {
|
|
|
1246
1387
|
const cell = cells[i];
|
|
1247
1388
|
const isHeader = cell.name === "th";
|
|
1248
1389
|
const baseStyle = isHeader ? { bold: true } : {};
|
|
1249
|
-
const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
|
|
1390
|
+
const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result, ctx);
|
|
1250
1391
|
const css = parseStyleAttribute(cell.attribs?.style);
|
|
1251
1392
|
const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
|
|
1252
1393
|
const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
|
|
@@ -1335,14 +1476,14 @@ function buildTableXml(tableNode, result) {
|
|
|
1335
1476
|
const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
|
|
1336
1477
|
return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
|
|
1337
1478
|
}
|
|
1338
|
-
function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
|
|
1479
|
+
function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result, ctx) {
|
|
1339
1480
|
const wrapper = {
|
|
1340
1481
|
type: "tag",
|
|
1341
1482
|
name: "p",
|
|
1342
1483
|
attribs: { style: "text-align: center;" },
|
|
1343
1484
|
children: [node]
|
|
1344
1485
|
};
|
|
1345
|
-
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
|
|
1486
|
+
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result, ctx);
|
|
1346
1487
|
}
|
|
1347
1488
|
function isRecognizedBlockTag(tag) {
|
|
1348
1489
|
if (!tag) return false;
|
|
@@ -1354,7 +1495,22 @@ function isRecognizedBlockTag(tag) {
|
|
|
1354
1495
|
if (tag === "pre") return true;
|
|
1355
1496
|
return false;
|
|
1356
1497
|
}
|
|
1357
|
-
function
|
|
1498
|
+
function subtreeHasRecognizedBlocks(root) {
|
|
1499
|
+
const stack = [root];
|
|
1500
|
+
while (stack.length) {
|
|
1501
|
+
const cur = stack.pop();
|
|
1502
|
+
if (cur.type === "tag") {
|
|
1503
|
+
if (isExplicitPageBreak(cur)) return true;
|
|
1504
|
+
if (isRecognizedBlockTag(cur.name?.toLowerCase())) return true;
|
|
1505
|
+
}
|
|
1506
|
+
const children = cur.children ?? [];
|
|
1507
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
1508
|
+
stack.push(children[i]);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
function collectDivBlocks(node, out, result, ctx) {
|
|
1358
1514
|
const parentStyle = node.attribs?.style;
|
|
1359
1515
|
const inlineBuffer = [];
|
|
1360
1516
|
const flushInline = () => {
|
|
@@ -1364,7 +1520,7 @@ function collectDivBlocks(node, out, result) {
|
|
|
1364
1520
|
attribs: { style: parentStyle },
|
|
1365
1521
|
children: inlineBuffer.splice(0)
|
|
1366
1522
|
};
|
|
1367
|
-
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
|
|
1523
|
+
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
|
|
1368
1524
|
if (pXml) out.push(pXml);
|
|
1369
1525
|
};
|
|
1370
1526
|
const children = node.children ?? [];
|
|
@@ -1378,19 +1534,15 @@ function collectDivBlocks(node, out, result) {
|
|
|
1378
1534
|
}
|
|
1379
1535
|
if (isRecognizedBlockTag(tag)) {
|
|
1380
1536
|
if (inlineBuffer.length) flushInline();
|
|
1381
|
-
collectBodyBlocks(child, out, result);
|
|
1537
|
+
collectBodyBlocks(child, out, result, ctx);
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (subtreeHasRecognizedBlocks(child)) {
|
|
1541
|
+
if (inlineBuffer.length) flushInline();
|
|
1542
|
+
collectBodyBlocks(child, out, result, ctx);
|
|
1382
1543
|
continue;
|
|
1383
1544
|
}
|
|
1384
1545
|
if (tag === "div") {
|
|
1385
|
-
const childHasRecognizedBlocks = (child.children ?? []).some((gc) => {
|
|
1386
|
-
if (gc.type !== "tag") return false;
|
|
1387
|
-
return isRecognizedBlockTag(gc.name?.toLowerCase());
|
|
1388
|
-
});
|
|
1389
|
-
if (childHasRecognizedBlocks) {
|
|
1390
|
-
if (inlineBuffer.length) flushInline();
|
|
1391
|
-
collectBodyBlocks(child, out, result);
|
|
1392
|
-
continue;
|
|
1393
|
-
}
|
|
1394
1546
|
if (inlineBuffer.length) flushInline();
|
|
1395
1547
|
const mergedStyle = [parentStyle, child.attribs?.style].filter(Boolean).join(";");
|
|
1396
1548
|
const wrapper = {
|
|
@@ -1399,7 +1551,7 @@ function collectDivBlocks(node, out, result) {
|
|
|
1399
1551
|
attribs: { style: mergedStyle || void 0 },
|
|
1400
1552
|
children: child.children ?? []
|
|
1401
1553
|
};
|
|
1402
|
-
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
|
|
1554
|
+
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
|
|
1403
1555
|
if (pXml) out.push(pXml);
|
|
1404
1556
|
continue;
|
|
1405
1557
|
}
|
|
@@ -1408,7 +1560,7 @@ function collectDivBlocks(node, out, result) {
|
|
|
1408
1560
|
}
|
|
1409
1561
|
if (inlineBuffer.length) flushInline();
|
|
1410
1562
|
}
|
|
1411
|
-
function collectBodyBlocks(node, out, result) {
|
|
1563
|
+
function collectBodyBlocks(node, out, result, ctx) {
|
|
1412
1564
|
if (isSkippableSubtree(node)) return;
|
|
1413
1565
|
if (node.type === "tag") {
|
|
1414
1566
|
const tag = node.name?.toLowerCase();
|
|
@@ -1417,36 +1569,51 @@ function collectBodyBlocks(node, out, result) {
|
|
|
1417
1569
|
return;
|
|
1418
1570
|
}
|
|
1419
1571
|
if (tag === "p") {
|
|
1420
|
-
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
|
|
1572
|
+
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result, ctx);
|
|
1421
1573
|
if (pXml) out.push(pXml);
|
|
1422
1574
|
return;
|
|
1423
1575
|
}
|
|
1424
1576
|
if (tag === "img" || tag === "canvas") {
|
|
1425
|
-
const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result);
|
|
1577
|
+
const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result, ctx);
|
|
1426
1578
|
if (pXml) out.push(pXml);
|
|
1427
1579
|
return;
|
|
1428
1580
|
}
|
|
1429
1581
|
if (tag && /^h[1-6]$/.test(tag)) {
|
|
1430
1582
|
const level = Number(tag.slice(1));
|
|
1431
|
-
const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
|
|
1583
|
+
const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result, ctx);
|
|
1432
1584
|
if (hXml) out.push(hXml);
|
|
1433
1585
|
return;
|
|
1434
1586
|
}
|
|
1435
1587
|
if (tag === "table") {
|
|
1436
|
-
const tblXml = buildTableXml(node, result);
|
|
1588
|
+
const tblXml = buildTableXml(node, result, ctx);
|
|
1437
1589
|
if (tblXml) out.push(tblXml);
|
|
1438
1590
|
return;
|
|
1439
1591
|
}
|
|
1440
1592
|
if (tag === "ul" || tag === "ol") {
|
|
1441
|
-
out.push(...buildListBlocks(node, tag === "ol", 0, result));
|
|
1593
|
+
out.push(...buildListBlocks(node, tag === "ol", 0, result, ctx));
|
|
1442
1594
|
return;
|
|
1443
1595
|
}
|
|
1444
1596
|
if (tag === "div") {
|
|
1445
|
-
|
|
1597
|
+
if (hasClass(node, "tableWrapper")) {
|
|
1598
|
+
const display = ctx.tableWrapperCss.display?.trim().toLowerCase();
|
|
1599
|
+
if (display !== "contents") {
|
|
1600
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(node) ?? ctx.defaultBaseFontHalfPoints;
|
|
1601
|
+
const { beforeTwips, afterTwips } = extractMarginBeforeAfterTwips(ctx.tableWrapperCss, baseFontHalfPoints);
|
|
1602
|
+
const beforeXml = typeof beforeTwips === "number" && beforeTwips > 0 ? buildSpacerParagraphXml(beforeTwips) : "";
|
|
1603
|
+
const afterXml = typeof afterTwips === "number" && afterTwips > 0 ? buildSpacerParagraphXml(afterTwips) : "";
|
|
1604
|
+
if (beforeXml) out.push(beforeXml);
|
|
1605
|
+
collectDivBlocks(node, out, result, ctx);
|
|
1606
|
+
if (afterXml) out.push(afterXml);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
collectDivBlocks(node, out, result, ctx);
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
collectDivBlocks(node, out, result, ctx);
|
|
1446
1613
|
return;
|
|
1447
1614
|
}
|
|
1448
1615
|
}
|
|
1449
|
-
for (const c of node.children ?? []) collectBodyBlocks(c, out, result);
|
|
1616
|
+
for (const c of node.children ?? []) collectBodyBlocks(c, out, result, ctx);
|
|
1450
1617
|
}
|
|
1451
1618
|
function textToWordBodyXml(text) {
|
|
1452
1619
|
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
@@ -1466,6 +1633,20 @@ function textToWordBodyXml(text) {
|
|
|
1466
1633
|
}
|
|
1467
1634
|
function htmlToWordBody(html) {
|
|
1468
1635
|
const normalized = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1636
|
+
const bodyCss = parseCssRuleFromHtmlFirst(normalized, "body");
|
|
1637
|
+
const pCss = parseCssRuleFromHtmlFirst(normalized, "p");
|
|
1638
|
+
const defaultBaseFontHalfPoints = parseFontSizeToHalfPoints(bodyCss["font-size"]) ?? 28;
|
|
1639
|
+
const defaultBodyCss = {};
|
|
1640
|
+
if (bodyCss["line-height"]) defaultBodyCss["line-height"] = bodyCss["line-height"];
|
|
1641
|
+
const defaultPCss = {};
|
|
1642
|
+
if (pCss["line-height"]) defaultPCss["line-height"] = pCss["line-height"];
|
|
1643
|
+
if (pCss["margin-top"]) defaultPCss["margin-top"] = pCss["margin-top"];
|
|
1644
|
+
if (pCss["margin-bottom"]) defaultPCss["margin-bottom"] = pCss["margin-bottom"];
|
|
1645
|
+
if (pCss["text-align"]) defaultPCss["text-align"] = pCss["text-align"];
|
|
1646
|
+
const tableWrapperCss = {
|
|
1647
|
+
...parseCssRuleFromHtml(normalized, ".tableWrapper"),
|
|
1648
|
+
...parseCssRuleFromHtml(normalized, ".tiptap .tableWrapper")
|
|
1649
|
+
};
|
|
1469
1650
|
const doc = (0, import_htmlparser2.parseDocument)(normalized, {
|
|
1470
1651
|
lowerCaseAttributeNames: true,
|
|
1471
1652
|
lowerCaseTags: true,
|
|
@@ -1473,7 +1654,8 @@ function htmlToWordBody(html) {
|
|
|
1473
1654
|
});
|
|
1474
1655
|
const result = { bodyXml: "", images: [] };
|
|
1475
1656
|
const out = [];
|
|
1476
|
-
|
|
1657
|
+
const ctx = { defaultBaseFontHalfPoints, defaultBodyCss, defaultPCss, tableWrapperCss };
|
|
1658
|
+
collectBodyBlocks(doc, out, result, ctx);
|
|
1477
1659
|
result.bodyXml = out.join("");
|
|
1478
1660
|
return result;
|
|
1479
1661
|
}
|