@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.
@@ -71,6 +71,92 @@ function normalizeFontFamily(value) {
71
71
  if (!first) return void 0;
72
72
  return first.replace(/^["']|["']$/g, "");
73
73
  }
74
+ function cssSelectorsHitTarget(selectorsText, targetSelector) {
75
+ const target = targetSelector.trim().toLowerCase();
76
+ if (!target) return false;
77
+ const selectors = selectorsText.replace(/\/\*[\s\S]*?\*\//g, " ").split(",").map((s) => s.replace(/\s+/g, " ").trim().toLowerCase()).filter(Boolean);
78
+ return selectors.some((s) => {
79
+ if (s === target) return true;
80
+ if (!s.startsWith(target)) return false;
81
+ const rest = s.slice(target.length);
82
+ return rest.startsWith(".") || rest.startsWith(":") || rest.startsWith("#") || rest.startsWith("[");
83
+ });
84
+ }
85
+ function parseCssRuleFromHtml(html, selector) {
86
+ const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
87
+ const merged = {};
88
+ let m;
89
+ while (m = styleTagRegex.exec(html)) {
90
+ const cssText = m[1] ?? "";
91
+ const target = selector.trim().toLowerCase();
92
+ if (!target) continue;
93
+ let depth = 0;
94
+ let lastRuleEnd = 0;
95
+ let ruleStart = -1;
96
+ let declStart = -1;
97
+ for (let i = 0; i < cssText.length; i++) {
98
+ const ch = cssText[i];
99
+ if (ch === "{") {
100
+ if (depth === 0) {
101
+ ruleStart = lastRuleEnd;
102
+ declStart = i + 1;
103
+ }
104
+ depth++;
105
+ continue;
106
+ }
107
+ if (ch === "}") {
108
+ if (depth > 0) depth--;
109
+ if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
110
+ const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
111
+ const decl = cssText.slice(declStart, i).trim();
112
+ lastRuleEnd = i + 1;
113
+ ruleStart = -1;
114
+ declStart = -1;
115
+ const hit = cssSelectorsHitTarget(selectorsText, target);
116
+ if (hit) Object.assign(merged, parseStyleAttribute(decl));
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return merged;
122
+ }
123
+ function parseCssRuleFromHtmlFirst(html, selector) {
124
+ const styleTagRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
125
+ let m;
126
+ while (m = styleTagRegex.exec(html)) {
127
+ const cssText = m[1] ?? "";
128
+ const target = selector.trim().toLowerCase();
129
+ if (!target) continue;
130
+ let depth = 0;
131
+ let lastRuleEnd = 0;
132
+ let ruleStart = -1;
133
+ let declStart = -1;
134
+ for (let i = 0; i < cssText.length; i++) {
135
+ const ch = cssText[i];
136
+ if (ch === "{") {
137
+ if (depth === 0) {
138
+ ruleStart = lastRuleEnd;
139
+ declStart = i + 1;
140
+ }
141
+ depth++;
142
+ continue;
143
+ }
144
+ if (ch === "}") {
145
+ if (depth > 0) depth--;
146
+ if (depth === 0 && ruleStart >= 0 && declStart >= 0) {
147
+ const selectorsText = cssText.slice(ruleStart, declStart - 1).trim().toLowerCase();
148
+ const decl = cssText.slice(declStart, i).trim();
149
+ lastRuleEnd = i + 1;
150
+ ruleStart = -1;
151
+ declStart = -1;
152
+ const hit = cssSelectorsHitTarget(selectorsText, target);
153
+ if (hit) return parseStyleAttribute(decl);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ return {};
159
+ }
74
160
  function mergeTextStyle(base, patch) {
75
161
  return {
76
162
  bold: patch.bold ?? base.bold,
@@ -348,10 +434,29 @@ function parseCssLengthToTwips(value, baseFontHalfPoints) {
348
434
  const basePt = baseFontHalfPoints / 2;
349
435
  return Math.round(Number(em[1]) * basePt * 20);
350
436
  }
437
+ const rem = v.match(/^(-?\d+(?:\.\d+)?)rem$/);
438
+ if (rem) return Math.round(Number(rem[1]) * 16 * 72 * 20 / 96);
351
439
  const num = v.match(/^(-?\d+(?:\.\d+)?)$/);
352
440
  if (num) return Math.round(Number(num[1]));
353
441
  return void 0;
354
442
  }
443
+ function extractMarginBeforeAfterTwips(css, baseFontHalfPoints) {
444
+ const before = parseCssLengthToTwips(css["margin-top"], baseFontHalfPoints);
445
+ const after = parseCssLengthToTwips(css["margin-bottom"], baseFontHalfPoints);
446
+ if (typeof before === "number" || typeof after === "number") {
447
+ return { beforeTwips: before, afterTwips: after };
448
+ }
449
+ const m = css.margin?.trim().toLowerCase();
450
+ if (!m) return {};
451
+ const tokens = m.split(/\s+/).filter(Boolean);
452
+ if (!tokens.length) return {};
453
+ const topToken = tokens[0];
454
+ const bottomToken = tokens.length === 1 ? tokens[0] : tokens.length === 2 ? tokens[0] : tokens[2] ?? tokens[0];
455
+ return {
456
+ beforeTwips: parseCssLengthToTwips(topToken, baseFontHalfPoints),
457
+ afterTwips: parseCssLengthToTwips(bottomToken, baseFontHalfPoints)
458
+ };
459
+ }
355
460
  function inferFirstFontSizeHalfPoints(node) {
356
461
  const stack = [node];
357
462
  while (stack.length) {
@@ -368,8 +473,10 @@ function inferFirstFontSizeHalfPoints(node) {
368
473
  }
369
474
  return void 0;
370
475
  }
371
- function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
372
- const css = parseStyleAttribute(node.attribs?.style);
476
+ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId, defaultCss) {
477
+ const tag = node.type === "tag" ? node.name?.toLowerCase() : void 0;
478
+ const inlineCss = parseStyleAttribute(node.attribs?.style);
479
+ const css = defaultCss ? { ...defaultCss, ...inlineCss } : inlineCss;
373
480
  const parts = [];
374
481
  if (pStyleId) parts.push(`<w:pStyle w:val="${escapeXmlText(pStyleId)}"/>`);
375
482
  const shdHex = extractBackgroundFillHex(css);
@@ -396,10 +503,17 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
396
503
  if (typeof hangingTwips === "number") indAttrs.push(`w:hanging="${hangingTwips}"`);
397
504
  if (typeof firstLine === "number") indAttrs.push(`w:firstLine="${firstLine}"`);
398
505
  if (indAttrs.length) parts.push(`<w:ind ${indAttrs.join(" ")}/>`);
399
- const before = parseCssLengthToTwips(css["margin-top"], baseFontHalfPoints);
400
- const after = parseCssLengthToTwips(css["margin-bottom"], baseFontHalfPoints);
506
+ const hasInlineBefore = inlineCss["margin-top"] != null;
507
+ const hasInlineAfter = inlineCss["margin-bottom"] != null;
508
+ const beforeToken = inlineCss["margin-top"] ?? (pStyleId ? void 0 : defaultCss?.["margin-top"]);
509
+ const afterToken = inlineCss["margin-bottom"] ?? (pStyleId ? void 0 : defaultCss?.["margin-bottom"]);
510
+ let before = parseCssLengthToTwips(beforeToken, baseFontHalfPoints);
511
+ let after = parseCssLengthToTwips(afterToken, baseFontHalfPoints);
512
+ if (tag === "p" && !hasInlineBefore && typeof before === "number") before = Math.min(before, 160);
513
+ if (tag === "p" && !hasInlineAfter && typeof after === "number") after = Math.min(after, 160);
401
514
  const lineHeight = (() => {
402
- const lh = css["line-height"]?.trim().toLowerCase();
515
+ const lhToken = inlineCss["line-height"] ?? (pStyleId ? void 0 : defaultCss?.["line-height"]);
516
+ const lh = lhToken?.trim().toLowerCase();
403
517
  if (!lh || lh === "normal") return void 0;
404
518
  const unitless = lh.match(/^(\d+(?:\.\d+)?)$/);
405
519
  if (unitless) {
@@ -414,8 +528,16 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
414
528
  })();
415
529
  if (typeof before === "number" || typeof after === "number" || typeof lineHeight === "number") {
416
530
  const attrs = [];
417
- if (typeof before === "number") attrs.push(`w:before="${Math.max(0, before)}"`);
418
- if (typeof after === "number") attrs.push(`w:after="${Math.max(0, after)}"`);
531
+ if (typeof before === "number") {
532
+ attrs.push(`w:before="${Math.max(0, before)}"`);
533
+ } else if (typeof lineHeight === "number") {
534
+ attrs.push('w:before="0"');
535
+ }
536
+ if (typeof after === "number") {
537
+ attrs.push(`w:after="${Math.max(0, after)}"`);
538
+ } else if (typeof lineHeight === "number") {
539
+ attrs.push('w:after="160"');
540
+ }
419
541
  if (typeof lineHeight === "number") {
420
542
  attrs.push(`w:line="${lineHeight}"`, 'w:lineRule="exact"');
421
543
  }
@@ -424,10 +546,13 @@ function buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId) {
424
546
  if (!parts.length) return "";
425
547
  return `<w:pPr>${parts.join("")}</w:pPr>`;
426
548
  }
427
- function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result) {
428
- const containerStyle = mergeTextStyle(baseStyle, styleFromElement(node));
549
+ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, result, ctx) {
550
+ const seededBaseStyle = ctx ? { fontSizeHalfPoints: ctx.defaultBaseFontHalfPoints } : {};
551
+ const containerStyle = mergeTextStyle(mergeTextStyle(seededBaseStyle, baseStyle), styleFromElement(node));
429
552
  const baseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;
430
- const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd, pStyleId);
553
+ const computedBaseFontHalfPoints = containerStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
554
+ const defaultCss = ctx ? node.type === "tag" && node.name?.toLowerCase() === "p" ? { ...ctx.defaultBodyCss, ...ctx.defaultPCss } : ctx.defaultBodyCss : void 0;
555
+ const pPrXml = buildParagraphPrXml(node, computedBaseFontHalfPoints, extraInd, pStyleId, defaultCss);
431
556
  const runs = [];
432
557
  const res = result ?? {
433
558
  bodyXml: "",
@@ -453,6 +578,11 @@ function buildParagraphXmlFromContainer(node, baseStyle, extraInd, pStyleId, res
453
578
  return `<w:p>${pPrXml}${rXml.join("")}</w:p>`;
454
579
  }
455
580
  var PAGE_BREAK_XML = '<w:p><w:r><w:br w:type="page"/></w:r></w:p>';
581
+ function buildSpacerParagraphXml(afterTwips) {
582
+ const after = Math.max(0, Math.round(afterTwips));
583
+ if (!after) return "";
584
+ 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>`;
585
+ }
456
586
  function isExplicitPageBreak(node) {
457
587
  if (node.type !== "tag") return false;
458
588
  const tag = node.name?.toLowerCase();
@@ -461,13 +591,14 @@ function isExplicitPageBreak(node) {
461
591
  const classList = cls ? cls.split(/\s+/) : [];
462
592
  if (tag === "hr" && classList.includes("page-break")) return true;
463
593
  if (classList.includes("page-break")) return true;
594
+ if (classList.includes("umo-page-break")) return true;
464
595
  if (node.attribs?.["data-page-break"] === "true") return true;
465
596
  const after = css["page-break-after"]?.toLowerCase() ?? css["break-after"]?.toLowerCase();
466
597
  const before = css["page-break-before"]?.toLowerCase() ?? css["break-before"]?.toLowerCase();
467
598
  if (after?.includes("always") || before?.includes("always")) return true;
468
599
  return false;
469
600
  }
470
- function buildListBlocks(listNode, ordered, level, result) {
601
+ function buildListBlocks(listNode, ordered, level, result, ctx) {
471
602
  const liNodes = (listNode.children ?? []).filter(
472
603
  (c) => c.type === "tag" && c.name?.toLowerCase() === "li"
473
604
  );
@@ -507,12 +638,13 @@ function buildListBlocks(listNode, ordered, level, result) {
507
638
  rXml.push(buildRunXml(token.style, text));
508
639
  }
509
640
  if (rXml.length) {
510
- const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? 28;
641
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(li) ?? ctx?.defaultBaseFontHalfPoints ?? 28;
511
642
  const pPrXml = buildParagraphPrXml(
512
643
  li,
513
644
  baseFontHalfPoints,
514
645
  { leftTwips, hangingTwips },
515
- void 0
646
+ void 0,
647
+ ctx?.defaultBodyCss
516
648
  );
517
649
  const numPrXml = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
518
650
  const mergedPPrXml = pPrXml ? pPrXml.replace("<w:pPr>", `<w:pPr>${numPrXml}`) : `<w:pPr>${numPrXml}<w:ind w:left="${leftTwips}" w:hanging="${hangingTwips}"/></w:pPr>`;
@@ -520,7 +652,7 @@ function buildListBlocks(listNode, ordered, level, result) {
520
652
  }
521
653
  for (const nested of nestedLists) {
522
654
  const nestedOrdered = nested.name?.toLowerCase() === "ol";
523
- out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result));
655
+ out.push(...buildListBlocks(nested, nestedOrdered, ilvl + 1, result, ctx));
524
656
  }
525
657
  }
526
658
  return out;
@@ -599,7 +731,7 @@ function injectTableCellParagraphSpacing(pXml) {
599
731
  }
600
732
  return pXml.replace("<w:p>", `<w:p><w:pPr>${spacingXml}</w:pPr>`);
601
733
  }
602
- function buildTableCellBlocksXml(cell, baseStyle, result) {
734
+ function buildTableCellBlocksXml(cell, baseStyle, result, ctx) {
603
735
  const children = cell.children ?? [];
604
736
  const hasBlocks = children.some((c) => {
605
737
  if (c.type !== "tag") return false;
@@ -608,7 +740,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
608
740
  });
609
741
  const out = [];
610
742
  if (!hasBlocks) {
611
- const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result);
743
+ const p = buildParagraphXmlFromContainer(cell, baseStyle, void 0, void 0, result, ctx);
612
744
  if (p) out.push(p);
613
745
  return out.length ? out.map(injectTableCellParagraphSpacing).join("") : "<w:p/>";
614
746
  }
@@ -616,18 +748,18 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
616
748
  if (c.type === "tag") {
617
749
  const tag = c.name?.toLowerCase();
618
750
  if (tag === "p") {
619
- const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result);
751
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, void 0, result, ctx);
620
752
  if (p) out.push(p);
621
753
  continue;
622
754
  }
623
755
  if (tag && /^h[1-6]$/.test(tag)) {
624
756
  const level = Number(tag.slice(1));
625
- const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result);
757
+ const p = buildParagraphXmlFromContainer(c, baseStyle, void 0, `Heading${level}`, result, ctx);
626
758
  if (p) out.push(p);
627
759
  continue;
628
760
  }
629
761
  if (tag === "ul" || tag === "ol") {
630
- out.push(...buildListBlocks(c, tag === "ol", 0, result));
762
+ out.push(...buildListBlocks(c, tag === "ol", 0, result, ctx));
631
763
  continue;
632
764
  }
633
765
  if (tag === "img" || tag === "canvas") {
@@ -640,7 +772,7 @@ function buildTableCellBlocksXml(cell, baseStyle, result) {
640
772
  if (!out.length) return "<w:p/>";
641
773
  return out.map(injectTableCellParagraphSpacing).join("");
642
774
  }
643
- function buildTableXml(tableNode, result) {
775
+ function buildTableXml(tableNode, result, ctx) {
644
776
  const rows = [];
645
777
  const stack = [...tableNode.children ?? []];
646
778
  while (stack.length) {
@@ -653,8 +785,18 @@ function buildTableXml(tableNode, result) {
653
785
  );
654
786
  const colCount = Math.max(0, ...rowCells.map((cells) => cells.length));
655
787
  const maxTableWidthTwips = 9360;
788
+ const colGroup = (tableNode.children ?? []).find((c) => c.type === "tag" && c.name === "colgroup");
789
+ const colWidthsFromGroup = [];
790
+ if (colGroup) {
791
+ const cols = (colGroup.children ?? []).filter((c) => c.type === "tag" && c.name === "col");
792
+ for (const col of cols) {
793
+ const css = parseStyleAttribute(col.attribs?.style);
794
+ const w = parseCssLengthToTwips(css.width ?? css["min-width"], 28);
795
+ colWidthsFromGroup.push(w);
796
+ }
797
+ }
656
798
  const estimatedColWidths = new Array(colCount).fill(0).map((_, i) => {
657
- let explicit;
799
+ let explicit = colWidthsFromGroup[i];
658
800
  let estimated = 0;
659
801
  for (const cells of rowCells) {
660
802
  const cell = cells[i];
@@ -667,7 +809,7 @@ function buildTableXml(tableNode, result) {
667
809
  const wTwips = estimateTextWidthTwips(text, baseFontHalfPoints) + 240;
668
810
  estimated = Math.max(estimated, wTwips);
669
811
  }
670
- const base = typeof explicit === "number" ? explicit : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
812
+ const base = typeof explicit === "number" ? Math.max(explicit, estimated) : estimated || Math.round(maxTableWidthTwips / Math.max(1, colCount));
671
813
  return Math.max(720, Math.min(6e3, Math.round(base)));
672
814
  });
673
815
  const normalizedColWidths = (() => {
@@ -692,7 +834,7 @@ function buildTableXml(tableNode, result) {
692
834
  const cell = cells[i];
693
835
  const isHeader = cell.name === "th";
694
836
  const baseStyle = isHeader ? { bold: true } : {};
695
- const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result);
837
+ const paragraphs = buildTableCellBlocksXml(cell, baseStyle, result, ctx);
696
838
  const css = parseStyleAttribute(cell.attribs?.style);
697
839
  const widthTwips = parseCellWidthTwips(cell) ?? normalizedColWidths[i];
698
840
  const tcW = typeof widthTwips === "number" ? `<w:tcW w:w="${widthTwips}" w:type="dxa"/>` : `<w:tcW w:w="0" w:type="auto"/>`;
@@ -781,14 +923,14 @@ function buildTableXml(tableNode, result) {
781
923
  const tblPr = `<w:tblPr>${tblW}<w:tblLayout w:type="fixed"/>${tblAlign}${tblBorder}</w:tblPr>`;
782
924
  return `<w:tbl>${tblPr}${tblGrid}${rowXml.join("")}</w:tbl>`;
783
925
  }
784
- function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result) {
926
+ function buildParagraphXmlFromSingleInlineNode(node, baseStyle, result, ctx) {
785
927
  const wrapper = {
786
928
  type: "tag",
787
929
  name: "p",
788
930
  attribs: { style: "text-align: center;" },
789
931
  children: [node]
790
932
  };
791
- return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result);
933
+ return buildParagraphXmlFromContainer(wrapper, baseStyle, void 0, void 0, result, ctx);
792
934
  }
793
935
  function isRecognizedBlockTag(tag) {
794
936
  if (!tag) return false;
@@ -800,7 +942,22 @@ function isRecognizedBlockTag(tag) {
800
942
  if (tag === "pre") return true;
801
943
  return false;
802
944
  }
803
- function collectDivBlocks(node, out, result) {
945
+ function subtreeHasRecognizedBlocks(root) {
946
+ const stack = [root];
947
+ while (stack.length) {
948
+ const cur = stack.pop();
949
+ if (cur.type === "tag") {
950
+ if (isExplicitPageBreak(cur)) return true;
951
+ if (isRecognizedBlockTag(cur.name?.toLowerCase())) return true;
952
+ }
953
+ const children = cur.children ?? [];
954
+ for (let i = children.length - 1; i >= 0; i--) {
955
+ stack.push(children[i]);
956
+ }
957
+ }
958
+ return false;
959
+ }
960
+ function collectDivBlocks(node, out, result, ctx) {
804
961
  const parentStyle = node.attribs?.style;
805
962
  const inlineBuffer = [];
806
963
  const flushInline = () => {
@@ -810,7 +967,7 @@ function collectDivBlocks(node, out, result) {
810
967
  attribs: { style: parentStyle },
811
968
  children: inlineBuffer.splice(0)
812
969
  };
813
- const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
970
+ const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
814
971
  if (pXml) out.push(pXml);
815
972
  };
816
973
  const children = node.children ?? [];
@@ -824,19 +981,15 @@ function collectDivBlocks(node, out, result) {
824
981
  }
825
982
  if (isRecognizedBlockTag(tag)) {
826
983
  if (inlineBuffer.length) flushInline();
827
- collectBodyBlocks(child, out, result);
984
+ collectBodyBlocks(child, out, result, ctx);
985
+ continue;
986
+ }
987
+ if (subtreeHasRecognizedBlocks(child)) {
988
+ if (inlineBuffer.length) flushInline();
989
+ collectBodyBlocks(child, out, result, ctx);
828
990
  continue;
829
991
  }
830
992
  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
993
  if (inlineBuffer.length) flushInline();
841
994
  const mergedStyle = [parentStyle, child.attribs?.style].filter(Boolean).join(";");
842
995
  const wrapper = {
@@ -845,7 +998,7 @@ function collectDivBlocks(node, out, result) {
845
998
  attribs: { style: mergedStyle || void 0 },
846
999
  children: child.children ?? []
847
1000
  };
848
- const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result);
1001
+ const pXml = buildParagraphXmlFromContainer(wrapper, {}, void 0, void 0, result, ctx);
849
1002
  if (pXml) out.push(pXml);
850
1003
  continue;
851
1004
  }
@@ -854,7 +1007,7 @@ function collectDivBlocks(node, out, result) {
854
1007
  }
855
1008
  if (inlineBuffer.length) flushInline();
856
1009
  }
857
- function collectBodyBlocks(node, out, result) {
1010
+ function collectBodyBlocks(node, out, result, ctx) {
858
1011
  if (isSkippableSubtree(node)) return;
859
1012
  if (node.type === "tag") {
860
1013
  const tag = node.name?.toLowerCase();
@@ -863,36 +1016,51 @@ function collectBodyBlocks(node, out, result) {
863
1016
  return;
864
1017
  }
865
1018
  if (tag === "p") {
866
- const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result);
1019
+ const pXml = buildParagraphXmlFromContainer(node, {}, void 0, void 0, result, ctx);
867
1020
  if (pXml) out.push(pXml);
868
1021
  return;
869
1022
  }
870
1023
  if (tag === "img" || tag === "canvas") {
871
- const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result);
1024
+ const pXml = buildParagraphXmlFromSingleInlineNode(node, {}, result, ctx);
872
1025
  if (pXml) out.push(pXml);
873
1026
  return;
874
1027
  }
875
1028
  if (tag && /^h[1-6]$/.test(tag)) {
876
1029
  const level = Number(tag.slice(1));
877
- const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result);
1030
+ const hXml = buildParagraphXmlFromContainer(node, {}, void 0, `Heading${level}`, result, ctx);
878
1031
  if (hXml) out.push(hXml);
879
1032
  return;
880
1033
  }
881
1034
  if (tag === "table") {
882
- const tblXml = buildTableXml(node, result);
1035
+ const tblXml = buildTableXml(node, result, ctx);
883
1036
  if (tblXml) out.push(tblXml);
884
1037
  return;
885
1038
  }
886
1039
  if (tag === "ul" || tag === "ol") {
887
- out.push(...buildListBlocks(node, tag === "ol", 0, result));
1040
+ out.push(...buildListBlocks(node, tag === "ol", 0, result, ctx));
888
1041
  return;
889
1042
  }
890
1043
  if (tag === "div") {
891
- collectDivBlocks(node, out, result);
1044
+ if (hasClass(node, "tableWrapper")) {
1045
+ const display = ctx.tableWrapperCss.display?.trim().toLowerCase();
1046
+ if (display !== "contents") {
1047
+ const baseFontHalfPoints = inferFirstFontSizeHalfPoints(node) ?? ctx.defaultBaseFontHalfPoints;
1048
+ const { beforeTwips, afterTwips } = extractMarginBeforeAfterTwips(ctx.tableWrapperCss, baseFontHalfPoints);
1049
+ const beforeXml = typeof beforeTwips === "number" && beforeTwips > 0 ? buildSpacerParagraphXml(beforeTwips) : "";
1050
+ const afterXml = typeof afterTwips === "number" && afterTwips > 0 ? buildSpacerParagraphXml(afterTwips) : "";
1051
+ if (beforeXml) out.push(beforeXml);
1052
+ collectDivBlocks(node, out, result, ctx);
1053
+ if (afterXml) out.push(afterXml);
1054
+ return;
1055
+ }
1056
+ collectDivBlocks(node, out, result, ctx);
1057
+ return;
1058
+ }
1059
+ collectDivBlocks(node, out, result, ctx);
892
1060
  return;
893
1061
  }
894
1062
  }
895
- for (const c of node.children ?? []) collectBodyBlocks(c, out, result);
1063
+ for (const c of node.children ?? []) collectBodyBlocks(c, out, result, ctx);
896
1064
  }
897
1065
  function textToWordBodyXml(text) {
898
1066
  const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
@@ -912,6 +1080,20 @@ function textToWordBodyXml(text) {
912
1080
  }
913
1081
  function htmlToWordBody(html) {
914
1082
  const normalized = html.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1083
+ const bodyCss = parseCssRuleFromHtmlFirst(normalized, "body");
1084
+ const pCss = parseCssRuleFromHtmlFirst(normalized, "p");
1085
+ const defaultBaseFontHalfPoints = parseFontSizeToHalfPoints(bodyCss["font-size"]) ?? 28;
1086
+ const defaultBodyCss = {};
1087
+ if (bodyCss["line-height"]) defaultBodyCss["line-height"] = bodyCss["line-height"];
1088
+ const defaultPCss = {};
1089
+ if (pCss["line-height"]) defaultPCss["line-height"] = pCss["line-height"];
1090
+ if (pCss["margin-top"]) defaultPCss["margin-top"] = pCss["margin-top"];
1091
+ if (pCss["margin-bottom"]) defaultPCss["margin-bottom"] = pCss["margin-bottom"];
1092
+ if (pCss["text-align"]) defaultPCss["text-align"] = pCss["text-align"];
1093
+ const tableWrapperCss = {
1094
+ ...parseCssRuleFromHtml(normalized, ".tableWrapper"),
1095
+ ...parseCssRuleFromHtml(normalized, ".tiptap .tableWrapper")
1096
+ };
915
1097
  const doc = parseDocument(normalized, {
916
1098
  lowerCaseAttributeNames: true,
917
1099
  lowerCaseTags: true,
@@ -919,7 +1101,8 @@ function htmlToWordBody(html) {
919
1101
  });
920
1102
  const result = { bodyXml: "", images: [] };
921
1103
  const out = [];
922
- collectBodyBlocks(doc, out, result);
1104
+ const ctx = { defaultBaseFontHalfPoints, defaultBodyCss, defaultPCss, tableWrapperCss };
1105
+ collectBodyBlocks(doc, out, result, ctx);
923
1106
  result.bodyXml = out.join("");
924
1107
  return result;
925
1108
  }
@@ -956,4 +1139,4 @@ export {
956
1139
  htmlToWordBodyXml,
957
1140
  textToWordBodyXml
958
1141
  };
959
- //# sourceMappingURL=htmlToWordBodyXml-SIVUZ7K7.js.map
1142
+ //# sourceMappingURL=htmlToWordBodyXml-JSGDLGOS.js.map