@bendyline/squisq-formats 2.5.0 → 2.6.0

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.
Files changed (42) hide show
  1. package/NOTICE.md +10 -9
  2. package/dist/{chunk-2LT3JL7U.js → chunk-3ITGQRL5.js} +8 -8
  3. package/dist/{chunk-FMQWNPCV.js → chunk-AGGNLRHV.js} +6 -6
  4. package/dist/{chunk-AONELFLA.js → chunk-B7GEAZBI.js} +6 -1
  5. package/dist/chunk-CKSTGNVZ.js +724 -0
  6. package/dist/chunk-DXYWKZ52.js +57 -0
  7. package/dist/{chunk-T4PX33AG.js → chunk-EEDQRZ2M.js} +17 -1
  8. package/dist/chunk-FLR2ARKC.js +240 -0
  9. package/dist/{chunk-JTGWQK5V.js → chunk-GRKQTQOF.js} +64 -12
  10. package/dist/{chunk-A6LSCIO5.js → chunk-OBADJV7C.js} +286 -420
  11. package/dist/{chunk-AD2WT564.js → chunk-Q77KNJIN.js} +45 -0
  12. package/dist/{chunk-5PUIFU5I.js → chunk-VDTGQ4MW.js} +1 -1
  13. package/dist/{chunk-6RQOV3B3.js → chunk-VPWPEMZJ.js} +1 -1
  14. package/dist/{chunk-FIOSE4BO.js → chunk-XJNOZTAY.js} +6 -6
  15. package/dist/csv/index.d.ts +67 -1
  16. package/dist/csv/index.js +10 -3
  17. package/dist/data/index.d.ts +78 -0
  18. package/dist/data/index.js +30 -0
  19. package/dist/docx/index.js +3 -3
  20. package/dist/epub/index.js +4 -4
  21. package/dist/export-6iQXd-lQ.d.ts +366 -0
  22. package/dist/html/index.d.ts +2 -8
  23. package/dist/html/index.js +3 -3
  24. package/dist/{images-ESPQKVTW.js → images-JLBFCDD4.js} +1 -1
  25. package/dist/{import-B0gBYUmd.d.ts → import-C7c9tQHK.d.ts} +5 -2
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.js +33 -26
  28. package/dist/infer/index.js +6 -6
  29. package/dist/materialize-A34OZGEU.js +11 -0
  30. package/dist/outside-in/index.d.ts +3 -3
  31. package/dist/outside-in/index.js +2 -2
  32. package/dist/pdf/index.js +2 -2
  33. package/dist/pptx/index.js +3 -3
  34. package/dist/registry/index.d.ts +4 -4
  35. package/dist/registry/index.js +1 -1
  36. package/dist/{types-DByrrXeB.d.ts → types-Dzd_5H2A.d.ts} +5 -5
  37. package/dist/xlsx/index.d.ts +85 -4
  38. package/dist/xlsx/index.js +25 -4
  39. package/package.json +18 -3
  40. package/dist/export-m0tr9r9d.d.ts +0 -130
  41. package/dist/{chunk-KMBBO5H7.js → chunk-7DQP2I57.js} +4 -4
  42. package/dist/{chunk-M7XPXGXW.js → chunk-ROF7SSQP.js} +3 -3
@@ -1,23 +1,9 @@
1
1
  import {
2
- extractPlainText,
3
- inlineToPlainText
4
- } from "./chunk-AVOZAKGP.js";
2
+ planDataSidecar
3
+ } from "./chunk-DXYWKZ52.js";
5
4
  import {
6
- createPackage
7
- } from "./chunk-ILCJ3WFD.js";
8
- import {
9
- escapeXml,
10
- xmlDeclaration
11
- } from "./chunk-JU2RHXUB.js";
12
- import {
13
- CONTENT_TYPE_XLSX_STYLES,
14
- CONTENT_TYPE_XLSX_WORKBOOK,
15
- CONTENT_TYPE_XLSX_WORKSHEET,
16
5
  NS_R,
17
6
  NS_SML,
18
- REL_OFFICE_DOCUMENT,
19
- REL_STYLES,
20
- REL_WORKSHEET,
21
7
  baseDirOf,
22
8
  getPartRelationships,
23
9
  getPartXml,
@@ -25,9 +11,6 @@ import {
25
11
  requireMainPartPath
26
12
  } from "./chunk-S5PCVMKU.js";
27
13
 
28
- // src/xlsx/index.ts
29
- import { markdownToDoc } from "@bendyline/squisq/doc";
30
-
31
14
  // src/xlsx/cells.ts
32
15
  var MAX_COL_INDEX = 16383;
33
16
  var MAX_ROW_INDEX = 1048575;
@@ -387,7 +370,116 @@ function peelCaptionRow(grid, rect) {
387
370
  return { caption, rect: { ...rect, top: rect.top + 1 } };
388
371
  }
389
372
 
373
+ // src/xlsx/tables.ts
374
+ function columnLetter2(index) {
375
+ let n = index;
376
+ let out = "";
377
+ do {
378
+ out = String.fromCharCode(65 + n % 26) + out;
379
+ n = Math.floor(n / 26) - 1;
380
+ } while (n >= 0);
381
+ return out;
382
+ }
383
+ function a1(row, col) {
384
+ return `${columnLetter2(col)}${row + 1}`;
385
+ }
386
+ function regionHasHeader(cells) {
387
+ if (cells.length < 2) return false;
388
+ const first = cells[0];
389
+ if (!first || first.length === 0) return false;
390
+ return first.every((cell) => cell.kind === "string");
391
+ }
392
+ function cellValue(cell) {
393
+ if (cell.kind === "empty" || cell.kind === "error") return null;
394
+ if (cell.value !== void 0) return cell.value;
395
+ return cell.text === "" ? null : cell.text;
396
+ }
397
+ function dominantKind(kinds) {
398
+ const counts = /* @__PURE__ */ new Map();
399
+ let total = 0;
400
+ for (const kind of kinds) {
401
+ if (kind === "empty") continue;
402
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
403
+ total += 1;
404
+ }
405
+ if (total === 0) return "empty";
406
+ let best = "string";
407
+ let bestCount = 0;
408
+ for (const [kind, count] of counts) {
409
+ if (count > bestCount) {
410
+ best = kind;
411
+ bestCount = count;
412
+ }
413
+ }
414
+ return bestCount * 2 > total ? best : "mixed";
415
+ }
416
+ function sliceRegion(grid, rect) {
417
+ const out = [];
418
+ for (let r = rect.top; r <= rect.bottom; r++) {
419
+ const row = [];
420
+ for (let c = rect.left; c <= rect.right; c++) {
421
+ row.push(grid[r]?.[c] ?? { text: "", kind: "empty" });
422
+ }
423
+ out.push(row);
424
+ }
425
+ return out;
426
+ }
427
+ function regionToTable(sheet, grid, rect, title, minRows) {
428
+ const cells = sliceRegion(grid, rect);
429
+ if (cells.length === 0) return null;
430
+ const hasHeader = regionHasHeader(cells);
431
+ const body = hasHeader ? cells.slice(1) : cells;
432
+ if (body.length < minRows) return null;
433
+ if (!body.some((row) => row.some(isOccupied))) return null;
434
+ const width = cells.reduce((max, row) => Math.max(max, row.length), 0);
435
+ const columns = [];
436
+ for (let c = 0; c < width; c++) {
437
+ const header = hasHeader ? cells[0]?.[c]?.text ?? "" : "";
438
+ columns.push({
439
+ name: header.trim() || columnLetter2(rect.left + c),
440
+ kind: dominantKind(body.map((row) => row[c]?.kind ?? "empty"))
441
+ });
442
+ }
443
+ return {
444
+ sheet,
445
+ anchor: a1(rect.top, rect.left),
446
+ ...title ? { title } : {},
447
+ columns,
448
+ hasHeader,
449
+ rows: body.map(
450
+ (row) => Array.from({ length: width }, (_, c) => cellValue(row[c] ?? { text: "", kind: "empty" }))
451
+ )
452
+ };
453
+ }
454
+ function gridToTables(sheet, grid, merges = [], options = {}) {
455
+ if (grid.length === 0) return [];
456
+ const minRows = options.minRows ?? 1;
457
+ const plan = detectRegions(grid, merges, {
458
+ ...options.maxRegionsPerSheet !== void 0 ? { maxRegionsPerSheet: options.maxRegionsPerSheet } : {},
459
+ ...options.minRegionCells !== void 0 ? { minRegionCells: options.minRegionCells } : {},
460
+ ...options.signal ? { signal: options.signal } : {}
461
+ });
462
+ if (plan.degraded || plan.regions.length === 0) {
463
+ const rect = {
464
+ top: 0,
465
+ left: 0,
466
+ bottom: grid.length - 1,
467
+ right: grid.reduce((max, row) => Math.max(max, row.length), 1) - 1
468
+ };
469
+ const whole = regionToTable(sheet, grid, rect, void 0, minRows);
470
+ return whole ? [whole] : [];
471
+ }
472
+ const out = [];
473
+ for (const region of plan.regions) {
474
+ const table = regionToTable(sheet, grid, region.rect, region.title, minRows);
475
+ if (table) out.push(table);
476
+ }
477
+ return out;
478
+ }
479
+
390
480
  // src/xlsx/import.ts
481
+ import { stringifyMarkdown } from "@bendyline/squisq/markdown";
482
+ import { MemoryContentContainer } from "@bendyline/squisq/storage";
391
483
  var XLSX_MAIN_PART = "xl/workbook.xml";
392
484
  function attrNS(el, ns, local, fallback) {
393
485
  return el.getAttributeNS(ns, local) ?? el.getAttribute(fallback);
@@ -401,7 +493,7 @@ function resolveTarget(baseDir, target) {
401
493
  }
402
494
  return stack.join("/");
403
495
  }
404
- async function readWorkbook(pkg, mainPart) {
496
+ async function listSheetParts(pkg, mainPart) {
405
497
  const wb = await getPartXml(pkg, mainPart);
406
498
  if (!wb) {
407
499
  throw new Error(`Invalid XLSX package: workbook part "${mainPart}" could not be parsed.`);
@@ -417,11 +509,22 @@ async function readWorkbook(pkg, mainPart) {
417
509
  const target = rid ? relById.get(rid) : void 0;
418
510
  if (target) out.push({ name, path: resolveTarget(baseDirOf(mainPart), target) });
419
511
  }
512
+ return out;
513
+ }
514
+ async function readWorkbook(pkg, mainPart) {
515
+ const wb = await getPartXml(pkg, mainPart);
516
+ if (!wb) {
517
+ throw new Error(`Invalid XLSX package: workbook part "${mainPart}" could not be parsed.`);
518
+ }
519
+ const sheets = await listSheetParts(pkg, mainPart);
420
520
  const workbookPr = wb.getElementsByTagNameNS(NS_SML, "workbookPr")[0];
421
521
  const date1904Value = workbookPr?.getAttribute("date1904");
522
+ const calcPr = wb.getElementsByTagNameNS(NS_SML, "calcPr")[0];
523
+ const fullCalcValue = calcPr?.getAttribute("fullCalcOnLoad");
422
524
  return {
423
- sheets: out,
424
- date1904: date1904Value === "1" || date1904Value === "true"
525
+ sheets,
526
+ date1904: date1904Value === "1" || date1904Value === "true",
527
+ fullCalcOnLoad: fullCalcValue === "1" || fullCalcValue === "true"
425
528
  };
426
529
  }
427
530
  function isPhonetic(el, root) {
@@ -553,6 +656,16 @@ function excelTimeText(serial, includeSeconds, elapsedHours) {
553
656
  const seconds = totalSeconds % 60;
554
657
  return `${twoDigits(hours)}:${twoDigits(minutes)}${includeSeconds ? `:${twoDigits(seconds)}` : ""}`;
555
658
  }
659
+ function dateValueText(serial, kind, style, date1904) {
660
+ if (!Number.isFinite(serial)) return null;
661
+ const normalized = style ? normalizeFormatCode(style.formatCode) : "";
662
+ if (kind === "time")
663
+ return excelTimeText(serial, /s/.test(normalized), /\[[h]+\]/.test(normalized));
664
+ const date = excelDateText(serial, date1904);
665
+ if (!date) return null;
666
+ if (kind === "datetime") return `${date} ${excelTimeText(serial, /s/.test(normalized), false)}`;
667
+ return date;
668
+ }
556
669
  function formattedNumberText(raw, style, date1904) {
557
670
  if (!style) return raw;
558
671
  const value = Number(raw);
@@ -581,32 +694,37 @@ function formattedNumberText(raw, style, date1904) {
581
694
  function readFormula(cell, row, col, ctx) {
582
695
  const fEls = cell.getElementsByTagNameNS(NS_SML, "f");
583
696
  const f = fEls.length ? fEls[0] : null;
584
- if (!f) return "";
697
+ if (!f) return { text: "" };
585
698
  const text = (f.textContent ?? "").trim();
586
- if (f.getAttribute("t") !== "shared") return text;
699
+ if (f.getAttribute("t") !== "shared") return { text };
587
700
  const si = f.getAttribute("si");
588
- if (si === null) return text;
701
+ if (si === null) return { text };
589
702
  if (text !== "") {
590
703
  ctx.sharedFormulas.set(si, { text, row, col });
591
- return text;
704
+ return { text, sharedRole: "master" };
592
705
  }
593
706
  const master = ctx.sharedFormulas.get(si);
594
- if (!master) return "";
707
+ if (!master) return { text: "", sharedRole: "follower" };
595
708
  try {
596
- return translateFormula(master.text, row - master.row, col - master.col);
709
+ return {
710
+ text: translateFormula(master.text, row - master.row, col - master.col),
711
+ sharedRole: "follower"
712
+ };
597
713
  } catch {
598
- return "";
714
+ return { text: "", sharedRole: "follower" };
599
715
  }
600
716
  }
601
717
  function readCell(cell, row, col, ctx) {
602
- const formula = readFormula(cell, row, col, ctx);
603
- const withFormula = (text2, kind2) => {
718
+ const { text: formula, sharedRole } = readFormula(cell, row, col, ctx);
719
+ const withFormula = (text2, kind2, value) => {
604
720
  const out = { text: text2, kind: text2 === "" ? "empty" : kind2 };
605
721
  if (formula !== "") out.formula = formula;
722
+ if (sharedRole !== void 0) out.sharedFormulaRole = sharedRole;
723
+ if (value !== void 0 && out.kind !== "empty") out.value = value;
606
724
  return out;
607
725
  };
608
726
  const stringCell = (item) => {
609
- const out = withFormula(item.text, "string");
727
+ const out = withFormula(item.text, "string", item.text);
610
728
  if (item.rich && out.kind !== "empty") out.richText = item.rich;
611
729
  return out;
612
730
  };
@@ -619,14 +737,19 @@ function readCell(cell, row, col, ctx) {
619
737
  const v = vEls.length ? vEls[0].textContent ?? "" : "";
620
738
  if (v === "") return withFormula("", "empty");
621
739
  if (t === "s") return stringCell(ctx.shared[Number.parseInt(v, 10)] ?? { text: "" });
622
- if (t === "b") return withFormula(v === "1" ? "TRUE" : "FALSE", "bool");
740
+ if (t === "b") return withFormula(v === "1" ? "TRUE" : "FALSE", "bool", v === "1");
623
741
  if (t === "e") return withFormula(v, "error");
624
- if (t === "str") return withFormula(v, "string");
742
+ if (t === "str") return withFormula(v, "string", v);
625
743
  const style = ctx.styles[Number.parseInt(cell.getAttribute("s") ?? "0", 10)];
626
744
  const text = formattedNumberText(v, style, ctx.date1904);
627
745
  const dateLike = style ? numberFormatKind(style.formatCode) : "general";
628
746
  const kind = dateLike === "date" || dateLike === "time" || dateLike === "datetime" ? "date" : "number";
629
- return withFormula(text, kind);
747
+ const numeric = Number(v);
748
+ if (kind === "date") {
749
+ const iso = dateValueText(numeric, dateLike, style, ctx.date1904);
750
+ return withFormula(text, kind, iso ?? text);
751
+ }
752
+ return withFormula(text, kind, Number.isFinite(numeric) ? numeric : v);
630
753
  }
631
754
  async function sheetToCells(pkg, path, shared, styles, date1904) {
632
755
  const doc = await getPartXml(pkg, path);
@@ -723,7 +846,70 @@ function annotatedHeading(depth, text, params) {
723
846
  templateAnnotation: { template: "dataTable", params }
724
847
  };
725
848
  }
849
+ async function xlsxToTables(data, options = {}) {
850
+ const pkg = await openPackage(data, options);
851
+ const mainPart = requireMainPartPath(pkg, XLSX_MAIN_PART, "XLSX");
852
+ const [{ sheets, date1904 }, shared, styles] = await Promise.all([
853
+ readWorkbook(pkg, mainPart),
854
+ readSharedStrings(pkg),
855
+ readCellStyles(pkg)
856
+ ]);
857
+ let selected = sheets;
858
+ if (options.sheet !== void 0) {
859
+ const picked = typeof options.sheet === "number" ? sheets[options.sheet] : sheets.find((s) => s.name === options.sheet);
860
+ selected = picked ? [picked] : [];
861
+ }
862
+ const out = [];
863
+ for (const sheet of selected) {
864
+ const { cells, merges } = await sheetToCells(pkg, sheet.path, shared, styles, date1904);
865
+ out.push(...gridToTables(sheet.name, cells, merges, options));
866
+ }
867
+ return out;
868
+ }
869
+ async function xlsxToCellGrids(data, options = {}) {
870
+ const pkg = await openPackage(data, options);
871
+ const mainPart = requireMainPartPath(pkg, XLSX_MAIN_PART, "XLSX");
872
+ const [{ sheets, date1904, fullCalcOnLoad }, shared, styles] = await Promise.all([
873
+ readWorkbook(pkg, mainPart),
874
+ readSharedStrings(pkg),
875
+ readCellStyles(pkg)
876
+ ]);
877
+ let selected = sheets;
878
+ if (options.sheet !== void 0) {
879
+ const picked = typeof options.sheet === "number" ? sheets[options.sheet] : sheets.find((s) => s.name === options.sheet);
880
+ selected = picked ? [picked] : [];
881
+ }
882
+ const out = [];
883
+ for (const sheet of selected) {
884
+ const { cells, merges } = await sheetToCells(pkg, sheet.path, shared, styles, date1904);
885
+ out.push({ name: sheet.name, cells, merges });
886
+ }
887
+ return { sheets: out, date1904, fullCalcOnLoad };
888
+ }
726
889
  async function xlsxToMarkdownDoc(data, options = {}) {
890
+ return workbookToMarkdown(data, options, void 0);
891
+ }
892
+ function shouldSpillRegion(spill, rect, hasHeader) {
893
+ if (spill.mode === "always") return true;
894
+ const height = rect.bottom - rect.top + 1;
895
+ const width = rect.right - rect.left + 1;
896
+ const dataRows = height - (hasHeader ? 1 : 0);
897
+ return dataRows > spill.maxInlineRows || height * width > spill.maxInlineCells;
898
+ }
899
+ function shouldSpillGrid(spill, cells) {
900
+ if (spill.mode === "always") return true;
901
+ const width = cells.reduce((max, row) => Math.max(max, row.length), 0);
902
+ return cells.length - 1 > spill.maxInlineRows || cells.length * width > spill.maxInlineCells;
903
+ }
904
+ function sidecarLinkParagraph(spill) {
905
+ return {
906
+ type: "paragraph",
907
+ children: [
908
+ { type: "link", url: spill.src, children: [{ type: "text", value: spill.fileName }] }
909
+ ]
910
+ };
911
+ }
912
+ async function workbookToMarkdown(data, options, spill) {
727
913
  const pkg = await openPackage(data, options);
728
914
  const mainPart = requireMainPartPath(pkg, XLSX_MAIN_PART, "XLSX");
729
915
  const [{ sheets, date1904 }, shared, styles] = await Promise.all([
@@ -747,6 +933,14 @@ async function xlsxToMarkdownDoc(data, options = {}) {
747
933
  }
748
934
  if (cells.length === 0) continue;
749
935
  if (!useRegions) {
936
+ if (spill && shouldSpillGrid(spill, cells)) {
937
+ children.push(
938
+ annotatedHeading(single ? 1 : 2, sheet.name, { src: spill.src, sheet: sheet.name }),
939
+ sidecarLinkParagraph(spill)
940
+ );
941
+ spill.used = true;
942
+ continue;
943
+ }
750
944
  children.push(cellsToTable(cells));
751
945
  continue;
752
946
  }
@@ -757,6 +951,14 @@ async function xlsxToMarkdownDoc(data, options = {}) {
757
951
  });
758
952
  for (const warning of plan.warnings) console.warn(`XLSX import: ${sheet.name}: ${warning}`);
759
953
  if (plan.degraded || plan.regions.length === 0 && plan.strays.length === 0) {
954
+ if (spill && shouldSpillGrid(spill, cells)) {
955
+ children.push(
956
+ annotatedHeading(single ? 1 : 2, sheet.name, { src: spill.src, sheet: sheet.name }),
957
+ sidecarLinkParagraph(spill)
958
+ );
959
+ spill.used = true;
960
+ continue;
961
+ }
760
962
  children.push(cellsToTable(cells));
761
963
  continue;
762
964
  }
@@ -765,6 +967,21 @@ async function xlsxToMarkdownDoc(data, options = {}) {
765
967
  const slice = sliceRect(cells, region.rect, EMPTY_CELL);
766
968
  const anchor = formatCellRef(region.rect.top, region.rect.left);
767
969
  const title = region.title ?? `${sheet.name} \u2014 ${anchor}`;
970
+ const hasHeaderRow = inferHeader(slice);
971
+ if (spill && shouldSpillRegion(spill, region.rect, hasHeaderRow)) {
972
+ children.push(
973
+ annotatedHeading(depth, title, {
974
+ src: spill.src,
975
+ sheet: sheet.name,
976
+ anchor,
977
+ ...hasHeaderRow ? {} : { headerRow: "false" },
978
+ ...region.titleCell ? { titleAnchor: formatCellRef(region.titleCell.row, region.titleCell.col) } : {}
979
+ }),
980
+ sidecarLinkParagraph(spill)
981
+ );
982
+ spill.used = true;
983
+ continue;
984
+ }
768
985
  children.push(
769
986
  annotatedHeading(depth, title, {
770
987
  sheet: sheet.name,
@@ -773,7 +990,7 @@ async function xlsxToMarkdownDoc(data, options = {}) {
773
990
  // says. (`headerRow`, not `header` — `dataTable` already declares a
774
991
  // `headers` input, and two params one letter apart in the same
775
992
  // annotation is a trap for anyone reading or editing the markdown.)
776
- ...inferHeader(slice) ? {} : { headerRow: "false" },
993
+ ...hasHeaderRow ? {} : { headerRow: "false" },
777
994
  // A caption promoted into the heading has left its cell behind;
778
995
  // record where, so the reverse path can put the text back.
779
996
  ...region.titleCell ? { titleAnchor: formatCellRef(region.titleCell.row, region.titleCell.col) } : {}
@@ -804,394 +1021,43 @@ async function xlsxToMarkdownDoc(data, options = {}) {
804
1021
  }
805
1022
  return { type: "document", children };
806
1023
  }
807
-
808
- // src/xlsx/export.ts
809
- import { docToMarkdown } from "@bendyline/squisq/doc";
810
-
811
- // src/xlsx/workbookPlan.ts
812
- function hasRichCellContent(nodes) {
813
- return nodes.some(
814
- (n) => n.type === "superscript" || n.type === "subscript" || "children" in n && Array.isArray(n.children) && hasRichCellContent(n.children)
815
- );
816
- }
817
- var KEY_STRIDE = MAX_COL_INDEX + 1;
818
- function cellKey(row, col) {
819
- return row * KEY_STRIDE + col;
820
- }
821
- function keyRow(key) {
822
- return Math.floor(key / KEY_STRIDE);
823
- }
824
- function keyCol(key) {
825
- return key - keyRow(key) * KEY_STRIDE;
826
- }
827
- function tableToGrid(table) {
828
- return table.children.map(
829
- (row) => row.children.map((cell) => extractPlainText(cell.children))
830
- );
831
- }
832
- function tableToRichGrid(table) {
833
- return table.children.map(
834
- (row) => row.children.map((cell) => hasRichCellContent(cell.children) ? cell.children : void 0)
835
- );
836
- }
837
- function readRole(raw) {
838
- return raw === "formulas" || raw === "loose" ? raw : "values";
839
- }
840
- function collectEntries(nodes) {
841
- const entries = [];
842
- let pending = null;
843
- for (const node of nodes) {
844
- if (node.type === "heading") {
845
- pending = node;
846
- continue;
847
- }
848
- if (node.type !== "table") continue;
849
- const params = pending?.templateAnnotation?.params ?? {};
850
- const sheetKey = typeof params.sheet === "string" && params.sheet !== "" ? params.sheet : null;
851
- entries.push({
852
- table: node,
853
- headingText: pending ? extractPlainText(pending.children) : "",
854
- params,
855
- sheetKey,
856
- role: readRole(params.role)
857
- });
858
- pending = null;
859
- }
860
- return entries;
861
- }
862
- function groupEntries(entries) {
863
- const groups = [];
864
- const bySheet = /* @__PURE__ */ new Map();
865
- for (const entry of entries) {
866
- if (entry.sheetKey !== null) {
867
- let group = bySheet.get(entry.sheetKey);
868
- if (!group) {
869
- group = { candidate: entry.sheetKey, entries: [], anchored: true };
870
- bySheet.set(entry.sheetKey, group);
871
- groups.push(group);
872
- }
873
- group.entries.push(entry);
874
- continue;
875
- }
876
- groups.push({ candidate: entry.headingText || null, entries: [entry], anchored: false });
877
- }
878
- return groups;
879
- }
880
- function place(cells, row, col, cell, clashes) {
881
- const key = cellKey(row, col);
882
- if (cells.has(key)) clashes.push(formatCellRef(row, col));
883
- cells.set(key, cell);
884
- }
885
- function overlayFormula(cells, row, col, formula) {
886
- const key = cellKey(row, col);
887
- const existing = cells.get(key);
888
- cells.set(key, { text: existing?.text ?? "", formula });
889
- }
890
- function formulaSource(raw) {
891
- const trimmed = raw.trim();
892
- return trimmed.startsWith("=") ? trimmed.slice(1) : trimmed;
893
- }
894
- function placeValues(entry, cells, warnings, sheetName, clashes) {
895
- const grid = tableToGrid(entry.table);
896
- const richGrid = tableToRichGrid(entry.table);
897
- const anchorRaw = entry.params.anchor ?? "A1";
898
- let anchor = parseCellRef(anchorRaw);
899
- if (!anchor) {
900
- warnings.push(
901
- `Sheet "${sheetName}": anchor "${anchorRaw}" is not a valid cell reference; placed at A1 instead.`
902
- );
903
- anchor = { row: 0, col: 0 };
904
- }
905
- const height = grid.length;
906
- const width = grid.reduce((m, r) => Math.max(m, r.length), 0);
907
- if (anchor.row + height - 1 > MAX_ROW_INDEX || anchor.col + width - 1 > MAX_COL_INDEX) {
908
- warnings.push(
909
- `Sheet "${sheetName}": a table anchored at ${anchorRaw} runs past the end of the sheet; placed at A1 instead.`
910
- );
911
- anchor = { row: 0, col: 0 };
912
- }
913
- const titleRef = entry.params.titleAnchor;
914
- if (titleRef !== void 0 && entry.headingText !== "") {
915
- const at = parseCellRef(titleRef);
916
- if (at) place(cells, at.row, at.col, { text: entry.headingText }, clashes);
917
- }
918
- for (let r = 0; r < grid.length; r++) {
919
- const row = grid[r];
920
- for (let c = 0; c < row.length; c++) {
921
- const text = row[c];
922
- if (text === "") continue;
923
- const rich = richGrid[r]?.[c];
924
- place(cells, anchor.row + r, anchor.col + c, rich ? { text, rich } : { text }, clashes);
925
- }
926
- }
927
- }
928
- function placeFormulas(entry, cells, warnings, sheetName) {
929
- const grid = tableToGrid(entry.table);
930
- if (grid.length < 2) return;
931
- const anchorRaw = entry.params.anchor ?? "A1";
932
- const anchor = parseCellRef(anchorRaw);
933
- if (!anchor) {
934
- warnings.push(
935
- `Sheet "${sheetName}": formulas block anchor "${anchorRaw}" is not a valid cell reference; skipped.`
936
- );
937
- return;
938
- }
939
- const columns = grid[0].map((letters) => {
940
- const trimmed = letters.trim();
941
- if (!/^[A-Za-z]{1,3}$/.test(trimmed)) return -1;
942
- const col = columnIndexFromLetters(trimmed);
943
- return col >= 0 && col <= MAX_COL_INDEX ? col : -1;
944
- });
945
- for (let r = 1; r < grid.length; r++) {
946
- const row = grid[r];
947
- for (let c = 0; c < row.length; c++) {
948
- const raw = row[c];
949
- if (raw.trim() === "") continue;
950
- const mapped = columns[c];
951
- const col = mapped !== void 0 && mapped >= 0 ? mapped : anchor.col + c;
952
- const targetRow = anchor.row + r - 1;
953
- if (targetRow > MAX_ROW_INDEX) continue;
954
- overlayFormula(cells, targetRow, col, formulaSource(raw));
955
- }
956
- }
957
- }
958
- function placeLoose(entry, cells, warnings, sheetName, clashes) {
959
- const grid = tableToGrid(entry.table);
960
- const richGrid = tableToRichGrid(entry.table);
961
- let skipped = 0;
962
- for (let r = 1; r < grid.length; r++) {
963
- const row = grid[r];
964
- const ref = (row[0] ?? "").trim();
965
- if (ref === "") continue;
966
- const at = parseCellRef(ref);
967
- if (!at) {
968
- skipped++;
969
- continue;
970
- }
971
- const text = row[1] ?? "";
972
- const formula = formulaSource(row[2] ?? "");
973
- const rich = richGrid[r]?.[1];
974
- const planned = { text };
975
- if (formula !== "") planned.formula = formula;
976
- if (rich) planned.rich = rich;
977
- place(cells, at.row, at.col, planned, clashes);
978
- }
979
- if (skipped > 0) {
980
- warnings.push(
981
- `Sheet "${sheetName}": ${skipped} loose-cell row(s) had an invalid cell reference and were skipped.`
982
- );
983
- }
984
- }
985
- function planWorkbook(doc, options) {
986
- const warnings = [];
987
- const groups = groupEntries(collectEntries(doc.children));
988
- const used = /* @__PURE__ */ new Set();
989
- const sheets = [];
990
- groups.forEach((group, index) => {
991
- const fallback = `${options.sheetNamePrefix}${index + 1}`;
992
- const name = options.sanitize(group.candidate ?? fallback, used, fallback);
993
- const cells = /* @__PURE__ */ new Map();
994
- const clashes = [];
995
- for (const entry of group.entries) {
996
- if (entry.role === "values") placeValues(entry, cells, warnings, name, clashes);
997
- }
998
- for (const entry of group.entries) {
999
- if (entry.role === "formulas") placeFormulas(entry, cells, warnings, name);
1000
- }
1001
- for (const entry of group.entries) {
1002
- if (entry.role === "loose") placeLoose(entry, cells, warnings, name, clashes);
1003
- }
1004
- if (clashes.length > 0) {
1005
- const shown = clashes.slice(0, 5).join(", ");
1006
- const rest = clashes.length > 5 ? ", and more" : "";
1007
- warnings.push(
1008
- `Sheet "${name}": ${clashes.length} overlapping cell(s) (${shown}${rest}); the later block won.`
1009
- );
1010
- }
1011
- sheets.push({ name, cells, anchored: group.anchored });
1012
- });
1013
- let cellCount = 0;
1014
- for (const sheet of sheets) cellCount += sheet.cells.size;
1015
- return { sheets, warnings, cellCount };
1016
- }
1017
-
1018
- // src/xlsx/export.ts
1019
- var NUMERIC_RE = /^-?\d+(\.\d+)?$/;
1020
- function isSafeNumericCell(value) {
1021
- if (!NUMERIC_RE.test(value)) return false;
1022
- const unsigned = value.startsWith("-") ? value.slice(1) : value;
1023
- const [integer] = unsigned.split(".");
1024
- if (integer.length > 1 && integer.startsWith("0")) return false;
1025
- const significantDigits = unsigned.replace(".", "").replace(/^0+/, "");
1026
- if (significantDigits.length > 15) return false;
1027
- return Number.isFinite(Number(value));
1028
- }
1029
- function cleanSheetName(raw) {
1030
- return raw.replace(/[[\]:*?/\\]/g, "").trim().slice(0, 31).trim().replace(/^'+|'+$/g, "");
1031
- }
1032
- function sanitizeSheetName(candidate, used, fallback) {
1033
- let base = cleanSheetName(candidate) || cleanSheetName(fallback) || "Sheet";
1034
- let name = base;
1035
- let n = 2;
1036
- while (used.has(name.toLocaleLowerCase("en-US"))) {
1037
- const suffix = String(n++);
1038
- base = base.slice(0, 31 - suffix.length);
1039
- name = `${base}${suffix}`;
1040
- }
1041
- used.add(name.toLocaleLowerCase("en-US"));
1042
- return name;
1043
- }
1044
- var ERROR_VALUE_RE = /^#[A-Z0-9_/]+[!?]?$/;
1045
- function cellXml(cell, ref, inferNumericCells) {
1046
- const { text, formula } = cell;
1047
- if (formula !== void 0 && formula !== "") {
1048
- const f = `<f>${escapeXml(formula)}</f>`;
1049
- if (text === "") return `<c r="${ref}">${f}</c>`;
1050
- if (isSafeNumericCell(text)) return `<c r="${ref}">${f}<v>${escapeXml(text)}</v></c>`;
1051
- if (ERROR_VALUE_RE.test(text)) return `<c r="${ref}" t="e">${f}<v>${escapeXml(text)}</v></c>`;
1052
- return `<c r="${ref}" t="str">${f}<v>${escapeXml(text)}</v></c>`;
1053
- }
1054
- if (inferNumericCells && isSafeNumericCell(text)) {
1055
- return `<c r="${ref}"><v>${escapeXml(text)}</v></c>`;
1056
- }
1057
- if (cell.rich) {
1058
- return `<c r="${ref}" t="inlineStr"><is>${richRunsXml(cell.rich)}</is></c>`;
1059
- }
1060
- return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXml(text)}</t></is></c>`;
1061
- }
1062
- function richRunsXml(nodes) {
1063
- const runs = [];
1064
- const emit = (text, vertAlign) => {
1065
- if (text === "") return;
1066
- const rPr = vertAlign ? `<rPr><vertAlign val="${vertAlign}"/></rPr>` : "";
1067
- runs.push(`<r>${rPr}<t xml:space="preserve">${escapeXml(text)}</t></r>`);
1024
+ var XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
1025
+ async function xlsxToContainer(data, options = {}) {
1026
+ const plan = planDataSidecar(options.sourceName, "workbook.xlsx");
1027
+ const mode = options.sidecar ?? "auto";
1028
+ const spill = mode === "never" ? void 0 : {
1029
+ src: plan.sidecarPath,
1030
+ fileName: plan.fileName,
1031
+ mode,
1032
+ maxInlineRows: options.maxInlineRows ?? 100,
1033
+ maxInlineCells: options.maxInlineCells ?? 2e3,
1034
+ used: false
1068
1035
  };
1069
- const walk = (list, vertAlign) => {
1070
- for (const node of list) {
1071
- if (node.type === "superscript" || node.type === "subscript") {
1072
- walk(node.children, node.type === "superscript" ? "superscript" : "subscript");
1073
- } else if ("children" in node && Array.isArray(node.children)) {
1074
- walk(node.children, vertAlign);
1075
- } else {
1076
- emit(inlineToPlainText(node), vertAlign);
1077
- }
1078
- }
1079
- };
1080
- walk(nodes, null);
1081
- return runs.join("");
1082
- }
1083
- function worksheetXml(sheet, inferNumericCells) {
1084
- const keys = [...sheet.cells.keys()].sort((a, b) => a - b);
1085
- const rows = [];
1086
- let maxRow = 0;
1087
- let maxCol = 0;
1088
- let i = 0;
1089
- while (i < keys.length) {
1090
- const rowIdx = keyRow(keys[i]);
1091
- let cellsXml = "";
1092
- while (i < keys.length && keyRow(keys[i]) === rowIdx) {
1093
- const key = keys[i];
1094
- const col = keyCol(key);
1095
- if (col > maxCol) maxCol = col;
1096
- cellsXml += cellXml(
1097
- sheet.cells.get(key),
1098
- `${columnLetter(col)}${rowIdx + 1}`,
1099
- inferNumericCells
1100
- );
1101
- i++;
1102
- }
1103
- if (rowIdx > maxRow) maxRow = rowIdx;
1104
- rows.push(`<row r="${rowIdx + 1}">${cellsXml}</row>`);
1036
+ const markdownDoc = await workbookToMarkdown(data, options, spill);
1037
+ const container = new MemoryContentContainer();
1038
+ await container.writeDocument(stringifyMarkdown(markdownDoc), plan.markdownFilename);
1039
+ if (spill?.used) {
1040
+ const bytes = data instanceof Blob ? await data.arrayBuffer() : data;
1041
+ await container.writeFile(plan.sidecarPath, bytes, XLSX_MIME);
1105
1042
  }
1106
- const dimension = keys.length > 0 ? `A1:${columnLetter(maxCol)}${maxRow + 1}` : "A1";
1107
- return `${xmlDeclaration()}
1108
- <worksheet xmlns="${NS_SML}" xmlns:r="${NS_R}"><dimension ref="${dimension}"/><sheetData>${rows.join("")}</sheetData></worksheet>`;
1109
- }
1110
- function workbookXml(sheets, hasFormulas) {
1111
- const sheetEls = sheets.map(
1112
- (sheet, i) => `<sheet name="${escapeXml(sheet.name)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`
1113
- ).join("");
1114
- const calcPr = hasFormulas ? `<calcPr calcId="0" fullCalcOnLoad="1"/>` : "";
1115
- return `${xmlDeclaration()}
1116
- <workbook xmlns="${NS_SML}" xmlns:r="${NS_R}"><sheets>${sheetEls}</sheets>${calcPr}</workbook>`;
1117
- }
1118
- function stylesXml() {
1119
- return `${xmlDeclaration()}
1120
- <styleSheet xmlns="${NS_SML}"><fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts><fills count="1"><fill><patternFill patternType="none"/></fill></fills><borders count="1"><border/></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs></styleSheet>`;
1121
- }
1122
- async function markdownDocToXlsx(doc, options = {}) {
1123
- options.signal?.throwIfAborted();
1124
- const prefix = cleanSheetName(options.sheetNamePrefix ?? "Sheet") || "Sheet";
1125
- const maxCells = options.maxCells ?? 1e5;
1126
- if (!Number.isSafeInteger(maxCells) || maxCells < 0) {
1127
- throw new RangeError("maxCells must be a non-negative safe integer");
1128
- }
1129
- const plan = planWorkbook(doc, { sheetNamePrefix: prefix, sanitize: sanitizeSheetName });
1130
- for (const warning of plan.warnings) options.onWarning?.(warning);
1131
- if (plan.cellCount > maxCells) {
1132
- throw new RangeError(`XLSX export exceeds the ${maxCells}-cell safety limit`);
1133
- }
1134
- let sheets = plan.sheets;
1135
- if (sheets.length === 0) {
1136
- sheets = [
1137
- {
1138
- name: sanitizeSheetName(`${prefix}1`, /* @__PURE__ */ new Set(), "Sheet1"),
1139
- cells: /* @__PURE__ */ new Map(),
1140
- anchored: false
1141
- }
1142
- ];
1143
- }
1144
- const anchored = sheets.some((sheet) => sheet.anchored);
1145
- const inferNumericCells = options.inferNumericCells ?? anchored;
1146
- const hasFormulas = sheets.some((sheet) => {
1147
- for (const cell of sheet.cells.values()) if (cell.formula) return true;
1148
- return false;
1149
- });
1150
- const pkg = createPackage();
1151
- sheets.forEach((sheet, i) => {
1152
- if ((i & 31) === 0) options.signal?.throwIfAborted();
1153
- const sheetPath = `xl/worksheets/sheet${i + 1}.xml`;
1154
- pkg.addPart(sheetPath, worksheetXml(sheet, inferNumericCells), CONTENT_TYPE_XLSX_WORKSHEET);
1155
- pkg.addRelationship("xl/workbook.xml", {
1156
- id: `rId${i + 1}`,
1157
- type: REL_WORKSHEET,
1158
- target: `worksheets/sheet${i + 1}.xml`
1159
- });
1160
- });
1161
- pkg.addPart("xl/styles.xml", stylesXml(), CONTENT_TYPE_XLSX_STYLES);
1162
- pkg.addRelationship("xl/workbook.xml", {
1163
- id: `rId${sheets.length + 1}`,
1164
- type: REL_STYLES,
1165
- target: "styles.xml"
1166
- });
1167
- pkg.addPart("xl/workbook.xml", workbookXml(sheets, hasFormulas), CONTENT_TYPE_XLSX_WORKBOOK);
1168
- pkg.addRelationship("", {
1169
- id: "rId1",
1170
- type: REL_OFFICE_DOCUMENT,
1171
- target: "xl/workbook.xml"
1172
- });
1173
- if (options.title || options.author) {
1174
- pkg.setCoreProperties({
1175
- title: options.title,
1176
- creator: options.author,
1177
- created: (/* @__PURE__ */ new Date()).toISOString(),
1178
- modified: (/* @__PURE__ */ new Date()).toISOString()
1179
- });
1180
- }
1181
- return pkg.toArrayBuffer();
1182
- }
1183
- async function docToXlsx(doc, options) {
1184
- return markdownDocToXlsx(docToMarkdown(doc), options);
1185
- }
1186
-
1187
- // src/xlsx/index.ts
1188
- async function xlsxToDoc(data, options) {
1189
- return markdownToDoc(await xlsxToMarkdownDoc(data, options));
1043
+ return container;
1190
1044
  }
1191
1045
 
1192
1046
  export {
1047
+ MAX_COL_INDEX,
1048
+ MAX_ROW_INDEX,
1049
+ columnLetter,
1050
+ columnIndexFromLetters,
1051
+ colIndex,
1052
+ parseCellRef,
1053
+ formatCellRef,
1054
+ columnLetter2,
1055
+ gridToTables,
1056
+ listSheetParts,
1057
+ readCellStyles,
1058
+ numberFormatKind,
1059
+ xlsxToTables,
1060
+ xlsxToCellGrids,
1193
1061
  xlsxToMarkdownDoc,
1194
- markdownDocToXlsx,
1195
- docToXlsx,
1196
- xlsxToDoc
1062
+ xlsxToContainer
1197
1063
  };