@noy-db/as-xlsx 0.2.0-pre.2 → 0.2.0-pre.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +312 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +310 -4
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -33,8 +33,10 @@ __export(index_exports, {
|
|
|
33
33
|
XlsxDictAmbiguityError: () => XlsxDictAmbiguityError,
|
|
34
34
|
colLetter: () => colLetter,
|
|
35
35
|
download: () => download,
|
|
36
|
+
formula: () => formula,
|
|
36
37
|
fromBytes: () => fromBytes,
|
|
37
38
|
readXlsx: () => readXlsx,
|
|
39
|
+
styled: () => styled,
|
|
38
40
|
toBytes: () => toBytes,
|
|
39
41
|
toBytesFromCollection: () => toBytesFromCollection,
|
|
40
42
|
write: () => write,
|
|
@@ -46,7 +48,19 @@ module.exports = __toCommonJS(index_exports);
|
|
|
46
48
|
var import_as_zip = require("@noy-db/as-zip");
|
|
47
49
|
var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
|
|
48
50
|
var ENCODER = new TextEncoder();
|
|
49
|
-
|
|
51
|
+
function formula(f, cachedValue) {
|
|
52
|
+
return cachedValue === void 0 ? { __xlsxFormula: f } : { __xlsxFormula: f, v: cachedValue };
|
|
53
|
+
}
|
|
54
|
+
function isFormulaCell(v) {
|
|
55
|
+
return typeof v === "object" && v !== null && typeof v.__xlsxFormula === "string";
|
|
56
|
+
}
|
|
57
|
+
function styled(value, numberFormat) {
|
|
58
|
+
return { __xlsxStyle: numberFormat, v: value };
|
|
59
|
+
}
|
|
60
|
+
function isStyledCell(v) {
|
|
61
|
+
return typeof v === "object" && v !== null && typeof v.__xlsxStyle === "string";
|
|
62
|
+
}
|
|
63
|
+
async function writeXlsx(sheets, options = {}) {
|
|
50
64
|
if (sheets.length === 0) {
|
|
51
65
|
throw new Error("writeXlsx: at least one sheet is required");
|
|
52
66
|
}
|
|
@@ -71,6 +85,16 @@ async function writeXlsx(sheets) {
|
|
|
71
85
|
stringIndex.set(s, idx);
|
|
72
86
|
return idx;
|
|
73
87
|
};
|
|
88
|
+
const numFmtCodes = [];
|
|
89
|
+
const styleIndexByCode = /* @__PURE__ */ new Map();
|
|
90
|
+
const internStyle = (code) => {
|
|
91
|
+
const existing = styleIndexByCode.get(code);
|
|
92
|
+
if (existing !== void 0) return existing;
|
|
93
|
+
const idx = numFmtCodes.length + 1;
|
|
94
|
+
numFmtCodes.push(code);
|
|
95
|
+
styleIndexByCode.set(code, idx);
|
|
96
|
+
return idx;
|
|
97
|
+
};
|
|
74
98
|
const sheetXmls = safeSheets.map((sheet) => {
|
|
75
99
|
const lines = [
|
|
76
100
|
XML_HEADER,
|
|
@@ -100,12 +124,42 @@ async function writeXlsx(sheets) {
|
|
|
100
124
|
}
|
|
101
125
|
for (const row of sheet.rows) {
|
|
102
126
|
rowNum++;
|
|
103
|
-
const cells = row.map((value, i) => cellXml(value, i + 1, rowNum, internString)).join("");
|
|
127
|
+
const cells = row.map((value, i) => cellXml(value, i + 1, rowNum, internString, internStyle)).join("");
|
|
104
128
|
lines.push(`<row r="${rowNum}">${cells}</row>`);
|
|
105
129
|
}
|
|
106
|
-
lines.push("</sheetData>"
|
|
130
|
+
lines.push("</sheetData>");
|
|
131
|
+
if (sheet.validations && sheet.validations.length > 0) {
|
|
132
|
+
lines.push(`<dataValidations count="${sheet.validations.length}">`);
|
|
133
|
+
for (const dv of sheet.validations) {
|
|
134
|
+
const f1 = dv.formula1 ?? `"${(dv.values ?? []).join(",")}"`;
|
|
135
|
+
lines.push(
|
|
136
|
+
`<dataValidation type="list" allowBlank="1" showInputMessage="1" showErrorMessage="1" sqref="${escapeXmlAttr(dv.sqref)}"><formula1>${escapeXmlText(f1)}</formula1></dataValidation>`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
lines.push("</dataValidations>");
|
|
140
|
+
}
|
|
141
|
+
lines.push("</worksheet>");
|
|
107
142
|
return lines.join("");
|
|
108
143
|
});
|
|
144
|
+
const hasStyles = numFmtCodes.length > 0;
|
|
145
|
+
const stylesXml = hasStyles ? [
|
|
146
|
+
XML_HEADER,
|
|
147
|
+
'<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">',
|
|
148
|
+
`<numFmts count="${numFmtCodes.length}">`,
|
|
149
|
+
...numFmtCodes.map((code, i) => `<numFmt numFmtId="${164 + i}" formatCode="${escapeXmlAttr(code)}"/>`),
|
|
150
|
+
"</numFmts>",
|
|
151
|
+
'<fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts>',
|
|
152
|
+
'<fills count="1"><fill><patternFill patternType="none"/></fill></fills>',
|
|
153
|
+
'<borders count="1"><border/></borders>',
|
|
154
|
+
'<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>',
|
|
155
|
+
`<cellXfs count="${numFmtCodes.length + 1}">`,
|
|
156
|
+
'<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>',
|
|
157
|
+
...numFmtCodes.map(
|
|
158
|
+
(_, i) => `<xf numFmtId="${164 + i}" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>`
|
|
159
|
+
),
|
|
160
|
+
"</cellXfs>",
|
|
161
|
+
"</styleSheet>"
|
|
162
|
+
].join("") : "";
|
|
109
163
|
const sheetEntries = safeSheets.map((s, i) => ({
|
|
110
164
|
index: i + 1,
|
|
111
165
|
id: `rId${i + 1}`,
|
|
@@ -122,6 +176,7 @@ async function writeXlsx(sheets) {
|
|
|
122
176
|
(s) => `<Override PartName="/${s.path}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`
|
|
123
177
|
),
|
|
124
178
|
'<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>',
|
|
179
|
+
...hasStyles ? ['<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>'] : [],
|
|
125
180
|
"</Types>"
|
|
126
181
|
].join("");
|
|
127
182
|
const rootRels = XML_HEADER + '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>';
|
|
@@ -133,6 +188,13 @@ async function writeXlsx(sheets) {
|
|
|
133
188
|
(s) => `<sheet name="${escapeXmlAttr(s.name)}" sheetId="${s.index}" r:id="${s.id}"/>`
|
|
134
189
|
),
|
|
135
190
|
"</sheets>",
|
|
191
|
+
...options.definedNames && options.definedNames.length > 0 ? [
|
|
192
|
+
"<definedNames>",
|
|
193
|
+
...options.definedNames.map(
|
|
194
|
+
(d) => `<definedName name="${escapeXmlAttr(d.name)}">${escapeXmlText(d.ref)}</definedName>`
|
|
195
|
+
),
|
|
196
|
+
"</definedNames>"
|
|
197
|
+
] : [],
|
|
136
198
|
"</workbook>"
|
|
137
199
|
].join("");
|
|
138
200
|
const workbookRels = [
|
|
@@ -142,6 +204,7 @@ async function writeXlsx(sheets) {
|
|
|
142
204
|
(s) => `<Relationship Id="${s.id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${s.index}.xml"/>`
|
|
143
205
|
),
|
|
144
206
|
`<Relationship Id="rIdSharedStrings" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`,
|
|
207
|
+
...hasStyles ? [`<Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`] : [],
|
|
145
208
|
"</Relationships>"
|
|
146
209
|
].join("");
|
|
147
210
|
const sharedStringsXml = [
|
|
@@ -156,12 +219,28 @@ async function writeXlsx(sheets) {
|
|
|
156
219
|
{ path: "xl/workbook.xml", bytes: ENCODER.encode(workbookXml) },
|
|
157
220
|
{ path: "xl/_rels/workbook.xml.rels", bytes: ENCODER.encode(workbookRels) },
|
|
158
221
|
{ path: "xl/sharedStrings.xml", bytes: ENCODER.encode(sharedStringsXml) },
|
|
222
|
+
...hasStyles ? [{ path: "xl/styles.xml", bytes: ENCODER.encode(stylesXml) }] : [],
|
|
159
223
|
...sheetEntries.map((s, i) => ({ path: s.path, bytes: ENCODER.encode(sheetXmls[i] ?? "") }))
|
|
160
224
|
];
|
|
161
225
|
return await (0, import_as_zip.writeZip)(entries);
|
|
162
226
|
}
|
|
163
|
-
function cellXml(value, colIdx, rowNum, intern) {
|
|
227
|
+
function cellXml(value, colIdx, rowNum, intern, internStyle) {
|
|
164
228
|
const ref = `${colLetter(colIdx)}${rowNum}`;
|
|
229
|
+
if (isStyledCell(value)) {
|
|
230
|
+
const s2 = internStyle(value.__xlsxStyle);
|
|
231
|
+
const v = value.v;
|
|
232
|
+
if (v === null || v === void 0 || v === "") return `<c r="${ref}" s="${s2}"/>`;
|
|
233
|
+
if (typeof v === "number" && Number.isFinite(v)) return `<c r="${ref}" s="${s2}"><v>${v}</v></c>`;
|
|
234
|
+
if (typeof v === "boolean") return `<c r="${ref}" s="${s2}" t="b"><v>${v ? 1 : 0}</v></c>`;
|
|
235
|
+
return `<c r="${ref}" s="${s2}" t="s"><v>${intern(typeof v === "string" ? v : String(v))}</v></c>`;
|
|
236
|
+
}
|
|
237
|
+
if (isFormulaCell(value)) {
|
|
238
|
+
const f = escapeXmlText(value.__xlsxFormula);
|
|
239
|
+
if (value.v === void 0) return `<c r="${ref}"><f>${f}</f></c>`;
|
|
240
|
+
if (typeof value.v === "number" && Number.isFinite(value.v)) return `<c r="${ref}"><f>${f}</f><v>${value.v}</v></c>`;
|
|
241
|
+
if (typeof value.v === "boolean") return `<c r="${ref}" t="b"><f>${f}</f><v>${value.v ? 1 : 0}</v></c>`;
|
|
242
|
+
return `<c r="${ref}" t="str"><f>${f}</f><v>${escapeXmlText(String(value.v))}</v></c>`;
|
|
243
|
+
}
|
|
165
244
|
if (value === null || value === void 0 || value === "") return `<c r="${ref}"/>`;
|
|
166
245
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
167
246
|
return `<c r="${ref}"><v>${value}</v></c>`;
|
|
@@ -398,6 +477,10 @@ async function toBytes(vault, options) {
|
|
|
398
477
|
if (options.sheets.length === 0) {
|
|
399
478
|
throw new Error("as-xlsx: at least one sheet is required");
|
|
400
479
|
}
|
|
480
|
+
if (options.smart) {
|
|
481
|
+
const { sheets, definedNames } = await buildSmartSheets(vault, options);
|
|
482
|
+
return writeXlsx(sheets, { definedNames });
|
|
483
|
+
}
|
|
401
484
|
const materialisedSheets = [];
|
|
402
485
|
for (const sheetOpt of options.sheets) {
|
|
403
486
|
const collection = vault.collection(sheetOpt.collection);
|
|
@@ -441,6 +524,229 @@ async function write(vault, path, options) {
|
|
|
441
524
|
const { writeFile } = await import("fs/promises");
|
|
442
525
|
await writeFile(path, bytes);
|
|
443
526
|
}
|
|
527
|
+
function safeStringify(v) {
|
|
528
|
+
if (typeof v === "string") return v;
|
|
529
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
|
|
530
|
+
if (v == null) return "";
|
|
531
|
+
try {
|
|
532
|
+
return JSON.stringify(v) ?? "";
|
|
533
|
+
} catch {
|
|
534
|
+
return "";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function asCached(v) {
|
|
538
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "string") return v;
|
|
539
|
+
return safeStringify(v);
|
|
540
|
+
}
|
|
541
|
+
async function buildSmartSheets(vault, options) {
|
|
542
|
+
const snapshot = await vault.dumpSchema();
|
|
543
|
+
const sheetNameByCollection = /* @__PURE__ */ new Map();
|
|
544
|
+
for (const s of options.sheets) sheetNameByCollection.set(s.collection, s.name);
|
|
545
|
+
const mats = [];
|
|
546
|
+
const localeSet = /* @__PURE__ */ new Set();
|
|
547
|
+
for (const sheetOpt of options.sheets) {
|
|
548
|
+
const collection = vault.collection(sheetOpt.collection);
|
|
549
|
+
const list = await collection.list();
|
|
550
|
+
const records = [];
|
|
551
|
+
for (const item of list) {
|
|
552
|
+
const r = item;
|
|
553
|
+
if (sheetOpt.filter && !sheetOpt.filter(r)) continue;
|
|
554
|
+
records.push(r);
|
|
555
|
+
}
|
|
556
|
+
const base = sheetOpt.columns ?? inferColumns(records);
|
|
557
|
+
const cols = ["id", ...base.filter((c) => c !== "id")];
|
|
558
|
+
const labelCol = cols.find((c) => c !== "id");
|
|
559
|
+
const labelMap = /* @__PURE__ */ new Map();
|
|
560
|
+
if (labelCol) {
|
|
561
|
+
for (const r of records) if (r.id != null) labelMap.set(safeStringify(r.id), r[labelCol]);
|
|
562
|
+
}
|
|
563
|
+
const i18nFields = (sheetOpt.i18nFields ?? []).filter((f) => cols.includes(f));
|
|
564
|
+
for (const f of i18nFields) {
|
|
565
|
+
for (const r of records) {
|
|
566
|
+
const v = r[f];
|
|
567
|
+
if (v && typeof v === "object") for (const loc of Object.keys(v)) localeSet.add(loc);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
mats.push({ opt: sheetOpt, records, cols, labelMap, i18nFields });
|
|
571
|
+
}
|
|
572
|
+
const matByCollection = new Map(mats.map((m) => [m.opt.collection, m]));
|
|
573
|
+
const dictEntries = /* @__PURE__ */ new Map();
|
|
574
|
+
for (const m of mats) {
|
|
575
|
+
for (const [field, dictName] of Object.entries(m.opt.dictFields ?? {})) {
|
|
576
|
+
if (!m.cols.includes(field) || dictEntries.has(dictName)) continue;
|
|
577
|
+
dictEntries.set(dictName, await vault.dictionary(dictName).list());
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const entries of dictEntries.values()) {
|
|
581
|
+
for (const e of entries) for (const loc of Object.keys(e.labels)) localeSet.add(loc);
|
|
582
|
+
}
|
|
583
|
+
const locales = [...localeSet].sort();
|
|
584
|
+
const hasLang = locales.length > 0;
|
|
585
|
+
const defaultLocale = locales.includes("en") ? "en" : locales[0] ?? "en";
|
|
586
|
+
const ifChain = (refOf) => {
|
|
587
|
+
if (locales.length === 0) return '""';
|
|
588
|
+
let expr = refOf(locales[0]);
|
|
589
|
+
for (let k = locales.length - 1; k >= 0; k--) {
|
|
590
|
+
expr = `IF(LANG="${locales[k]}",${refOf(locales[k])},${expr})`;
|
|
591
|
+
}
|
|
592
|
+
return expr;
|
|
593
|
+
};
|
|
594
|
+
const sheetMeta = /* @__PURE__ */ new Map();
|
|
595
|
+
const dataSheets = mats.map((m) => {
|
|
596
|
+
const refs = snapshot.collections[m.opt.collection]?.refs ?? {};
|
|
597
|
+
const fields = snapshot.collections[m.opt.collection]?.fields ?? {};
|
|
598
|
+
const i18nSet = new Set(m.i18nFields);
|
|
599
|
+
const refFields = Object.keys(refs).filter((f) => m.cols.includes(f) && matByCollection.has(refs[f].target));
|
|
600
|
+
const baseCols = m.cols.filter((c) => !i18nSet.has(c));
|
|
601
|
+
const i18nFlat = m.i18nFields.flatMap((f) => [f, ...locales.map((l) => `${f}__${l}`)]);
|
|
602
|
+
const dictPairs = Object.entries(m.opt.dictFields ?? {}).filter(([f]) => m.cols.includes(f));
|
|
603
|
+
const header = [
|
|
604
|
+
...baseCols,
|
|
605
|
+
...i18nFlat,
|
|
606
|
+
...dictPairs.map(([f]) => `${f}__label`),
|
|
607
|
+
...refFields.map((f) => `${f}__label`)
|
|
608
|
+
];
|
|
609
|
+
const colIndex = /* @__PURE__ */ new Map();
|
|
610
|
+
header.forEach((h, i) => colIndex.set(h, i + 1));
|
|
611
|
+
sheetMeta.set(m.opt.collection, { name: m.opt.name, colIndex });
|
|
612
|
+
const lastRow = Math.max(m.records.length + 1, 2);
|
|
613
|
+
const validations = [];
|
|
614
|
+
for (const field of baseCols) {
|
|
615
|
+
const colL = colLetter(colIndex.get(field));
|
|
616
|
+
const sqref = `${colL}2:${colL}${lastRow}`;
|
|
617
|
+
const explicit = m.opt.dropdowns?.[field];
|
|
618
|
+
if (explicit && explicit.length > 0) {
|
|
619
|
+
validations.push({ sqref, values: explicit });
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
const rf = refs[field];
|
|
623
|
+
if (rf && matByCollection.has(rf.target)) {
|
|
624
|
+
const targetSheet = sheetNameByCollection.get(rf.target);
|
|
625
|
+
const targetRows = Math.max(matByCollection.get(rf.target).records.length + 1, 2);
|
|
626
|
+
validations.push({ sqref, formula1: `'${targetSheet}'!$A$2:$A$${targetRows}` });
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
const enumVals = fields[field]?.constraints?.["values"];
|
|
630
|
+
if (Array.isArray(enumVals) && enumVals.length > 0) validations.push({ sqref, values: enumVals.map((v) => safeStringify(v)) });
|
|
631
|
+
}
|
|
632
|
+
const rows = m.records.map((r, i) => {
|
|
633
|
+
const rowNum = i + 2;
|
|
634
|
+
const baseCells = baseCols.map((c) => {
|
|
635
|
+
const raw = c === "id" ? r.id ?? null : r[c] ?? null;
|
|
636
|
+
const fmt = m.opt.numberFormats?.[c];
|
|
637
|
+
if (fmt === void 0) return raw;
|
|
638
|
+
const num = typeof raw === "string" && raw.trim() !== "" && Number.isFinite(Number(raw)) ? Number(raw) : raw;
|
|
639
|
+
return styled(num, fmt);
|
|
640
|
+
});
|
|
641
|
+
const i18nCells = m.i18nFields.flatMap((f) => {
|
|
642
|
+
const rawMap = r[f] && typeof r[f] === "object" ? r[f] : {};
|
|
643
|
+
const display = formula(
|
|
644
|
+
ifChain((l) => `${colLetter(colIndex.get(`${f}__${l}`))}${rowNum}`),
|
|
645
|
+
asCached(rawMap[defaultLocale] ?? "")
|
|
646
|
+
);
|
|
647
|
+
return [display, ...locales.map((l) => rawMap[l] ?? null)];
|
|
648
|
+
});
|
|
649
|
+
const dictCells = dictPairs.map(([f, dn]) => {
|
|
650
|
+
const codeRef = `${colLetter(colIndex.get(f))}${rowNum}`;
|
|
651
|
+
const lk = `_Lookups_${dn}`;
|
|
652
|
+
const f1 = `IFERROR(VLOOKUP(${codeRef},'${lk}'!$A:$ZZ,MATCH(LANG,'${lk}'!$1:$1,0),FALSE),${codeRef})`;
|
|
653
|
+
const code = r[f];
|
|
654
|
+
const entry = code == null ? void 0 : dictEntries.get(dn)?.find((e) => e.key === safeStringify(code));
|
|
655
|
+
return formula(f1, asCached(entry?.labels[defaultLocale] ?? (code ?? "")));
|
|
656
|
+
});
|
|
657
|
+
const refCells = refFields.map((f) => {
|
|
658
|
+
const target = refs[f].target;
|
|
659
|
+
const targetSheet = sheetNameByCollection.get(target);
|
|
660
|
+
const targetMat = matByCollection.get(target);
|
|
661
|
+
const codeRef = `${colLetter(colIndex.get(f))}${rowNum}`;
|
|
662
|
+
const f1 = `IFERROR(VLOOKUP(${codeRef},'${targetSheet}'!$A:$ZZ,2,FALSE),"")`;
|
|
663
|
+
const code = r[f];
|
|
664
|
+
const cached = code == null ? "" : asCached(targetMat.labelMap.get(safeStringify(code)));
|
|
665
|
+
return formula(f1, cached);
|
|
666
|
+
});
|
|
667
|
+
return [...baseCells, ...i18nCells, ...dictCells, ...refCells];
|
|
668
|
+
});
|
|
669
|
+
return {
|
|
670
|
+
name: m.opt.name,
|
|
671
|
+
header,
|
|
672
|
+
rows,
|
|
673
|
+
...validations.length > 0 ? { validations } : {},
|
|
674
|
+
...m.opt.widths !== void 0 ? { widths: m.opt.widths } : {}
|
|
675
|
+
};
|
|
676
|
+
});
|
|
677
|
+
const manifest = {
|
|
678
|
+
name: "_manifest",
|
|
679
|
+
header: ["Collection", "Records", "Refs"],
|
|
680
|
+
rows: mats.map((m) => {
|
|
681
|
+
const refs = snapshot.collections[m.opt.collection]?.refs ?? {};
|
|
682
|
+
return [m.opt.name, m.records.length, Object.entries(refs).map(([f, r]) => `${f}\u2192${r.target}`).join(", ")];
|
|
683
|
+
})
|
|
684
|
+
};
|
|
685
|
+
const summarySheets = [];
|
|
686
|
+
for (const spec of options.summaries ?? []) {
|
|
687
|
+
const src = sheetMeta.get(spec.from);
|
|
688
|
+
const srcMat = matByCollection.get(spec.from);
|
|
689
|
+
const gCol = src?.colIndex.get(spec.groupBy);
|
|
690
|
+
if (!src || !srcMat || gCol === void 0) continue;
|
|
691
|
+
const gLetter = colLetter(gCol);
|
|
692
|
+
const seen = /* @__PURE__ */ new Set();
|
|
693
|
+
const groups = [];
|
|
694
|
+
for (const r of srcMat.records) {
|
|
695
|
+
const k = safeStringify(r[spec.groupBy]);
|
|
696
|
+
if (!seen.has(k)) {
|
|
697
|
+
seen.add(k);
|
|
698
|
+
groups.push(r[spec.groupBy]);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const header = [spec.groupBy, ...spec.aggregates.map((a) => a.label)];
|
|
702
|
+
const rows = groups.map((g, i) => {
|
|
703
|
+
const rowNum = i + 2;
|
|
704
|
+
const groupRecs = srcMat.records.filter((r) => safeStringify(r[spec.groupBy]) === safeStringify(g));
|
|
705
|
+
const cells = [g];
|
|
706
|
+
for (const a of spec.aggregates) {
|
|
707
|
+
if (a.op === "count") {
|
|
708
|
+
cells.push(formula(`COUNTIFS('${src.name}'!$${gLetter}:$${gLetter},$A${rowNum})`, groupRecs.length));
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const vIdx = a.field ? src.colIndex.get(a.field) : void 0;
|
|
712
|
+
if (vIdx === void 0) {
|
|
713
|
+
cells.push(null);
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const vLetter = colLetter(vIdx);
|
|
717
|
+
const nums = groupRecs.map((r) => Number(r[a.field])).filter((n) => Number.isFinite(n));
|
|
718
|
+
const sum = nums.reduce((s, n) => s + n, 0);
|
|
719
|
+
const cached = a.op === "sum" ? sum : nums.length ? sum / nums.length : 0;
|
|
720
|
+
const fn = a.op === "sum" ? "SUMIFS" : "AVERAGEIFS";
|
|
721
|
+
cells.push(
|
|
722
|
+
formula(`${fn}('${src.name}'!$${vLetter}:$${vLetter},'${src.name}'!$${gLetter}:$${gLetter},$A${rowNum})`, cached)
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
return cells;
|
|
726
|
+
});
|
|
727
|
+
summarySheets.push({ name: spec.name, header, rows });
|
|
728
|
+
}
|
|
729
|
+
const lookupSheets = [...dictEntries.entries()].map(([dn, entries]) => ({
|
|
730
|
+
name: `_Lookups_${dn}`,
|
|
731
|
+
header: ["Code", ...locales],
|
|
732
|
+
rows: entries.map((e) => [e.key, ...locales.map((l) => e.labels[l] ?? "")])
|
|
733
|
+
}));
|
|
734
|
+
const settingsSheets = [];
|
|
735
|
+
const definedNames = [];
|
|
736
|
+
if (hasLang) {
|
|
737
|
+
settingsSheets.push({
|
|
738
|
+
name: "_settings",
|
|
739
|
+
header: ["Setting", "Value"],
|
|
740
|
+
rows: [["Language", defaultLocale]],
|
|
741
|
+
validations: [{ sqref: "B2:B2", values: locales }]
|
|
742
|
+
});
|
|
743
|
+
definedNames.push({ name: "LANG", ref: `'_settings'!$B$2` });
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
sheets: [...settingsSheets, ...lookupSheets, manifest, ...summarySheets, ...dataSheets],
|
|
747
|
+
definedNames
|
|
748
|
+
};
|
|
749
|
+
}
|
|
444
750
|
function inferColumns(records) {
|
|
445
751
|
const seen = /* @__PURE__ */ new Set();
|
|
446
752
|
const out = [];
|
|
@@ -627,8 +933,10 @@ function excelSerialToMs(serial) {
|
|
|
627
933
|
XlsxDictAmbiguityError,
|
|
628
934
|
colLetter,
|
|
629
935
|
download,
|
|
936
|
+
formula,
|
|
630
937
|
fromBytes,
|
|
631
938
|
readXlsx,
|
|
939
|
+
styled,
|
|
632
940
|
toBytes,
|
|
633
941
|
toBytesFromCollection,
|
|
634
942
|
write,
|