@yinyoudexing/xml2word 0.1.4 → 0.1.6
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 → createDocxZip-BVX7SQOB.js} +85 -12
- package/dist/{createDocxZip-BWHSZ7VQ.js.map → createDocxZip-BVX7SQOB.js.map} +1 -1
- package/dist/{htmlToWordBodyXml-SIVUZ7K7.js → htmlToWordBodyXml-VH5U3L5J.js} +261 -62
- package/dist/htmlToWordBodyXml-VH5U3L5J.js.map +1 -0
- package/dist/index.cjs +392 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +51 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/htmlToWordBodyXml-SIVUZ7K7.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -39,7 +39,38 @@ function ensureXmlDeclaration(xml) {
|
|
|
39
39
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
40
40
|
${xml}`;
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function resolvePageSetupTwips(page) {
|
|
43
|
+
const size = page?.size;
|
|
44
|
+
const defaultSize = { widthTwips: 11906, heightTwips: 16838 };
|
|
45
|
+
const resolvedSize = typeof size === "object" && size && "widthTwips" in size && "heightTwips" in size ? { widthTwips: Math.round(size.widthTwips), heightTwips: Math.round(size.heightTwips) } : size === "Letter" ? { widthTwips: 12240, heightTwips: 15840 } : size === "Letter_Landscape" ? { widthTwips: 15840, heightTwips: 12240 } : size === "A4_Landscape" ? { widthTwips: 16838, heightTwips: 11906 } : defaultSize;
|
|
46
|
+
const defaultMargins = {
|
|
47
|
+
top: 1440,
|
|
48
|
+
right: 1440,
|
|
49
|
+
bottom: 1440,
|
|
50
|
+
left: 1440,
|
|
51
|
+
header: 708,
|
|
52
|
+
footer: 708,
|
|
53
|
+
gutter: 0
|
|
54
|
+
};
|
|
55
|
+
const m = page?.marginTwips;
|
|
56
|
+
const margins = typeof m === "number" ? { ...defaultMargins, top: m, right: m, bottom: m, left: m } : m ? {
|
|
57
|
+
...defaultMargins,
|
|
58
|
+
top: m.top ?? defaultMargins.top,
|
|
59
|
+
right: m.right ?? defaultMargins.right,
|
|
60
|
+
bottom: m.bottom ?? defaultMargins.bottom,
|
|
61
|
+
left: m.left ?? defaultMargins.left,
|
|
62
|
+
header: m.header ?? defaultMargins.header,
|
|
63
|
+
footer: m.footer ?? defaultMargins.footer,
|
|
64
|
+
gutter: m.gutter ?? defaultMargins.gutter
|
|
65
|
+
} : defaultMargins;
|
|
66
|
+
return {
|
|
67
|
+
pageWidthTwips: Math.max(1, Math.round(resolvedSize.widthTwips)),
|
|
68
|
+
pageHeightTwips: Math.max(1, Math.round(resolvedSize.heightTwips)),
|
|
69
|
+
margins
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function wrapBodyXml(bodyXml, page) {
|
|
73
|
+
const { pageWidthTwips, pageHeightTwips, margins } = resolvePageSetupTwips(page);
|
|
43
74
|
const xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
44
75
|
<w:document
|
|
45
76
|
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
@@ -50,10 +81,9 @@ ${bodyXml}
|
|
|
50
81
|
<w:sectPr>
|
|
51
82
|
<w:headerReference w:type="default" r:id="rId6"/>
|
|
52
83
|
<w:footerReference w:type="default" r:id="rId7"/>
|
|
53
|
-
<w:pgSz w:w="
|
|
54
|
-
<w:pgMar w:top="
|
|
84
|
+
<w:pgSz w:w="${pageWidthTwips}" w:h="${pageHeightTwips}"/>
|
|
85
|
+
<w:pgMar w:top="${margins.top}" w:right="${margins.right}" w:bottom="${margins.bottom}" w:left="${margins.left}" w:header="${margins.header}" w:footer="${margins.footer}" w:gutter="${margins.gutter}"/>
|
|
55
86
|
<w:cols w:space="708"/>
|
|
56
|
-
<w:docGrid w:linePitch="360"/>
|
|
57
87
|
</w:sectPr>
|
|
58
88
|
</w:body>
|
|
59
89
|
</w:document>
|
|
@@ -70,7 +100,50 @@ function ensureWordNamespace(xml) {
|
|
|
70
100
|
`<w:document xmlns:w="${WORD_MAIN_NS}"`
|
|
71
101
|
);
|
|
72
102
|
}
|
|
73
|
-
function
|
|
103
|
+
function applyPageSetupToDocumentXml(xml, page) {
|
|
104
|
+
if (!page) return xml;
|
|
105
|
+
const { pageWidthTwips, pageHeightTwips, margins } = resolvePageSetupTwips(page);
|
|
106
|
+
const pgSzTag = `<w:pgSz w:w="${pageWidthTwips}" w:h="${pageHeightTwips}"/>`;
|
|
107
|
+
const pgMarTag = `<w:pgMar w:top="${margins.top}" w:right="${margins.right}" w:bottom="${margins.bottom}" w:left="${margins.left}" w:header="${margins.header}" w:footer="${margins.footer}" w:gutter="${margins.gutter}"/>`;
|
|
108
|
+
const sectPrRegex = /<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>/g;
|
|
109
|
+
const hasSectPr = sectPrRegex.test(xml);
|
|
110
|
+
if (!hasSectPr) {
|
|
111
|
+
const insertAt = xml.lastIndexOf("</w:body>");
|
|
112
|
+
if (insertAt < 0) return xml;
|
|
113
|
+
const sectPr = `
|
|
114
|
+
<w:sectPr>
|
|
115
|
+
${pgSzTag}
|
|
116
|
+
${pgMarTag}
|
|
117
|
+
<w:cols w:space="708"/>
|
|
118
|
+
</w:sectPr>
|
|
119
|
+
`;
|
|
120
|
+
return xml.slice(0, insertAt) + sectPr + xml.slice(insertAt);
|
|
121
|
+
}
|
|
122
|
+
return xml.replace(sectPrRegex, (sect) => {
|
|
123
|
+
let out = sect;
|
|
124
|
+
const insertTag = (tag) => {
|
|
125
|
+
const headerFooterRegex = /<w:(?:headerReference|footerReference)\b[^>]*\/>/g;
|
|
126
|
+
let last = null;
|
|
127
|
+
let m;
|
|
128
|
+
while (m = headerFooterRegex.exec(out)) last = m;
|
|
129
|
+
const open = out.match(/<w:sectPr\b[^>]*>/);
|
|
130
|
+
const insertPos = last ? last.index + last[0].length : open ? open[0].length : 0;
|
|
131
|
+
out = out.slice(0, insertPos) + tag + out.slice(insertPos);
|
|
132
|
+
};
|
|
133
|
+
if (/<w:pgSz\b[^>]*\/>/.test(out)) {
|
|
134
|
+
out = out.replace(/<w:pgSz\b[^>]*\/>/g, pgSzTag);
|
|
135
|
+
} else {
|
|
136
|
+
insertTag(pgSzTag);
|
|
137
|
+
}
|
|
138
|
+
if (/<w:pgMar\b[^>]*\/>/.test(out)) {
|
|
139
|
+
out = out.replace(/<w:pgMar\b[^>]*\/>/g, pgMarTag);
|
|
140
|
+
} else {
|
|
141
|
+
insertTag(pgMarTag);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function normalizeDocumentXml(xml, inputKind, page) {
|
|
74
147
|
const trimmed = xml.trim();
|
|
75
148
|
if (!trimmed) {
|
|
76
149
|
throw new Error("XML is empty.");
|
|
@@ -81,16 +154,16 @@ function normalizeDocumentXml(xml, inputKind) {
|
|
|
81
154
|
if (!hasWordDocumentRoot(withNs)) {
|
|
82
155
|
throw new Error('inputKind="document" requires a <w:document> root.');
|
|
83
156
|
}
|
|
84
|
-
return withNs;
|
|
157
|
+
return applyPageSetupToDocumentXml(withNs, page);
|
|
85
158
|
}
|
|
86
159
|
if (inputKind === "body") {
|
|
87
|
-
return wrapBodyXml(trimmed);
|
|
160
|
+
return wrapBodyXml(trimmed, page);
|
|
88
161
|
}
|
|
89
162
|
if (hasWordDocumentRoot(trimmed)) {
|
|
90
163
|
const withDecl = ensureXmlDeclaration(trimmed);
|
|
91
|
-
return ensureWordNamespace(withDecl);
|
|
164
|
+
return applyPageSetupToDocumentXml(ensureWordNamespace(withDecl), page);
|
|
92
165
|
}
|
|
93
|
-
return wrapBodyXml(trimmed);
|
|
166
|
+
return wrapBodyXml(trimmed, page);
|
|
94
167
|
}
|
|
95
168
|
var WORD_MAIN_NS;
|
|
96
169
|
var init_normalizeDocumentXml = __esm({
|
|
@@ -175,7 +248,7 @@ ${relLines}
|
|
|
175
248
|
`;
|
|
176
249
|
}
|
|
177
250
|
async function createDocxZipUint8Array(xml, options = {}) {
|
|
178
|
-
const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
|
|
251
|
+
const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto", options.page);
|
|
179
252
|
validateXmlIfNeeded(documentXml, options.validateXml ?? true);
|
|
180
253
|
const zip = new import_jszip.default();
|
|
181
254
|
zip.file("[Content_Types].xml", buildContentTypesXml([]));
|
|
@@ -195,7 +268,7 @@ async function createDocxZipUint8Array(xml, options = {}) {
|
|
|
195
268
|
return zip.generateAsync({ type: "uint8array" });
|
|
196
269
|
}
|
|
197
270
|
async function createDocxZipWithAssetsUint8Array(xml, options, assets) {
|
|
198
|
-
const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto");
|
|
271
|
+
const documentXml = normalizeDocumentXml(xml, options.inputKind ?? "auto", options.page);
|
|
199
272
|
validateXmlIfNeeded(documentXml, options.validateXml ?? true);
|
|
200
273
|
const zip = new import_jszip.default();
|
|
201
274
|
zip.file("[Content_Types].xml", buildContentTypesXml(assets));
|
|
@@ -627,6 +700,92 @@ function normalizeFontFamily(value) {
|
|
|
627
700
|
if (!first) return void 0;
|
|
628
701
|
return first.replace(/^["']|["']$/g, "");
|
|
629
702
|
}
|
|
703
|
+
function cssSelectorsHitTarget(selectorsText, targetSelector) {
|
|
704
|
+
const target = targetSelector.trim().toLowerCase();
|
|
705
|
+
if (!target) return false;
|
|
706
|
+
const selectors = selectorsText.replace(/\/\*[\s\S]*?\*\//g, " ").split(",").map((s) => s.replace(/\s+/g, " ").trim().toLowerCase()).filter(Boolean);
|
|
707
|
+
return selectors.some((s) => {
|
|
708
|
+
if (s === target) return true;
|
|
709
|
+
if (!s.startsWith(target)) return false;
|
|
710
|
+
const rest = s.slice(target.length);
|
|
711
|
+
return rest.startsWith(".") || rest.startsWith(":") || rest.startsWith("#") || rest.startsWith("[");
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
function parseCssRuleFromHtml(html, selector) {
|
|
715
|
+
const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
716
|
+
const merged = {};
|
|
717
|
+
let m;
|
|
718
|
+
while (m = styleTagRegex.exec(html)) {
|
|
719
|
+
const cssText = m[1] ?? "";
|
|
720
|
+
const target = selector.trim().toLowerCase();
|
|
721
|
+
if (!target) continue;
|
|
722
|
+
let depth = 0;
|
|
723
|
+
let lastRuleEnd = 0;
|
|
724
|
+
let ruleStart = -1;
|
|
725
|
+
let declStart = -1;
|
|
726
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
727
|
+
const ch = cssText[i];
|
|
728
|
+
if (ch === "{") {
|
|
729
|
+
if (depth === 0) {
|
|
730
|
+
ruleStart = lastRuleEnd;
|
|
731
|
+
declStart = i + 1;
|
|
732
|
+
}
|
|
733
|
+
depth++;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (ch === "}") {
|
|
737
|
+
if (depth > 0) depth--;
|
|
738
|
+
if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
|
|
739
|
+
const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
|
|
740
|
+
const decl = cssText.slice(declStart, i).trim();
|
|
741
|
+
lastRuleEnd = i + 1;
|
|
742
|
+
ruleStart = -1;
|
|
743
|
+
declStart = -1;
|
|
744
|
+
const hit = cssSelectorsHitTarget(selectorsText, target);
|
|
745
|
+
if (hit) Object.assign(merged, parseStyleAttribute(decl));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return merged;
|
|
751
|
+
}
|
|
752
|
+
function parseCssRuleFromHtmlFirst(html, selector) {
|
|
753
|
+
const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
754
|
+
let m;
|
|
755
|
+
while (m = styleTagRegex.exec(html)) {
|
|
756
|
+
const cssText = m[1] ?? "";
|
|
757
|
+
const target = selector.trim().toLowerCase();
|
|
758
|
+
if (!target) continue;
|
|
759
|
+
let depth = 0;
|
|
760
|
+
let lastRuleEnd = 0;
|
|
761
|
+
let ruleStart = -1;
|
|
762
|
+
let declStart = -1;
|
|
763
|
+
for (let i = 0; i < cssText.length; i++) {
|
|
764
|
+
const ch = cssText[i];
|
|
765
|
+
if (ch === "{") {
|
|
766
|
+
if (depth === 0) {
|
|
767
|
+
ruleStart = lastRuleEnd;
|
|
768
|
+
declStart = i + 1;
|
|
769
|
+
}
|
|
770
|
+
depth++;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (ch === "}") {
|
|
774
|
+
if (depth > 0) depth--;
|
|
775
|
+
if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
|
|
776
|
+
const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
|
|
777
|
+
const decl = cssText.slice(declStart, i).trim();
|
|
778
|
+
lastRuleEnd = i + 1;
|
|
779
|
+
ruleStart = -1;
|
|
780
|
+
declStart = -1;
|
|
781
|
+
const hit = cssSelectorsHitTarget(selectorsText, target);
|
|
782
|
+
if (hit) return parseStyleAttribute(decl);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return {};
|
|
788
|
+
}
|
|
630
789
|
function mergeTextStyle(base, patch) {
|
|
631
790
|
return {
|
|
632
791
|
bold: patch.bold ?? base.bold,
|
|
@@ -765,7 +924,7 @@ function applyMaxBoxPx(size, maxBox) {
|
|
|
765
924
|
const scale = Math.min(1, maxBox.maxWidthPx / w, maxBox.maxHeightPx / h);
|
|
766
925
|
return { widthPx: Math.max(1, Math.round(w * scale)), heightPx: Math.max(1, Math.round(h * scale)) };
|
|
767
926
|
}
|
|
768
|
-
function computeImageSizePx(node, intrinsic) {
|
|
927
|
+
function computeImageSizePx(node, intrinsic, maxBox) {
|
|
769
928
|
const wAttr = node.attribs?.width ? Number(node.attribs.width) : void 0;
|
|
770
929
|
const hAttr = node.attribs?.height ? Number(node.attribs.height) : void 0;
|
|
771
930
|
const css = parseStyleAttribute(node.attribs?.style);
|
|
@@ -777,9 +936,9 @@ function computeImageSizePx(node, intrinsic) {
|
|
|
777
936
|
const widthPx = typeof wCss === "number" ? wCss : typeof widthAttrPx === "number" ? widthAttrPx : intrinsic?.widthPx ?? 300;
|
|
778
937
|
const heightPx = typeof hCss === "number" ? hCss : typeof heightAttrPx === "number" ? heightAttrPx : intrinsic?.heightPx ?? 150;
|
|
779
938
|
const finalSize = typeof wCss === "number" && typeof hCss !== "number" ? { widthPx, heightPx: Math.max(1, Math.round(widthPx * ratio)) } : typeof hCss === "number" && typeof wCss !== "number" ? { widthPx: Math.max(1, Math.round(heightPx / ratio)), heightPx } : typeof widthAttrPx === "number" && typeof heightAttrPx !== "number" && intrinsic ? { widthPx, heightPx: Math.max(1, Math.round(widthPx * ratio)) } : typeof heightAttrPx === "number" && typeof widthAttrPx !== "number" && intrinsic ? { widthPx: Math.max(1, Math.round(heightPx / ratio)), heightPx } : { widthPx, heightPx };
|
|
780
|
-
return applyMaxBoxPx(finalSize, { maxWidthPx: 624, maxHeightPx: 864 });
|
|
939
|
+
return applyMaxBoxPx(finalSize, maxBox ?? { maxWidthPx: 624, maxHeightPx: 864 });
|
|
781
940
|
}
|
|
782
|
-
function collectInlineRuns(node, inherited, out, result) {
|
|
941
|
+
function collectInlineRuns(node, inherited, out, result, ctx) {
|
|
783
942
|
if (node.type === "text") {
|
|
784
943
|
const text = node.data ?? "";
|
|
785
944
|
if (text) out.push({ kind: "text", text, style: inherited });
|
|
@@ -797,7 +956,11 @@ function collectInlineRuns(node, inherited, out, result) {
|
|
|
797
956
|
const parsed = parseImageDataUrl(src);
|
|
798
957
|
if (!parsed) return;
|
|
799
958
|
const intrinsic = parseIntrinsicImageSizePx(parsed.contentType, parsed.data);
|
|
800
|
-
const { widthPx, heightPx } = computeImageSizePx(
|
|
959
|
+
const { widthPx, heightPx } = computeImageSizePx(
|
|
960
|
+
node,
|
|
961
|
+
intrinsic,
|
|
962
|
+
ctx ? { maxWidthPx: ctx.maxImageWidthPx, maxHeightPx: ctx.maxImageHeightPx } : void 0
|
|
963
|
+
);
|
|
801
964
|
const id = result.images.length + 1;
|
|
802
965
|
const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
|
|
803
966
|
const target = `media/image${id}.${parsed.extension}`;
|
|
@@ -820,7 +983,11 @@ function collectInlineRuns(node, inherited, out, result) {
|
|
|
820
983
|
const bufferW = node.attribs?.width ? Number(node.attribs.width) : void 0;
|
|
821
984
|
const bufferH = node.attribs?.height ? Number(node.attribs.height) : void 0;
|
|
822
985
|
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);
|
|
823
|
-
const { widthPx, heightPx } = computeImageSizePx(
|
|
986
|
+
const { widthPx, heightPx } = computeImageSizePx(
|
|
987
|
+
node,
|
|
988
|
+
intrinsic,
|
|
989
|
+
ctx ? { maxWidthPx: ctx.maxImageWidthPx, maxHeightPx: ctx.maxImageHeightPx } : void 0
|
|
990
|
+
);
|
|
824
991
|
const id = result.images.length + 1;
|
|
825
992
|
const relationshipId = `rId${id + IMAGE_RELATIONSHIP_ID_OFFSET}`;
|
|
826
993
|
const target = `media/image${id}.${parsed.extension}`;
|
|
@@ -837,11 +1004,11 @@ function collectInlineRuns(node, inherited, out, result) {
|
|
|
837
1004
|
}
|
|
838
1005
|
const next = mergeTextStyle(inherited, styleFromElement(node));
|
|
839
1006
|
const children2 = node.children ?? [];
|
|
840
|
-
for (const c of children2) collectInlineRuns(c, next, out, result);
|
|
1007
|
+
for (const c of children2) collectInlineRuns(c, next, out, result, ctx);
|
|
841
1008
|
return;
|
|
842
1009
|
}
|
|
843
1010
|
const children = node.children ?? [];
|
|
844
|
-
for (const c of children) collectInlineRuns(c, inherited, out, result);
|
|
1011
|
+
for (const c of children) collectInlineRuns(c, inherited, out, result, ctx);
|
|
845
1012
|
}
|
|
846
1013
|
function buildRunXml(style, text) {
|
|
847
1014
|
const rPrParts = [];
|
|
@@ -903,10 +1070,29 @@ function parseCssLengthToTwips(value, baseFontHalfPoints) {
|
|
|
903
1070
|
const basePt = baseFontHalfPoints / 2;
|
|
904
1071
|
return Math.round(Number(em[1]) * basePt * 20);
|
|
905
1072
|
}
|
|
1073
|
+
const rem = v.match(/^(-?\d+(?:\.\d+)?)rem$/);
|
|
1074
|
+
if (rem) return Math.round(Number(rem[1]) * 16 * 72 * 20 / 96);
|
|
906
1075
|
const num = v.match(/^(-?\d+(?:\.\d+)?)$/);
|
|
907
1076
|
if (num) return Math.round(Number(num[1]));
|
|
908
1077
|
return void 0;
|
|
909
1078
|
}
|
|
1079
|
+
function extractMarginBeforeAfterTwips(css, baseFontHalfPoints) {
|
|
1080
|
+
const before = parseCssLengthToTwips(css["margin-top"], baseFontHalfPoints);
|
|
1081
|
+
const after = parseCssLengthToTwips(css["margin-bottom"], baseFontHalfPoints);
|
|
1082
|
+
if (typeof before === "number" || typeof after === "number") {
|
|
1083
|
+
return { beforeTwips: before, afterTwips: after };
|
|
1084
|
+
}
|
|
1085
|
+
const m = css.margin?.trim().toLowerCase();
|
|
1086
|
+
if (!m) return {};
|
|
1087
|
+
const tokens = m.split(/\s+/).filter(Boolean);
|
|
1088
|
+
if (!tokens.length) return {};
|
|
1089
|
+
const topToken = tokens[0];
|
|
1090
|
+
const bottomToken = tokens.length === 1 ? tokens[0] : tokens.length === 2 ? tokens[0] : tokens[2] ?? tokens[0];
|
|
1091
|
+
return {
|
|
1092
|
+
beforeTwips: parseCssLengthToTwips(topToken, baseFontHalfPoints),
|
|
1093
|
+
afterTwips: parseCssLengthToTwips(bottomToken, baseFontHalfPoints)
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
910
1096
|
function inferFirstFontSizeHalfPoints(node) {
|
|
911
1097
|
const stack = [node];
|
|
912
1098
|
while (stack.length) {
|
|
@@ -923,8 +1109,10 @@ function inferFirstFontSizeHalfPoints(node) {
|
|
|
923
1109
|
}
|
|
924
1110
|
return void 0;
|
|
925
1111
|
}
|
|
926
|
-
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
927
|
-
const
|
|
1112
|
+
function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId, defaultCss) {
|
|
1113
|
+
const tag = node.type === "tag" ? node.name?.toLowerCase() : void 0;
|
|
1114
|
+
const inlineCss = parseStyleAttribute(node.attribs?.style);
|
|
1115
|
+
const css = defaultCss ? { ...defaultCss, ...inlineCss } : inlineCss;
|
|
928
1116
|
const parts = [];
|
|
929
1117
|
if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
|
|
930
1118
|
const shdHex = extractBackgroundFillHex(css);
|
|
@@ -951,10 +1139,17 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
|
951
1139
|
if (typeof hangingTwips === "number") indAttrs.push(`w:hanging="${hangingTwips}"`);
|
|
952
1140
|
if (typeof firstLine === "number") indAttrs.push(`w:firstLine="${firstLine}"`);
|
|
953
1141
|
if (indAttrs.length) parts.push(`<w:ind ${indAttrs.join(" ")}/>`);
|
|
954
|
-
const
|
|
955
|
-
const
|
|
1142
|
+
const hasInlineBefore = inlineCss["margin-top"] != null;
|
|
1143
|
+
const hasInlineAfter = inlineCss["margin-bottom"] != null;
|
|
1144
|
+
const beforeToken = inlineCss["margin-top"] ?? (pStyleId ? void 0 : defaultCss?.["margin-top"]);
|
|
1145
|
+
const afterToken = inlineCss["margin-bottom"] ?? (pStyleId ? void 0 : defaultCss?.["margin-bottom"]);
|
|
1146
|
+
let before = parseCssLengthToTwips(beforeToken, baseFontHalfPoints);
|
|
1147
|
+
let after = parseCssLengthToTwips(afterToken, baseFontHalfPoints);
|
|
1148
|
+
if (tag === "p" && !hasInlineBefore && typeof before === "number") before = Math.min(before, 160);
|
|
1149
|
+
if (tag === "p" && !hasInlineAfter && typeof after === "number") after = Math.min(after, 160);
|
|
956
1150
|
const lineHeight = (() => {
|
|
957
|
-
const
|
|
1151
|
+
const lhToken = inlineCss["line-height"] ?? (pStyleId ? void 0 : defaultCss?.["line-height"]);
|
|
1152
|
+
const lh = lhToken?.trim().toLowerCase();
|
|
958
1153
|
if (!lh || lh === "normal") return void 0;
|
|
959
1154
|
const unitless = lh.match(/^(\d+(?:\.\d+)?)$/);
|
|
960
1155
|
if (unitless) {
|
|
@@ -969,8 +1164,16 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
|
969
1164
|
})();
|
|
970
1165
|
if (typeof before === "number" || typeof after === "number" || typeof lineHeight === "number") {
|
|
971
1166
|
const attrs = [];
|
|
972
|
-
if (typeof before === "number")
|
|
973
|
-
|
|
1167
|
+
if (typeof before === "number") {
|
|
1168
|
+
attrs.push(`w:before="${Math.max(0, before)}"`);
|
|
1169
|
+
} else if (typeof lineHeight === "number") {
|
|
1170
|
+
attrs.push('w:before="0"');
|
|
1171
|
+
}
|
|
1172
|
+
if (typeof after === "number") {
|
|
1173
|
+
attrs.push(`w:after="${Math.max(0, after)}"`);
|
|
1174
|
+
} else if (typeof lineHeight === "number") {
|
|
1175
|
+
attrs.push('w:after="160"');
|
|
1176
|
+
}
|
|
974
1177
|
if (typeof lineHeight === "number") {
|
|
975
1178
|
attrs.push(`w:line="${lineHeight}"`, 'w:lineRule="exact"');
|
|
976
1179
|
}
|
|
@@ -979,16 +1182,19 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
|
|
|
979
1182
|
if (!parts.length) return "";
|
|
980
1183
|
return `<w:pPr>${parts.join("")}</w:pPr>`;
|
|
981
1184
|
}
|
|
982
|
-
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
|
|
983
|
-
const
|
|
1185
|
+
function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result, ctx) {
|
|
1186
|
+
const seededBaseStyle = ctx ? { fontSizeHalfPoints: ctx.defaultBaseFontHalfPoints } : {};
|
|
1187
|
+
const containerStyle = mergeTextStyle(mergeTextStyle(seededBaseStyle, baseStyle), styleFromElement(node));
|
|
984
1188
|
const baseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
|
|
985
|
-
const
|
|
1189
|
+
const computedBaseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
|
|
1190
|
+
const defaultCss = ctx ? node.type === "tag" && node.name?.toLowerCase() === "p" ? { ...ctx.defaultBodyCss, ...ctx.defaultPCss } : ctx.defaultBodyCss : void 0;
|
|
1191
|
+
const pPrXml = buildParagraphPrXml(node, computedBaseFontHalfPoints, extraInd, pStyleId, defaultCss);
|
|
986
1192
|
const runs = [];
|
|
987
1193
|
const res = result ?? {
|
|
988
1194
|
bodyXml: "",
|
|
989
1195
|
images: []
|
|
990
1196
|
};
|
|
991
|
-
for (const c of node.children ?? []) collectInlineRuns(c, containerStyle, runs, res);
|
|
1197
|
+
for (const c of node.children ?? []) collectInlineRuns(c, containerStyle, runs, res, ctx);
|
|
992
1198
|
const rXml = [];
|
|
993
1199
|
for (const token of runs) {
|
|
994
1200
|
if (token.kind === "br") {
|
|
@@ -1007,6 +1213,11 @@ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, res
|
|
|
1007
1213
|
if (!rXml.length) return "";
|
|
1008
1214
|
return `<w:p>${pPrXml}${rXml.join("")}</w:p>`;
|
|
1009
1215
|
}
|
|
1216
|
+
function buildSpacerParagraphXml(afterTwips) {
|
|
1217
|
+
const after = Math.max(0, Math.round(afterTwips));
|
|
1218
|
+
if (!after) return "";
|
|
1219
|
+
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>`;
|
|
1220
|
+
}
|
|
1010
1221
|
function isExplicitPageBreak(node) {
|
|
1011
1222
|
if (node.type !== "tag") return false;
|
|
1012
1223
|
const tag = node.name?.toLowerCase();
|
|
@@ -1015,13 +1226,14 @@ function isExplicitPageBreak(node) {
|
|
|
1015
1226
|
const classList = cls ? cls.split(/\s+/) : [];
|
|
1016
1227
|
if (tag === "hr" && classList.includes("page-break")) return true;
|
|
1017
1228
|
if (classList.includes("page-break")) return true;
|
|
1229
|
+
if (classList.includes("umo-page-break")) return true;
|
|
1018
1230
|
if (node.attribs?.["data-page-break"] === "true") return true;
|
|
1019
1231
|
const after = css["page-break-after"]?.toLowerCase() ?? css["break-after"]?.toLowerCase();
|
|
1020
1232
|
const before = css["page-break-before"]?.toLowerCase() ?? css["break-before"]?.toLowerCase();
|
|
1021
1233
|
if (after?.includes("always") || before?.includes("always")) return true;
|
|
1022
1234
|
return false;
|
|
1023
1235
|
}
|
|
1024
|
-
function buildListBlocks(listNode, ordered, level, result) {
|
|
1236
|
+
function buildListBlocks(listNode, ordered, level, result, ctx) {
|
|
1025
1237
|
const liNodes = (listNode.children ?? []).filter(
|
|
1026
1238
|
(c) => c.type === "tag" && c.name?.toLowerCase() === "li"
|
|
1027
1239
|
);
|
|
@@ -1043,7 +1255,7 @@ function buildListBlocks(listNode, ordered, level, result) {
|
|
|
1043
1255
|
continue;
|
|
1044
1256
|
}
|
|
1045
1257
|
}
|
|
1046
|
-
collectInlineRuns(c, baseStyle, runs, result);
|
|
1258
|
+
collectInlineRuns(c, baseStyle, runs, result, ctx);
|
|
1047
1259
|
}
|
|
1048
1260
|
const rXml = [];
|
|
1049
1261
|
for (const token of runs) {
|
|
@@ -1061,12 +1273,13 @@ function buildListBlocks(listNode, ordered, level, result) {
|
|
|
1061
1273
|
rXml.push(buildRunXml(token.style, text));
|
|
1062
1274
|
}
|
|
1063
1275
|
if (rXml.length) {
|
|
1064
|
-
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
|
|
1276
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
|
|
1065
1277
|
const pPrXml = buildParagraphPrXml(
|
|
1066
1278
|
li,
|
|
1067
1279
|
baseFontHalfPoints,
|
|
1068
1280
|
{ leftTwips, hangingTwips },
|
|
1069
|
-
void 0
|
|
1281
|
+
void 0,
|
|
1282
|
+
ctx?.defaultBodyCss
|
|
1070
1283
|
);
|
|
1071
1284
|
const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
|
|
1072
1285
|
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 +1287,7 @@ function buildListBlocks(listNode, ordered, level, result) {
|
|
|
1074
1287
|
}
|
|
1075
1288
|
for (const nested of nestedLists) {
|
|
1076
1289
|
const nestedOrdered = nested.name?.toLowerCase() === "ol";
|
|
1077
|
-
out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
|
|
1290
|
+
out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result, ctx));
|
|
1078
1291
|
}
|
|
1079
1292
|
}
|
|
1080
1293
|
return out;
|
|
@@ -1153,7 +1366,7 @@ function injectTableCellParagraphSpacing(pXml) {
|
|
|
1153
1366
|
}
|
|
1154
1367
|
return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
|
|
1155
1368
|
}
|
|
1156
|
-
function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
1369
|
+
function buildTableCellBlocksXml(cell, baseStyle, result, ctx) {
|
|
1157
1370
|
const children = cell.children ?? [];
|
|
1158
1371
|
const hasBlocks = children.some((c) => {
|
|
1159
1372
|
if (c.type !== "tag") return false;
|
|
@@ -1162,7 +1375,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
|
1162
1375
|
});
|
|
1163
1376
|
const out = [];
|
|
1164
1377
|
if (!hasBlocks) {
|
|
1165
|
-
const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
|
|
1378
|
+
const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result, ctx);
|
|
1166
1379
|
if (p) out.push(p);
|
|
1167
1380
|
return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
|
|
1168
1381
|
}
|
|
@@ -1170,18 +1383,18 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
|
1170
1383
|
if (c.type === "tag") {
|
|
1171
1384
|
const tag = c.name?.toLowerCase();
|
|
1172
1385
|
if (tag === "p") {
|
|
1173
|
-
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
|
|
1386
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result, ctx);
|
|
1174
1387
|
if (p) out.push(p);
|
|
1175
1388
|
continue;
|
|
1176
1389
|
}
|
|
1177
1390
|
if (tag && /^h[1-6]$/.test(tag)) {
|
|
1178
1391
|
const level = Number(tag.slice(1));
|
|
1179
|
-
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
|
|
1392
|
+
const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result, ctx);
|
|
1180
1393
|
if (p) out.push(p);
|
|
1181
1394
|
continue;
|
|
1182
1395
|
}
|
|
1183
1396
|
if (tag === "ul" || tag === "ol") {
|
|
1184
|
-
out.push(...buildListBlocks(c, tag === "ol", 0, result));
|
|
1397
|
+
out.push(...buildListBlocks(c, tag === "ol", 0, result, ctx));
|
|
1185
1398
|
continue;
|
|
1186
1399
|
}
|
|
1187
1400
|
if (tag === "img" || tag === "canvas") {
|
|
@@ -1194,7 +1407,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
|
|
|
1194
1407
|
if (!out.length) return "<w:p/>";
|
|
1195
1408
|
return out.map(injectTableCellParagraphSpacing).join("");
|
|
1196
1409
|
}
|
|
1197
|
-
function buildTableXml(tableNode, result) {
|
|
1410
|
+
function buildTableXml(tableNode, result, ctx) {
|
|
1198
1411
|
const rows = [];
|
|
1199
1412
|
const stack = [...tableNode.children ?? []];
|
|
1200
1413
|
while (stack.length) {
|
|
@@ -1206,9 +1419,19 @@ function buildTableXml(tableNode, result) {
|
|
|
1206
1419
|
(tr) => (tr.children ?? []).filter((c) => c.type === "tag" && (c.name === "td" || c.name === "th"))
|
|
1207
1420
|
);
|
|
1208
1421
|
const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
|
|
1209
|
-
const maxTableWidthTwips = 9360;
|
|
1422
|
+
const maxTableWidthTwips = ctx?.maxTableWidthTwips ?? 9360;
|
|
1423
|
+
const colGroup = (tableNode.children ?? []).find((c) => c.type === "tag" && c.name === "colgroup");
|
|
1424
|
+
const colWidthsFromGroup = [];
|
|
1425
|
+
if (colGroup) {
|
|
1426
|
+
const cols = (colGroup.children ?? []).filter((c) => c.type === "tag" && c.name === "col");
|
|
1427
|
+
for (const col of cols) {
|
|
1428
|
+
const css = parseStyleAttribute(col.attribs?.style);
|
|
1429
|
+
const w = parseCssLengthToTwips(css.width ?? css["min-width"], 28);
|
|
1430
|
+
colWidthsFromGroup.push(w);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1210
1433
|
const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
|
|
1211
|
-
let explicit;
|
|
1434
|
+
let explicit = colWidthsFromGroup[i];
|
|
1212
1435
|
let estimated = 0;
|
|
1213
1436
|
for (const cells of rowCells) {
|
|
1214
1437
|
const cell = cells[i];
|
|
@@ -1221,7 +1444,7 @@ function buildTableXml(tableNode, result) {
|
|
|
1221
1444
|
const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
|
|
1222
1445
|
estimated = Math.max(estimated, wTwips);
|
|
1223
1446
|
}
|
|
1224
|
-
const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
|
|
1447
|
+
const base = typeof explicit === "number" ? Math.max(explicit, estimated) : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
|
|
1225
1448
|
return Math.max(720, Math.min(6e3, Math.round(base)));
|
|
1226
1449
|
});
|
|
1227
1450
|
const normalizedColWidths = (() => {
|
|
@@ -1246,7 +1469,7 @@ function buildTableXml(tableNode, result) {
|
|
|
1246
1469
|
const cell = cells[i];
|
|
1247
1470
|
const isHeader = cell.name === "th";
|
|
1248
1471
|
const baseStyle = isHeader ? { bold: true } : {};
|
|
1249
|
-
const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
|
|
1472
|
+
const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result, ctx);
|
|
1250
1473
|
const css = parseStyleAttribute(cell.attribs?.style);
|
|
1251
1474
|
const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
|
|
1252
1475
|
const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
|
|
@@ -1335,14 +1558,14 @@ function buildTableXml(tableNode, result) {
|
|
|
1335
1558
|
const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
|
|
1336
1559
|
return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
|
|
1337
1560
|
}
|
|
1338
|
-
function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
|
|
1561
|
+
function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result, ctx) {
|
|
1339
1562
|
const wrapper = {
|
|
1340
1563
|
type: "tag",
|
|
1341
1564
|
name: "p",
|
|
1342
1565
|
attribs: { style: "text-align: center;" },
|
|
1343
1566
|
children: [node]
|
|
1344
1567
|
};
|
|
1345
|
-
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
|
|
1568
|
+
return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result, ctx);
|
|
1346
1569
|
}
|
|
1347
1570
|
function isRecognizedBlockTag(tag) {
|
|
1348
1571
|
if (!tag) return false;
|
|
@@ -1354,7 +1577,22 @@ function isRecognizedBlockTag(tag) {
|
|
|
1354
1577
|
if (tag === "pre") return true;
|
|
1355
1578
|
return false;
|
|
1356
1579
|
}
|
|
1357
|
-
function
|
|
1580
|
+
function subtreeHasRecognizedBlocks(root) {
|
|
1581
|
+
const stack = [root];
|
|
1582
|
+
while (stack.length) {
|
|
1583
|
+
const cur = stack.pop();
|
|
1584
|
+
if (cur.type === "tag") {
|
|
1585
|
+
if (isExplicitPageBreak(cur)) return true;
|
|
1586
|
+
if (isRecognizedBlockTag(cur.name?.toLowerCase())) return true;
|
|
1587
|
+
}
|
|
1588
|
+
const children = cur.children ?? [];
|
|
1589
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
1590
|
+
stack.push(children[i]);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
function collectDivBlocks(node, out, result, ctx) {
|
|
1358
1596
|
const parentStyle = node.attribs?.style;
|
|
1359
1597
|
const inlineBuffer = [];
|
|
1360
1598
|
const flushInline = () => {
|
|
@@ -1364,7 +1602,7 @@ function collectDivBlocks(node, out, result) {
|
|
|
1364
1602
|
attribs: { style: parentStyle },
|
|
1365
1603
|
children: inlineBuffer.splice(0)
|
|
1366
1604
|
};
|
|
1367
|
-
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
|
|
1605
|
+
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
|
|
1368
1606
|
if (pXml) out.push(pXml);
|
|
1369
1607
|
};
|
|
1370
1608
|
const children = node.children ?? [];
|
|
@@ -1378,19 +1616,15 @@ function collectDivBlocks(node, out, result) {
|
|
|
1378
1616
|
}
|
|
1379
1617
|
if (isRecognizedBlockTag(tag)) {
|
|
1380
1618
|
if (inlineBuffer.length) flushInline();
|
|
1381
|
-
collectBodyBlocks(child, out, result);
|
|
1619
|
+
collectBodyBlocks(child, out, result, ctx);
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
if (subtreeHasRecognizedBlocks(child)) {
|
|
1623
|
+
if (inlineBuffer.length) flushInline();
|
|
1624
|
+
collectBodyBlocks(child, out, result, ctx);
|
|
1382
1625
|
continue;
|
|
1383
1626
|
}
|
|
1384
1627
|
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
1628
|
if (inlineBuffer.length) flushInline();
|
|
1395
1629
|
const mergedStyle = [parentStyle, child.attribs?.style].filter(Boolean).join(";");
|
|
1396
1630
|
const wrapper = {
|
|
@@ -1399,7 +1633,7 @@ function collectDivBlocks(node, out, result) {
|
|
|
1399
1633
|
attribs: { style: mergedStyle || void 0 },
|
|
1400
1634
|
children: child.children ?? []
|
|
1401
1635
|
};
|
|
1402
|
-
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
|
|
1636
|
+
const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
|
|
1403
1637
|
if (pXml) out.push(pXml);
|
|
1404
1638
|
continue;
|
|
1405
1639
|
}
|
|
@@ -1408,7 +1642,7 @@ function collectDivBlocks(node, out, result) {
|
|
|
1408
1642
|
}
|
|
1409
1643
|
if (inlineBuffer.length) flushInline();
|
|
1410
1644
|
}
|
|
1411
|
-
function collectBodyBlocks(node, out, result) {
|
|
1645
|
+
function collectBodyBlocks(node, out, result, ctx) {
|
|
1412
1646
|
if (isSkippableSubtree(node)) return;
|
|
1413
1647
|
if (node.type === "tag") {
|
|
1414
1648
|
const tag = node.name?.toLowerCase();
|
|
@@ -1417,36 +1651,51 @@ function collectBodyBlocks(node, out, result) {
|
|
|
1417
1651
|
return;
|
|
1418
1652
|
}
|
|
1419
1653
|
if (tag === "p") {
|
|
1420
|
-
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
|
|
1654
|
+
const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result, ctx);
|
|
1421
1655
|
if (pXml) out.push(pXml);
|
|
1422
1656
|
return;
|
|
1423
1657
|
}
|
|
1424
1658
|
if (tag === "img" || tag === "canvas") {
|
|
1425
|
-
const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result);
|
|
1659
|
+
const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result, ctx);
|
|
1426
1660
|
if (pXml) out.push(pXml);
|
|
1427
1661
|
return;
|
|
1428
1662
|
}
|
|
1429
1663
|
if (tag && /^h[1-6]$/.test(tag)) {
|
|
1430
1664
|
const level = Number(tag.slice(1));
|
|
1431
|
-
const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
|
|
1665
|
+
const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result, ctx);
|
|
1432
1666
|
if (hXml) out.push(hXml);
|
|
1433
1667
|
return;
|
|
1434
1668
|
}
|
|
1435
1669
|
if (tag === "table") {
|
|
1436
|
-
const tblXml = buildTableXml(node, result);
|
|
1670
|
+
const tblXml = buildTableXml(node, result, ctx);
|
|
1437
1671
|
if (tblXml) out.push(tblXml);
|
|
1438
1672
|
return;
|
|
1439
1673
|
}
|
|
1440
1674
|
if (tag === "ul" || tag === "ol") {
|
|
1441
|
-
out.push(...buildListBlocks(node, tag === "ol", 0, result));
|
|
1675
|
+
out.push(...buildListBlocks(node, tag === "ol", 0, result, ctx));
|
|
1442
1676
|
return;
|
|
1443
1677
|
}
|
|
1444
1678
|
if (tag === "div") {
|
|
1445
|
-
|
|
1679
|
+
if (hasClass(node, "tableWrapper")) {
|
|
1680
|
+
const display = ctx.tableWrapperCss.display?.trim().toLowerCase();
|
|
1681
|
+
if (display !== "contents") {
|
|
1682
|
+
const baseFontHalfPoints = inferFirstFontSizeHalfPoints(node) ?? ctx.defaultBaseFontHalfPoints;
|
|
1683
|
+
const { beforeTwips, afterTwips } = extractMarginBeforeAfterTwips(ctx.tableWrapperCss, baseFontHalfPoints);
|
|
1684
|
+
const beforeXml = typeof beforeTwips === "number" && beforeTwips > 0 ? buildSpacerParagraphXml(beforeTwips) : "";
|
|
1685
|
+
const afterXml = typeof afterTwips === "number" && afterTwips > 0 ? buildSpacerParagraphXml(afterTwips) : "";
|
|
1686
|
+
if (beforeXml) out.push(beforeXml);
|
|
1687
|
+
collectDivBlocks(node, out, result, ctx);
|
|
1688
|
+
if (afterXml) out.push(afterXml);
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
collectDivBlocks(node, out, result, ctx);
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
collectDivBlocks(node, out, result, ctx);
|
|
1446
1695
|
return;
|
|
1447
1696
|
}
|
|
1448
1697
|
}
|
|
1449
|
-
for (const c of node.children ?? []) collectBodyBlocks(c, out, result);
|
|
1698
|
+
for (const c of node.children ?? []) collectBodyBlocks(c, out, result, ctx);
|
|
1450
1699
|
}
|
|
1451
1700
|
function textToWordBodyXml(text) {
|
|
1452
1701
|
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
@@ -1464,8 +1713,22 @@ function textToWordBodyXml(text) {
|
|
|
1464
1713
|
}
|
|
1465
1714
|
return out.join("");
|
|
1466
1715
|
}
|
|
1467
|
-
function htmlToWordBody(html) {
|
|
1716
|
+
function htmlToWordBody(html, layout) {
|
|
1468
1717
|
const normalized = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1718
|
+
const bodyCss = parseCssRuleFromHtmlFirst(normalized, "body");
|
|
1719
|
+
const pCss = parseCssRuleFromHtmlFirst(normalized, "p");
|
|
1720
|
+
const defaultBaseFontHalfPoints = parseFontSizeToHalfPoints(bodyCss["font-size"]) ?? 28;
|
|
1721
|
+
const defaultBodyCss = {};
|
|
1722
|
+
if (bodyCss["line-height"]) defaultBodyCss["line-height"] = bodyCss["line-height"];
|
|
1723
|
+
const defaultPCss = {};
|
|
1724
|
+
if (pCss["line-height"]) defaultPCss["line-height"] = pCss["line-height"];
|
|
1725
|
+
if (pCss["margin-top"]) defaultPCss["margin-top"] = pCss["margin-top"];
|
|
1726
|
+
if (pCss["margin-bottom"]) defaultPCss["margin-bottom"] = pCss["margin-bottom"];
|
|
1727
|
+
if (pCss["text-align"]) defaultPCss["text-align"] = pCss["text-align"];
|
|
1728
|
+
const tableWrapperCss = {
|
|
1729
|
+
...parseCssRuleFromHtml(normalized, ".tableWrapper"),
|
|
1730
|
+
...parseCssRuleFromHtml(normalized, ".tiptap .tableWrapper")
|
|
1731
|
+
};
|
|
1469
1732
|
const doc = (0, import_htmlparser2.parseDocument)(normalized, {
|
|
1470
1733
|
lowerCaseAttributeNames: true,
|
|
1471
1734
|
lowerCaseTags: true,
|
|
@@ -1473,12 +1736,21 @@ function htmlToWordBody(html) {
|
|
|
1473
1736
|
});
|
|
1474
1737
|
const result = { bodyXml: "", images: [] };
|
|
1475
1738
|
const out = [];
|
|
1476
|
-
|
|
1739
|
+
const ctx = {
|
|
1740
|
+
defaultBaseFontHalfPoints,
|
|
1741
|
+
defaultBodyCss,
|
|
1742
|
+
defaultPCss,
|
|
1743
|
+
tableWrapperCss,
|
|
1744
|
+
maxTableWidthTwips: layout?.maxTableWidthTwips ?? 9360,
|
|
1745
|
+
maxImageWidthPx: layout?.maxImageWidthPx ?? 624,
|
|
1746
|
+
maxImageHeightPx: layout?.maxImageHeightPx ?? 864
|
|
1747
|
+
};
|
|
1748
|
+
collectBodyBlocks(doc, out, result, ctx);
|
|
1477
1749
|
result.bodyXml = out.join("");
|
|
1478
1750
|
return result;
|
|
1479
1751
|
}
|
|
1480
1752
|
function htmlToWordBodyXml(html) {
|
|
1481
|
-
const { bodyXml } = htmlToWordBody(html);
|
|
1753
|
+
const { bodyXml } = htmlToWordBody(html, void 0);
|
|
1482
1754
|
if (!bodyXml) {
|
|
1483
1755
|
const text = getTextContent(
|
|
1484
1756
|
(0, import_htmlparser2.parseDocument)(html, {
|
|
@@ -1491,8 +1763,8 @@ function htmlToWordBodyXml(html) {
|
|
|
1491
1763
|
}
|
|
1492
1764
|
return bodyXml;
|
|
1493
1765
|
}
|
|
1494
|
-
function htmlToWordBodyWithAssets(html) {
|
|
1495
|
-
const result = htmlToWordBody(html);
|
|
1766
|
+
function htmlToWordBodyWithAssets(html, layout) {
|
|
1767
|
+
const result = htmlToWordBody(html, layout);
|
|
1496
1768
|
if (!result.bodyXml) {
|
|
1497
1769
|
const text = getTextContent(
|
|
1498
1770
|
(0, import_htmlparser2.parseDocument)(html, {
|
|
@@ -1532,6 +1804,47 @@ function looksLikeHtml(input) {
|
|
|
1532
1804
|
if (!s.includes("<") || !s.includes(">")) return false;
|
|
1533
1805
|
return /<\/?[a-zA-Z][\s\S]*?>/.test(s);
|
|
1534
1806
|
}
|
|
1807
|
+
function resolvePageSetupTwips2(page) {
|
|
1808
|
+
const size = page?.size;
|
|
1809
|
+
const defaultSize = { widthTwips: 11906, heightTwips: 16838 };
|
|
1810
|
+
const resolvedSize = typeof size === "object" && size && "widthTwips" in size && "heightTwips" in size ? { widthTwips: Math.round(size.widthTwips), heightTwips: Math.round(size.heightTwips) } : size === "Letter" ? { widthTwips: 12240, heightTwips: 15840 } : size === "Letter_Landscape" ? { widthTwips: 15840, heightTwips: 12240 } : size === "A4_Landscape" ? { widthTwips: 16838, heightTwips: 11906 } : defaultSize;
|
|
1811
|
+
const defaultMargins = {
|
|
1812
|
+
top: 1440,
|
|
1813
|
+
right: 1440,
|
|
1814
|
+
bottom: 1440,
|
|
1815
|
+
left: 1440,
|
|
1816
|
+
header: 708,
|
|
1817
|
+
footer: 708,
|
|
1818
|
+
gutter: 0
|
|
1819
|
+
};
|
|
1820
|
+
const m = page?.marginTwips;
|
|
1821
|
+
const mergedMargins = typeof m === "number" ? { ...defaultMargins, top: m, right: m, bottom: m, left: m } : m ? {
|
|
1822
|
+
...defaultMargins,
|
|
1823
|
+
top: m.top ?? defaultMargins.top,
|
|
1824
|
+
right: m.right ?? defaultMargins.right,
|
|
1825
|
+
bottom: m.bottom ?? defaultMargins.bottom,
|
|
1826
|
+
left: m.left ?? defaultMargins.left,
|
|
1827
|
+
header: m.header ?? defaultMargins.header,
|
|
1828
|
+
footer: m.footer ?? defaultMargins.footer,
|
|
1829
|
+
gutter: m.gutter ?? defaultMargins.gutter
|
|
1830
|
+
} : defaultMargins;
|
|
1831
|
+
return {
|
|
1832
|
+
pageWidthTwips: Math.max(1, Math.round(resolvedSize.widthTwips)),
|
|
1833
|
+
pageHeightTwips: Math.max(1, Math.round(resolvedSize.heightTwips)),
|
|
1834
|
+
margins: mergedMargins
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
function computeHtmlConversionLayoutFromPage(page) {
|
|
1838
|
+
const { pageWidthTwips, pageHeightTwips, margins } = resolvePageSetupTwips2(page);
|
|
1839
|
+
const contentWidthTwips = Math.max(1, pageWidthTwips - margins.left - margins.right);
|
|
1840
|
+
const contentHeightTwips = Math.max(1, pageHeightTwips - margins.top - margins.bottom);
|
|
1841
|
+
const twipsToPx = (twips) => Math.max(1, Math.round(twips / 15));
|
|
1842
|
+
return {
|
|
1843
|
+
maxTableWidthTwips: contentWidthTwips,
|
|
1844
|
+
maxImageWidthPx: twipsToPx(contentWidthTwips),
|
|
1845
|
+
maxImageHeightPx: twipsToPx(contentHeightTwips)
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1535
1848
|
async function xmlToDocxUint8Array(xml, options = {}) {
|
|
1536
1849
|
const { createDocxZipUint8Array: createDocxZipUint8Array2 } = await Promise.resolve().then(() => (init_createDocxZip(), createDocxZip_exports));
|
|
1537
1850
|
return createDocxZipUint8Array2(xml, options);
|
|
@@ -1560,14 +1873,17 @@ async function htmlToDocxUint8Array(html, options = {}) {
|
|
|
1560
1873
|
const bodyXml2 = textToWordBodyXml2(html);
|
|
1561
1874
|
return xmlToDocxUint8Array(bodyXml2, {
|
|
1562
1875
|
inputKind: "body",
|
|
1563
|
-
validateXml: options.validateXml
|
|
1876
|
+
validateXml: options.validateXml,
|
|
1877
|
+
page: options.page
|
|
1564
1878
|
});
|
|
1565
1879
|
}
|
|
1566
|
-
const
|
|
1880
|
+
const layout = options.page ? computeHtmlConversionLayoutFromPage(options.page) : void 0;
|
|
1881
|
+
const { bodyXml, images } = htmlToWordBodyWithAssets2(html, layout);
|
|
1567
1882
|
if (!images.length) {
|
|
1568
1883
|
return xmlToDocxUint8Array(bodyXml || htmlToWordBodyXml2(html), {
|
|
1569
1884
|
inputKind: "body",
|
|
1570
|
-
validateXml: options.validateXml
|
|
1885
|
+
validateXml: options.validateXml,
|
|
1886
|
+
page: options.page
|
|
1571
1887
|
});
|
|
1572
1888
|
}
|
|
1573
1889
|
const assets = images.map((img) => ({
|
|
@@ -1579,7 +1895,7 @@ async function htmlToDocxUint8Array(html, options = {}) {
|
|
|
1579
1895
|
}));
|
|
1580
1896
|
return createDocxZipWithAssetsUint8Array2(
|
|
1581
1897
|
bodyXml,
|
|
1582
|
-
{ inputKind: "body", validateXml: options.validateXml },
|
|
1898
|
+
{ inputKind: "body", validateXml: options.validateXml, page: options.page },
|
|
1583
1899
|
assets
|
|
1584
1900
|
);
|
|
1585
1901
|
}
|