@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.
- package/package.json +3 -3
- package/skills/document/SKILL.md +143 -215
- package/skills/document/art-directions/base.css +27 -2
- package/skills/document/art-directions/cinematic-depth.css +3 -1
- package/skills/document/art-directions/collage-paper.css +5 -1
- package/skills/document/art-directions/editorial-cut.css +5 -1
- package/skills/document/art-directions/geometric-pop.css +6 -0
- package/skills/document/art-directions/gradient-mesh.css +5 -1
- package/skills/document/art-directions/illustrated-doodle.css +5 -0
- package/skills/document/art-directions/luxury-ornament.css +4 -1
- package/skills/document/art-directions/maximalist-blocks.css +6 -0
- package/skills/document/art-directions/technical-grid.css +4 -0
- package/skills/document/assets/fonts/fonts.json +2 -2
- package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Bold.ttf +0 -0
- package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Regular.ttf +0 -0
- package/skills/document/assets/pdf-fonts/OFL.txt +93 -0
- package/skills/document/assets/pdf-fonts/README.md +35 -0
- package/skills/document/assets/tessdata/LICENSE-Apache-2.0.txt +202 -0
- package/skills/document/assets/tessdata/README.md +21 -0
- package/skills/document/assets/tessdata/chi_sim.traineddata.gz +0 -0
- package/skills/document/assets/tessdata/eng.traineddata.gz +0 -0
- package/skills/document/bin/deck-build.cjs +2311 -41
- package/skills/document/bin/deck-chart.cjs +762 -0
- package/skills/document/bin/deck-plan.cjs +802 -0
- package/skills/document/bin/deck-qa.cjs +984 -249
- package/skills/document/bin/deck-scenario.cjs +1346 -0
- package/skills/document/bin/doc-convert.cjs +130 -39
- package/skills/document/bin/doc-fill.cjs +73121 -170
- package/skills/document/bin/doc-gen.cjs +625 -68
- package/skills/document/bin/doc-run.cjs +162 -24
- package/skills/document/bin/doc-scan.cjs +13 -0
- package/skills/document/bin/doc-text.cjs +13 -0
- package/skills/document/bin/doc-validate.cjs +718 -23
- package/skills/document/bin/doc-xml.cjs +9 -5
- package/skills/document/bin/docx-annotate.cjs +95 -19
- package/skills/document/bin/fontkit.cjs +1 -0
- package/skills/document/bin/html2pptx.cjs +1372 -121
- package/skills/document/bin/ocr-worker.cjs +1 -0
- package/skills/document/bin/ocr.cjs +131 -40
- package/skills/document/bin/pdf-form.cjs +674 -177
- package/skills/document/bin/pdf-images.cjs +13 -0
- package/skills/document/bin/pdf-ops.cjs +634 -199
- package/skills/document/bin/pdf-render.cjs +19 -3
- package/skills/document/bin/pdf-tables.cjs +13 -0
- package/skills/document/bin/pptx-clone.cjs +3 -3
- package/skills/document/bin/pptx-slide.cjs +3 -3
- package/skills/document/bin/xlsx-query.cjs +192 -61
- package/skills/document/bin-reference.md +56 -33
- package/skills/document/content-pipeline.md +129 -242
- package/skills/document/deck-design.md +355 -638
- package/skills/document/layouts/README.md +46 -33
- package/skills/document/layouts/capacity.json +1 -0
- package/skills/document/layouts/slots.json +1 -1
- package/skills/document/layouts/toc-panel.tpl +1 -1
- package/skills/document/motion/README.md +3 -2
- package/skills/document/office.md +3 -3
- package/skills/document/packs/board-brief/backdrop.html +2 -0
- package/skills/document/packs/board-brief/pack.css +2 -1
- package/skills/document/packs/mckinsey-photo/backdrop.html +2 -0
- package/skills/document/packs/mckinsey-photo/pack.css +2 -1
- package/skills/document/packs/press-room/backdrop.html +2 -0
- package/skills/document/packs/press-room/pack.css +2 -1
- package/skills/document/packs/prismatic-3d/backdrop.html +2 -0
- package/skills/document/packs/prismatic-3d/pack.css +4 -1
- package/skills/document/pdf.md +53 -49
- package/skills/document/rewrite.md +241 -308
- package/skills/document/scenarios.json +1290 -0
- package/skills/document/scenarios.schema.json +103 -0
- package/skills/document/styles/README.md +15 -25
- package/skills/document/styles/_contract-defaults.css +25 -1
- package/skills/document/styles/anthropic/skin.css +6 -3
- package/skills/document/styles/apple/skin.css +4 -3
- package/skills/document/styles/bistro-warm/backdrop.html +2 -0
- package/skills/document/styles/bistro-warm/skin.css +4 -1
- package/skills/document/styles/black-gold/skin.css +6 -5
- package/skills/document/styles/bold-clash/backdrop.html +2 -0
- package/skills/document/styles/bold-clash/skin.css +2 -1
- package/skills/document/styles/branding/skin.css +4 -4
- package/skills/document/styles/campus-teal/backdrop.html +2 -0
- package/skills/document/styles/campus-teal/skin.css +2 -1
- package/skills/document/styles/cinereel/skin.css +7 -3
- package/skills/document/styles/clinic-care/backdrop.html +2 -0
- package/skills/document/styles/clinic-care/skin.css +2 -1
- package/skills/document/styles/cool/skin.css +6 -5
- package/skills/document/styles/dark-glass/backdrop.html +2 -0
- package/skills/document/styles/dark-glass/skin.css +2 -1
- package/skills/document/styles/gov-brief/backdrop.html +2 -0
- package/skills/document/styles/gov-brief/skin.css +2 -1
- package/skills/document/styles/grid-tech/backdrop.html +2 -0
- package/skills/document/styles/grid-tech/skin.css +2 -1
- package/skills/document/styles/hand-drawn/skin.css +3 -3
- package/skills/document/styles/ivory-gold/skin.css +7 -6
- package/skills/document/styles/legal-slate/backdrop.html +2 -0
- package/skills/document/styles/legal-slate/skin.css +2 -1
- package/skills/document/styles/magazine-ink/skin.css +4 -3
- package/skills/document/styles/mckinsey/skin.css +2 -1
- package/skills/document/styles/memphis/backdrop.html +2 -0
- package/skills/document/styles/memphis/skin.css +2 -1
- package/skills/document/styles/mono/skin.css +6 -2
- package/skills/document/styles/neon-cyber/backdrop.html +2 -0
- package/skills/document/styles/neon-cyber/skin.css +2 -1
- package/skills/document/styles/olive-gold/skin.css +7 -6
- package/skills/document/styles/organic-earth/backdrop.html +2 -0
- package/skills/document/styles/organic-earth/skin.css +2 -1
- package/skills/document/styles/pastel-saas/backdrop.html +2 -0
- package/skills/document/styles/pastel-saas/skin.css +2 -1
- package/skills/document/styles/portfolio-noir/backdrop.html +2 -0
- package/skills/document/styles/portfolio-noir/skin.css +2 -1
- package/skills/document/styles/retro-press/backdrop.html +2 -0
- package/skills/document/styles/retro-press/skin.css +2 -1
- package/skills/document/styles/sport-arena/backdrop.html +2 -0
- package/skills/document/styles/sport-arena/skin.css +4 -1
- package/skills/document/styles/swiss-grid/skin.css +10 -5
- package/skills/document/styles/wedding-blush/backdrop.html +2 -0
- package/skills/document/styles/wedding-blush/skin.css +4 -1
- package/skills/document/theme-system.md +85 -77
- package/skills/document/themes/README.md +7 -5
- package/skills/document/vision.md +8 -5
- 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
|
|
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 =
|
|
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.
|
|
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 =
|
|
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.
|
|
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
|
|
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 (
|
|
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
|
|
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 <
|
|
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 >=
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
33267
|
-
|
|
33268
|
-
|
|
33269
|
-
|
|
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
|
|
33276
|
-
const
|
|
33277
|
-
|
|
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
|
-
|
|
33293
|
-
|
|
33294
|
-
|
|
33295
|
-
|
|
33296
|
-
|
|
33297
|
-
|
|
33298
|
-
|
|
33299
|
-
|
|
33300
|
-
|
|
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
|
-
|
|
33305
|
-
|
|
33306
|
-
const
|
|
33307
|
-
const
|
|
33308
|
-
|
|
33309
|
-
|
|
33310
|
-
|
|
33311
|
-
|
|
33312
|
-
|
|
33313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33319
|
-
return
|
|
33421
|
+
result.value = cells.filter(({ cell }) => !isEmptyCell(cell)).length;
|
|
33422
|
+
return result;
|
|
33320
33423
|
}
|
|
33321
|
-
const nums =
|
|
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
|
|
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
|
|
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
|
|
33361
|
-
|
|
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
|
-
|
|
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
|
|
33384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|