dsh-plugin-daily-report 1.0.0 → 1.0.2

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/lib/xlsx.js CHANGED
@@ -1,416 +1,416 @@
1
- /**
2
- * Minimal, dependency-free XLSX writer.
3
- *
4
- * It emits the subset of SpreadsheetML needed by the daily-report workbook:
5
- * multiple worksheets, inline strings, numbers, frozen header rows, auto
6
- * filters, column widths, merged cells and a small fixed style palette.
7
- */
8
-
9
- import { deflateRawSync } from "node:zlib";
10
-
11
- const encoder = new TextEncoder();
12
-
13
- const STYLE_DEFAULT = 0;
14
- const STYLE_HEADER = 1;
15
- const STYLE_BODY = 2;
16
- const STYLE_CENTER = 3;
17
- const STYLE_BOLD = 4;
18
- const STYLE_WEEKEND = 5;
19
- const STYLE_SECTION = 6;
20
- const STYLE_NOTE = 7;
21
- const STYLE_TITLE = 8;
22
- const STYLE_CENTER_BOLD = 9;
23
- const STYLE_TOTAL = 10;
24
- const STYLE_TOTAL_CENTER = 11;
25
-
26
- export const styles = {
27
- default: STYLE_DEFAULT,
28
- header: STYLE_HEADER,
29
- body: STYLE_BODY,
30
- center: STYLE_CENTER,
31
- bold: STYLE_BOLD,
32
- weekend: STYLE_WEEKEND,
33
- section: STYLE_SECTION,
34
- note: STYLE_NOTE,
35
- title: STYLE_TITLE,
36
- centerBold: STYLE_CENTER_BOLD,
37
- total: STYLE_TOTAL,
38
- totalCenter: STYLE_TOTAL_CENTER,
39
- };
40
-
41
- function escapeXml(value) {
42
- return String(value)
43
- .replace(/&/g, "&")
44
- .replace(/</g, "&lt;")
45
- .replace(/>/g, "&gt;")
46
- .replace(/"/g, "&quot;")
47
- .replace(/'/g, "&apos;")
48
- .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, "");
49
- }
50
-
51
- function columnName(index) {
52
- let value = index + 1;
53
- let name = "";
54
- while (value > 0) {
55
- const remainder = (value - 1) % 26;
56
- name = String.fromCharCode(65 + remainder) + name;
57
- value = Math.floor((value - 1) / 26);
58
- }
59
- return name;
60
- }
61
-
62
- function sanitizeSheetName(name, used) {
63
- let base = String(name || "Sheet")
64
- .replace(/[\[\]:*?/\\]/g, " ")
65
- .replace(/\s+/g, " ")
66
- .trim()
67
- .slice(0, 31) || "Sheet";
68
- let candidate = base;
69
- let suffix = 2;
70
- while (used.has(candidate)) {
71
- const tail = " (" + suffix + ")";
72
- candidate = base.slice(0, 31 - tail.length) + tail;
73
- suffix += 1;
74
- }
75
- used.add(candidate);
76
- return candidate;
77
- }
78
-
79
- let crcTable = null;
80
- function crc32(buffer) {
81
- if (crcTable === null) {
82
- crcTable = new Uint32Array(256);
83
- for (let n = 0; n < 256; n += 1) {
84
- let c = n;
85
- for (let k = 0; k < 8; k += 1) {
86
- c = (c & 1) !== 0 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
87
- }
88
- crcTable[n] = c >>> 0;
89
- }
90
- }
91
- let crc = 0xffffffff;
92
- for (let i = 0; i < buffer.length; i += 1) {
93
- crc = crcTable[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8);
94
- }
95
- return (crc ^ 0xffffffff) >>> 0;
96
- }
97
-
98
- function dosDateTime(date) {
99
- const year = Math.max(1980, date.getFullYear());
100
- const dosTime = (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2);
101
- const dosDate = ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
102
- return { dosTime, dosDate };
103
- }
104
-
105
- function zip(files) {
106
- const now = new Date();
107
- const { dosTime, dosDate } = dosDateTime(now);
108
- const localParts = [];
109
- const centralParts = [];
110
- let offset = 0;
111
-
112
- for (const file of files) {
113
- const nameBytes = encoder.encode(file.name);
114
- const raw = Buffer.isBuffer(file.data) ? file.data : Buffer.from(String(file.data), "utf8");
115
- const compressed = deflateRawSync(raw, { level: 9 });
116
- const crc = crc32(raw);
117
-
118
- const local = Buffer.alloc(30);
119
- local.writeUInt32LE(0x04034b50, 0);
120
- local.writeUInt16LE(20, 4);
121
- local.writeUInt16LE(0x0800, 6);
122
- local.writeUInt16LE(8, 8);
123
- local.writeUInt16LE(dosTime, 10);
124
- local.writeUInt16LE(dosDate, 12);
125
- local.writeUInt32LE(crc, 14);
126
- local.writeUInt32LE(compressed.length, 18);
127
- local.writeUInt32LE(raw.length, 22);
128
- local.writeUInt16LE(nameBytes.length, 26);
129
- local.writeUInt16LE(0, 28);
130
- localParts.push(local, nameBytes, compressed);
131
-
132
- const central = Buffer.alloc(46);
133
- central.writeUInt32LE(0x02014b50, 0);
134
- central.writeUInt16LE(20, 4);
135
- central.writeUInt16LE(20, 6);
136
- central.writeUInt16LE(0x0800, 8);
137
- central.writeUInt16LE(8, 10);
138
- central.writeUInt16LE(dosTime, 12);
139
- central.writeUInt16LE(dosDate, 14);
140
- central.writeUInt32LE(crc, 16);
141
- central.writeUInt32LE(compressed.length, 20);
142
- central.writeUInt32LE(raw.length, 24);
143
- central.writeUInt16LE(nameBytes.length, 28);
144
- central.writeUInt16LE(0, 30);
145
- central.writeUInt16LE(0, 32);
146
- central.writeUInt16LE(0, 34);
147
- central.writeUInt16LE(0, 36);
148
- central.writeUInt32LE(0, 38);
149
- central.writeUInt32LE(offset, 42);
150
- centralParts.push(central, nameBytes);
151
-
152
- offset += local.length + nameBytes.length + compressed.length;
153
- }
154
-
155
- const centralSize = centralParts.reduce((size, part) => size + part.length, 0);
156
- const end = Buffer.alloc(22);
157
- end.writeUInt32LE(0x06054b50, 0);
158
- end.writeUInt16LE(0, 4);
159
- end.writeUInt16LE(0, 6);
160
- end.writeUInt16LE(files.length, 8);
161
- end.writeUInt16LE(files.length, 10);
162
- end.writeUInt32LE(centralSize, 12);
163
- end.writeUInt32LE(offset, 16);
164
- end.writeUInt16LE(0, 20);
165
-
166
- return Buffer.concat([...localParts, ...centralParts, end]);
167
- }
168
-
169
- function cellXml(ref, value, style) {
170
- const s = Number.isInteger(style) ? style : STYLE_DEFAULT;
171
- if (value === null || value === undefined || value === "") {
172
- return s === STYLE_DEFAULT ? "" : '<c r="' + ref + '" s="' + s + '"/>';
173
- }
174
- if (typeof value === "number" && Number.isFinite(value)) {
175
- return '<c r="' + ref + '" s="' + s + '"><v>' + value + "</v></c>";
176
- }
177
- if (typeof value === "boolean") {
178
- return '<c r="' + ref + '" s="' + s + '" t="b"><v>' + (value ? 1 : 0) + "</v></c>";
179
- }
180
- const text = String(value).slice(0, 32767);
181
- return '<c r="' + ref + '" s="' + s + '" t="inlineStr"><is><t xml:space="preserve">' + escapeXml(text) + "</t></is></c>";
182
- }
183
-
184
- function sheetXml(sheet, index) {
185
- const rows = Array.isArray(sheet.rows) ? sheet.rows : [];
186
- const widths = Array.isArray(sheet.widths) ? sheet.widths : [];
187
- const rowStyles = sheet.rowStyles || {};
188
- const headerRows = Number.isInteger(sheet.headerRows) ? sheet.headerRows : 1;
189
- const maxCols = rows.reduce((max, row) => Math.max(max, Array.isArray(row) ? row.length : 0), 1);
190
- const lastCell = columnName(Math.max(0, maxCols - 1)) + Math.max(1, rows.length);
191
-
192
- const parts = [];
193
- parts.push('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
194
- parts.push('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
195
- parts.push('<dimension ref="A1:' + lastCell + '"/>');
196
- parts.push('<sheetViews><sheetView' + (index === 0 ? ' tabSelected="1"' : "") + ' workbookViewId="0">');
197
- const freezeColumns = Number.isInteger(sheet.freezeColumns) ? Math.max(0, sheet.freezeColumns) : 0;
198
- if (sheet.freeze !== false && (headerRows > 0 || freezeColumns > 0)) {
199
- const paneAttrs = [];
200
- if (freezeColumns > 0) paneAttrs.push('xSplit="' + freezeColumns + '"');
201
- if (headerRows > 0) paneAttrs.push('ySplit="' + headerRows + '"');
202
- paneAttrs.push('topLeftCell="' + columnName(freezeColumns) + (headerRows + 1) + '"');
203
- paneAttrs.push('activePane="' + (freezeColumns > 0 ? (headerRows > 0 ? "bottomRight" : "topRight") : "bottomLeft") + '"');
204
- paneAttrs.push('state="frozen"');
205
- parts.push("<pane " + paneAttrs.join(" ") + "/>");
206
- }
207
- parts.push("</sheetView></sheetViews>");
208
- parts.push('<sheetFormatPr defaultRowHeight="18"/>');
209
-
210
- if (widths.length > 0) {
211
- parts.push("<cols>");
212
- for (let c = 0; c < widths.length; c += 1) {
213
- const width = Number(widths[c]);
214
- if (Number.isFinite(width) && width > 0) {
215
- parts.push('<col min="' + (c + 1) + '" max="' + (c + 1) + '" width="' + width + '" customWidth="1"/>');
216
- }
217
- }
218
- parts.push("</cols>");
219
- }
220
-
221
- parts.push("<sheetData>");
222
- for (let r = 0; r < rows.length; r += 1) {
223
- const row = Array.isArray(rows[r]) ? rows[r] : [];
224
- let baseStyle = STYLE_BODY;
225
- if (r < headerRows) baseStyle = STYLE_HEADER;
226
- if (Object.prototype.hasOwnProperty.call(rowStyles, r)) {
227
- baseStyle = rowStyles[r];
228
- }
229
- const height = r < headerRows ? ' ht="24" customHeight="1"' : "";
230
- parts.push('<row r="' + (r + 1) + '"' + height + ">");
231
- for (let c = 0; c < row.length; c += 1) {
232
- const raw = row[c];
233
- let value = raw;
234
- let style = baseStyle;
235
- if (raw && typeof raw === "object" && !Array.isArray(raw) && Object.prototype.hasOwnProperty.call(raw, "value")) {
236
- value = raw.value;
237
- if (Number.isInteger(raw.style)) style = raw.style;
238
- }
239
- parts.push(cellXml(columnName(c) + (r + 1), value, style));
240
- }
241
- parts.push("</row>");
242
- }
243
- parts.push("</sheetData>");
244
-
245
- if (sheet.filter !== false && rows.length > 1 && maxCols > 0) {
246
- parts.push('<autoFilter ref="A1:' + columnName(maxCols - 1) + rows.length + '"/>');
247
- }
248
-
249
- if (Array.isArray(sheet.merges) && sheet.merges.length > 0) {
250
- parts.push('<mergeCells count="' + sheet.merges.length + '">');
251
- for (const ref of sheet.merges) {
252
- parts.push('<mergeCell ref="' + escapeXml(ref) + '"/>');
253
- }
254
- parts.push("</mergeCells>");
255
- }
256
-
257
- parts.push('<pageMargins left="0.4" right="0.4" top="0.5" bottom="0.5" header="0.2" footer="0.2"/>');
258
- parts.push("</worksheet>");
259
- return parts.join("");
260
- }
261
-
262
- function stylesXml() {
263
- const fonts = [
264
- '<font><sz val="10"/><name val="Microsoft YaHei"/><family val="2"/></font>',
265
- '<font><b/><sz val="10"/><color rgb="FFFFFFFF"/><name val="Microsoft YaHei"/><family val="2"/></font>',
266
- '<font><b/><sz val="10"/><name val="Microsoft YaHei"/><family val="2"/></font>',
267
- '<font><b/><sz val="14"/><name val="Microsoft YaHei"/><family val="2"/></font>',
268
- '<font><b/><sz val="11"/><name val="Microsoft YaHei"/><family val="2"/></font>',
269
- ];
270
- const fills = [
271
- '<fill><patternFill patternType="none"/></fill>',
272
- '<fill><patternFill patternType="gray125"/></fill>',
273
- '<fill><patternFill patternType="solid"><fgColor rgb="FF1F4E79"/><bgColor indexed="64"/></patternFill></fill>',
274
- '<fill><patternFill patternType="solid"><fgColor rgb="FFF2F2F2"/><bgColor indexed="64"/></patternFill></fill>',
275
- '<fill><patternFill patternType="solid"><fgColor rgb="FFDDEBF7"/><bgColor indexed="64"/></patternFill></fill>',
276
- '<fill><patternFill patternType="solid"><fgColor rgb="FFFFF2CC"/><bgColor indexed="64"/></patternFill></fill>',
277
- ];
278
- const border = '<border><left style="thin"><color rgb="FFBFBFBF"/></left><right style="thin"><color rgb="FFBFBFBF"/></right><top style="thin"><color rgb="FFBFBFBF"/></top><bottom style="thin"><color rgb="FFBFBFBF"/></bottom><diagonal/></border>';
279
- const xfs = [
280
- '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>',
281
- '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
282
- '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" xfId="0" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
283
- '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" xfId="0" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
284
- '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" xfId="0" applyFont="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
285
- '<xf numFmtId="0" fontId="0" fillId="3" borderId="1" xfId="0" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
286
- '<xf numFmtId="0" fontId="4" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1"><alignment vertical="center"/></xf>',
287
- '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
288
- '<xf numFmtId="0" fontId="3" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1"><alignment vertical="center"/></xf>',
289
- '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" xfId="0" applyFont="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
290
- '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
291
- '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
292
- ];
293
- return [
294
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
295
- '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">',
296
- '<fonts count="' + fonts.length + '">' + fonts.join("") + "</fonts>",
297
- '<fills count="' + fills.length + '">' + fills.join("") + "</fills>",
298
- '<borders count="2"><border><left/><right/><top/><bottom/><diagonal/></border>' + border + "</borders>",
299
- '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>',
300
- '<cellXfs count="' + xfs.length + '">' + xfs.join("") + "</cellXfs>",
301
- '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>',
302
- '<dxfs count="0"/>',
303
- '<tableStyles count="0" defaultTableStyle="TableStyleMedium2" defaultPivotStyle="PivotStyleLight16"/>',
304
- "</styleSheet>",
305
- ].join("");
306
- }
307
-
308
- function workbookXml(sheets) {
309
- const parts = [
310
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
311
- '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">',
312
- '<bookViews><workbookView activeTab="0"/></bookViews>',
313
- '<sheets>',
314
- ];
315
- for (let i = 0; i < sheets.length; i += 1) {
316
- parts.push('<sheet name="' + escapeXml(sheets[i].name) + '" sheetId="' + (i + 1) + '" r:id="rId' + (i + 1) + '"/>');
317
- }
318
- parts.push("</sheets>", "</workbook>");
319
- return parts.join("");
320
- }
321
-
322
- function workbookRels(sheets) {
323
- const parts = [
324
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
325
- '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
326
- ];
327
- for (let i = 0; i < sheets.length; i += 1) {
328
- parts.push('<Relationship Id="rId' + (i + 1) + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet' + (i + 1) + '.xml"/>');
329
- }
330
- parts.push('<Relationship Id="rId' + (sheets.length + 1) + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>');
331
- parts.push("</Relationships>");
332
- return parts.join("");
333
- }
334
-
335
- function contentTypes(sheets) {
336
- const parts = [
337
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
338
- '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
339
- '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
340
- '<Default Extension="xml" ContentType="application/xml"/>',
341
- '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>',
342
- '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>',
343
- '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>',
344
- '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>',
345
- ];
346
- for (let i = 0; i < sheets.length; i += 1) {
347
- parts.push('<Override PartName="/xl/worksheets/sheet' + (i + 1) + '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>');
348
- }
349
- parts.push("</Types>");
350
- return parts.join("");
351
- }
352
-
353
- function packageRels() {
354
- return [
355
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
356
- '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
357
- '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>',
358
- '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>',
359
- '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>',
360
- "</Relationships>",
361
- ].join("");
362
- }
363
-
364
- function coreXml(title) {
365
- const now = new Date().toISOString();
366
- return [
367
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
368
- '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">',
369
- "<dc:title>" + escapeXml(title) + "</dc:title>",
370
- "<dc:creator>DSH daily-report plugin</dc:creator>",
371
- '<dcterms:created xsi:type="dcterms:W3CDTF">' + now + "</dcterms:created>",
372
- '<dcterms:modified xsi:type="dcterms:W3CDTF">' + now + "</dcterms:modified>",
373
- "</cp:coreProperties>",
374
- ].join("");
375
- }
376
-
377
- function appXml(sheets) {
378
- const parts = [
379
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
380
- '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">',
381
- "<Application>DSH</Application>",
382
- "<HeadingPairs><vt:vector size=\"2\" baseType=\"variant\"><vt:variant><vt:lpstr>Worksheets</vt:lpstr></vt:variant><vt:variant><vt:i4>" + sheets.length + "</vt:i4></vt:variant></vt:vector></HeadingPairs>",
383
- "<TitlesOfParts><vt:vector size=\"" + sheets.length + "\" baseType=\"lpstr\">",
384
- ];
385
- for (const sheet of sheets) {
386
- parts.push("<vt:lpstr>" + escapeXml(sheet.name) + "</vt:lpstr>");
387
- }
388
- parts.push("</vt:vector></TitlesOfParts>", "</Properties>");
389
- return parts.join("");
390
- }
391
-
392
- export function buildXlsx(sheetDefinitions, options = {}) {
393
- if (!Array.isArray(sheetDefinitions) || sheetDefinitions.length === 0) {
394
- throw new Error("buildXlsx requires at least one sheet");
395
- }
396
- const used = new Set();
397
- const sheets = sheetDefinitions.map((sheet) => ({
398
- ...sheet,
399
- name: sanitizeSheetName(sheet.name, used),
400
- rows: Array.isArray(sheet.rows) ? sheet.rows : [],
401
- }));
402
-
403
- const files = [
404
- { name: "[Content_Types].xml", data: contentTypes(sheets) },
405
- { name: "_rels/.rels", data: packageRels() },
406
- { name: "docProps/core.xml", data: coreXml(options.title || "Daily report") },
407
- { name: "docProps/app.xml", data: appXml(sheets) },
408
- { name: "xl/workbook.xml", data: workbookXml(sheets) },
409
- { name: "xl/_rels/workbook.xml.rels", data: workbookRels(sheets) },
410
- { name: "xl/styles.xml", data: stylesXml() },
411
- ];
412
- for (let i = 0; i < sheets.length; i += 1) {
413
- files.push({ name: "xl/worksheets/sheet" + (i + 1) + ".xml", data: sheetXml(sheets[i], i) });
414
- }
415
- return zip(files);
416
- }
1
+ /**
2
+ * Minimal, dependency-free XLSX writer.
3
+ *
4
+ * It emits the subset of SpreadsheetML needed by the daily-report workbook:
5
+ * multiple worksheets, inline strings, numbers, frozen header rows, auto
6
+ * filters, column widths, merged cells and a small fixed style palette.
7
+ */
8
+
9
+ import { deflateRawSync } from "node:zlib";
10
+
11
+ const encoder = new TextEncoder();
12
+
13
+ const STYLE_DEFAULT = 0;
14
+ const STYLE_HEADER = 1;
15
+ const STYLE_BODY = 2;
16
+ const STYLE_CENTER = 3;
17
+ const STYLE_BOLD = 4;
18
+ const STYLE_WEEKEND = 5;
19
+ const STYLE_SECTION = 6;
20
+ const STYLE_NOTE = 7;
21
+ const STYLE_TITLE = 8;
22
+ const STYLE_CENTER_BOLD = 9;
23
+ const STYLE_TOTAL = 10;
24
+ const STYLE_TOTAL_CENTER = 11;
25
+
26
+ export const styles = {
27
+ default: STYLE_DEFAULT,
28
+ header: STYLE_HEADER,
29
+ body: STYLE_BODY,
30
+ center: STYLE_CENTER,
31
+ bold: STYLE_BOLD,
32
+ weekend: STYLE_WEEKEND,
33
+ section: STYLE_SECTION,
34
+ note: STYLE_NOTE,
35
+ title: STYLE_TITLE,
36
+ centerBold: STYLE_CENTER_BOLD,
37
+ total: STYLE_TOTAL,
38
+ totalCenter: STYLE_TOTAL_CENTER,
39
+ };
40
+
41
+ function escapeXml(value) {
42
+ return String(value)
43
+ .replace(/&/g, "&amp;")
44
+ .replace(/</g, "&lt;")
45
+ .replace(/>/g, "&gt;")
46
+ .replace(/"/g, "&quot;")
47
+ .replace(/'/g, "&apos;")
48
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, "");
49
+ }
50
+
51
+ function columnName(index) {
52
+ let value = index + 1;
53
+ let name = "";
54
+ while (value > 0) {
55
+ const remainder = (value - 1) % 26;
56
+ name = String.fromCharCode(65 + remainder) + name;
57
+ value = Math.floor((value - 1) / 26);
58
+ }
59
+ return name;
60
+ }
61
+
62
+ function sanitizeSheetName(name, used) {
63
+ let base = String(name || "Sheet")
64
+ .replace(/[\[\]:*?/\\]/g, " ")
65
+ .replace(/\s+/g, " ")
66
+ .trim()
67
+ .slice(0, 31) || "Sheet";
68
+ let candidate = base;
69
+ let suffix = 2;
70
+ while (used.has(candidate)) {
71
+ const tail = " (" + suffix + ")";
72
+ candidate = base.slice(0, 31 - tail.length) + tail;
73
+ suffix += 1;
74
+ }
75
+ used.add(candidate);
76
+ return candidate;
77
+ }
78
+
79
+ let crcTable = null;
80
+ function crc32(buffer) {
81
+ if (crcTable === null) {
82
+ crcTable = new Uint32Array(256);
83
+ for (let n = 0; n < 256; n += 1) {
84
+ let c = n;
85
+ for (let k = 0; k < 8; k += 1) {
86
+ c = (c & 1) !== 0 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
87
+ }
88
+ crcTable[n] = c >>> 0;
89
+ }
90
+ }
91
+ let crc = 0xffffffff;
92
+ for (let i = 0; i < buffer.length; i += 1) {
93
+ crc = crcTable[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8);
94
+ }
95
+ return (crc ^ 0xffffffff) >>> 0;
96
+ }
97
+
98
+ function dosDateTime(date) {
99
+ const year = Math.max(1980, date.getFullYear());
100
+ const dosTime = (date.getHours() << 11) | (date.getMinutes() << 5) | Math.floor(date.getSeconds() / 2);
101
+ const dosDate = ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
102
+ return { dosTime, dosDate };
103
+ }
104
+
105
+ function zip(files) {
106
+ const now = new Date();
107
+ const { dosTime, dosDate } = dosDateTime(now);
108
+ const localParts = [];
109
+ const centralParts = [];
110
+ let offset = 0;
111
+
112
+ for (const file of files) {
113
+ const nameBytes = encoder.encode(file.name);
114
+ const raw = Buffer.isBuffer(file.data) ? file.data : Buffer.from(String(file.data), "utf8");
115
+ const compressed = deflateRawSync(raw, { level: 9 });
116
+ const crc = crc32(raw);
117
+
118
+ const local = Buffer.alloc(30);
119
+ local.writeUInt32LE(0x04034b50, 0);
120
+ local.writeUInt16LE(20, 4);
121
+ local.writeUInt16LE(0x0800, 6);
122
+ local.writeUInt16LE(8, 8);
123
+ local.writeUInt16LE(dosTime, 10);
124
+ local.writeUInt16LE(dosDate, 12);
125
+ local.writeUInt32LE(crc, 14);
126
+ local.writeUInt32LE(compressed.length, 18);
127
+ local.writeUInt32LE(raw.length, 22);
128
+ local.writeUInt16LE(nameBytes.length, 26);
129
+ local.writeUInt16LE(0, 28);
130
+ localParts.push(local, nameBytes, compressed);
131
+
132
+ const central = Buffer.alloc(46);
133
+ central.writeUInt32LE(0x02014b50, 0);
134
+ central.writeUInt16LE(20, 4);
135
+ central.writeUInt16LE(20, 6);
136
+ central.writeUInt16LE(0x0800, 8);
137
+ central.writeUInt16LE(8, 10);
138
+ central.writeUInt16LE(dosTime, 12);
139
+ central.writeUInt16LE(dosDate, 14);
140
+ central.writeUInt32LE(crc, 16);
141
+ central.writeUInt32LE(compressed.length, 20);
142
+ central.writeUInt32LE(raw.length, 24);
143
+ central.writeUInt16LE(nameBytes.length, 28);
144
+ central.writeUInt16LE(0, 30);
145
+ central.writeUInt16LE(0, 32);
146
+ central.writeUInt16LE(0, 34);
147
+ central.writeUInt16LE(0, 36);
148
+ central.writeUInt32LE(0, 38);
149
+ central.writeUInt32LE(offset, 42);
150
+ centralParts.push(central, nameBytes);
151
+
152
+ offset += local.length + nameBytes.length + compressed.length;
153
+ }
154
+
155
+ const centralSize = centralParts.reduce((size, part) => size + part.length, 0);
156
+ const end = Buffer.alloc(22);
157
+ end.writeUInt32LE(0x06054b50, 0);
158
+ end.writeUInt16LE(0, 4);
159
+ end.writeUInt16LE(0, 6);
160
+ end.writeUInt16LE(files.length, 8);
161
+ end.writeUInt16LE(files.length, 10);
162
+ end.writeUInt32LE(centralSize, 12);
163
+ end.writeUInt32LE(offset, 16);
164
+ end.writeUInt16LE(0, 20);
165
+
166
+ return Buffer.concat([...localParts, ...centralParts, end]);
167
+ }
168
+
169
+ function cellXml(ref, value, style) {
170
+ const s = Number.isInteger(style) ? style : STYLE_DEFAULT;
171
+ if (value === null || value === undefined || value === "") {
172
+ return s === STYLE_DEFAULT ? "" : '<c r="' + ref + '" s="' + s + '"/>';
173
+ }
174
+ if (typeof value === "number" && Number.isFinite(value)) {
175
+ return '<c r="' + ref + '" s="' + s + '"><v>' + value + "</v></c>";
176
+ }
177
+ if (typeof value === "boolean") {
178
+ return '<c r="' + ref + '" s="' + s + '" t="b"><v>' + (value ? 1 : 0) + "</v></c>";
179
+ }
180
+ const text = String(value).slice(0, 32767);
181
+ return '<c r="' + ref + '" s="' + s + '" t="inlineStr"><is><t xml:space="preserve">' + escapeXml(text) + "</t></is></c>";
182
+ }
183
+
184
+ function sheetXml(sheet, index) {
185
+ const rows = Array.isArray(sheet.rows) ? sheet.rows : [];
186
+ const widths = Array.isArray(sheet.widths) ? sheet.widths : [];
187
+ const rowStyles = sheet.rowStyles || {};
188
+ const headerRows = Number.isInteger(sheet.headerRows) ? sheet.headerRows : 1;
189
+ const maxCols = rows.reduce((max, row) => Math.max(max, Array.isArray(row) ? row.length : 0), 1);
190
+ const lastCell = columnName(Math.max(0, maxCols - 1)) + Math.max(1, rows.length);
191
+
192
+ const parts = [];
193
+ parts.push('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
194
+ parts.push('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
195
+ parts.push('<dimension ref="A1:' + lastCell + '"/>');
196
+ parts.push('<sheetViews><sheetView' + (index === 0 ? ' tabSelected="1"' : "") + ' workbookViewId="0">');
197
+ const freezeColumns = Number.isInteger(sheet.freezeColumns) ? Math.max(0, sheet.freezeColumns) : 0;
198
+ if (sheet.freeze !== false && (headerRows > 0 || freezeColumns > 0)) {
199
+ const paneAttrs = [];
200
+ if (freezeColumns > 0) paneAttrs.push('xSplit="' + freezeColumns + '"');
201
+ if (headerRows > 0) paneAttrs.push('ySplit="' + headerRows + '"');
202
+ paneAttrs.push('topLeftCell="' + columnName(freezeColumns) + (headerRows + 1) + '"');
203
+ paneAttrs.push('activePane="' + (freezeColumns > 0 ? (headerRows > 0 ? "bottomRight" : "topRight") : "bottomLeft") + '"');
204
+ paneAttrs.push('state="frozen"');
205
+ parts.push("<pane " + paneAttrs.join(" ") + "/>");
206
+ }
207
+ parts.push("</sheetView></sheetViews>");
208
+ parts.push('<sheetFormatPr defaultRowHeight="18"/>');
209
+
210
+ if (widths.length > 0) {
211
+ parts.push("<cols>");
212
+ for (let c = 0; c < widths.length; c += 1) {
213
+ const width = Number(widths[c]);
214
+ if (Number.isFinite(width) && width > 0) {
215
+ parts.push('<col min="' + (c + 1) + '" max="' + (c + 1) + '" width="' + width + '" customWidth="1"/>');
216
+ }
217
+ }
218
+ parts.push("</cols>");
219
+ }
220
+
221
+ parts.push("<sheetData>");
222
+ for (let r = 0; r < rows.length; r += 1) {
223
+ const row = Array.isArray(rows[r]) ? rows[r] : [];
224
+ let baseStyle = STYLE_BODY;
225
+ if (r < headerRows) baseStyle = STYLE_HEADER;
226
+ if (Object.prototype.hasOwnProperty.call(rowStyles, r)) {
227
+ baseStyle = rowStyles[r];
228
+ }
229
+ const height = r < headerRows ? ' ht="24" customHeight="1"' : "";
230
+ parts.push('<row r="' + (r + 1) + '"' + height + ">");
231
+ for (let c = 0; c < row.length; c += 1) {
232
+ const raw = row[c];
233
+ let value = raw;
234
+ let style = baseStyle;
235
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && Object.prototype.hasOwnProperty.call(raw, "value")) {
236
+ value = raw.value;
237
+ if (Number.isInteger(raw.style)) style = raw.style;
238
+ }
239
+ parts.push(cellXml(columnName(c) + (r + 1), value, style));
240
+ }
241
+ parts.push("</row>");
242
+ }
243
+ parts.push("</sheetData>");
244
+
245
+ if (sheet.filter !== false && rows.length > 1 && maxCols > 0) {
246
+ parts.push('<autoFilter ref="A1:' + columnName(maxCols - 1) + rows.length + '"/>');
247
+ }
248
+
249
+ if (Array.isArray(sheet.merges) && sheet.merges.length > 0) {
250
+ parts.push('<mergeCells count="' + sheet.merges.length + '">');
251
+ for (const ref of sheet.merges) {
252
+ parts.push('<mergeCell ref="' + escapeXml(ref) + '"/>');
253
+ }
254
+ parts.push("</mergeCells>");
255
+ }
256
+
257
+ parts.push('<pageMargins left="0.4" right="0.4" top="0.5" bottom="0.5" header="0.2" footer="0.2"/>');
258
+ parts.push("</worksheet>");
259
+ return parts.join("");
260
+ }
261
+
262
+ function stylesXml() {
263
+ const fonts = [
264
+ '<font><sz val="10"/><name val="Microsoft YaHei"/><family val="2"/></font>',
265
+ '<font><b/><sz val="10"/><color rgb="FFFFFFFF"/><name val="Microsoft YaHei"/><family val="2"/></font>',
266
+ '<font><b/><sz val="10"/><name val="Microsoft YaHei"/><family val="2"/></font>',
267
+ '<font><b/><sz val="14"/><name val="Microsoft YaHei"/><family val="2"/></font>',
268
+ '<font><b/><sz val="11"/><name val="Microsoft YaHei"/><family val="2"/></font>',
269
+ ];
270
+ const fills = [
271
+ '<fill><patternFill patternType="none"/></fill>',
272
+ '<fill><patternFill patternType="gray125"/></fill>',
273
+ '<fill><patternFill patternType="solid"><fgColor rgb="FF1F4E79"/><bgColor indexed="64"/></patternFill></fill>',
274
+ '<fill><patternFill patternType="solid"><fgColor rgb="FFF2F2F2"/><bgColor indexed="64"/></patternFill></fill>',
275
+ '<fill><patternFill patternType="solid"><fgColor rgb="FFDDEBF7"/><bgColor indexed="64"/></patternFill></fill>',
276
+ '<fill><patternFill patternType="solid"><fgColor rgb="FFFFF2CC"/><bgColor indexed="64"/></patternFill></fill>',
277
+ ];
278
+ const border = '<border><left style="thin"><color rgb="FFBFBFBF"/></left><right style="thin"><color rgb="FFBFBFBF"/></right><top style="thin"><color rgb="FFBFBFBF"/></top><bottom style="thin"><color rgb="FFBFBFBF"/></bottom><diagonal/></border>';
279
+ const xfs = [
280
+ '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>',
281
+ '<xf numFmtId="0" fontId="1" fillId="2" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
282
+ '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" xfId="0" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
283
+ '<xf numFmtId="0" fontId="0" fillId="0" borderId="1" xfId="0" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
284
+ '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" xfId="0" applyFont="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
285
+ '<xf numFmtId="0" fontId="0" fillId="3" borderId="1" xfId="0" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
286
+ '<xf numFmtId="0" fontId="4" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1"><alignment vertical="center"/></xf>',
287
+ '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
288
+ '<xf numFmtId="0" fontId="3" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1"><alignment vertical="center"/></xf>',
289
+ '<xf numFmtId="0" fontId="2" fillId="0" borderId="1" xfId="0" applyFont="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
290
+ '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment vertical="top" wrapText="1"/></xf>',
291
+ '<xf numFmtId="0" fontId="2" fillId="4" borderId="1" xfId="0" applyFont="1" applyFill="1" applyBorder="1" applyAlignment="1"><alignment horizontal="center" vertical="center" wrapText="1"/></xf>',
292
+ ];
293
+ return [
294
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
295
+ '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">',
296
+ '<fonts count="' + fonts.length + '">' + fonts.join("") + "</fonts>",
297
+ '<fills count="' + fills.length + '">' + fills.join("") + "</fills>",
298
+ '<borders count="2"><border><left/><right/><top/><bottom/><diagonal/></border>' + border + "</borders>",
299
+ '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>',
300
+ '<cellXfs count="' + xfs.length + '">' + xfs.join("") + "</cellXfs>",
301
+ '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>',
302
+ '<dxfs count="0"/>',
303
+ '<tableStyles count="0" defaultTableStyle="TableStyleMedium2" defaultPivotStyle="PivotStyleLight16"/>',
304
+ "</styleSheet>",
305
+ ].join("");
306
+ }
307
+
308
+ function workbookXml(sheets) {
309
+ const parts = [
310
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
311
+ '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">',
312
+ '<bookViews><workbookView activeTab="0"/></bookViews>',
313
+ '<sheets>',
314
+ ];
315
+ for (let i = 0; i < sheets.length; i += 1) {
316
+ parts.push('<sheet name="' + escapeXml(sheets[i].name) + '" sheetId="' + (i + 1) + '" r:id="rId' + (i + 1) + '"/>');
317
+ }
318
+ parts.push("</sheets>", "</workbook>");
319
+ return parts.join("");
320
+ }
321
+
322
+ function workbookRels(sheets) {
323
+ const parts = [
324
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
325
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
326
+ ];
327
+ for (let i = 0; i < sheets.length; i += 1) {
328
+ parts.push('<Relationship Id="rId' + (i + 1) + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet' + (i + 1) + '.xml"/>');
329
+ }
330
+ parts.push('<Relationship Id="rId' + (sheets.length + 1) + '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>');
331
+ parts.push("</Relationships>");
332
+ return parts.join("");
333
+ }
334
+
335
+ function contentTypes(sheets) {
336
+ const parts = [
337
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
338
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
339
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
340
+ '<Default Extension="xml" ContentType="application/xml"/>',
341
+ '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>',
342
+ '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>',
343
+ '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>',
344
+ '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>',
345
+ ];
346
+ for (let i = 0; i < sheets.length; i += 1) {
347
+ parts.push('<Override PartName="/xl/worksheets/sheet' + (i + 1) + '.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>');
348
+ }
349
+ parts.push("</Types>");
350
+ return parts.join("");
351
+ }
352
+
353
+ function packageRels() {
354
+ return [
355
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
356
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
357
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>',
358
+ '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>',
359
+ '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>',
360
+ "</Relationships>",
361
+ ].join("");
362
+ }
363
+
364
+ function coreXml(title) {
365
+ const now = new Date().toISOString();
366
+ return [
367
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
368
+ '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">',
369
+ "<dc:title>" + escapeXml(title) + "</dc:title>",
370
+ "<dc:creator>DSH daily-report plugin</dc:creator>",
371
+ '<dcterms:created xsi:type="dcterms:W3CDTF">' + now + "</dcterms:created>",
372
+ '<dcterms:modified xsi:type="dcterms:W3CDTF">' + now + "</dcterms:modified>",
373
+ "</cp:coreProperties>",
374
+ ].join("");
375
+ }
376
+
377
+ function appXml(sheets) {
378
+ const parts = [
379
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>',
380
+ '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">',
381
+ "<Application>DSH</Application>",
382
+ "<HeadingPairs><vt:vector size=\"2\" baseType=\"variant\"><vt:variant><vt:lpstr>Worksheets</vt:lpstr></vt:variant><vt:variant><vt:i4>" + sheets.length + "</vt:i4></vt:variant></vt:vector></HeadingPairs>",
383
+ "<TitlesOfParts><vt:vector size=\"" + sheets.length + "\" baseType=\"lpstr\">",
384
+ ];
385
+ for (const sheet of sheets) {
386
+ parts.push("<vt:lpstr>" + escapeXml(sheet.name) + "</vt:lpstr>");
387
+ }
388
+ parts.push("</vt:vector></TitlesOfParts>", "</Properties>");
389
+ return parts.join("");
390
+ }
391
+
392
+ export function buildXlsx(sheetDefinitions, options = {}) {
393
+ if (!Array.isArray(sheetDefinitions) || sheetDefinitions.length === 0) {
394
+ throw new Error("buildXlsx requires at least one sheet");
395
+ }
396
+ const used = new Set();
397
+ const sheets = sheetDefinitions.map((sheet) => ({
398
+ ...sheet,
399
+ name: sanitizeSheetName(sheet.name, used),
400
+ rows: Array.isArray(sheet.rows) ? sheet.rows : [],
401
+ }));
402
+
403
+ const files = [
404
+ { name: "[Content_Types].xml", data: contentTypes(sheets) },
405
+ { name: "_rels/.rels", data: packageRels() },
406
+ { name: "docProps/core.xml", data: coreXml(options.title || "Daily report") },
407
+ { name: "docProps/app.xml", data: appXml(sheets) },
408
+ { name: "xl/workbook.xml", data: workbookXml(sheets) },
409
+ { name: "xl/_rels/workbook.xml.rels", data: workbookRels(sheets) },
410
+ { name: "xl/styles.xml", data: stylesXml() },
411
+ ];
412
+ for (let i = 0; i < sheets.length; i += 1) {
413
+ files.push({ name: "xl/worksheets/sheet" + (i + 1) + ".xml", data: sheetXml(sheets[i], i) });
414
+ }
415
+ return zip(files);
416
+ }