@taloswork/ose-shared 0.1.11 → 0.1.12

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 (119) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +143 -215
  3. package/skills/document/art-directions/base.css +27 -2
  4. package/skills/document/art-directions/cinematic-depth.css +3 -1
  5. package/skills/document/art-directions/collage-paper.css +5 -1
  6. package/skills/document/art-directions/editorial-cut.css +5 -1
  7. package/skills/document/art-directions/geometric-pop.css +6 -0
  8. package/skills/document/art-directions/gradient-mesh.css +5 -1
  9. package/skills/document/art-directions/illustrated-doodle.css +5 -0
  10. package/skills/document/art-directions/luxury-ornament.css +4 -1
  11. package/skills/document/art-directions/maximalist-blocks.css +6 -0
  12. package/skills/document/art-directions/technical-grid.css +4 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Bold.ttf +0 -0
  15. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Regular.ttf +0 -0
  16. package/skills/document/assets/pdf-fonts/OFL.txt +93 -0
  17. package/skills/document/assets/pdf-fonts/README.md +35 -0
  18. package/skills/document/assets/tessdata/LICENSE-Apache-2.0.txt +202 -0
  19. package/skills/document/assets/tessdata/README.md +21 -0
  20. package/skills/document/assets/tessdata/chi_sim.traineddata.gz +0 -0
  21. package/skills/document/assets/tessdata/eng.traineddata.gz +0 -0
  22. package/skills/document/bin/deck-build.cjs +2311 -41
  23. package/skills/document/bin/deck-chart.cjs +762 -0
  24. package/skills/document/bin/deck-plan.cjs +802 -0
  25. package/skills/document/bin/deck-qa.cjs +984 -249
  26. package/skills/document/bin/deck-scenario.cjs +1346 -0
  27. package/skills/document/bin/doc-convert.cjs +130 -39
  28. package/skills/document/bin/doc-fill.cjs +73121 -170
  29. package/skills/document/bin/doc-gen.cjs +625 -68
  30. package/skills/document/bin/doc-run.cjs +162 -24
  31. package/skills/document/bin/doc-scan.cjs +13 -0
  32. package/skills/document/bin/doc-text.cjs +13 -0
  33. package/skills/document/bin/doc-validate.cjs +718 -23
  34. package/skills/document/bin/doc-xml.cjs +9 -5
  35. package/skills/document/bin/docx-annotate.cjs +95 -19
  36. package/skills/document/bin/fontkit.cjs +1 -0
  37. package/skills/document/bin/html2pptx.cjs +1372 -121
  38. package/skills/document/bin/ocr-worker.cjs +1 -0
  39. package/skills/document/bin/ocr.cjs +131 -40
  40. package/skills/document/bin/pdf-form.cjs +674 -177
  41. package/skills/document/bin/pdf-images.cjs +13 -0
  42. package/skills/document/bin/pdf-ops.cjs +634 -199
  43. package/skills/document/bin/pdf-render.cjs +19 -3
  44. package/skills/document/bin/pdf-tables.cjs +13 -0
  45. package/skills/document/bin/pptx-clone.cjs +3 -3
  46. package/skills/document/bin/pptx-slide.cjs +3 -3
  47. package/skills/document/bin/xlsx-query.cjs +192 -61
  48. package/skills/document/bin-reference.md +56 -33
  49. package/skills/document/content-pipeline.md +129 -242
  50. package/skills/document/deck-design.md +355 -638
  51. package/skills/document/layouts/README.md +46 -33
  52. package/skills/document/layouts/capacity.json +1 -0
  53. package/skills/document/layouts/slots.json +1 -1
  54. package/skills/document/layouts/toc-panel.tpl +1 -1
  55. package/skills/document/motion/README.md +3 -2
  56. package/skills/document/office.md +3 -3
  57. package/skills/document/packs/board-brief/backdrop.html +2 -0
  58. package/skills/document/packs/board-brief/pack.css +2 -1
  59. package/skills/document/packs/mckinsey-photo/backdrop.html +2 -0
  60. package/skills/document/packs/mckinsey-photo/pack.css +2 -1
  61. package/skills/document/packs/press-room/backdrop.html +2 -0
  62. package/skills/document/packs/press-room/pack.css +2 -1
  63. package/skills/document/packs/prismatic-3d/backdrop.html +2 -0
  64. package/skills/document/packs/prismatic-3d/pack.css +4 -1
  65. package/skills/document/pdf.md +53 -49
  66. package/skills/document/rewrite.md +241 -308
  67. package/skills/document/scenarios.json +1290 -0
  68. package/skills/document/scenarios.schema.json +103 -0
  69. package/skills/document/styles/README.md +15 -25
  70. package/skills/document/styles/_contract-defaults.css +25 -1
  71. package/skills/document/styles/anthropic/skin.css +6 -3
  72. package/skills/document/styles/apple/skin.css +4 -3
  73. package/skills/document/styles/bistro-warm/backdrop.html +2 -0
  74. package/skills/document/styles/bistro-warm/skin.css +4 -1
  75. package/skills/document/styles/black-gold/skin.css +6 -5
  76. package/skills/document/styles/bold-clash/backdrop.html +2 -0
  77. package/skills/document/styles/bold-clash/skin.css +2 -1
  78. package/skills/document/styles/branding/skin.css +4 -4
  79. package/skills/document/styles/campus-teal/backdrop.html +2 -0
  80. package/skills/document/styles/campus-teal/skin.css +2 -1
  81. package/skills/document/styles/cinereel/skin.css +7 -3
  82. package/skills/document/styles/clinic-care/backdrop.html +2 -0
  83. package/skills/document/styles/clinic-care/skin.css +2 -1
  84. package/skills/document/styles/cool/skin.css +6 -5
  85. package/skills/document/styles/dark-glass/backdrop.html +2 -0
  86. package/skills/document/styles/dark-glass/skin.css +2 -1
  87. package/skills/document/styles/gov-brief/backdrop.html +2 -0
  88. package/skills/document/styles/gov-brief/skin.css +2 -1
  89. package/skills/document/styles/grid-tech/backdrop.html +2 -0
  90. package/skills/document/styles/grid-tech/skin.css +2 -1
  91. package/skills/document/styles/hand-drawn/skin.css +3 -3
  92. package/skills/document/styles/ivory-gold/skin.css +7 -6
  93. package/skills/document/styles/legal-slate/backdrop.html +2 -0
  94. package/skills/document/styles/legal-slate/skin.css +2 -1
  95. package/skills/document/styles/magazine-ink/skin.css +4 -3
  96. package/skills/document/styles/mckinsey/skin.css +2 -1
  97. package/skills/document/styles/memphis/backdrop.html +2 -0
  98. package/skills/document/styles/memphis/skin.css +2 -1
  99. package/skills/document/styles/mono/skin.css +6 -2
  100. package/skills/document/styles/neon-cyber/backdrop.html +2 -0
  101. package/skills/document/styles/neon-cyber/skin.css +2 -1
  102. package/skills/document/styles/olive-gold/skin.css +7 -6
  103. package/skills/document/styles/organic-earth/backdrop.html +2 -0
  104. package/skills/document/styles/organic-earth/skin.css +2 -1
  105. package/skills/document/styles/pastel-saas/backdrop.html +2 -0
  106. package/skills/document/styles/pastel-saas/skin.css +2 -1
  107. package/skills/document/styles/portfolio-noir/backdrop.html +2 -0
  108. package/skills/document/styles/portfolio-noir/skin.css +2 -1
  109. package/skills/document/styles/retro-press/backdrop.html +2 -0
  110. package/skills/document/styles/retro-press/skin.css +2 -1
  111. package/skills/document/styles/sport-arena/backdrop.html +2 -0
  112. package/skills/document/styles/sport-arena/skin.css +4 -1
  113. package/skills/document/styles/swiss-grid/skin.css +10 -5
  114. package/skills/document/styles/wedding-blush/backdrop.html +2 -0
  115. package/skills/document/styles/wedding-blush/skin.css +4 -1
  116. package/skills/document/theme-system.md +85 -77
  117. package/skills/document/themes/README.md +7 -5
  118. package/skills/document/vision.md +8 -5
  119. package/skills/document/xlsx.md +15 -25
@@ -8,6 +8,17 @@ var import_node_path2 = require("node:path");
8
8
  // packages/core/dist/document/pdf-extract.js
9
9
  var import_node_path = require("node:path");
10
10
  var import_node_url = require("node:url");
11
+
12
+ // packages/core/dist/document/pdf-encrypted.js
13
+ var ENCRYPTED_PDF_MESSAGE = "PDF is password-protected (encrypted); decrypt first: pdf-ops decrypt <in> <password> <out>";
14
+ var PdfEncryptedError = class extends Error {
15
+ constructor() {
16
+ super(ENCRYPTED_PDF_MESSAGE);
17
+ this.name = "PdfEncryptedError";
18
+ }
19
+ };
20
+
21
+ // packages/core/dist/document/pdf-extract.js
11
22
  var _mupdf = null;
12
23
  function loadMupdf() {
13
24
  if (!_mupdf) {
@@ -20,21 +31,26 @@ function loadMupdf() {
20
31
  async function openDoc(bytes) {
21
32
  const mupdf = await loadMupdf();
22
33
  const doc = mupdf.Document.openDocument(bytes, "application/pdf");
34
+ if (doc.needsPassword())
35
+ throw new PdfEncryptedError();
23
36
  if (doc.countPages() === 0) {
24
37
  throw new Error("PDF has no readable pages (corrupt, encrypted, or empty)");
25
38
  }
26
39
  return { mupdf, doc };
27
40
  }
28
- async function renderPdfPages(bytes, pages, scale = 2) {
41
+ async function renderPdfPages(bytes, pages, scale = 2, maxEdge) {
29
42
  const { mupdf, doc } = await openDoc(bytes);
30
43
  const total = doc.countPages();
31
44
  const wanted = pages === "all" ? Array.from({ length: total }, (_, i) => i + 1) : pages;
32
45
  const out = [];
33
- const matrix = mupdf.Matrix.scale(scale, scale);
34
46
  for (const p of wanted) {
35
47
  if (p < 1 || p > total)
36
48
  continue;
37
- const pix = doc.loadPage(p - 1).toPixmap(matrix, mupdf.ColorSpace.DeviceRGB, false, true);
49
+ const page = doc.loadPage(p - 1);
50
+ const [x0, y0, x1, y1] = page.getBounds();
51
+ const longest = Math.max(x1 - x0, y1 - y0);
52
+ const s = maxEdge && longest > 0 ? Math.min(scale, maxEdge / longest) : scale;
53
+ const pix = page.toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false, true);
38
54
  out.push({ page: p, png: pix.asPNG() });
39
55
  }
40
56
  return out;
@@ -8,6 +8,17 @@ var import_node_path2 = require("node:path");
8
8
  // packages/core/dist/document/pdf-extract.js
9
9
  var import_node_path = require("node:path");
10
10
  var import_node_url = require("node:url");
11
+
12
+ // packages/core/dist/document/pdf-encrypted.js
13
+ var ENCRYPTED_PDF_MESSAGE = "PDF is password-protected (encrypted); decrypt first: pdf-ops decrypt <in> <password> <out>";
14
+ var PdfEncryptedError = class extends Error {
15
+ constructor() {
16
+ super(ENCRYPTED_PDF_MESSAGE);
17
+ this.name = "PdfEncryptedError";
18
+ }
19
+ };
20
+
21
+ // packages/core/dist/document/pdf-extract.js
11
22
  var _mupdf = null;
12
23
  function loadMupdf() {
13
24
  if (!_mupdf) {
@@ -20,6 +31,8 @@ function loadMupdf() {
20
31
  async function openDoc(bytes) {
21
32
  const mupdf = await loadMupdf();
22
33
  const doc = mupdf.Document.openDocument(bytes, "application/pdf");
34
+ if (doc.needsPassword())
35
+ throw new PdfEncryptedError();
23
36
  if (doc.countPages() === 0) {
24
37
  throw new Error("PDF has no readable pages (corrupt, encrypted, or empty)");
25
38
  }
@@ -9892,7 +9892,7 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
9892
9892
  let flat = "";
9893
9893
  for (const m of para.matchAll(textRe)) {
9894
9894
  const text = unescXml(m[2]);
9895
- segs.push({ el: m[0], attrs: m[1] ?? "", text, start: flat.length });
9895
+ segs.push({ el: m[0], at: m.index, attrs: m[1] ?? "", text, start: flat.length });
9896
9896
  flat += text;
9897
9897
  }
9898
9898
  if (!flat.includes(find))
@@ -9921,7 +9921,7 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
9921
9921
  return pieces.join("");
9922
9922
  });
9923
9923
  let out = para;
9924
- for (let i = 0; i < segs.length; i++) {
9924
+ for (let i = segs.length - 1; i >= 0; i--) {
9925
9925
  if (newTexts[i] === segs[i].text)
9926
9926
  continue;
9927
9927
  const escaped = escXml(newTexts[i]);
@@ -9929,7 +9929,7 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
9929
9929
  if (textTag === "w:t" && /^\s|\s$/.test(newTexts[i]) && !/xml:space="preserve"/.test(attrs)) {
9930
9930
  attrs = `${attrs} xml:space="preserve"`;
9931
9931
  }
9932
- out = out.replace(segs[i].el, `<${textTag}${attrs}>${escaped}</${textTag}>`);
9932
+ out = out.slice(0, segs[i].at) + `<${textTag}${attrs}>${escaped}</${textTag}>` + out.slice(segs[i].at + segs[i].el.length);
9933
9933
  }
9934
9934
  return out;
9935
9935
  });
@@ -10007,7 +10007,7 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
10007
10007
  let flat = "";
10008
10008
  for (const m of para.matchAll(textRe)) {
10009
10009
  const text = unescXml(m[2]);
10010
- segs.push({ el: m[0], attrs: m[1] ?? "", text, start: flat.length });
10010
+ segs.push({ el: m[0], at: m.index, attrs: m[1] ?? "", text, start: flat.length });
10011
10011
  flat += text;
10012
10012
  }
10013
10013
  if (!flat.includes(find))
@@ -10036,7 +10036,7 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
10036
10036
  return pieces.join("");
10037
10037
  });
10038
10038
  let out2 = para;
10039
- for (let i = 0; i < segs.length; i++) {
10039
+ for (let i = segs.length - 1; i >= 0; i--) {
10040
10040
  if (newTexts[i] === segs[i].text)
10041
10041
  continue;
10042
10042
  const escaped = escXml(newTexts[i]);
@@ -10044,7 +10044,7 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
10044
10044
  if (textTag === "w:t" && /^\s|\s$/.test(newTexts[i]) && !/xml:space="preserve"/.test(attrs)) {
10045
10045
  attrs = `${attrs} xml:space="preserve"`;
10046
10046
  }
10047
- out2 = out2.replace(segs[i].el, `<${textTag}${attrs}>${escaped}</${textTag}>`);
10047
+ out2 = out2.slice(0, segs[i].at) + `<${textTag}${attrs}>${escaped}</${textTag}>` + out2.slice(segs[i].at + segs[i].el.length);
10048
10048
  }
10049
10049
  return out2;
10050
10050
  });
@@ -12683,7 +12683,7 @@ var require_xlsx = __commonJS({
12683
12683
  var s = o.dense ? [] : {}, n = "Sheet1", next_n = "", sidx = 0;
12684
12684
  var sheets = {}, snames = [], realnames = [];
12685
12685
  var refguess = { s: { r: 0, c: 0 }, e: { r: 0, c: 0 } };
12686
- var sheetRows2 = o.sheetRows || 0;
12686
+ var sheetRows = o.sheetRows || 0;
12687
12687
  if (d[2] == 0) {
12688
12688
  if (d[3] == 8 || d[3] == 9) {
12689
12689
  if (d.length >= 16 && d[14] == 5 && d[15] === 108) throw new Error("Unsupported Works 3 for Mac file");
@@ -12789,7 +12789,7 @@ var require_xlsx = __commonJS({
12789
12789
  sidx = val[3];
12790
12790
  n = "Sheet" + (sidx + 1);
12791
12791
  }
12792
- if (sheetRows2 > 0 && val[0].r >= sheetRows2) break;
12792
+ if (sheetRows > 0 && val[0].r >= sheetRows) break;
12793
12793
  if (o.dense) {
12794
12794
  if (!s[val[0].r]) s[val[0].r] = [];
12795
12795
  s[val[0].r][val[0].c] = val[1];
@@ -29309,7 +29309,7 @@ var require_xlsx = __commonJS({
29309
29309
  }
29310
29310
  }
29311
29311
  var rows = table.getElementsByTagName("tr");
29312
- var sheetRows2 = Math.min(opts.sheetRows || 1e7, rows.length);
29312
+ var sheetRows = Math.min(opts.sheetRows || 1e7, rows.length);
29313
29313
  var range = { s: { r: 0, c: 0 }, e: { r: or_R, c: or_C } };
29314
29314
  if (ws["!ref"]) {
29315
29315
  var _range = decode_range(ws["!ref"]);
@@ -29323,7 +29323,7 @@ var require_xlsx = __commonJS({
29323
29323
  var rowinfo = ws["!rows"] || (ws["!rows"] = []);
29324
29324
  var _R = 0, R = 0, _C = 0, C = 0, RS = 0, CS = 0;
29325
29325
  if (!ws["!cols"]) ws["!cols"] = [];
29326
- for (; _R < rows.length && R < sheetRows2; ++_R) {
29326
+ for (; _R < rows.length && R < sheetRows; ++_R) {
29327
29327
  var row = rows[_R];
29328
29328
  if (is_dom_element_hidden(row)) {
29329
29329
  if (opts.display) continue;
@@ -29379,7 +29379,7 @@ var require_xlsx = __commonJS({
29379
29379
  if (merges.length) ws["!merges"] = (ws["!merges"] || []).concat(merges);
29380
29380
  range.e.r = Math.max(range.e.r, R - 1 + or_R);
29381
29381
  ws["!ref"] = encode_range(range);
29382
- if (R >= sheetRows2) ws["!fullref"] = encode_range((range.e.r = rows.length - _R + R - 1 + or_R, range));
29382
+ if (R >= sheetRows) ws["!fullref"] = encode_range((range.e.r = rows.length - _R + R - 1 + or_R, range));
29383
29383
  return ws;
29384
29384
  }
29385
29385
  function parse_dom_table(table, _opts) {
@@ -33253,30 +33253,104 @@ var import_promises = require("node:fs/promises");
33253
33253
 
33254
33254
  // packages/core/dist/document/xlsx-query.js
33255
33255
  var XLSX = __toESM(require_xlsx(), 1);
33256
+ var AGGREGATE_OPS = ["sum", "avg", "min", "max", "count", "count-nonempty"];
33256
33257
  function parseWorkbook(buffer) {
33257
33258
  const data = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
33258
33259
  return XLSX.read(data, { type: "array" });
33259
33260
  }
33260
- function sheetRows(wb, sheetName) {
33261
+ var hasValue = (c) => c != null && c.t !== "z" && c.v != null;
33262
+ function sheetGrid(wb, sheetName) {
33261
33263
  const ws = wb.Sheets[sheetName];
33262
- if (!ws)
33263
- return [];
33264
- return XLSX.utils.sheet_to_json(ws, { header: 1, blankrows: false, defval: "" });
33264
+ if (!ws || !ws["!ref"])
33265
+ return { rows: [], width: 0, firstCol: 0 };
33266
+ const range = XLSX.utils.decode_range(ws["!ref"]);
33267
+ const rows = [];
33268
+ for (let r = range.s.r; r <= range.e.r; r++) {
33269
+ const cells = [];
33270
+ for (let c = range.s.c; c <= range.e.c; c++)
33271
+ cells.push(ws[XLSX.utils.encode_cell({ r, c })]);
33272
+ if (cells.some(hasValue))
33273
+ rows.push({ row: r + 1, cells });
33274
+ }
33275
+ return { rows, width: rows.length ? range.e.c - range.s.c + 1 : 0, firstCol: range.s.c };
33276
+ }
33277
+ function parseNumberText(text) {
33278
+ let t = text.replace(/[¥¥$€£,\s]/g, "").replace(/元$/, "");
33279
+ let sign = 1;
33280
+ const paren = /^\((.*)\)$/.exec(t);
33281
+ if (paren) {
33282
+ sign = -1;
33283
+ t = paren[1];
33284
+ }
33285
+ let scale = 1;
33286
+ if (t.endsWith("%")) {
33287
+ scale = 0.01;
33288
+ t = t.slice(0, -1);
33289
+ }
33290
+ if (t === "" || !/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(t))
33291
+ return null;
33292
+ const n = Number(t);
33293
+ return Number.isFinite(n) ? cleanFloat(sign * n * scale) : null;
33294
+ }
33295
+ function cellNumber(c) {
33296
+ if (!hasValue(c))
33297
+ return null;
33298
+ if (c.t === "n")
33299
+ return typeof c.v === "number" && Number.isFinite(c.v) ? c.v : null;
33300
+ if (c.t === "s")
33301
+ return parseNumberText(String(c.v));
33302
+ return null;
33303
+ }
33304
+ var isEmptyCell = (c) => !hasValue(c) || String(c.v).trim() === "";
33305
+ var isTextCell = (c) => !isEmptyCell(c) && c.t === "s" && cellNumber(c) == null;
33306
+ function cleanFloat(v) {
33307
+ if (!Number.isFinite(v) || v === 0)
33308
+ return v;
33309
+ return Number(v.toPrecision(15));
33310
+ }
33311
+ var HEADER_SCAN_ROWS = 10;
33312
+ function detectHeaderIndex(grid) {
33313
+ const { rows, width } = grid;
33314
+ for (let k = 0; k < Math.min(HEADER_SCAN_ROWS, rows.length - 1); k++) {
33315
+ const cells = rows[k].cells;
33316
+ const filled = cells.filter((c) => !isEmptyCell(c));
33317
+ if (!filled.length || filled.length * 2 <= width)
33318
+ continue;
33319
+ if (cells.filter(isTextCell).length * 2 < filled.length)
33320
+ continue;
33321
+ if (rows[k + 1].cells.some((c) => cellNumber(c) != null))
33322
+ return k;
33323
+ }
33324
+ return 0;
33265
33325
  }
33266
- function headerRow(rows) {
33267
- const first = rows[0] ?? [];
33268
- return first.map((c, i) => {
33269
- const s = c == null ? "" : String(c).trim();
33326
+ function layout(grid, opts) {
33327
+ if (!grid.rows.length)
33328
+ return { headerRow: opts.headerRow ?? 1, headers: [], data: [] };
33329
+ let headerCells;
33330
+ let headerRow;
33331
+ let data;
33332
+ if (opts.headerRow != null) {
33333
+ headerRow = opts.headerRow;
33334
+ headerCells = grid.rows.find((r) => r.row === headerRow)?.cells ?? [];
33335
+ data = grid.rows.filter((r) => r.row > headerRow);
33336
+ } else {
33337
+ const k = detectHeaderIndex(grid);
33338
+ headerRow = grid.rows[k].row;
33339
+ headerCells = grid.rows[k].cells;
33340
+ data = grid.rows.slice(k + 1);
33341
+ }
33342
+ const headers = Array.from({ length: grid.width }, (_, i) => {
33343
+ const c = headerCells[i];
33344
+ const s = hasValue(c) ? String(c.v).trim() : "";
33270
33345
  return s.length > 0 ? s : `col${i + 1}`;
33271
33346
  });
33347
+ return { headerRow, headers, data };
33272
33348
  }
33273
- function summarizeWorkbook(wb) {
33349
+ function summarizeWorkbook(wb, opts = {}) {
33274
33350
  const sheets = wb.SheetNames.map((name) => {
33275
- const rows = sheetRows(wb, name);
33276
- const headers = rows.length > 0 ? headerRow(rows) : [];
33277
- const dataRows = rows.length > 0 ? rows.length - 1 : 0;
33278
- const cols = rows.reduce((m, r) => Math.max(m, r.length), 0);
33279
- return { name, rows: dataRows, cols, headers };
33351
+ const grid = sheetGrid(wb, name);
33352
+ const { headerRow, headers, data } = layout(grid, opts);
33353
+ return { name, rows: data.length, cols: grid.width, headerRow, headers };
33280
33354
  });
33281
33355
  return { sheets };
33282
33356
  }
@@ -33289,54 +33363,95 @@ function resolveColumnIndex(headers, column) {
33289
33363
  return asNum - 1;
33290
33364
  return -1;
33291
33365
  }
33292
- function toNumber(cell) {
33293
- if (typeof cell === "number" && Number.isFinite(cell))
33294
- return cell;
33295
- if (typeof cell === "string") {
33296
- const cleaned = cell.replace(/[$,\s%]/g, "");
33297
- if (cleaned === "")
33298
- return null;
33299
- const n = Number(cleaned);
33300
- return Number.isFinite(n) ? n : null;
33366
+ var TOTAL_LABEL = /^(合计|总计|小计|total|subtotal|sum)$/i;
33367
+ function totalReason(row, colIdx, colLetter) {
33368
+ for (const c of row.cells) {
33369
+ if (!hasValue(c) || c.t !== "s")
33370
+ continue;
33371
+ const label = String(c.v).replace(/\s+/g, "").replace(/[::]$/, "");
33372
+ if (TOTAL_LABEL.test(label))
33373
+ return `total label "${String(c.v).trim()}"`;
33374
+ }
33375
+ const f = row.cells[colIdx]?.f;
33376
+ if (f) {
33377
+ const m = /^\s*=?\s*SUM\(\s*\$?([A-Z]+)\$?\d+\s*:\s*\$?([A-Z]+)\$?\d+\s*\)\s*$/i.exec(f);
33378
+ if (m && m[1].toUpperCase() === colLetter && m[2].toUpperCase() === colLetter)
33379
+ return `${f.trim().replace(/^=/, "")} over this column`;
33301
33380
  }
33302
33381
  return null;
33303
33382
  }
33304
- function aggregateColumn(wb, sheetName, column, op) {
33305
- const rows = sheetRows(wb, sheetName);
33306
- const colIdx = rows.length > 0 ? resolveColumnIndex(headerRow(rows), column) : -1;
33307
- const base = { sheet: sheetName, column, op, colIdx };
33308
- if (rows.length === 0)
33309
- return { ...base, rowsScanned: 0, numericCells: 0, value: null };
33310
- const dataRows = rows.slice(1);
33311
- if (colIdx === -1)
33312
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: null };
33313
- const cells = dataRows.map((r) => r[colIdx]);
33383
+ var SKIPPED_CELLS_CAP = 20;
33384
+ function aggregateColumn(wb, sheetName, column, op, opts = {}) {
33385
+ const grid = sheetGrid(wb, sheetName);
33386
+ const { headerRow, headers, data } = layout(grid, opts);
33387
+ const colIdx = headers.length > 0 ? resolveColumnIndex(headers, column) : -1;
33388
+ const result = {
33389
+ sheet: sheetName,
33390
+ column,
33391
+ op,
33392
+ headerRow,
33393
+ colIdx,
33394
+ rowsScanned: 0,
33395
+ numericCells: 0,
33396
+ value: null,
33397
+ excludedRows: [],
33398
+ skippedCells: [],
33399
+ skippedCellCount: 0
33400
+ };
33401
+ if (colIdx === -1) {
33402
+ result.rowsScanned = data.length;
33403
+ return result;
33404
+ }
33405
+ const colLetter = XLSX.utils.encode_col(grid.firstCol + colIdx);
33406
+ const kept = [];
33407
+ for (const r of data) {
33408
+ const reason = opts.includeTotals ? null : totalReason(r, colIdx, colLetter);
33409
+ if (reason)
33410
+ result.excludedRows.push({ row: r.row, reason });
33411
+ else
33412
+ kept.push(r);
33413
+ }
33414
+ result.rowsScanned = kept.length;
33314
33415
  if (op === "count") {
33315
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: dataRows.length };
33416
+ result.value = kept.length;
33417
+ return result;
33316
33418
  }
33419
+ const cells = kept.map((r) => ({ row: r.row, cell: r.cells[colIdx] }));
33317
33420
  if (op === "count-nonempty") {
33318
- const nonEmpty = cells.filter((c) => c != null && String(c).trim() !== "").length;
33319
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: nonEmpty };
33421
+ result.value = cells.filter(({ cell }) => !isEmptyCell(cell)).length;
33422
+ return result;
33320
33423
  }
33321
- const nums = cells.map(toNumber).filter((n) => n != null);
33424
+ const nums = [];
33425
+ for (const { row, cell } of cells) {
33426
+ if (isEmptyCell(cell))
33427
+ continue;
33428
+ const n = cellNumber(cell);
33429
+ if (n != null) {
33430
+ nums.push(n);
33431
+ continue;
33432
+ }
33433
+ result.skippedCellCount++;
33434
+ if (result.skippedCells.length < SKIPPED_CELLS_CAP)
33435
+ result.skippedCells.push({ row, value: cell.v });
33436
+ }
33437
+ result.numericCells = nums.length;
33322
33438
  if (nums.length === 0)
33323
- return { ...base, rowsScanned: dataRows.length, numericCells: 0, value: null };
33324
- let value;
33439
+ return result;
33325
33440
  switch (op) {
33326
33441
  case "sum":
33327
- value = nums.reduce((a, b) => a + b, 0);
33442
+ result.value = cleanFloat(nums.reduce((a, b) => a + b, 0));
33328
33443
  break;
33329
33444
  case "avg":
33330
- value = nums.reduce((a, b) => a + b, 0) / nums.length;
33445
+ result.value = cleanFloat(nums.reduce((a, b) => a + b, 0) / nums.length);
33331
33446
  break;
33332
33447
  case "min":
33333
- value = Math.min(...nums);
33448
+ result.value = Math.min(...nums);
33334
33449
  break;
33335
33450
  case "max":
33336
- value = Math.max(...nums);
33451
+ result.value = Math.max(...nums);
33337
33452
  break;
33338
33453
  }
33339
- return { ...base, rowsScanned: dataRows.length, numericCells: nums.length, value };
33454
+ return result;
33340
33455
  }
33341
33456
  var COMPUTE_KEYWORDS_EN = [
33342
33457
  "sum",
@@ -33356,15 +33471,13 @@ var COMPUTE_KEYWORDS_EN = [
33356
33471
  "percentage"
33357
33472
  ];
33358
33473
  var COMPUTE_EN_RE = new RegExp(`\\b(${COMPUTE_KEYWORDS_EN.map((k) => k.replace(/ /g, "\\s+")).join("|")})\\b`, "i");
33359
- function sampleRows(wb, sheetName, limit = 20) {
33360
- const rows = sheetRows(wb, sheetName);
33361
- if (rows.length <= 1)
33362
- return [];
33363
- const headers = headerRow(rows);
33364
- return rows.slice(1, 1 + Math.max(0, limit)).map((r) => {
33474
+ function sampleRows(wb, sheetName, limit = 20, opts = {}) {
33475
+ const { headers, data } = layout(sheetGrid(wb, sheetName), opts);
33476
+ return data.slice(0, Math.max(0, limit)).map((r) => {
33365
33477
  const obj = {};
33366
33478
  headers.forEach((h, i) => {
33367
- obj[h] = r[i] ?? "";
33479
+ const c = r.cells[i];
33480
+ obj[h] = hasValue(c) ? c.v : "";
33368
33481
  });
33369
33482
  return obj;
33370
33483
  });
@@ -33379,9 +33492,24 @@ function die(msg, code = 1) {
33379
33492
  function out(obj) {
33380
33493
  process.stdout.write(JSON.stringify(obj, null, 2) + "\n");
33381
33494
  }
33495
+ var USAGE = "usage: xlsx-query <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]] [--header-row N] [--include-totals]";
33496
+ function parseFlags(argv) {
33497
+ const opts = {};
33498
+ const positional = [];
33499
+ for (let i = 0; i < argv.length; i++) {
33500
+ if (argv[i] === "--include-totals") opts.includeTotals = true;
33501
+ else if (argv[i] === "--header-row") {
33502
+ const n = Number(argv[++i]);
33503
+ if (!Number.isInteger(n) || n < 1) die("--header-row needs a 1-based row number", 64);
33504
+ opts.headerRow = n;
33505
+ } else positional.push(argv[i]);
33506
+ }
33507
+ return { opts, positional };
33508
+ }
33382
33509
  async function main() {
33383
- const [file, mode, ...rest] = process.argv.slice(2);
33384
- if (!file) die("usage: xlsx-query <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]]", 64);
33510
+ const { opts, positional } = parseFlags(process.argv.slice(2));
33511
+ const [file, mode, ...rest] = positional;
33512
+ if (!file) die(USAGE, 64);
33385
33513
  let raw;
33386
33514
  try {
33387
33515
  raw = await (0, import_promises.readFile)(file);
@@ -33395,19 +33523,22 @@ async function main() {
33395
33523
  die(`parse failed: ${e.message}`);
33396
33524
  }
33397
33525
  if (!mode || mode === "--summary") {
33398
- out(summarizeWorkbook(wb));
33526
+ out(summarizeWorkbook(wb, opts));
33399
33527
  return;
33400
33528
  }
33401
33529
  if (mode === "--agg") {
33402
33530
  const [sheet, col, op] = rest;
33403
33531
  if (!sheet || !col || !op) die("usage: --agg <sheet> <col> <op>", 64);
33404
- out(aggregateColumn(wb, sheet, col, op));
33532
+ if (!AGGREGATE_OPS.includes(op)) die(`unknown op "${op}" \u2014 use one of: ${AGGREGATE_OPS.join(", ")}`, 64);
33533
+ if (!wb.SheetNames.includes(sheet)) die(`no sheet "${sheet}" \u2014 the workbook has: ${wb.SheetNames.join(", ")}`);
33534
+ out(aggregateColumn(wb, sheet, col, op, opts));
33405
33535
  return;
33406
33536
  }
33407
33537
  if (mode === "--sample") {
33408
33538
  const [sheet, n] = rest;
33409
33539
  if (!sheet) die("usage: --sample <sheet> [n]", 64);
33410
- out({ sheet, sample: sampleRows(wb, sheet, Math.max(0, Number(n) || 20)) });
33540
+ if (!wb.SheetNames.includes(sheet)) die(`no sheet "${sheet}" \u2014 the workbook has: ${wb.SheetNames.join(", ")}`);
33541
+ out({ sheet, sample: sampleRows(wb, sheet, Math.max(0, Number(n) || 20), opts) });
33411
33542
  return;
33412
33543
  }
33413
33544
  die(`unknown mode: ${mode}`, 64);