@justybase/spreadsheet-tasks 2.2.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +11 -0
- package/browser/browser-spreadsheet.js +163 -60
- package/browser/justybase-spreadsheet.min.js +13 -13
- package/dist/cjs/index.d.ts +134 -7
- package/dist/cjs/index.js +1381 -464
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1364 -464
- package/dist/esm/index.js.map +1 -1
- package/docs/API.md +66 -1
- package/package.json +4 -3
package/dist/cjs/index.js
CHANGED
|
@@ -35,6 +35,9 @@ __export(index_exports, {
|
|
|
35
35
|
ExcelReaderAbstract: () => ExcelReaderAbstract,
|
|
36
36
|
F: () => F,
|
|
37
37
|
INVALID_SHEET_NAME_CHARS: () => INVALID_SHEET_NAME_CHARS,
|
|
38
|
+
MAX_CELL_CHARS: () => MAX_CELL_CHARS,
|
|
39
|
+
MAX_COLS: () => MAX_COLS,
|
|
40
|
+
MAX_ROWS: () => MAX_ROWS,
|
|
38
41
|
ReaderFactory: () => ReaderFactory,
|
|
39
42
|
StreamingSheetState: () => StreamingSheetState,
|
|
40
43
|
XlsbReader: () => XlsbReader,
|
|
@@ -45,13 +48,27 @@ __export(index_exports, {
|
|
|
45
48
|
XlsxUpdater: () => XlsxUpdater,
|
|
46
49
|
XlsxWriter: () => XlsxWriter,
|
|
47
50
|
applyHeaderWidths: () => applyHeaderWidths,
|
|
51
|
+
assertCellTextLength: () => assertCellTextLength,
|
|
52
|
+
assertColumnCount: () => assertColumnCount,
|
|
53
|
+
assertRowCount: () => assertRowCount,
|
|
54
|
+
assertRowFitsColumns: () => assertRowFitsColumns,
|
|
55
|
+
columnIndexToLetter: () => columnIndexToLetter,
|
|
56
|
+
columnLetterToIndex: () => columnLetterToIndex,
|
|
48
57
|
convertXlsbToXlsx: () => convertXlsbToXlsx,
|
|
49
58
|
convertXlsxToXlsb: () => convertXlsxToXlsb,
|
|
50
59
|
defaultColWidth: () => defaultColWidth,
|
|
60
|
+
escapeXlsxText: () => escapeXlsxText,
|
|
61
|
+
escapeXmlText: () => escapeXmlText,
|
|
51
62
|
getFormat: () => getFormat,
|
|
52
63
|
initColWidths: () => initColWidths,
|
|
53
64
|
isFormattedCell: () => isFormattedCell,
|
|
65
|
+
parseSharedStringsXml: () => parseSharedStringsXml,
|
|
66
|
+
parseTagAttributes: () => parseTagAttributes,
|
|
67
|
+
resolveRelTarget: () => resolveRelTarget,
|
|
54
68
|
sanitizeSheetName: () => sanitizeSheetName,
|
|
69
|
+
trimTrailingEmptyRows: () => trimTrailingEmptyRows,
|
|
70
|
+
unescapeXml: () => unescapeXml,
|
|
71
|
+
unescapeXmlText: () => unescapeXmlText,
|
|
55
72
|
unwrapCell: () => unwrapCell,
|
|
56
73
|
updateColWidthsFromRows: () => updateColWidthsFromRows
|
|
57
74
|
});
|
|
@@ -171,6 +188,153 @@ var BigBuffer = class {
|
|
|
171
188
|
}
|
|
172
189
|
};
|
|
173
190
|
|
|
191
|
+
// src/Formats.ts
|
|
192
|
+
var F = {
|
|
193
|
+
THOUSANDS_SEP: "#,##0",
|
|
194
|
+
CURRENCY_PLN: '#,##0.00 "z\u0142"',
|
|
195
|
+
CURRENCY_EUR: "#,##0.00 \u20AC",
|
|
196
|
+
PERCENTAGE: "0%",
|
|
197
|
+
SCIENTIFIC: "0.00E+00",
|
|
198
|
+
TWO_DECIMALS: "#,##0.00",
|
|
199
|
+
TEXT: "@",
|
|
200
|
+
LEADING_ZEROS: "000000000",
|
|
201
|
+
DATE_SHORT: "dd.mm.yyyy",
|
|
202
|
+
DATE_LONG: "d mmmm yyyy",
|
|
203
|
+
DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
|
|
204
|
+
DATE_ISO: "yyyy-mm-dd",
|
|
205
|
+
DATE_MONTH_YEAR: "mmmm yyyy",
|
|
206
|
+
DATE_WEEKDAY: "dddd, d mmmm yyyy",
|
|
207
|
+
DATE_DAY_MONTH: "d mmmm",
|
|
208
|
+
DATE_YEAR_ONLY: "yyyy",
|
|
209
|
+
DATETIME_SHORT: "dd.mm.yyyy hh:mm",
|
|
210
|
+
DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
|
|
211
|
+
TIME_HH_MM: "hh:mm",
|
|
212
|
+
TIME_HH_MM_SS: "hh:mm:ss",
|
|
213
|
+
TIME_12H: "h:mm AM/PM",
|
|
214
|
+
DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
|
|
215
|
+
DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
|
|
216
|
+
TIME_MS: "hh:mm:ss.000"
|
|
217
|
+
};
|
|
218
|
+
function isFormattedCell(val) {
|
|
219
|
+
return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
|
|
220
|
+
}
|
|
221
|
+
function unwrapCell(val) {
|
|
222
|
+
if (isFormattedCell(val)) {
|
|
223
|
+
return val.value;
|
|
224
|
+
}
|
|
225
|
+
return val;
|
|
226
|
+
}
|
|
227
|
+
function getFormat(val) {
|
|
228
|
+
if (isFormattedCell(val)) {
|
|
229
|
+
return val.format;
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/writerHelpers.ts
|
|
235
|
+
var OA_EPOCH_1900 = Date.UTC(1899, 11, 30);
|
|
236
|
+
var MARCH_1_1900 = Date.UTC(1900, 2, 1);
|
|
237
|
+
function oaDateToTime(oaDate, epochMs) {
|
|
238
|
+
let serial = oaDate;
|
|
239
|
+
if (epochMs === OA_EPOCH_1900 && serial >= 1 && serial < 60) {
|
|
240
|
+
serial += 1;
|
|
241
|
+
}
|
|
242
|
+
return Math.round(serial * 864e5) + epochMs;
|
|
243
|
+
}
|
|
244
|
+
function timeToOaDate(timeMs, epochMs) {
|
|
245
|
+
let serial = (timeMs - epochMs) / 864e5;
|
|
246
|
+
if (epochMs === OA_EPOCH_1900 && timeMs < MARCH_1_1900) {
|
|
247
|
+
serial -= 1;
|
|
248
|
+
}
|
|
249
|
+
return serial;
|
|
250
|
+
}
|
|
251
|
+
var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
|
|
252
|
+
function sanitizeSheetName(name, sheetCount) {
|
|
253
|
+
if (!name || typeof name !== "string") {
|
|
254
|
+
return `Sheet${sheetCount + 1}`;
|
|
255
|
+
}
|
|
256
|
+
let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
|
|
257
|
+
if (sanitized.length > 31) {
|
|
258
|
+
sanitized = sanitized.substring(0, 31);
|
|
259
|
+
}
|
|
260
|
+
if (sanitized.trim().length === 0) {
|
|
261
|
+
sanitized = `Sheet${sheetCount + 1}`;
|
|
262
|
+
}
|
|
263
|
+
return sanitized;
|
|
264
|
+
}
|
|
265
|
+
var MAX_ROWS = 1048576;
|
|
266
|
+
var MAX_COLS = 16384;
|
|
267
|
+
var MAX_CELL_CHARS = 32767;
|
|
268
|
+
function assertCellTextLength(text) {
|
|
269
|
+
if (text.length > MAX_CELL_CHARS) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Excel cell text exceeds ${MAX_CELL_CHARS} UTF-16 code units (got ${text.length}).`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function assertColumnCount(columnCount) {
|
|
276
|
+
if (!Number.isInteger(columnCount) || columnCount < 1 || columnCount > MAX_COLS) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`Excel column count must be 1..${MAX_COLS} (got ${columnCount}).`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function assertRowFitsColumns(kind, length, columnCount) {
|
|
283
|
+
if (length > columnCount) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Excel ${kind} length ${length} exceeds columnCount ${columnCount}.`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function assertRowCount(totalRows) {
|
|
290
|
+
if (totalRows > MAX_ROWS) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Excel row count exceeds ${MAX_ROWS} (got ${totalRows}).`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function initColWidths(columnCount) {
|
|
297
|
+
return new Array(columnCount).fill(-1);
|
|
298
|
+
}
|
|
299
|
+
function applyHeaderWidths(colWidths, headers, columnCount) {
|
|
300
|
+
for (let i = 0; i < columnCount; i++) {
|
|
301
|
+
const len = headers[i] ? headers[i].length + 1 : 0;
|
|
302
|
+
let width = 1.25 * len + 2;
|
|
303
|
+
if (width > 80) width = 80;
|
|
304
|
+
if (colWidths[i] < width) colWidths[i] = width;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
|
|
308
|
+
for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
|
|
309
|
+
const row = rows[r];
|
|
310
|
+
for (let c = 0; c < row.length; c++) {
|
|
311
|
+
const raw = row[c];
|
|
312
|
+
if (raw === null || raw === void 0) continue;
|
|
313
|
+
const val = isFormattedCell(raw) ? raw.value : raw;
|
|
314
|
+
const len = val instanceof Date ? 20 : String(val).length + 1;
|
|
315
|
+
let width = 1.25 * len + 2;
|
|
316
|
+
if (width > 80) width = 80;
|
|
317
|
+
if (colWidths[c] < width) colWidths[c] = width;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function defaultColWidth(width, floor = false) {
|
|
322
|
+
if (width > 0) {
|
|
323
|
+
return floor ? Math.floor(width) : width;
|
|
324
|
+
}
|
|
325
|
+
return 10;
|
|
326
|
+
}
|
|
327
|
+
function cellRefAttr(col, prevCol, colRef, rowNum) {
|
|
328
|
+
return col === prevCol + 1 ? "" : ` r="${colRef}${rowNum}"`;
|
|
329
|
+
}
|
|
330
|
+
var DEFAULT_COMPRESSION_LEVEL = 6;
|
|
331
|
+
function assertCompressionLevel(level) {
|
|
332
|
+
if (!Number.isInteger(level) || level < 0 || level > 9) {
|
|
333
|
+
throw new RangeError(`compressionLevel must be an integer from 0 to 9, got ${level}`);
|
|
334
|
+
}
|
|
335
|
+
return level;
|
|
336
|
+
}
|
|
337
|
+
|
|
174
338
|
// src/ExcelReaderAbstract.ts
|
|
175
339
|
var ExcelReaderAbstract = class {
|
|
176
340
|
constructor() {
|
|
@@ -185,8 +349,7 @@ var ExcelReaderAbstract = class {
|
|
|
185
349
|
dispose() {
|
|
186
350
|
}
|
|
187
351
|
getDateTimeFromOaDate(oaDate) {
|
|
188
|
-
|
|
189
|
-
return new Date(ms);
|
|
352
|
+
return new Date(oaDateToTime(oaDate, this._oaEpoch));
|
|
190
353
|
}
|
|
191
354
|
};
|
|
192
355
|
|
|
@@ -446,6 +609,216 @@ var BiffReaderWriter = class {
|
|
|
446
609
|
|
|
447
610
|
// src/XlsbReader.ts
|
|
448
611
|
var import_adm_zip = __toESM(require("adm-zip"));
|
|
612
|
+
|
|
613
|
+
// src/xmlUtils.ts
|
|
614
|
+
function hasInvalidControlChars(str) {
|
|
615
|
+
for (let i = 0; i < str.length; i++) {
|
|
616
|
+
const c = str.charCodeAt(i);
|
|
617
|
+
if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
function stripInvalidControlChars(str) {
|
|
624
|
+
let out = "";
|
|
625
|
+
for (let i = 0; i < str.length; i++) {
|
|
626
|
+
const c = str.charCodeAt(i);
|
|
627
|
+
if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
|
|
628
|
+
out += str[i];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
633
|
+
function escapeXmlText(str) {
|
|
634
|
+
if (!/[&<>"'\r]/.test(str) && !hasInvalidControlChars(str)) {
|
|
635
|
+
return str;
|
|
636
|
+
}
|
|
637
|
+
let out = str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r/g, " ");
|
|
638
|
+
if (hasInvalidControlChars(out)) {
|
|
639
|
+
out = stripInvalidControlChars(out);
|
|
640
|
+
}
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
643
|
+
function unescapeXml(str) {
|
|
644
|
+
if (!str || str.indexOf("&") === -1) return str;
|
|
645
|
+
return str.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);/g, (entity) => {
|
|
646
|
+
switch (entity) {
|
|
647
|
+
case "&":
|
|
648
|
+
return "&";
|
|
649
|
+
case "<":
|
|
650
|
+
return "<";
|
|
651
|
+
case ">":
|
|
652
|
+
return ">";
|
|
653
|
+
case """:
|
|
654
|
+
return '"';
|
|
655
|
+
case "'":
|
|
656
|
+
return "'";
|
|
657
|
+
default: {
|
|
658
|
+
if (entity[1] === "#") {
|
|
659
|
+
const body = entity.slice(2, -1);
|
|
660
|
+
const code = body[0] === "x" || body[0] === "X" ? parseInt(body.slice(1), 16) : parseInt(body, 10);
|
|
661
|
+
if (Number.isFinite(code)) {
|
|
662
|
+
try {
|
|
663
|
+
return String.fromCodePoint(code);
|
|
664
|
+
} catch {
|
|
665
|
+
return entity;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return entity;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
function escapeXlsxText(str) {
|
|
675
|
+
return escapeXmlText(str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_"));
|
|
676
|
+
}
|
|
677
|
+
function unescapeXmlText(str) {
|
|
678
|
+
if (!str) return str;
|
|
679
|
+
const text = unescapeXml(str.indexOf("\r") === -1 ? str : str.replace(/\r\n?/g, "\n"));
|
|
680
|
+
return text.indexOf("_x") === -1 ? text : text.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
681
|
+
}
|
|
682
|
+
function parseTagAttributes(tag) {
|
|
683
|
+
const attrs = {};
|
|
684
|
+
const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
685
|
+
let m;
|
|
686
|
+
while ((m = re.exec(tag)) !== null) {
|
|
687
|
+
const name = m[1];
|
|
688
|
+
const value = m[3] !== void 0 ? m[3] : m[4];
|
|
689
|
+
attrs[name] = value;
|
|
690
|
+
const colon = name.indexOf(":");
|
|
691
|
+
if (colon !== -1) {
|
|
692
|
+
const local = name.slice(colon + 1);
|
|
693
|
+
if (!(local in attrs)) attrs[local] = value;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return attrs;
|
|
697
|
+
}
|
|
698
|
+
function resolveRelTarget(target) {
|
|
699
|
+
if (!target) return target;
|
|
700
|
+
if (target.startsWith("/")) return target.slice(1);
|
|
701
|
+
if (target.startsWith("xl/")) return target;
|
|
702
|
+
return "xl/" + target;
|
|
703
|
+
}
|
|
704
|
+
function columnIndexToLetter(colIndex) {
|
|
705
|
+
let n = colIndex + 1;
|
|
706
|
+
let result = "";
|
|
707
|
+
while (n > 0) {
|
|
708
|
+
const rem = (n - 1) % 26;
|
|
709
|
+
result = String.fromCharCode(65 + rem) + result;
|
|
710
|
+
n = Math.floor((n - 1) / 26);
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
713
|
+
}
|
|
714
|
+
function columnLetterToIndex(letter) {
|
|
715
|
+
let column = 0;
|
|
716
|
+
const length = letter.length;
|
|
717
|
+
for (let i = 0; i < length; i++) {
|
|
718
|
+
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
|
|
719
|
+
}
|
|
720
|
+
return column - 1;
|
|
721
|
+
}
|
|
722
|
+
function trimTrailingEmptyRows(rows) {
|
|
723
|
+
let end = rows.length;
|
|
724
|
+
while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
|
|
725
|
+
end--;
|
|
726
|
+
}
|
|
727
|
+
return end === rows.length ? rows : rows.slice(0, end);
|
|
728
|
+
}
|
|
729
|
+
function parseSharedStringsXml(xml) {
|
|
730
|
+
const result = [];
|
|
731
|
+
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>|<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*\/>/gi;
|
|
732
|
+
let itemMatch;
|
|
733
|
+
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
734
|
+
const full = itemMatch[0];
|
|
735
|
+
if (full.endsWith("/>") && itemMatch[1] === void 0) {
|
|
736
|
+
result.push("");
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
let content = itemMatch[1] ?? "";
|
|
740
|
+
content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*>[\s\S]*?<\/(?:(?:[A-Za-z_][\w.-]*):)?rPh\s*>/gi, "");
|
|
741
|
+
content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*\/>/gi, "");
|
|
742
|
+
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
743
|
+
let value = "";
|
|
744
|
+
let textMatch;
|
|
745
|
+
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
746
|
+
value += textMatch[1];
|
|
747
|
+
}
|
|
748
|
+
result.push(unescapeXmlText(value));
|
|
749
|
+
}
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/biff12Utils.ts
|
|
754
|
+
function readVlq(buf, pos) {
|
|
755
|
+
let value = 0;
|
|
756
|
+
let shift = 0;
|
|
757
|
+
let byte;
|
|
758
|
+
do {
|
|
759
|
+
byte = buf[pos++];
|
|
760
|
+
value |= (byte & 127) << shift;
|
|
761
|
+
shift += 7;
|
|
762
|
+
} while ((byte & 128) !== 0);
|
|
763
|
+
return { value, nextPos: pos };
|
|
764
|
+
}
|
|
765
|
+
function vlqBytes(value) {
|
|
766
|
+
const out = [];
|
|
767
|
+
while (value >= 128) {
|
|
768
|
+
out.push(value & 127 | 128);
|
|
769
|
+
value >>>= 7;
|
|
770
|
+
}
|
|
771
|
+
out.push(value & 127);
|
|
772
|
+
return out;
|
|
773
|
+
}
|
|
774
|
+
function readRecord(buf, pos, rec) {
|
|
775
|
+
const idRes = readVlq(buf, pos);
|
|
776
|
+
if (idRes.nextPos > buf.length) return false;
|
|
777
|
+
const lenRes = readVlq(buf, idRes.nextPos);
|
|
778
|
+
if (lenRes.nextPos + lenRes.value > buf.length) return false;
|
|
779
|
+
rec.headerStart = pos;
|
|
780
|
+
rec.dataStart = lenRes.nextPos;
|
|
781
|
+
rec.dataEnd = lenRes.nextPos + lenRes.value;
|
|
782
|
+
rec.id = idRes.value;
|
|
783
|
+
rec.len = lenRes.value;
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
function buildRecord(id, payload) {
|
|
787
|
+
const idBytes = vlqBytes(id);
|
|
788
|
+
const lenBytes = vlqBytes(payload.length);
|
|
789
|
+
const header = Buffer.alloc(idBytes.length + lenBytes.length);
|
|
790
|
+
idBytes.forEach((b, i) => header[i] = b);
|
|
791
|
+
lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
|
|
792
|
+
return Buffer.concat([header, payload]);
|
|
793
|
+
}
|
|
794
|
+
function readUtf16(buf, start, charCount) {
|
|
795
|
+
const end = start + charCount * 2;
|
|
796
|
+
if (end > buf.length) return "";
|
|
797
|
+
return buf.toString("utf16le", start, end);
|
|
798
|
+
}
|
|
799
|
+
function parseSharedStringsBin(buf) {
|
|
800
|
+
const values = [];
|
|
801
|
+
let total = 0;
|
|
802
|
+
let unique = 0;
|
|
803
|
+
let endSstOffset = buf.length;
|
|
804
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
805
|
+
let pos = 0;
|
|
806
|
+
while (readRecord(buf, pos, rec)) {
|
|
807
|
+
pos = rec.dataEnd;
|
|
808
|
+
if (rec.id === 159) {
|
|
809
|
+
total = buf.readUInt32LE(rec.dataStart);
|
|
810
|
+
unique = buf.readUInt32LE(rec.dataStart + 4);
|
|
811
|
+
} else if (rec.id === 19) {
|
|
812
|
+
const cch = buf.readUInt32LE(rec.dataStart + 1);
|
|
813
|
+
values.push(readUtf16(buf, rec.dataStart + 5, cch));
|
|
814
|
+
} else if (rec.id === 160) {
|
|
815
|
+
endSstOffset = rec.headerStart;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return { values, total, unique, endSstOffset };
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/XlsbReader.ts
|
|
449
822
|
var XlsbReader = class extends ExcelReaderAbstract {
|
|
450
823
|
constructor() {
|
|
451
824
|
super();
|
|
@@ -457,12 +830,29 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
457
830
|
this._reader = null;
|
|
458
831
|
this._currentRow = [];
|
|
459
832
|
this._pendingRowIndex = -1;
|
|
833
|
+
this._nextExpectedRow = 0;
|
|
460
834
|
this._eof = false;
|
|
461
835
|
this.xfIdToNumFmtId = [];
|
|
462
836
|
this.customDateFormats = null;
|
|
463
837
|
}
|
|
464
|
-
async open(
|
|
465
|
-
this.
|
|
838
|
+
async open(path9, readSharedStrings = true) {
|
|
839
|
+
await this.close();
|
|
840
|
+
this.sheetNames = [];
|
|
841
|
+
this.sheets = [];
|
|
842
|
+
this.sharedStrings = [];
|
|
843
|
+
this.xfIdToNumFmtId = [];
|
|
844
|
+
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
845
|
+
this.fieldCount = 0;
|
|
846
|
+
this.resultsCount = 0;
|
|
847
|
+
this.actualSheetName = "";
|
|
848
|
+
this._currentSheetIndex = -1;
|
|
849
|
+
this._currentRow = [];
|
|
850
|
+
this._pendingRowIndex = -1;
|
|
851
|
+
this._nextExpectedRow = 0;
|
|
852
|
+
this._eof = false;
|
|
853
|
+
this._reader = null;
|
|
854
|
+
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
855
|
+
this.zip = new import_adm_zip.default(path9);
|
|
466
856
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
467
857
|
if (wbEntry) {
|
|
468
858
|
const buf = wbEntry.getData();
|
|
@@ -479,20 +869,39 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
479
869
|
const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
|
|
480
870
|
if (relsEntry) {
|
|
481
871
|
const xml = relsEntry.getData().toString("utf8");
|
|
482
|
-
const
|
|
483
|
-
let
|
|
872
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
873
|
+
let tagMatch;
|
|
484
874
|
const rIdToTarget = {};
|
|
485
|
-
while ((
|
|
486
|
-
|
|
875
|
+
while ((tagMatch = relTagRe.exec(xml)) !== null) {
|
|
876
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
877
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
878
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
879
|
+
if (id !== void 0 && target !== void 0) {
|
|
880
|
+
rIdToTarget[id] = target;
|
|
881
|
+
}
|
|
487
882
|
}
|
|
488
883
|
for (const sheet of this.sheets) {
|
|
489
|
-
|
|
884
|
+
const target = rIdToTarget[sheet.rId];
|
|
490
885
|
if (target) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
886
|
+
sheet.path = resolveRelTarget(target);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
const raw = wbEntry ? wbEntry.getData() : null;
|
|
892
|
+
if (raw) {
|
|
893
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
894
|
+
let pos = 0;
|
|
895
|
+
while (readRecord(raw, pos, rec)) {
|
|
896
|
+
if (rec.id === 153 && rec.len >= 4) {
|
|
897
|
+
const is1904 = (raw[rec.dataStart] & 1) !== 0;
|
|
898
|
+
this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
pos = rec.dataEnd;
|
|
494
902
|
}
|
|
495
903
|
}
|
|
904
|
+
} catch {
|
|
496
905
|
}
|
|
497
906
|
if (readSharedStrings) {
|
|
498
907
|
const ssPath = "xl/sharedStrings.bin";
|
|
@@ -507,8 +916,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
507
916
|
this.resultsCount = this.sheets.length;
|
|
508
917
|
this._currentSheetIndex = -1;
|
|
509
918
|
}
|
|
510
|
-
_readSharedStrings(
|
|
511
|
-
const entry = this.zip.getEntry(
|
|
919
|
+
_readSharedStrings(path9) {
|
|
920
|
+
const entry = this.zip.getEntry(path9);
|
|
512
921
|
if (!entry) return;
|
|
513
922
|
const reader = new BiffReaderWriter(entry.getData());
|
|
514
923
|
while (reader.readSharedStrings()) {
|
|
@@ -517,8 +926,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
517
926
|
}
|
|
518
927
|
}
|
|
519
928
|
}
|
|
520
|
-
_readStyles(
|
|
521
|
-
const entry = this.zip.getEntry(
|
|
929
|
+
_readStyles(path9) {
|
|
930
|
+
const entry = this.zip.getEntry(path9);
|
|
522
931
|
if (!entry) return;
|
|
523
932
|
const reader = new BiffReaderWriter(entry.getData());
|
|
524
933
|
while (reader.readStyles()) {
|
|
@@ -529,21 +938,54 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
529
938
|
getSheetNames() {
|
|
530
939
|
return this.sheetNames;
|
|
531
940
|
}
|
|
941
|
+
async selectSheet(nameOrIndex) {
|
|
942
|
+
let index = -1;
|
|
943
|
+
if (typeof nameOrIndex === "number") {
|
|
944
|
+
index = nameOrIndex;
|
|
945
|
+
} else {
|
|
946
|
+
index = this.sheetNames.indexOf(nameOrIndex);
|
|
947
|
+
if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
|
|
948
|
+
}
|
|
949
|
+
if (index < 0 || index >= this.sheets.length) {
|
|
950
|
+
throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
|
|
951
|
+
}
|
|
952
|
+
this._currentSheetIndex = index;
|
|
953
|
+
if (!this._initSheet(index)) {
|
|
954
|
+
throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[index]}'.`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async nextResult() {
|
|
958
|
+
const next = this._currentSheetIndex + 1;
|
|
959
|
+
if (next >= this.sheets.length) return false;
|
|
960
|
+
this._currentSheetIndex = next;
|
|
961
|
+
if (!this._initSheet(next)) {
|
|
962
|
+
throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[next]}'.`);
|
|
963
|
+
}
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
532
966
|
async read() {
|
|
533
967
|
if (this._currentSheetIndex === -1) {
|
|
534
968
|
this._currentSheetIndex = 0;
|
|
535
969
|
if (!this._initSheet(0)) return false;
|
|
536
970
|
}
|
|
537
971
|
if (this._eof) return false;
|
|
972
|
+
if (this._pendingRowIndex > this._nextExpectedRow && this._pendingRowIndex < MAX_ROWS) {
|
|
973
|
+
this._currentRow = [];
|
|
974
|
+
this._nextExpectedRow++;
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
538
977
|
this._currentRow = [];
|
|
539
978
|
while (true) {
|
|
540
979
|
const hasRecord = this._reader.readWorksheet();
|
|
541
980
|
if (!hasRecord) {
|
|
542
981
|
this._eof = true;
|
|
982
|
+
this._nextExpectedRow = this._pendingRowIndex + 1;
|
|
543
983
|
return true;
|
|
544
984
|
}
|
|
545
985
|
if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
|
|
986
|
+
const completed = this._pendingRowIndex;
|
|
546
987
|
this._pendingRowIndex = this._reader._rowIndex;
|
|
988
|
+
this._nextExpectedRow = completed + 1;
|
|
547
989
|
return true;
|
|
548
990
|
}
|
|
549
991
|
if (this._reader._readCell) {
|
|
@@ -584,13 +1026,17 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
584
1026
|
}
|
|
585
1027
|
}
|
|
586
1028
|
_initSheet(index) {
|
|
587
|
-
if (index >= this.sheets.length) return false;
|
|
1029
|
+
if (index < 0 || index >= this.sheets.length) return false;
|
|
588
1030
|
const sheet = this.sheets[index];
|
|
589
|
-
const entry = this.zip.getEntry(sheet.path);
|
|
1031
|
+
const entry = sheet.path ? this.zip.getEntry(sheet.path) : void 0;
|
|
590
1032
|
if (!entry) return false;
|
|
591
1033
|
this._reader = new BiffReaderWriter(entry.getData());
|
|
592
1034
|
this._eof = false;
|
|
593
1035
|
this._pendingRowIndex = -1;
|
|
1036
|
+
this._nextExpectedRow = 0;
|
|
1037
|
+
this._currentRow = [];
|
|
1038
|
+
this.fieldCount = 0;
|
|
1039
|
+
this.actualSheetName = sheet.name;
|
|
594
1040
|
while (this._reader.readWorksheet()) {
|
|
595
1041
|
if (this._reader._rowIndex !== -1) {
|
|
596
1042
|
this._pendingRowIndex = this._reader._rowIndex;
|
|
@@ -598,7 +1044,7 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
598
1044
|
}
|
|
599
1045
|
}
|
|
600
1046
|
this._eof = true;
|
|
601
|
-
return
|
|
1047
|
+
return true;
|
|
602
1048
|
}
|
|
603
1049
|
getValue(i) {
|
|
604
1050
|
if (i < 0 || i >= this._currentRow.length) return null;
|
|
@@ -608,84 +1054,15 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
608
1054
|
|
|
609
1055
|
// src/XlsxReader.ts
|
|
610
1056
|
var import_yauzl = __toESM(require("yauzl"));
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
return false;
|
|
621
|
-
}
|
|
622
|
-
function stripInvalidControlChars(str) {
|
|
623
|
-
let out = "";
|
|
624
|
-
for (let i = 0; i < str.length; i++) {
|
|
625
|
-
const c = str.charCodeAt(i);
|
|
626
|
-
if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
|
|
627
|
-
out += str[i];
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
return out;
|
|
631
|
-
}
|
|
632
|
-
function escapeXmlText(str) {
|
|
633
|
-
if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
|
|
634
|
-
return str;
|
|
635
|
-
}
|
|
636
|
-
let out = str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
637
|
-
if (hasInvalidControlChars(out)) {
|
|
638
|
-
out = stripInvalidControlChars(out);
|
|
639
|
-
}
|
|
640
|
-
return out;
|
|
641
|
-
}
|
|
642
|
-
function unescapeXml(str) {
|
|
643
|
-
if (!str || str.indexOf("&") === -1) return str;
|
|
644
|
-
return str.replace(/&#x([0-9a-f]+);/gi, (_match, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_match, decimal) => String.fromCodePoint(parseInt(decimal, 10))).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
645
|
-
}
|
|
646
|
-
function columnIndexToLetter(colIndex) {
|
|
647
|
-
let n = colIndex + 1;
|
|
648
|
-
let result = "";
|
|
649
|
-
while (n > 0) {
|
|
650
|
-
const rem = (n - 1) % 26;
|
|
651
|
-
result = String.fromCharCode(65 + rem) + result;
|
|
652
|
-
n = Math.floor((n - 1) / 26);
|
|
653
|
-
}
|
|
654
|
-
return result;
|
|
655
|
-
}
|
|
656
|
-
function columnLetterToIndex(letter) {
|
|
657
|
-
let column = 0;
|
|
658
|
-
const length = letter.length;
|
|
659
|
-
for (let i = 0; i < length; i++) {
|
|
660
|
-
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
|
|
661
|
-
}
|
|
662
|
-
return column - 1;
|
|
663
|
-
}
|
|
664
|
-
function trimTrailingEmptyRows(rows) {
|
|
665
|
-
let end = rows.length;
|
|
666
|
-
while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
|
|
667
|
-
end--;
|
|
668
|
-
}
|
|
669
|
-
return end === rows.length ? rows : rows.slice(0, end);
|
|
670
|
-
}
|
|
671
|
-
function parseSharedStringsXml(xml) {
|
|
672
|
-
const result = [];
|
|
673
|
-
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>/gi;
|
|
674
|
-
let itemMatch;
|
|
675
|
-
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
676
|
-
const content = itemMatch[1];
|
|
677
|
-
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
678
|
-
let value = "";
|
|
679
|
-
let textMatch;
|
|
680
|
-
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
681
|
-
value += textMatch[1];
|
|
682
|
-
}
|
|
683
|
-
result.push(unescapeXml(value));
|
|
684
|
-
}
|
|
685
|
-
return result;
|
|
1057
|
+
function parseIsoDateUtc(value) {
|
|
1058
|
+
let text = value.trim().replace(" ", "T");
|
|
1059
|
+
if (!text) return null;
|
|
1060
|
+
if (!/z$|[+-]\d{2}:?\d{2}$/i.test(text) && text.includes("T")) {
|
|
1061
|
+
text += "Z";
|
|
1062
|
+
}
|
|
1063
|
+
const date = new Date(text);
|
|
1064
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
686
1065
|
}
|
|
687
|
-
|
|
688
|
-
// src/XlsxReader.ts
|
|
689
1066
|
var XlsxReader = class extends ExcelReaderAbstract {
|
|
690
1067
|
constructor() {
|
|
691
1068
|
super();
|
|
@@ -697,13 +1074,34 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
697
1074
|
this._currentSheetIndex = -1;
|
|
698
1075
|
this._sheetXml = null;
|
|
699
1076
|
this._xmlPos = 0;
|
|
1077
|
+
/** End of the sheetData element; row tags must not be searched past it. */
|
|
1078
|
+
this._xmlEnd = 0;
|
|
700
1079
|
this._currentRow = [];
|
|
1080
|
+
this._expectedRowNum = 1;
|
|
1081
|
+
this._pendingEmptyCount = 0;
|
|
701
1082
|
this.xfIdToNumFmtId = [];
|
|
702
1083
|
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
703
1084
|
}
|
|
704
|
-
async open(
|
|
705
|
-
|
|
706
|
-
|
|
1085
|
+
async open(path9, readSharedStrings = true) {
|
|
1086
|
+
await this.close();
|
|
1087
|
+
this.sheetNames = [];
|
|
1088
|
+
this.sheets = [];
|
|
1089
|
+
this.sharedStrings = [];
|
|
1090
|
+
this.xfIdToNumFmtId = [];
|
|
1091
|
+
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
1092
|
+
this.fieldCount = 0;
|
|
1093
|
+
this.resultsCount = 0;
|
|
1094
|
+
this.actualSheetName = "";
|
|
1095
|
+
this._currentSheetIndex = -1;
|
|
1096
|
+
this._sheetXml = null;
|
|
1097
|
+
this._xmlPos = 0;
|
|
1098
|
+
this._xmlEnd = 0;
|
|
1099
|
+
this._currentRow = [];
|
|
1100
|
+
this._expectedRowNum = 1;
|
|
1101
|
+
this._pendingEmptyCount = 0;
|
|
1102
|
+
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
1103
|
+
return new Promise((resolve6, reject) => {
|
|
1104
|
+
import_yauzl.default.open(path9, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
|
|
707
1105
|
if (err) return reject(err);
|
|
708
1106
|
this.zipfile = zipfile;
|
|
709
1107
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -716,28 +1114,40 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
716
1114
|
const wbRelsContent = await this._readZipEntryContent("xl/_rels/workbook.xml.rels");
|
|
717
1115
|
const rIdToTarget = {};
|
|
718
1116
|
if (wbRelsContent) {
|
|
719
|
-
const
|
|
720
|
-
let
|
|
721
|
-
while ((
|
|
722
|
-
|
|
1117
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
1118
|
+
let tagMatch;
|
|
1119
|
+
while ((tagMatch = relTagRe.exec(wbRelsContent)) !== null) {
|
|
1120
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
1121
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
1122
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
1123
|
+
if (id !== void 0 && target !== void 0) {
|
|
1124
|
+
rIdToTarget[id] = target;
|
|
1125
|
+
}
|
|
723
1126
|
}
|
|
724
1127
|
}
|
|
725
1128
|
const wbContent = await this._readZipEntryContent("xl/workbook.xml");
|
|
726
1129
|
if (wbContent) {
|
|
727
|
-
const
|
|
728
|
-
let
|
|
729
|
-
while ((
|
|
730
|
-
const
|
|
731
|
-
const
|
|
732
|
-
const
|
|
1130
|
+
const sheetTagRe = /<sheet\b[^>]*>/g;
|
|
1131
|
+
let tagMatch;
|
|
1132
|
+
while ((tagMatch = sheetTagRe.exec(wbContent)) !== null) {
|
|
1133
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
1134
|
+
const rawName = attrs["name"];
|
|
1135
|
+
const sheetId = attrs["sheetId"] ?? "";
|
|
1136
|
+
const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
|
|
1137
|
+
if (rawName === void 0 || rId === void 0) continue;
|
|
1138
|
+
const name = unescapeXml(rawName);
|
|
733
1139
|
const target = rIdToTarget[rId];
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
fullPath = "xl/" + fullPath;
|
|
737
|
-
}
|
|
1140
|
+
if (!target) continue;
|
|
1141
|
+
const fullPath = resolveRelTarget(target);
|
|
738
1142
|
this.sheetNames.push(name);
|
|
739
1143
|
this.sheets.push({ name, sheetId, rId, path: fullPath });
|
|
740
1144
|
}
|
|
1145
|
+
const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbContent);
|
|
1146
|
+
if (wbPrMatch) {
|
|
1147
|
+
const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
|
|
1148
|
+
const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
|
|
1149
|
+
this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
1150
|
+
}
|
|
741
1151
|
}
|
|
742
1152
|
if (readSharedStrings) {
|
|
743
1153
|
const ssContent = await this._readZipEntryContent("xl/sharedStrings.xml");
|
|
@@ -751,7 +1161,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
751
1161
|
}
|
|
752
1162
|
this.resultsCount = this.sheets.length;
|
|
753
1163
|
this._currentSheetIndex = -1;
|
|
754
|
-
|
|
1164
|
+
resolve6();
|
|
755
1165
|
} catch (e) {
|
|
756
1166
|
reject(e);
|
|
757
1167
|
}
|
|
@@ -765,16 +1175,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
765
1175
|
this.zipfile.close();
|
|
766
1176
|
}
|
|
767
1177
|
}
|
|
768
|
-
async _readZipEntryContent(
|
|
769
|
-
const entry = this.entries.get(
|
|
1178
|
+
async _readZipEntryContent(path9) {
|
|
1179
|
+
const entry = this.entries.get(path9);
|
|
770
1180
|
if (!entry) return null;
|
|
771
|
-
return new Promise((
|
|
1181
|
+
return new Promise((resolve6, reject) => {
|
|
772
1182
|
this.zipfile.openReadStream(entry, (err, readStream) => {
|
|
773
1183
|
if (err) return reject(err);
|
|
774
1184
|
const chunks = [];
|
|
775
1185
|
readStream.on("data", (chunk) => chunks.push(chunk));
|
|
776
1186
|
readStream.on("end", () => {
|
|
777
|
-
|
|
1187
|
+
resolve6(Buffer.concat(chunks).toString("utf8"));
|
|
778
1188
|
});
|
|
779
1189
|
readStream.on("error", reject);
|
|
780
1190
|
});
|
|
@@ -822,6 +1232,27 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
822
1232
|
return false;
|
|
823
1233
|
}
|
|
824
1234
|
}
|
|
1235
|
+
async selectSheet(nameOrIndex) {
|
|
1236
|
+
let index = -1;
|
|
1237
|
+
if (typeof nameOrIndex === "number") {
|
|
1238
|
+
index = nameOrIndex;
|
|
1239
|
+
} else {
|
|
1240
|
+
index = this.sheetNames.indexOf(nameOrIndex);
|
|
1241
|
+
if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
|
|
1242
|
+
}
|
|
1243
|
+
if (index < 0 || index >= this.sheets.length) {
|
|
1244
|
+
throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
|
|
1245
|
+
}
|
|
1246
|
+
this._currentSheetIndex = index;
|
|
1247
|
+
await this._initSheet(index);
|
|
1248
|
+
}
|
|
1249
|
+
async nextResult() {
|
|
1250
|
+
const next = this._currentSheetIndex + 1;
|
|
1251
|
+
if (next >= this.sheets.length) return false;
|
|
1252
|
+
this._currentSheetIndex = next;
|
|
1253
|
+
await this._initSheet(next);
|
|
1254
|
+
return true;
|
|
1255
|
+
}
|
|
825
1256
|
async _initSheet(index) {
|
|
826
1257
|
if (index >= this.sheets.length) return;
|
|
827
1258
|
const sheet = this.sheets[index];
|
|
@@ -831,23 +1262,106 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
831
1262
|
this._sheetXml = "";
|
|
832
1263
|
}
|
|
833
1264
|
this._xmlPos = 0;
|
|
1265
|
+
this._xmlEnd = this._sheetXml.length;
|
|
1266
|
+
this._expectedRowNum = 1;
|
|
1267
|
+
this._pendingEmptyCount = 0;
|
|
1268
|
+
this._currentRow = [];
|
|
1269
|
+
this.fieldCount = 0;
|
|
834
1270
|
const sd = this._sheetXml.indexOf("<sheetData>");
|
|
835
|
-
if (sd !== -1)
|
|
1271
|
+
if (sd !== -1) {
|
|
1272
|
+
this._xmlPos = sd + 11;
|
|
1273
|
+
const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
|
|
1274
|
+
if (se !== -1) this._xmlEnd = se;
|
|
1275
|
+
} else {
|
|
1276
|
+
const m = /<(?:[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(this._sheetXml);
|
|
1277
|
+
if (m && m.index !== void 0) {
|
|
1278
|
+
this._xmlPos = m.index + m[0].length;
|
|
1279
|
+
if (/\/>\s*$/.test(m[0])) {
|
|
1280
|
+
this._xmlEnd = this._xmlPos;
|
|
1281
|
+
} else {
|
|
1282
|
+
const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
|
|
1283
|
+
if (se !== -1) this._xmlEnd = se;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
836
1287
|
}
|
|
837
1288
|
_readNextRow() {
|
|
838
1289
|
if (!this._sheetXml) return false;
|
|
839
|
-
|
|
1290
|
+
if (this._pendingEmptyCount > 0) {
|
|
1291
|
+
this._pendingEmptyCount--;
|
|
1292
|
+
this._currentRow = [];
|
|
1293
|
+
this._expectedRowNum++;
|
|
1294
|
+
return true;
|
|
1295
|
+
}
|
|
1296
|
+
const rowStart = this._findRowTagStart(this._xmlPos);
|
|
840
1297
|
if (rowStart === -1) return false;
|
|
1298
|
+
const rowTagEnd = this._sheetXml.indexOf(">", rowStart);
|
|
1299
|
+
if (rowTagEnd === -1) return false;
|
|
1300
|
+
const rowTag = this._sheetXml.substring(rowStart, rowTagEnd + 1);
|
|
1301
|
+
const isSelfClosing = this._sheetXml.charCodeAt(rowTagEnd - 1) === 47;
|
|
1302
|
+
const rowAttrs = parseTagAttributes(rowTag);
|
|
1303
|
+
const rRaw = rowAttrs["r"];
|
|
1304
|
+
let rowNum = this._expectedRowNum;
|
|
1305
|
+
if (rRaw !== void 0) {
|
|
1306
|
+
const parsed = parseInt(rRaw, 10);
|
|
1307
|
+
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= MAX_ROWS) rowNum = parsed;
|
|
1308
|
+
}
|
|
1309
|
+
if (rowNum > this._expectedRowNum) {
|
|
1310
|
+
const gap = rowNum - this._expectedRowNum;
|
|
1311
|
+
this._pendingEmptyCount = gap - 1;
|
|
1312
|
+
this._currentRow = [];
|
|
1313
|
+
this._expectedRowNum++;
|
|
1314
|
+
return true;
|
|
1315
|
+
}
|
|
1316
|
+
if (rowNum < this._expectedRowNum) {
|
|
1317
|
+
if (isSelfClosing) {
|
|
1318
|
+
this._xmlPos = rowTagEnd + 1;
|
|
1319
|
+
this._currentRow = [];
|
|
1320
|
+
return true;
|
|
1321
|
+
}
|
|
1322
|
+
const rowEnd2 = this._sheetXml.indexOf("</row>", rowStart);
|
|
1323
|
+
if (rowEnd2 === -1) return false;
|
|
1324
|
+
this._xmlPos = rowEnd2 + 6;
|
|
1325
|
+
this._parseRowByIndex(rowStart, rowEnd2);
|
|
1326
|
+
this._expectedRowNum = Math.max(this._expectedRowNum + 1, rowNum + 1);
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
if (isSelfClosing) {
|
|
1330
|
+
this._xmlPos = rowTagEnd + 1;
|
|
1331
|
+
this._currentRow = [];
|
|
1332
|
+
this._expectedRowNum = rowNum + 1;
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
841
1335
|
const rowEnd = this._sheetXml.indexOf("</row>", rowStart);
|
|
842
1336
|
if (rowEnd === -1) return false;
|
|
843
1337
|
this._xmlPos = rowEnd + 6;
|
|
844
1338
|
this._parseRowByIndex(rowStart, rowEnd);
|
|
1339
|
+
this._expectedRowNum = rowNum + 1;
|
|
845
1340
|
return true;
|
|
846
1341
|
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Find the next `<row …>` start, ignoring similarly named elements such as
|
|
1344
|
+
* `<rowBreaks>` that follow `</sheetData>`.
|
|
1345
|
+
*/
|
|
1346
|
+
_findRowTagStart(from) {
|
|
1347
|
+
const xml = this._sheetXml;
|
|
1348
|
+
let pos = from;
|
|
1349
|
+
while (pos < this._xmlEnd) {
|
|
1350
|
+
const idx = xml.indexOf("<row", pos);
|
|
1351
|
+
if (idx === -1 || idx >= this._xmlEnd) return -1;
|
|
1352
|
+
const next = xml.charCodeAt(idx + 4);
|
|
1353
|
+
if (next === 62 || next === 47 || next === 32 || next === 9 || next === 10 || next === 13) {
|
|
1354
|
+
return idx;
|
|
1355
|
+
}
|
|
1356
|
+
pos = idx + 4;
|
|
1357
|
+
}
|
|
1358
|
+
return -1;
|
|
1359
|
+
}
|
|
847
1360
|
_parseRowByIndex(rowStart, rowEnd) {
|
|
848
1361
|
this._currentRow = [];
|
|
849
1362
|
const xml = this._sheetXml;
|
|
850
1363
|
let pos = xml.indexOf(">", rowStart) + 1;
|
|
1364
|
+
let nextCol = 0;
|
|
851
1365
|
while (pos < rowEnd) {
|
|
852
1366
|
const cStart = xml.indexOf("<c", pos);
|
|
853
1367
|
if (cStart === -1 || cStart >= rowEnd) break;
|
|
@@ -881,6 +1395,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
881
1395
|
}
|
|
882
1396
|
}
|
|
883
1397
|
let val = null;
|
|
1398
|
+
let inlineVals = null;
|
|
884
1399
|
if (xml.charCodeAt(nextTagClose - 1) === 47) {
|
|
885
1400
|
pos = nextTagClose + 1;
|
|
886
1401
|
} else {
|
|
@@ -895,27 +1410,41 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
895
1410
|
}
|
|
896
1411
|
} else {
|
|
897
1412
|
if (type === "inlineStr") {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1413
|
+
inlineVals = [];
|
|
1414
|
+
let tSearch = nextTagClose;
|
|
1415
|
+
while (true) {
|
|
1416
|
+
const tStart = xml.indexOf("<t", tSearch);
|
|
1417
|
+
if (tStart === -1 || tStart >= cellContentEnd) break;
|
|
1418
|
+
const tClose = xml.indexOf(">", tStart);
|
|
1419
|
+
if (tClose === -1 || tClose >= cellContentEnd) break;
|
|
1420
|
+
const tContentStart = tClose + 1;
|
|
901
1421
|
const tEnd = xml.indexOf("</t>", tContentStart);
|
|
902
|
-
if (tEnd
|
|
903
|
-
|
|
904
|
-
|
|
1422
|
+
if (tEnd === -1 || tEnd > cellContentEnd) break;
|
|
1423
|
+
inlineVals.push(xml.substring(tContentStart, tEnd));
|
|
1424
|
+
tSearch = tEnd + 4;
|
|
905
1425
|
}
|
|
1426
|
+
if (inlineVals.length === 0) inlineVals = null;
|
|
906
1427
|
}
|
|
907
1428
|
}
|
|
908
1429
|
pos = cellContentEnd + 4;
|
|
909
1430
|
}
|
|
910
1431
|
let finalVal = null;
|
|
911
|
-
if (
|
|
1432
|
+
if (inlineVals !== null) {
|
|
1433
|
+
finalVal = unescapeXmlText(inlineVals.join(""));
|
|
1434
|
+
} else if (val !== null) {
|
|
912
1435
|
if (type === "s") {
|
|
913
1436
|
const idx = parseInt(val, 10);
|
|
914
1437
|
finalVal = this.sharedStrings[idx];
|
|
915
1438
|
} else if (type === "b") {
|
|
916
1439
|
finalVal = val === "1" || val === "true";
|
|
917
1440
|
} else if (type === "inlineStr") {
|
|
1441
|
+
finalVal = unescapeXmlText(val);
|
|
1442
|
+
} else if (type === "str") {
|
|
1443
|
+
finalVal = unescapeXmlText(val);
|
|
1444
|
+
} else if (type === "e") {
|
|
918
1445
|
finalVal = unescapeXml(val);
|
|
1446
|
+
} else if (type === "d") {
|
|
1447
|
+
finalVal = parseIsoDateUtc(val) ?? unescapeXmlText(val);
|
|
919
1448
|
} else if (type === "n" || type === "") {
|
|
920
1449
|
finalVal = parseFloat(val);
|
|
921
1450
|
if (styleIndex > 0 && this.xfIdToNumFmtId) {
|
|
@@ -933,10 +1462,10 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
933
1462
|
}
|
|
934
1463
|
}
|
|
935
1464
|
}
|
|
936
|
-
if (colIndex
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1465
|
+
if (colIndex === -1) colIndex = nextCol;
|
|
1466
|
+
nextCol = colIndex + 1;
|
|
1467
|
+
this._currentRow[colIndex] = finalVal;
|
|
1468
|
+
if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
|
|
940
1469
|
}
|
|
941
1470
|
}
|
|
942
1471
|
getValue(i) {
|
|
@@ -966,99 +1495,11 @@ var ReaderFactory = class {
|
|
|
966
1495
|
|
|
967
1496
|
// src/XlsbWriter.ts
|
|
968
1497
|
var fs = __toESM(require("fs"));
|
|
1498
|
+
var path2 = __toESM(require("path"));
|
|
1499
|
+
var import_crypto = require("crypto");
|
|
969
1500
|
var import_archiver = __toESM(require("archiver"));
|
|
970
1501
|
var import_stream = require("stream");
|
|
971
1502
|
|
|
972
|
-
// src/Formats.ts
|
|
973
|
-
var F = {
|
|
974
|
-
THOUSANDS_SEP: "#,##0",
|
|
975
|
-
CURRENCY_PLN: '#,##0.00 "z\u0142"',
|
|
976
|
-
CURRENCY_EUR: "#,##0.00 \u20AC",
|
|
977
|
-
PERCENTAGE: "0%",
|
|
978
|
-
SCIENTIFIC: "0.00E+00",
|
|
979
|
-
TWO_DECIMALS: "#,##0.00",
|
|
980
|
-
TEXT: "@",
|
|
981
|
-
LEADING_ZEROS: "000000000",
|
|
982
|
-
DATE_SHORT: "dd.mm.yyyy",
|
|
983
|
-
DATE_LONG: "d mmmm yyyy",
|
|
984
|
-
DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
|
|
985
|
-
DATE_ISO: "yyyy-mm-dd",
|
|
986
|
-
DATE_MONTH_YEAR: "mmmm yyyy",
|
|
987
|
-
DATE_WEEKDAY: "dddd, d mmmm yyyy",
|
|
988
|
-
DATE_DAY_MONTH: "d mmmm",
|
|
989
|
-
DATE_YEAR_ONLY: "yyyy",
|
|
990
|
-
DATETIME_SHORT: "dd.mm.yyyy hh:mm",
|
|
991
|
-
DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
|
|
992
|
-
TIME_HH_MM: "hh:mm",
|
|
993
|
-
TIME_HH_MM_SS: "hh:mm:ss",
|
|
994
|
-
TIME_12H: "h:mm AM/PM",
|
|
995
|
-
DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
|
|
996
|
-
DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
|
|
997
|
-
TIME_MS: "hh:mm:ss.000"
|
|
998
|
-
};
|
|
999
|
-
function isFormattedCell(val) {
|
|
1000
|
-
return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
|
|
1001
|
-
}
|
|
1002
|
-
function unwrapCell(val) {
|
|
1003
|
-
if (isFormattedCell(val)) {
|
|
1004
|
-
return val.value;
|
|
1005
|
-
}
|
|
1006
|
-
return val;
|
|
1007
|
-
}
|
|
1008
|
-
function getFormat(val) {
|
|
1009
|
-
if (isFormattedCell(val)) {
|
|
1010
|
-
return val.format;
|
|
1011
|
-
}
|
|
1012
|
-
return null;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// src/writerHelpers.ts
|
|
1016
|
-
var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
|
|
1017
|
-
function sanitizeSheetName(name, sheetCount) {
|
|
1018
|
-
if (!name || typeof name !== "string") {
|
|
1019
|
-
return `Sheet${sheetCount + 1}`;
|
|
1020
|
-
}
|
|
1021
|
-
let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
|
|
1022
|
-
if (sanitized.length > 31) {
|
|
1023
|
-
sanitized = sanitized.substring(0, 31);
|
|
1024
|
-
}
|
|
1025
|
-
if (sanitized.trim().length === 0) {
|
|
1026
|
-
sanitized = `Sheet${sheetCount + 1}`;
|
|
1027
|
-
}
|
|
1028
|
-
return sanitized;
|
|
1029
|
-
}
|
|
1030
|
-
function initColWidths(columnCount) {
|
|
1031
|
-
return new Array(columnCount).fill(-1);
|
|
1032
|
-
}
|
|
1033
|
-
function applyHeaderWidths(colWidths, headers, columnCount) {
|
|
1034
|
-
for (let i = 0; i < columnCount; i++) {
|
|
1035
|
-
const len = headers[i] ? headers[i].length + 1 : 0;
|
|
1036
|
-
let width = 1.25 * len + 2;
|
|
1037
|
-
if (width > 80) width = 80;
|
|
1038
|
-
if (colWidths[i] < width) colWidths[i] = width;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
|
|
1042
|
-
for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
|
|
1043
|
-
const row = rows[r];
|
|
1044
|
-
for (let c = 0; c < row.length; c++) {
|
|
1045
|
-
const raw = row[c];
|
|
1046
|
-
if (raw === null || raw === void 0) continue;
|
|
1047
|
-
const val = isFormattedCell(raw) ? raw.value : raw;
|
|
1048
|
-
const len = val instanceof Date ? 20 : String(val).length + 1;
|
|
1049
|
-
let width = 1.25 * len + 2;
|
|
1050
|
-
if (width > 80) width = 80;
|
|
1051
|
-
if (colWidths[c] < width) colWidths[c] = width;
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
function defaultColWidth(width, floor = false) {
|
|
1056
|
-
if (width > 0) {
|
|
1057
|
-
return floor ? Math.floor(width) : width;
|
|
1058
|
-
}
|
|
1059
|
-
return 10;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
1503
|
// src/StreamingSheetState.ts
|
|
1063
1504
|
var StreamingSheetState = class {
|
|
1064
1505
|
constructor() {
|
|
@@ -1110,7 +1551,13 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
1110
1551
|
/**
|
|
1111
1552
|
* @param filePath Destination `.xlsb` path on disk.
|
|
1112
1553
|
*/
|
|
1113
|
-
|
|
1554
|
+
/**
|
|
1555
|
+
* @param filePath Destination `.xlsb` path on disk.
|
|
1556
|
+
* @param options.compressionLevel Deflate level 0-9 (default 6).
|
|
1557
|
+
*/
|
|
1558
|
+
constructor(filePath, options = {}) {
|
|
1559
|
+
this._outputError = null;
|
|
1560
|
+
this._finalized = false;
|
|
1114
1561
|
this.sheetCount = 0;
|
|
1115
1562
|
this.sheetList = [];
|
|
1116
1563
|
this.sstDic = /* @__PURE__ */ new Map();
|
|
@@ -1121,8 +1568,21 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
1121
1568
|
this._nextXfIdx = 4;
|
|
1122
1569
|
this._stFmtRecords = /* @__PURE__ */ new Map();
|
|
1123
1570
|
this.stream = new StreamingSheetState();
|
|
1124
|
-
|
|
1125
|
-
this.
|
|
1571
|
+
const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
|
|
1572
|
+
this._targetPath = path2.resolve(filePath);
|
|
1573
|
+
fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
|
|
1574
|
+
this._tempPath = path2.join(
|
|
1575
|
+
path2.dirname(this._targetPath),
|
|
1576
|
+
`.${path2.basename(this._targetPath)}.${(0, import_crypto.randomUUID)()}.tmp`
|
|
1577
|
+
);
|
|
1578
|
+
this.output = fs.createWriteStream(this._tempPath);
|
|
1579
|
+
this.output.on("error", (err) => {
|
|
1580
|
+
this._outputError = err;
|
|
1581
|
+
});
|
|
1582
|
+
this.archive = (0, import_archiver.default)("zip", { zlib: { level: compressionLevel } });
|
|
1583
|
+
this.archive.on("error", (err) => {
|
|
1584
|
+
this._outputError = err;
|
|
1585
|
+
});
|
|
1126
1586
|
this.archive.pipe(this.output);
|
|
1127
1587
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
1128
1588
|
this._sheet1Bytes = Buffer.from([
|
|
@@ -2992,6 +3452,11 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
2992
3452
|
* @param options Optional settings (hidden, doAutofilter)
|
|
2993
3453
|
*/
|
|
2994
3454
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
3455
|
+
assertColumnCount(columnCount);
|
|
3456
|
+
if (headers !== void 0) {
|
|
3457
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
3458
|
+
for (const h of headers) assertCellTextLength(h);
|
|
3459
|
+
}
|
|
2995
3460
|
const { hidden = false, doAutofilter = true, sampleRows } = options;
|
|
2996
3461
|
this.addSheet(sheetName, hidden);
|
|
2997
3462
|
this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
|
|
@@ -3057,6 +3522,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3057
3522
|
`Row length mismatch. Expected ${this.stream.endCol - this.stream.startCol} columns, got ${row.length}`
|
|
3058
3523
|
);
|
|
3059
3524
|
}
|
|
3525
|
+
assertRowCount(this.stream.rowNum + 1);
|
|
3060
3526
|
this.createRowHeader(bigBuf, this.stream.rowNum, this.stream.startCol, this.stream.endCol);
|
|
3061
3527
|
for (let c = 0; c < row.length; c++) {
|
|
3062
3528
|
const raw = row[c];
|
|
@@ -3079,7 +3545,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3079
3545
|
} else if (typeof val === "boolean") {
|
|
3080
3546
|
this.writeBool(bigBuf, val, c);
|
|
3081
3547
|
} else if (val instanceof Date) {
|
|
3082
|
-
const oaDate = (val.getTime()
|
|
3548
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
3083
3549
|
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
3084
3550
|
} else if (val !== null && val !== void 0) {
|
|
3085
3551
|
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
|
|
@@ -3136,6 +3602,16 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3136
3602
|
} else if (headers) {
|
|
3137
3603
|
columnCount = headers.length;
|
|
3138
3604
|
}
|
|
3605
|
+
if (columnCount > 0) assertColumnCount(columnCount);
|
|
3606
|
+
const totalRowsCheck = rows.length + (headers ? 1 : 0);
|
|
3607
|
+
assertRowCount(totalRowsCheck);
|
|
3608
|
+
if (headers) {
|
|
3609
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
3610
|
+
for (const h of headers) assertCellTextLength(h);
|
|
3611
|
+
}
|
|
3612
|
+
for (let r = 0; r < rows.length; r++) {
|
|
3613
|
+
assertRowFitsColumns("row", rows[r].length, columnCount);
|
|
3614
|
+
}
|
|
3139
3615
|
const colWidths = initColWidths(columnCount);
|
|
3140
3616
|
if (headers) {
|
|
3141
3617
|
applyHeaderWidths(colWidths, headers, columnCount);
|
|
@@ -3211,7 +3687,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3211
3687
|
} else if (typeof val === "boolean") {
|
|
3212
3688
|
this.writeBool(bigBuf, val, c);
|
|
3213
3689
|
} else if (val instanceof Date) {
|
|
3214
|
-
const oaDate = (val.getTime()
|
|
3690
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
3215
3691
|
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
3216
3692
|
} else if (val !== null && val !== void 0) {
|
|
3217
3693
|
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
|
|
@@ -3294,13 +3770,14 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3294
3770
|
bigBuf.writeUnsafeByte(val ? 1 : 0);
|
|
3295
3771
|
}
|
|
3296
3772
|
writeDateTime(bigBuf, date, colNum) {
|
|
3297
|
-
const oaDate = (date.getTime()
|
|
3773
|
+
const oaDate = timeToOaDate(date.getTime(), this._oaEpoch);
|
|
3298
3774
|
this.writeDouble(bigBuf, oaDate, colNum, 1);
|
|
3299
3775
|
}
|
|
3300
3776
|
_getDateWidth(_date) {
|
|
3301
3777
|
return 19;
|
|
3302
3778
|
}
|
|
3303
3779
|
writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
|
|
3780
|
+
assertCellTextLength(val);
|
|
3304
3781
|
let index;
|
|
3305
3782
|
if (this.sstDic.has(val)) {
|
|
3306
3783
|
index = this.sstDic.get(val);
|
|
@@ -3326,8 +3803,8 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3326
3803
|
bigBuf.writeByte(159);
|
|
3327
3804
|
bigBuf.writeByte(1);
|
|
3328
3805
|
bigBuf.writeByte(8);
|
|
3329
|
-
bigBuf.writeInt32LE(this.sstCntUnique);
|
|
3330
3806
|
bigBuf.writeInt32LE(this.sstCntAll);
|
|
3807
|
+
bigBuf.writeInt32LE(this.sstCntUnique);
|
|
3331
3808
|
for (const [txt] of this.sstDic) {
|
|
3332
3809
|
const txtLen = txt.length;
|
|
3333
3810
|
bigBuf.writeByte(19);
|
|
@@ -3380,8 +3857,61 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3380
3857
|
* Must be awaited before the file is fully written.
|
|
3381
3858
|
*/
|
|
3382
3859
|
finalize() {
|
|
3383
|
-
return new Promise((
|
|
3860
|
+
return new Promise((resolve6, reject) => {
|
|
3861
|
+
if (this._finalized) {
|
|
3862
|
+
reject(new Error("XlsbWriter: already finalized"));
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
this._finalized = true;
|
|
3866
|
+
let settled = false;
|
|
3867
|
+
const cleanupTemp = () => {
|
|
3868
|
+
try {
|
|
3869
|
+
if (fs.existsSync(this._tempPath)) fs.rmSync(this._tempPath);
|
|
3870
|
+
} catch {
|
|
3871
|
+
}
|
|
3872
|
+
};
|
|
3873
|
+
const fail = (err) => {
|
|
3874
|
+
if (settled) return;
|
|
3875
|
+
settled = true;
|
|
3876
|
+
try {
|
|
3877
|
+
this.archive.abort();
|
|
3878
|
+
} catch {
|
|
3879
|
+
}
|
|
3880
|
+
try {
|
|
3881
|
+
this.output.destroy();
|
|
3882
|
+
} catch {
|
|
3883
|
+
}
|
|
3884
|
+
cleanupTemp();
|
|
3885
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3886
|
+
};
|
|
3887
|
+
const succeed = () => {
|
|
3888
|
+
if (settled) return;
|
|
3889
|
+
if (this._outputError) {
|
|
3890
|
+
fail(this._outputError);
|
|
3891
|
+
return;
|
|
3892
|
+
}
|
|
3893
|
+
settled = true;
|
|
3894
|
+
try {
|
|
3895
|
+
const fd = fs.openSync(this._tempPath, "r+");
|
|
3896
|
+
try {
|
|
3897
|
+
fs.fsyncSync(fd);
|
|
3898
|
+
} finally {
|
|
3899
|
+
fs.closeSync(fd);
|
|
3900
|
+
}
|
|
3901
|
+
try {
|
|
3902
|
+
const existing = fs.statSync(this._targetPath);
|
|
3903
|
+
fs.chmodSync(this._tempPath, existing.mode & 511);
|
|
3904
|
+
} catch {
|
|
3905
|
+
}
|
|
3906
|
+
fs.renameSync(this._tempPath, this._targetPath);
|
|
3907
|
+
resolve6();
|
|
3908
|
+
} catch (err) {
|
|
3909
|
+
cleanupTemp();
|
|
3910
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3384
3913
|
try {
|
|
3914
|
+
if (this._outputError) throw this._outputError;
|
|
3385
3915
|
this.saveSst();
|
|
3386
3916
|
this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
|
|
3387
3917
|
const wbBuffers = [];
|
|
@@ -3478,11 +4008,12 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3478
4008
|
</Relationships>`;
|
|
3479
4009
|
this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
|
|
3480
4010
|
}
|
|
3481
|
-
this.output.
|
|
3482
|
-
this.
|
|
4011
|
+
this.output.once("close", succeed);
|
|
4012
|
+
this.output.once("error", fail);
|
|
4013
|
+
this.archive.once("error", fail);
|
|
3483
4014
|
this.archive.finalize();
|
|
3484
4015
|
} catch (err) {
|
|
3485
|
-
|
|
4016
|
+
fail(err);
|
|
3486
4017
|
}
|
|
3487
4018
|
});
|
|
3488
4019
|
}
|
|
@@ -3490,27 +4021,21 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3490
4021
|
|
|
3491
4022
|
// src/XlsxWriter.ts
|
|
3492
4023
|
var fs2 = __toESM(require("fs"));
|
|
4024
|
+
var path3 = __toESM(require("path"));
|
|
4025
|
+
var import_crypto2 = require("crypto");
|
|
3493
4026
|
var import_archiver2 = __toESM(require("archiver"));
|
|
3494
4027
|
var import_stream2 = require("stream");
|
|
3495
|
-
var COLUMN_LETTERS = (() => {
|
|
3496
|
-
const letters = [];
|
|
3497
|
-
for (let i = 65; i < 91; i++) {
|
|
3498
|
-
letters.push(String.fromCharCode(i));
|
|
3499
|
-
}
|
|
3500
|
-
const temp = [];
|
|
3501
|
-
for (const p of letters) {
|
|
3502
|
-
for (let i = 65; i < 91; i++) {
|
|
3503
|
-
temp.push(p + String.fromCharCode(i));
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3506
|
-
letters.push(...temp);
|
|
3507
|
-
return letters;
|
|
3508
|
-
})();
|
|
3509
4028
|
var XlsxWriter = class {
|
|
3510
4029
|
/**
|
|
3511
4030
|
* @param filePath Destination `.xlsx` path on disk.
|
|
3512
4031
|
*/
|
|
3513
|
-
|
|
4032
|
+
/**
|
|
4033
|
+
* @param filePath Destination `.xlsx` path on disk.
|
|
4034
|
+
* @param options.compressionLevel Deflate level 0-9 (default 6).
|
|
4035
|
+
*/
|
|
4036
|
+
constructor(filePath, options = {}) {
|
|
4037
|
+
this._outputError = null;
|
|
4038
|
+
this._finalized = false;
|
|
3514
4039
|
this.sheetCount = 0;
|
|
3515
4040
|
this.sheetList = [];
|
|
3516
4041
|
this.sstArray = [];
|
|
@@ -3524,13 +4049,26 @@ var XlsxWriter = class {
|
|
|
3524
4049
|
this._nextXfIndex = 4;
|
|
3525
4050
|
this.stream = new StreamingSheetState();
|
|
3526
4051
|
this.currentColLetters = [];
|
|
3527
|
-
|
|
3528
|
-
this.
|
|
4052
|
+
const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
|
|
4053
|
+
this._targetPath = path3.resolve(filePath);
|
|
4054
|
+
fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
|
|
4055
|
+
this._tempPath = path3.join(
|
|
4056
|
+
path3.dirname(this._targetPath),
|
|
4057
|
+
`.${path3.basename(this._targetPath)}.${(0, import_crypto2.randomUUID)()}.tmp`
|
|
4058
|
+
);
|
|
4059
|
+
this.output = fs2.createWriteStream(this._tempPath);
|
|
4060
|
+
this.output.on("error", (err) => {
|
|
4061
|
+
this._outputError = err;
|
|
4062
|
+
});
|
|
4063
|
+
this.archive = (0, import_archiver2.default)("zip", { zlib: { level: compressionLevel } });
|
|
4064
|
+
this.archive.on("error", (err) => {
|
|
4065
|
+
this._outputError = err;
|
|
4066
|
+
});
|
|
3529
4067
|
this.archive.pipe(this.output);
|
|
3530
4068
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3531
4069
|
}
|
|
3532
4070
|
_getColumnLetter(colIndex) {
|
|
3533
|
-
return colIndex
|
|
4071
|
+
return columnIndexToLetter(colIndex);
|
|
3534
4072
|
}
|
|
3535
4073
|
/** @deprecated Internal; kept for unit-test access. Prefer sanitizeSheetName(). */
|
|
3536
4074
|
_sanitizeSheetName(name) {
|
|
@@ -3567,7 +4105,7 @@ var XlsxWriter = class {
|
|
|
3567
4105
|
_needsEscape(str) {
|
|
3568
4106
|
for (let i = 0; i < str.length; i++) {
|
|
3569
4107
|
const c = str.charCodeAt(i);
|
|
3570
|
-
if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
|
|
4108
|
+
if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c === 13 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
|
|
3571
4109
|
return true;
|
|
3572
4110
|
}
|
|
3573
4111
|
}
|
|
@@ -3575,13 +4113,19 @@ var XlsxWriter = class {
|
|
|
3575
4113
|
}
|
|
3576
4114
|
_escape(str) {
|
|
3577
4115
|
if (typeof str !== "string") return str;
|
|
4116
|
+
str = str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_");
|
|
3578
4117
|
if (!this._needsEscape(str)) return str;
|
|
3579
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
|
|
4118
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r/g, " ").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
|
|
3580
4119
|
}
|
|
3581
4120
|
/**
|
|
3582
4121
|
* Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
|
|
3583
4122
|
*/
|
|
3584
4123
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
4124
|
+
assertColumnCount(columnCount);
|
|
4125
|
+
if (headers !== void 0) {
|
|
4126
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
4127
|
+
for (const h of headers) assertCellTextLength(h);
|
|
4128
|
+
}
|
|
3585
4129
|
const { hidden = false, doAutofilter = true, sampleRows } = options;
|
|
3586
4130
|
this.addSheet(sheetName, hidden);
|
|
3587
4131
|
this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
|
|
@@ -3624,7 +4168,7 @@ var XlsxWriter = class {
|
|
|
3624
4168
|
this.stream.rowNum++;
|
|
3625
4169
|
bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
|
|
3626
4170
|
for (let c = 0; c < headers.length; c++) {
|
|
3627
|
-
this._writeStringCell(bigBuf, headers[c],
|
|
4171
|
+
this._writeStringCell(bigBuf, headers[c], "", 3);
|
|
3628
4172
|
}
|
|
3629
4173
|
bigBuf.writeString("</row>");
|
|
3630
4174
|
}
|
|
@@ -3638,7 +4182,9 @@ var XlsxWriter = class {
|
|
|
3638
4182
|
);
|
|
3639
4183
|
}
|
|
3640
4184
|
this.stream.rowNum++;
|
|
4185
|
+
assertRowCount(this.stream.rowNum);
|
|
3641
4186
|
bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
|
|
4187
|
+
let prevCol = -1;
|
|
3642
4188
|
for (let c = 0; c < row.length; c++) {
|
|
3643
4189
|
const raw = row[c];
|
|
3644
4190
|
if (raw === null || raw === void 0) continue;
|
|
@@ -3646,27 +4192,28 @@ var XlsxWriter = class {
|
|
|
3646
4192
|
const val = fmtString !== null ? unwrapCell(raw) : raw;
|
|
3647
4193
|
const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
|
|
3648
4194
|
const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
|
|
3649
|
-
const
|
|
4195
|
+
const ref = cellRefAttr(c, prevCol, this.currentColLetters[c], this.stream.rowNum);
|
|
4196
|
+
prevCol = c;
|
|
3650
4197
|
if (typeof val === "number") {
|
|
3651
4198
|
if (Number.isFinite(val)) {
|
|
3652
|
-
bigBuf.writeString(`<c
|
|
4199
|
+
bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
|
|
3653
4200
|
} else {
|
|
3654
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4201
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3655
4202
|
}
|
|
3656
4203
|
} else if (typeof val === "bigint") {
|
|
3657
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4204
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3658
4205
|
} else if (typeof val === "boolean") {
|
|
3659
|
-
bigBuf.writeString(`<c
|
|
4206
|
+
bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
|
|
3660
4207
|
} else if (val instanceof Date) {
|
|
3661
4208
|
const oaDate = this._toOADate(val);
|
|
3662
4209
|
if (Number.isFinite(oaDate)) {
|
|
3663
4210
|
const finalStyle = xfIdx >= 0 ? xfIdx : 1;
|
|
3664
|
-
bigBuf.writeString(`<c
|
|
4211
|
+
bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
|
|
3665
4212
|
} else {
|
|
3666
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4213
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3667
4214
|
}
|
|
3668
4215
|
} else if (val !== null && val !== void 0) {
|
|
3669
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4216
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3670
4217
|
}
|
|
3671
4218
|
}
|
|
3672
4219
|
bigBuf.writeString("</row>");
|
|
@@ -3701,6 +4248,16 @@ var XlsxWriter = class {
|
|
|
3701
4248
|
} else if (headers) {
|
|
3702
4249
|
columnCount = headers.length;
|
|
3703
4250
|
}
|
|
4251
|
+
if (columnCount > 0) assertColumnCount(columnCount);
|
|
4252
|
+
const totalRows = rows.length + (headers ? 1 : 0);
|
|
4253
|
+
assertRowCount(totalRows);
|
|
4254
|
+
if (headers) {
|
|
4255
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
4256
|
+
for (const h of headers) assertCellTextLength(h);
|
|
4257
|
+
}
|
|
4258
|
+
for (let r = 0; r < rows.length; r++) {
|
|
4259
|
+
assertRowFitsColumns("row", rows[r].length, columnCount);
|
|
4260
|
+
}
|
|
3704
4261
|
const colWidths = initColWidths(columnCount);
|
|
3705
4262
|
const colLetters = new Array(columnCount);
|
|
3706
4263
|
for (let i = 0; i < columnCount; i++) {
|
|
@@ -3712,7 +4269,6 @@ var XlsxWriter = class {
|
|
|
3712
4269
|
updateColWidthsFromRows(colWidths, rows);
|
|
3713
4270
|
bigBuf.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
|
|
3714
4271
|
bigBuf.writeString('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
|
|
3715
|
-
const totalRows = rows.length + (headers ? 1 : 0);
|
|
3716
4272
|
if (totalRows > 0 && columnCount > 0) {
|
|
3717
4273
|
bigBuf.writeString(`<dimension ref="A1:${colLetters[columnCount - 1]}${totalRows}"/>`);
|
|
3718
4274
|
} else {
|
|
@@ -3743,7 +4299,7 @@ var XlsxWriter = class {
|
|
|
3743
4299
|
rowNum++;
|
|
3744
4300
|
bigBuf.writeString(`<row r="${rowNum}">`);
|
|
3745
4301
|
for (let c = 0; c < headers.length; c++) {
|
|
3746
|
-
this._writeStringCell(bigBuf, headers[c],
|
|
4302
|
+
this._writeStringCell(bigBuf, headers[c], "", 3);
|
|
3747
4303
|
}
|
|
3748
4304
|
bigBuf.writeString("</row>");
|
|
3749
4305
|
}
|
|
@@ -3751,6 +4307,7 @@ var XlsxWriter = class {
|
|
|
3751
4307
|
rowNum++;
|
|
3752
4308
|
bigBuf.writeString(`<row r="${rowNum}">`);
|
|
3753
4309
|
const row = rows[r];
|
|
4310
|
+
let prevCol = -1;
|
|
3754
4311
|
for (let c = 0; c < row.length; c++) {
|
|
3755
4312
|
const raw = row[c];
|
|
3756
4313
|
if (raw === null || raw === void 0) continue;
|
|
@@ -3758,27 +4315,28 @@ var XlsxWriter = class {
|
|
|
3758
4315
|
const val = fmtString !== null ? unwrapCell(raw) : raw;
|
|
3759
4316
|
const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
|
|
3760
4317
|
const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
|
|
3761
|
-
const
|
|
4318
|
+
const ref = cellRefAttr(c, prevCol, colLetters[c], rowNum);
|
|
4319
|
+
prevCol = c;
|
|
3762
4320
|
if (typeof val === "number") {
|
|
3763
4321
|
if (Number.isFinite(val)) {
|
|
3764
|
-
bigBuf.writeString(`<c
|
|
4322
|
+
bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
|
|
3765
4323
|
} else {
|
|
3766
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4324
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3767
4325
|
}
|
|
3768
4326
|
} else if (typeof val === "bigint") {
|
|
3769
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4327
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3770
4328
|
} else if (typeof val === "boolean") {
|
|
3771
|
-
bigBuf.writeString(`<c
|
|
4329
|
+
bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
|
|
3772
4330
|
} else if (val instanceof Date) {
|
|
3773
4331
|
const oaDate = this._toOADate(val);
|
|
3774
4332
|
if (Number.isFinite(oaDate)) {
|
|
3775
4333
|
const finalStyle = xfIdx >= 0 ? xfIdx : 1;
|
|
3776
|
-
bigBuf.writeString(`<c
|
|
4334
|
+
bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
|
|
3777
4335
|
} else {
|
|
3778
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4336
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3779
4337
|
}
|
|
3780
4338
|
} else if (val !== null && val !== void 0) {
|
|
3781
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4339
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3782
4340
|
}
|
|
3783
4341
|
}
|
|
3784
4342
|
bigBuf.writeString("</row>");
|
|
@@ -3797,7 +4355,9 @@ var XlsxWriter = class {
|
|
|
3797
4355
|
name: this.sheetList[this.sheetCount - 1].pathInArchive
|
|
3798
4356
|
});
|
|
3799
4357
|
}
|
|
3800
|
-
|
|
4358
|
+
/** `ref` is the cell's `r` attribute from `cellRefAttr`, or '' when omitted. */
|
|
4359
|
+
_writeStringCell(bigBuf, val, ref, styleOverride) {
|
|
4360
|
+
assertCellTextLength(val);
|
|
3801
4361
|
let index = this.sstMap.get(val);
|
|
3802
4362
|
if (index === void 0) {
|
|
3803
4363
|
index = this.sstArray.length;
|
|
@@ -3806,7 +4366,7 @@ var XlsxWriter = class {
|
|
|
3806
4366
|
}
|
|
3807
4367
|
this.sstCntAll++;
|
|
3808
4368
|
const styleAttr = styleOverride !== void 0 ? ` s="${styleOverride}"` : "";
|
|
3809
|
-
bigBuf.writeString(`<c
|
|
4369
|
+
bigBuf.writeString(`<c${ref} t="s"${styleAttr}><v>${index}</v></c>`);
|
|
3810
4370
|
}
|
|
3811
4371
|
_registerFormat(fmtString) {
|
|
3812
4372
|
if (this._formatXfMap.has(fmtString)) {
|
|
@@ -3819,29 +4379,79 @@ var XlsxWriter = class {
|
|
|
3819
4379
|
return xfIndex;
|
|
3820
4380
|
}
|
|
3821
4381
|
_toOADate(date) {
|
|
3822
|
-
return (date.getTime()
|
|
4382
|
+
return timeToOaDate(date.getTime(), this._oaEpoch);
|
|
3823
4383
|
}
|
|
3824
4384
|
_getDateWidth(_date) {
|
|
3825
4385
|
return 19;
|
|
3826
4386
|
}
|
|
3827
4387
|
/** Finalize the ZIP package and close the output stream. */
|
|
3828
4388
|
finalize() {
|
|
3829
|
-
return new Promise((
|
|
4389
|
+
return new Promise((resolve6, reject) => {
|
|
4390
|
+
if (this._finalized) {
|
|
4391
|
+
reject(new Error("XlsxWriter: already finalized"));
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
this._finalized = true;
|
|
4395
|
+
let settled = false;
|
|
4396
|
+
const cleanupTemp = () => {
|
|
4397
|
+
try {
|
|
4398
|
+
if (fs2.existsSync(this._tempPath)) fs2.rmSync(this._tempPath);
|
|
4399
|
+
} catch {
|
|
4400
|
+
}
|
|
4401
|
+
};
|
|
4402
|
+
const fail = (err) => {
|
|
4403
|
+
if (settled) return;
|
|
4404
|
+
settled = true;
|
|
4405
|
+
try {
|
|
4406
|
+
this.archive.abort();
|
|
4407
|
+
} catch {
|
|
4408
|
+
}
|
|
4409
|
+
try {
|
|
4410
|
+
this.output.destroy();
|
|
4411
|
+
} catch {
|
|
4412
|
+
}
|
|
4413
|
+
cleanupTemp();
|
|
4414
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4415
|
+
};
|
|
4416
|
+
const succeed = () => {
|
|
4417
|
+
if (settled) return;
|
|
4418
|
+
if (this._outputError) {
|
|
4419
|
+
fail(this._outputError);
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
settled = true;
|
|
4423
|
+
try {
|
|
4424
|
+
const fd = fs2.openSync(this._tempPath, "r+");
|
|
4425
|
+
try {
|
|
4426
|
+
fs2.fsyncSync(fd);
|
|
4427
|
+
} finally {
|
|
4428
|
+
fs2.closeSync(fd);
|
|
4429
|
+
}
|
|
4430
|
+
try {
|
|
4431
|
+
const existing = fs2.statSync(this._targetPath);
|
|
4432
|
+
fs2.chmodSync(this._tempPath, existing.mode & 511);
|
|
4433
|
+
} catch {
|
|
4434
|
+
}
|
|
4435
|
+
fs2.renameSync(this._tempPath, this._targetPath);
|
|
4436
|
+
resolve6();
|
|
4437
|
+
} catch (err) {
|
|
4438
|
+
cleanupTemp();
|
|
4439
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4440
|
+
}
|
|
4441
|
+
};
|
|
3830
4442
|
try {
|
|
4443
|
+
if (this._outputError) throw this._outputError;
|
|
3831
4444
|
this._writeSharedStrings();
|
|
3832
4445
|
this._writeStyles();
|
|
3833
4446
|
this._writeWorkbook();
|
|
3834
4447
|
this._writeContentTypes();
|
|
3835
4448
|
this._writeRels();
|
|
3836
|
-
this.output.
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
this.archive.on("error", (err) => {
|
|
3840
|
-
reject(err);
|
|
3841
|
-
});
|
|
4449
|
+
this.output.once("close", succeed);
|
|
4450
|
+
this.output.once("error", fail);
|
|
4451
|
+
this.archive.once("error", fail);
|
|
3842
4452
|
this.archive.finalize();
|
|
3843
4453
|
} catch (err) {
|
|
3844
|
-
|
|
4454
|
+
fail(err);
|
|
3845
4455
|
}
|
|
3846
4456
|
});
|
|
3847
4457
|
}
|
|
@@ -3973,13 +4583,13 @@ ${xfEntries.join("\n")}
|
|
|
3973
4583
|
// src/XlsxUpdater.ts
|
|
3974
4584
|
var fs5 = __toESM(require("fs"));
|
|
3975
4585
|
var os = __toESM(require("os"));
|
|
3976
|
-
var
|
|
4586
|
+
var path6 = __toESM(require("path"));
|
|
3977
4587
|
var import_adm_zip2 = __toESM(require("adm-zip"));
|
|
3978
4588
|
|
|
3979
4589
|
// src/atomicFile.ts
|
|
3980
4590
|
var fs3 = __toESM(require("fs"));
|
|
3981
|
-
var
|
|
3982
|
-
var
|
|
4591
|
+
var path4 = __toESM(require("path"));
|
|
4592
|
+
var import_crypto3 = require("crypto");
|
|
3983
4593
|
var realFileOperations = {
|
|
3984
4594
|
exists: (filePath) => fs3.existsSync(filePath),
|
|
3985
4595
|
rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
|
|
@@ -4002,7 +4612,7 @@ function readDestinationMode(target) {
|
|
|
4002
4612
|
}
|
|
4003
4613
|
}
|
|
4004
4614
|
function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
|
|
4005
|
-
const backupPath = `${destinationPath}.${(0,
|
|
4615
|
+
const backupPath = `${destinationPath}.${(0, import_crypto3.randomUUID)()}.replacement-backup`;
|
|
4006
4616
|
let backupCreated = false;
|
|
4007
4617
|
let installed = false;
|
|
4008
4618
|
if (operations.exists(destinationPath)) {
|
|
@@ -4050,11 +4660,11 @@ function installTemporaryOutput(temporaryPath, destinationPath, overwrite, opera
|
|
|
4050
4660
|
}
|
|
4051
4661
|
}
|
|
4052
4662
|
function writeBufferAtomically(buffer, destinationPath) {
|
|
4053
|
-
const target =
|
|
4054
|
-
fs3.mkdirSync(
|
|
4055
|
-
const temporaryPath =
|
|
4056
|
-
|
|
4057
|
-
`.${
|
|
4663
|
+
const target = path4.resolve(destinationPath);
|
|
4664
|
+
fs3.mkdirSync(path4.dirname(target), { recursive: true });
|
|
4665
|
+
const temporaryPath = path4.join(
|
|
4666
|
+
path4.dirname(target),
|
|
4667
|
+
`.${path4.basename(target)}.${(0, import_crypto3.randomUUID)()}.tmp`
|
|
4058
4668
|
);
|
|
4059
4669
|
const destinationMode = readDestinationMode(target);
|
|
4060
4670
|
try {
|
|
@@ -4090,8 +4700,8 @@ function writeBufferAtomically(buffer, destinationPath) {
|
|
|
4090
4700
|
|
|
4091
4701
|
// src/streamingZip.ts
|
|
4092
4702
|
var fs4 = __toESM(require("fs"));
|
|
4093
|
-
var
|
|
4094
|
-
var
|
|
4703
|
+
var path5 = __toESM(require("path"));
|
|
4704
|
+
var import_crypto4 = require("crypto");
|
|
4095
4705
|
var import_promises = require("stream/promises");
|
|
4096
4706
|
var import_stream3 = require("stream");
|
|
4097
4707
|
var import_archiver3 = __toESM(require("archiver"));
|
|
@@ -4103,8 +4713,8 @@ function readDestinationMode2(target) {
|
|
|
4103
4713
|
return void 0;
|
|
4104
4714
|
}
|
|
4105
4715
|
}
|
|
4106
|
-
function appendAndWait(archive, source, name) {
|
|
4107
|
-
return new Promise((
|
|
4716
|
+
function appendAndWait(archive, source, name, output) {
|
|
4717
|
+
return new Promise((resolve6, reject) => {
|
|
4108
4718
|
const cleanup = () => {
|
|
4109
4719
|
archive.off("entry", onEntry);
|
|
4110
4720
|
archive.off("error", onError);
|
|
@@ -4112,10 +4722,13 @@ function appendAndWait(archive, source, name) {
|
|
|
4112
4722
|
if (typeof maybeEmitter2.off === "function") {
|
|
4113
4723
|
maybeEmitter2.off("error", onError);
|
|
4114
4724
|
}
|
|
4725
|
+
if (output) {
|
|
4726
|
+
output.off("error", onError);
|
|
4727
|
+
}
|
|
4115
4728
|
};
|
|
4116
4729
|
const onEntry = () => {
|
|
4117
4730
|
cleanup();
|
|
4118
|
-
|
|
4731
|
+
resolve6();
|
|
4119
4732
|
};
|
|
4120
4733
|
const onError = (error) => {
|
|
4121
4734
|
cleanup();
|
|
@@ -4123,6 +4736,9 @@ function appendAndWait(archive, source, name) {
|
|
|
4123
4736
|
};
|
|
4124
4737
|
archive.once("entry", onEntry);
|
|
4125
4738
|
archive.once("error", onError);
|
|
4739
|
+
if (output) {
|
|
4740
|
+
output.once("error", onError);
|
|
4741
|
+
}
|
|
4126
4742
|
const maybeEmitter = source;
|
|
4127
4743
|
if (typeof maybeEmitter.once === "function") {
|
|
4128
4744
|
maybeEmitter.once("error", onError);
|
|
@@ -4136,11 +4752,11 @@ function appendAndWait(archive, source, name) {
|
|
|
4136
4752
|
});
|
|
4137
4753
|
}
|
|
4138
4754
|
async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
4139
|
-
const target =
|
|
4140
|
-
fs4.mkdirSync(
|
|
4141
|
-
const temporaryPath =
|
|
4142
|
-
|
|
4143
|
-
`.${
|
|
4755
|
+
const target = path5.resolve(destinationPath);
|
|
4756
|
+
fs4.mkdirSync(path5.dirname(target), { recursive: true });
|
|
4757
|
+
const temporaryPath = path5.join(
|
|
4758
|
+
path5.dirname(target),
|
|
4759
|
+
`.${path5.basename(target)}.${(0, import_crypto4.randomUUID)()}.tmp`
|
|
4144
4760
|
);
|
|
4145
4761
|
const destinationMode = readDestinationMode2(target);
|
|
4146
4762
|
let output = null;
|
|
@@ -4150,18 +4766,32 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
|
4150
4766
|
temporaryPath,
|
|
4151
4767
|
destinationMode !== void 0 ? { mode: destinationMode } : {}
|
|
4152
4768
|
);
|
|
4153
|
-
|
|
4769
|
+
let outputError = null;
|
|
4770
|
+
output.on("error", (err) => {
|
|
4771
|
+
outputError = err;
|
|
4772
|
+
try {
|
|
4773
|
+
archive == null ? void 0 : archive.abort();
|
|
4774
|
+
} catch {
|
|
4775
|
+
}
|
|
4776
|
+
});
|
|
4777
|
+
archive = (0, import_archiver3.default)("zip", { zlib: { level: DEFAULT_COMPRESSION_LEVEL } });
|
|
4778
|
+
archive.on("error", (err) => {
|
|
4779
|
+
outputError = err;
|
|
4780
|
+
});
|
|
4154
4781
|
archive.pipe(output);
|
|
4155
4782
|
for (const entry of zip.getEntries()) {
|
|
4783
|
+
if (outputError) throw outputError;
|
|
4156
4784
|
const stagedPath = stagedParts.get(entry.entryName);
|
|
4157
4785
|
if (stagedPath) {
|
|
4158
|
-
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName);
|
|
4786
|
+
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName, output);
|
|
4159
4787
|
} else {
|
|
4160
|
-
await appendAndWait(archive, import_stream3.Readable.from([entry.getData()]), entry.entryName);
|
|
4788
|
+
await appendAndWait(archive, import_stream3.Readable.from([entry.getData()]), entry.entryName, output);
|
|
4161
4789
|
}
|
|
4162
4790
|
}
|
|
4791
|
+
if (outputError) throw outputError;
|
|
4163
4792
|
await archive.finalize();
|
|
4164
4793
|
await (0, import_promises.finished)(output);
|
|
4794
|
+
if (outputError) throw outputError;
|
|
4165
4795
|
if (destinationMode !== void 0) {
|
|
4166
4796
|
try {
|
|
4167
4797
|
fs4.chmodSync(temporaryPath, destinationMode);
|
|
@@ -4195,6 +4825,23 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
|
4195
4825
|
}
|
|
4196
4826
|
|
|
4197
4827
|
// src/XlsxUpdater.ts
|
|
4828
|
+
function parseCellRange(ref) {
|
|
4829
|
+
const m = /^\$?([A-Za-z]+)\$?(\d+)(?::\$?([A-Za-z]+)\$?(\d+))?$/.exec(ref.trim());
|
|
4830
|
+
if (!m) return null;
|
|
4831
|
+
const c1 = columnLetterToIndex(m[1].toUpperCase());
|
|
4832
|
+
const r1 = parseInt(m[2], 10);
|
|
4833
|
+
const c2 = m[3] ? columnLetterToIndex(m[3].toUpperCase()) : c1;
|
|
4834
|
+
const r2 = m[4] ? parseInt(m[4], 10) : r1;
|
|
4835
|
+
if (!Number.isFinite(c1) || !Number.isFinite(r1) || !Number.isFinite(c2) || !Number.isFinite(r2)) {
|
|
4836
|
+
return null;
|
|
4837
|
+
}
|
|
4838
|
+
return {
|
|
4839
|
+
c1: Math.min(c1, c2),
|
|
4840
|
+
r1: Math.min(r1, r2),
|
|
4841
|
+
c2: Math.max(c1, c2),
|
|
4842
|
+
r2: Math.max(r1, r2)
|
|
4843
|
+
};
|
|
4844
|
+
}
|
|
4198
4845
|
var XlsxUpdater = class _XlsxUpdater {
|
|
4199
4846
|
/**
|
|
4200
4847
|
* @param path Path to an existing `.xlsx` file.
|
|
@@ -4281,6 +4928,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4281
4928
|
const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
|
|
4282
4929
|
this._updatePivotCaches(sheetName, dimRef, rows.length);
|
|
4283
4930
|
this._addRefreshOnLoadToPivotTables();
|
|
4931
|
+
this._removeCalcChain();
|
|
4932
|
+
this._updateTableRefs(sheetName, dimRef);
|
|
4284
4933
|
}
|
|
4285
4934
|
/**
|
|
4286
4935
|
* Replace worksheet rows from a one-pass source without materialising the
|
|
@@ -4353,10 +5002,10 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4353
5002
|
if (outputError) throw outputError;
|
|
4354
5003
|
writtenBytes += data.length;
|
|
4355
5004
|
if (rowOutput.write(data)) return;
|
|
4356
|
-
await new Promise((
|
|
5005
|
+
await new Promise((resolve6, reject) => {
|
|
4357
5006
|
const onDrain = () => {
|
|
4358
5007
|
cleanup();
|
|
4359
|
-
|
|
5008
|
+
resolve6();
|
|
4360
5009
|
};
|
|
4361
5010
|
const onError = (error) => {
|
|
4362
5011
|
cleanup();
|
|
@@ -4396,8 +5045,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4396
5045
|
lastKeptEnd = writtenBytes;
|
|
4397
5046
|
}
|
|
4398
5047
|
}
|
|
4399
|
-
await new Promise((
|
|
4400
|
-
rowOutput.once("close",
|
|
5048
|
+
await new Promise((resolve6, reject) => {
|
|
5049
|
+
rowOutput.once("close", resolve6);
|
|
4401
5050
|
rowOutput.once("error", reject);
|
|
4402
5051
|
rowOutput.end();
|
|
4403
5052
|
});
|
|
@@ -4416,6 +5065,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4416
5065
|
this._commitSharedStrings();
|
|
4417
5066
|
this._updatePivotCaches(sheetName, dimRef, keptDataRows);
|
|
4418
5067
|
this._addRefreshOnLoadToPivotTables();
|
|
5068
|
+
this._removeCalcChain();
|
|
5069
|
+
this._updateTableRefs(sheetName, dimRef);
|
|
4419
5070
|
if (oldStage && oldStage !== outputPath) {
|
|
4420
5071
|
this._removeTemporaryFile(oldStage);
|
|
4421
5072
|
}
|
|
@@ -4489,7 +5140,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4489
5140
|
}
|
|
4490
5141
|
_ensureTemporaryDirectory() {
|
|
4491
5142
|
if (this.temporaryDirectory === null) {
|
|
4492
|
-
this.temporaryDirectory = fs5.mkdtempSync(
|
|
5143
|
+
this.temporaryDirectory = fs5.mkdtempSync(path6.join(os.tmpdir(), "spreadsheet-updater-"));
|
|
4493
5144
|
} else {
|
|
4494
5145
|
try {
|
|
4495
5146
|
fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
@@ -4499,7 +5150,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4499
5150
|
return this.temporaryDirectory;
|
|
4500
5151
|
}
|
|
4501
5152
|
_temporaryPath(suffix) {
|
|
4502
|
-
return
|
|
5153
|
+
return path6.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
4503
5154
|
}
|
|
4504
5155
|
_removeTemporaryFile(filePath) {
|
|
4505
5156
|
try {
|
|
@@ -4539,10 +5190,15 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4539
5190
|
const rIdToTarget = {};
|
|
4540
5191
|
if (relsEntry) {
|
|
4541
5192
|
const rels = relsEntry.getData().toString("utf8");
|
|
4542
|
-
const
|
|
4543
|
-
let
|
|
4544
|
-
while ((
|
|
4545
|
-
|
|
5193
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5194
|
+
let tagMatch;
|
|
5195
|
+
while ((tagMatch = relTagRe.exec(rels)) !== null) {
|
|
5196
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
5197
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
5198
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
5199
|
+
if (id !== void 0 && target !== void 0) {
|
|
5200
|
+
rIdToTarget[id] = target;
|
|
5201
|
+
}
|
|
4546
5202
|
}
|
|
4547
5203
|
}
|
|
4548
5204
|
const wbEntry = this.zip.getEntry("xl/workbook.xml");
|
|
@@ -4551,21 +5207,23 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4551
5207
|
let match;
|
|
4552
5208
|
while ((match = sheetRe.exec(wbXml)) !== null) {
|
|
4553
5209
|
const tag = match[0];
|
|
4554
|
-
const
|
|
4555
|
-
const
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
const
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
fullPath = fullPath.substring(1);
|
|
4562
|
-
} else if (!fullPath.startsWith("xl/")) {
|
|
4563
|
-
fullPath = "xl/" + fullPath;
|
|
4564
|
-
}
|
|
5210
|
+
const attrs = parseTagAttributes(tag);
|
|
5211
|
+
const rawName = attrs["name"];
|
|
5212
|
+
const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
|
|
5213
|
+
if (rawName === void 0 || rId === void 0) continue;
|
|
5214
|
+
const name = unescapeXml(rawName);
|
|
5215
|
+
const target = rIdToTarget[rId] ?? "";
|
|
5216
|
+
const fullPath = resolveRelTarget(target);
|
|
4565
5217
|
if (fullPath) {
|
|
4566
5218
|
this.sheetNameToPath.set(name, fullPath);
|
|
4567
5219
|
}
|
|
4568
5220
|
}
|
|
5221
|
+
const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbXml);
|
|
5222
|
+
if (wbPrMatch) {
|
|
5223
|
+
const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
|
|
5224
|
+
const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
|
|
5225
|
+
this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
5226
|
+
}
|
|
4569
5227
|
for (const entry of this.zip.getEntries()) {
|
|
4570
5228
|
const name = entry.entryName;
|
|
4571
5229
|
if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
|
|
@@ -4625,7 +5283,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4625
5283
|
throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
|
|
4626
5284
|
}
|
|
4627
5285
|
const appended = this.sharedStringsPending.map((txt) => {
|
|
4628
|
-
const clean =
|
|
5286
|
+
const clean = escapeXlsxText(txt);
|
|
4629
5287
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4630
5288
|
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
4631
5289
|
return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
|
|
@@ -4648,14 +5306,25 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4648
5306
|
const sdEnd = sheetXml.indexOf("</sheetData>", sdStart);
|
|
4649
5307
|
if (sdEnd === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
|
|
4650
5308
|
const region = sheetXml.substring(sdStart, sdEnd);
|
|
4651
|
-
const cellRe = /<c\b[^>]*>/g;
|
|
5309
|
+
const cellRe = /<row\b[^>]*>|<c\b[^>]*>/g;
|
|
4652
5310
|
let m;
|
|
5311
|
+
let row = 0;
|
|
5312
|
+
let nextCol = 0;
|
|
4653
5313
|
while ((m = cellRe.exec(region)) !== null) {
|
|
4654
5314
|
const tag = m[0];
|
|
5315
|
+
if (tag.startsWith("<row")) {
|
|
5316
|
+
const rowAttr = /\br="(\d+)"/.exec(tag);
|
|
5317
|
+
row = rowAttr ? parseInt(rowAttr[1], 10) : row + 1;
|
|
5318
|
+
nextCol = 0;
|
|
5319
|
+
continue;
|
|
5320
|
+
}
|
|
4655
5321
|
const rMatch = /r="([A-Z]+)(\d+)"/.exec(tag);
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
5322
|
+
let col = nextCol;
|
|
5323
|
+
if (rMatch) {
|
|
5324
|
+
col = columnLetterToIndex(rMatch[1]);
|
|
5325
|
+
row = parseInt(rMatch[2], 10);
|
|
5326
|
+
}
|
|
5327
|
+
nextCol = col + 1;
|
|
4659
5328
|
const sMatch = /s="(\d+)"/.exec(tag);
|
|
4660
5329
|
if (!sMatch) continue;
|
|
4661
5330
|
const s = parseInt(sMatch[1], 10);
|
|
@@ -4760,7 +5429,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4760
5429
|
return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
|
|
4761
5430
|
}
|
|
4762
5431
|
if (val instanceof Date) {
|
|
4763
|
-
const oaDate = (val.getTime()
|
|
5432
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
4764
5433
|
if (Number.isFinite(oaDate)) {
|
|
4765
5434
|
const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
|
|
4766
5435
|
return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
|
|
@@ -4776,7 +5445,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4776
5445
|
const colRef = columnIndexToLetter(col);
|
|
4777
5446
|
const styleAttr = style > 0 ? ` s="${style}"` : "";
|
|
4778
5447
|
if (this.sharedStringsXml === null) {
|
|
4779
|
-
const clean =
|
|
5448
|
+
const clean = escapeXlsxText(text);
|
|
4780
5449
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4781
5450
|
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
4782
5451
|
return `<c r="${colRef}${rowNum}" t="inlineStr"${styleAttr}><is><t${tAttr}>${clean}</t></is></c>`;
|
|
@@ -4977,8 +5646,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4977
5646
|
}
|
|
4978
5647
|
}
|
|
4979
5648
|
_addRefreshOnLoadToPivotTables() {
|
|
4980
|
-
for (const
|
|
4981
|
-
const entry = this.zip.getEntry(
|
|
5649
|
+
for (const path9 of this.pivotTableDefPaths) {
|
|
5650
|
+
const entry = this.zip.getEntry(path9);
|
|
4982
5651
|
if (!entry) continue;
|
|
4983
5652
|
let xml = entry.getData().toString("utf8");
|
|
4984
5653
|
const rootRe = /<pivotTableDefinition\b[^>]*>/;
|
|
@@ -4986,7 +5655,233 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4986
5655
|
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
4987
5656
|
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
4988
5657
|
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
4989
|
-
this.zip.updateFile(
|
|
5658
|
+
this.zip.updateFile(path9, Buffer.from(xml, "utf8"));
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
/**
|
|
5662
|
+
* Remove the stale calculation chain (xl/calcChain.xml). Excel rebuilds it
|
|
5663
|
+
* on open; keeping stale <c> entries for the replaced sheet would force a
|
|
5664
|
+
* repair prompt. Removes the part, its Content_Types override and its
|
|
5665
|
+
* workbook rels entry (xlspy parity).
|
|
5666
|
+
*/
|
|
5667
|
+
_removeCalcChain() {
|
|
5668
|
+
const calcPath = "xl/calcChain.xml";
|
|
5669
|
+
if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) {
|
|
5670
|
+
this._ensureCalcPrFullCalc();
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5673
|
+
try {
|
|
5674
|
+
this.zip.deleteFile(calcPath);
|
|
5675
|
+
} catch {
|
|
5676
|
+
}
|
|
5677
|
+
const stagedCalc = this.stagedParts.get(calcPath);
|
|
5678
|
+
if (stagedCalc) {
|
|
5679
|
+
this.stagedParts.delete(calcPath);
|
|
5680
|
+
try {
|
|
5681
|
+
fs5.rmSync(stagedCalc, { force: true });
|
|
5682
|
+
} catch {
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
const ctEntry = this.zip.getEntry("[Content_Types].xml");
|
|
5686
|
+
if (ctEntry || this.stagedParts.has("[Content_Types].xml")) {
|
|
5687
|
+
try {
|
|
5688
|
+
let ct = this._entryData("[Content_Types].xml").toString("utf8");
|
|
5689
|
+
const before = ct;
|
|
5690
|
+
ct = ct.replace(/<Override\b[^>]*PartName="\/xl\/calcChain\.xml"[^>]*\/?>/gi, "");
|
|
5691
|
+
if (ct !== before) {
|
|
5692
|
+
this._writePartText("[Content_Types].xml", ct);
|
|
5693
|
+
}
|
|
5694
|
+
} catch {
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
const relsPath = "xl/_rels/workbook.xml.rels";
|
|
5698
|
+
if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
|
|
5699
|
+
try {
|
|
5700
|
+
const rels = this._entryData(relsPath).toString("utf8");
|
|
5701
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5702
|
+
let m;
|
|
5703
|
+
let out = "";
|
|
5704
|
+
let last = 0;
|
|
5705
|
+
let changed = false;
|
|
5706
|
+
while ((m = relTagRe.exec(rels)) !== null) {
|
|
5707
|
+
const attrs = parseTagAttributes(m[0]);
|
|
5708
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
5709
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
5710
|
+
const isCalcChain = type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.xml";
|
|
5711
|
+
if (isCalcChain) {
|
|
5712
|
+
out += rels.substring(last, m.index);
|
|
5713
|
+
last = m.index + m[0].length;
|
|
5714
|
+
changed = true;
|
|
5715
|
+
}
|
|
5716
|
+
}
|
|
5717
|
+
if (changed) {
|
|
5718
|
+
out += rels.substring(last);
|
|
5719
|
+
this._writePartText(relsPath, out);
|
|
5720
|
+
}
|
|
5721
|
+
} catch {
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
this._ensureCalcPrFullCalc();
|
|
5725
|
+
}
|
|
5726
|
+
_writePartText(name, text) {
|
|
5727
|
+
if (this.stagedParts.has(name)) {
|
|
5728
|
+
fs5.writeFileSync(this.stagedParts.get(name), text, "utf8");
|
|
5729
|
+
} else {
|
|
5730
|
+
this.zip.updateFile(name, Buffer.from(text, "utf8"));
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
_ensureCalcPrFullCalc() {
|
|
5734
|
+
try {
|
|
5735
|
+
const wbPath = "xl/workbook.xml";
|
|
5736
|
+
if (!this.zip.getEntry(wbPath) && !this.stagedParts.has(wbPath)) return;
|
|
5737
|
+
let xml = this._entryData(wbPath).toString("utf8");
|
|
5738
|
+
const calcPrRe = /<(?:[A-Za-z_][\w.-]*:)?calcPr\b[^>]*>/i;
|
|
5739
|
+
const match = calcPrRe.exec(xml);
|
|
5740
|
+
if (match && match.index !== void 0) {
|
|
5741
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(match[0], "fullCalcOnLoad", "1");
|
|
5742
|
+
xml = xml.substring(0, match.index) + newTag + xml.substring(match.index + match[0].length);
|
|
5743
|
+
} else {
|
|
5744
|
+
const insertAt = _XlsxUpdater._calcPrInsertPosition(xml);
|
|
5745
|
+
if (insertAt < 0) return;
|
|
5746
|
+
xml = xml.substring(0, insertAt) + '<calcPr fullCalcOnLoad="1"/>' + xml.substring(insertAt);
|
|
5747
|
+
}
|
|
5748
|
+
this._writePartText(wbPath, xml);
|
|
5749
|
+
} catch {
|
|
5750
|
+
}
|
|
5751
|
+
}
|
|
5752
|
+
/**
|
|
5753
|
+
* Insertion offset for a missing `<calcPr>`. The element follows
|
|
5754
|
+
* sheets/externalReferences/definedNames in the workbook schema, so insert
|
|
5755
|
+
* after the last of those that exists instead of right after `<workbook>`.
|
|
5756
|
+
*/
|
|
5757
|
+
static _calcPrInsertPosition(xml) {
|
|
5758
|
+
const prefixed = "(?:[A-Za-z_][\\w.-]*:)?";
|
|
5759
|
+
const candidates = [
|
|
5760
|
+
new RegExp(`</${prefixed}sheets\\s*>`, "i"),
|
|
5761
|
+
new RegExp(`<${prefixed}externalReferences\\b[^>]*/>`, "i"),
|
|
5762
|
+
new RegExp(`<${prefixed}externalReferences\\b[^>]*>[\\s\\S]*?</${prefixed}externalReferences\\s*>`, "i"),
|
|
5763
|
+
new RegExp(`<${prefixed}definedNames\\b[^>]*/>`, "i"),
|
|
5764
|
+
new RegExp(`<${prefixed}definedNames\\b[^>]*>[\\s\\S]*?</${prefixed}definedNames\\s*>`, "i")
|
|
5765
|
+
];
|
|
5766
|
+
let insertAt = -1;
|
|
5767
|
+
for (const re of candidates) {
|
|
5768
|
+
const m = re.exec(xml);
|
|
5769
|
+
if (m && m.index !== void 0) {
|
|
5770
|
+
insertAt = Math.max(insertAt, m.index + m[0].length);
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
if (insertAt >= 0) return insertAt;
|
|
5774
|
+
const close = new RegExp(`</${prefixed}workbook\\s*>`, "i").exec(xml);
|
|
5775
|
+
if (close && close.index !== void 0) return close.index;
|
|
5776
|
+
const root = new RegExp(`<${prefixed}workbook\\b[^>]*>`, "i").exec(xml);
|
|
5777
|
+
if (!root || root.index === void 0) return -1;
|
|
5778
|
+
return root.index + root[0].length;
|
|
5779
|
+
}
|
|
5780
|
+
/**
|
|
5781
|
+
* Update ListObject table ranges (xl/tables/*.xml) for the replaced sheet.
|
|
5782
|
+
* Only tables anchored at A1 are updated to the new dimension; tables
|
|
5783
|
+
* elsewhere are left untouched (moving them would corrupt the sheet).
|
|
5784
|
+
* Empty replacements (dimRef "A1") do not shrink multi-column tables.
|
|
5785
|
+
*/
|
|
5786
|
+
_updateTableRefs(sheetName, dimRef) {
|
|
5787
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
5788
|
+
if (!sheetPath) return;
|
|
5789
|
+
const slash = sheetPath.lastIndexOf("/");
|
|
5790
|
+
const dir = slash >= 0 ? sheetPath.substring(0, slash + 1) : "";
|
|
5791
|
+
const base = slash >= 0 ? sheetPath.substring(slash + 1) : sheetPath;
|
|
5792
|
+
const sheetRelsPath = `${dir}_rels/${base}.rels`;
|
|
5793
|
+
const sheetRelsEntry = this.zip.getEntry(sheetRelsPath);
|
|
5794
|
+
const hasStagedRels = this.stagedParts.has(sheetRelsPath);
|
|
5795
|
+
if (!sheetRelsEntry && !hasStagedRels) return;
|
|
5796
|
+
const tableTargets = [];
|
|
5797
|
+
try {
|
|
5798
|
+
const relsXml = this._entryData(sheetRelsPath).toString("utf8");
|
|
5799
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5800
|
+
let m;
|
|
5801
|
+
while ((m = relTagRe.exec(relsXml)) !== null) {
|
|
5802
|
+
const attrs = parseTagAttributes(m[0]);
|
|
5803
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
5804
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
5805
|
+
if (!type.includes("/table") || !target) continue;
|
|
5806
|
+
let tablePath;
|
|
5807
|
+
if (target.startsWith("/")) {
|
|
5808
|
+
tablePath = resolveRelTarget(target);
|
|
5809
|
+
} else {
|
|
5810
|
+
const combined = dir + target;
|
|
5811
|
+
const parts = [];
|
|
5812
|
+
for (const seg of combined.split("/")) {
|
|
5813
|
+
if (seg === "..") parts.pop();
|
|
5814
|
+
else if (seg !== "." && seg !== "") parts.push(seg);
|
|
5815
|
+
}
|
|
5816
|
+
tablePath = parts.join("/");
|
|
5817
|
+
}
|
|
5818
|
+
tableTargets.push(tablePath);
|
|
5819
|
+
}
|
|
5820
|
+
} catch {
|
|
5821
|
+
return;
|
|
5822
|
+
}
|
|
5823
|
+
if (tableTargets.length === 0) return;
|
|
5824
|
+
const tables = [];
|
|
5825
|
+
for (const tablePath of tableTargets) {
|
|
5826
|
+
try {
|
|
5827
|
+
const hasEntry = !!this.zip.getEntry(tablePath);
|
|
5828
|
+
const hasStaged = this.stagedParts.has(tablePath);
|
|
5829
|
+
if (!hasEntry && !hasStaged) continue;
|
|
5830
|
+
const xml = this._entryData(tablePath).toString("utf8");
|
|
5831
|
+
const tableTagRe = /<(?:[A-Za-z_][\w.-]*:)?table\b[^>]*>/i;
|
|
5832
|
+
const tableMatch = tableTagRe.exec(xml);
|
|
5833
|
+
if (!tableMatch || tableMatch.index === void 0) continue;
|
|
5834
|
+
const refMatch = /\bref\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tableMatch[0]);
|
|
5835
|
+
const ref = refMatch ? refMatch[2] ?? refMatch[3] ?? "" : "";
|
|
5836
|
+
tables.push({
|
|
5837
|
+
path: tablePath,
|
|
5838
|
+
xml,
|
|
5839
|
+
tag: tableMatch[0],
|
|
5840
|
+
tagIndex: tableMatch.index,
|
|
5841
|
+
ref,
|
|
5842
|
+
range: parseCellRange(ref),
|
|
5843
|
+
staged: hasStaged
|
|
5844
|
+
});
|
|
5845
|
+
} catch {
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
if (tables.length === 0) return;
|
|
5849
|
+
const dimRange = parseCellRange(dimRef);
|
|
5850
|
+
if (!dimRange) return;
|
|
5851
|
+
for (const table of tables) {
|
|
5852
|
+
try {
|
|
5853
|
+
const origRange = table.range;
|
|
5854
|
+
if (!origRange) continue;
|
|
5855
|
+
if (origRange.r1 !== 1 || origRange.c1 !== 0) continue;
|
|
5856
|
+
if (dimRef === "A1" && !/^A1$/i.test(table.ref)) continue;
|
|
5857
|
+
const origCols = origRange.c2 - origRange.c1 + 1;
|
|
5858
|
+
const newCols = dimRange.c2 - dimRange.c1 + 1;
|
|
5859
|
+
if (newCols !== origCols) continue;
|
|
5860
|
+
let lastRow = dimRange.r2;
|
|
5861
|
+
for (const other of tables) {
|
|
5862
|
+
if (other === table || !other.range) continue;
|
|
5863
|
+
if (other.range.r1 <= 1) continue;
|
|
5864
|
+
if (other.range.c1 > origRange.c2 || other.range.c2 < origRange.c1) continue;
|
|
5865
|
+
lastRow = Math.min(lastRow, other.range.r1 - 1);
|
|
5866
|
+
}
|
|
5867
|
+
if (lastRow < 1) continue;
|
|
5868
|
+
const newRef = origRange.c2 === 0 && lastRow === 1 ? "A1" : `A1:${columnIndexToLetter(origRange.c2)}${lastRow}`;
|
|
5869
|
+
let xml = table.xml;
|
|
5870
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(table.tag, "ref", newRef);
|
|
5871
|
+
xml = xml.substring(0, table.tagIndex) + newTag + xml.substring(table.tagIndex + table.tag.length);
|
|
5872
|
+
const afRe = /<(?:[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i;
|
|
5873
|
+
const afMatch = afRe.exec(xml);
|
|
5874
|
+
if (afMatch && afMatch.index !== void 0) {
|
|
5875
|
+
const newAf = _XlsxUpdater._replaceOrAddXmlAttribute(afMatch[0], "ref", newRef);
|
|
5876
|
+
xml = xml.substring(0, afMatch.index) + newAf + xml.substring(afMatch.index + afMatch[0].length);
|
|
5877
|
+
}
|
|
5878
|
+
if (table.staged) {
|
|
5879
|
+
fs5.writeFileSync(this.stagedParts.get(table.path), xml, "utf8");
|
|
5880
|
+
} else {
|
|
5881
|
+
this.zip.updateFile(table.path, Buffer.from(xml, "utf8"));
|
|
5882
|
+
}
|
|
5883
|
+
} catch {
|
|
5884
|
+
}
|
|
4990
5885
|
}
|
|
4991
5886
|
}
|
|
4992
5887
|
};
|
|
@@ -4994,78 +5889,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4994
5889
|
// src/XlsbUpdater.ts
|
|
4995
5890
|
var fs6 = __toESM(require("fs"));
|
|
4996
5891
|
var os2 = __toESM(require("os"));
|
|
4997
|
-
var
|
|
5892
|
+
var path7 = __toESM(require("path"));
|
|
4998
5893
|
var import_adm_zip3 = __toESM(require("adm-zip"));
|
|
4999
|
-
|
|
5000
|
-
// src/biff12Utils.ts
|
|
5001
|
-
function readVlq(buf, pos) {
|
|
5002
|
-
let value = 0;
|
|
5003
|
-
let shift = 0;
|
|
5004
|
-
let byte;
|
|
5005
|
-
do {
|
|
5006
|
-
byte = buf[pos++];
|
|
5007
|
-
value |= (byte & 127) << shift;
|
|
5008
|
-
shift += 7;
|
|
5009
|
-
} while ((byte & 128) !== 0);
|
|
5010
|
-
return { value, nextPos: pos };
|
|
5011
|
-
}
|
|
5012
|
-
function vlqBytes(value) {
|
|
5013
|
-
const out = [];
|
|
5014
|
-
while (value >= 128) {
|
|
5015
|
-
out.push(value & 127 | 128);
|
|
5016
|
-
value >>>= 7;
|
|
5017
|
-
}
|
|
5018
|
-
out.push(value & 127);
|
|
5019
|
-
return out;
|
|
5020
|
-
}
|
|
5021
|
-
function readRecord(buf, pos, rec) {
|
|
5022
|
-
const idRes = readVlq(buf, pos);
|
|
5023
|
-
if (idRes.nextPos > buf.length) return false;
|
|
5024
|
-
const lenRes = readVlq(buf, idRes.nextPos);
|
|
5025
|
-
if (lenRes.nextPos + lenRes.value > buf.length) return false;
|
|
5026
|
-
rec.headerStart = pos;
|
|
5027
|
-
rec.dataStart = lenRes.nextPos;
|
|
5028
|
-
rec.dataEnd = lenRes.nextPos + lenRes.value;
|
|
5029
|
-
rec.id = idRes.value;
|
|
5030
|
-
rec.len = lenRes.value;
|
|
5031
|
-
return true;
|
|
5032
|
-
}
|
|
5033
|
-
function buildRecord(id, payload) {
|
|
5034
|
-
const idBytes = vlqBytes(id);
|
|
5035
|
-
const lenBytes = vlqBytes(payload.length);
|
|
5036
|
-
const header = Buffer.alloc(idBytes.length + lenBytes.length);
|
|
5037
|
-
idBytes.forEach((b, i) => header[i] = b);
|
|
5038
|
-
lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
|
|
5039
|
-
return Buffer.concat([header, payload]);
|
|
5040
|
-
}
|
|
5041
|
-
function readUtf16(buf, start, charCount) {
|
|
5042
|
-
const end = start + charCount * 2;
|
|
5043
|
-
if (end > buf.length) return "";
|
|
5044
|
-
return buf.toString("utf16le", start, end);
|
|
5045
|
-
}
|
|
5046
|
-
function parseSharedStringsBin(buf) {
|
|
5047
|
-
const values = [];
|
|
5048
|
-
let total = 0;
|
|
5049
|
-
let unique = 0;
|
|
5050
|
-
let endSstOffset = buf.length;
|
|
5051
|
-
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5052
|
-
let pos = 0;
|
|
5053
|
-
while (readRecord(buf, pos, rec)) {
|
|
5054
|
-
pos = rec.dataEnd;
|
|
5055
|
-
if (rec.id === 159) {
|
|
5056
|
-
total = buf.readUInt32LE(rec.dataStart);
|
|
5057
|
-
unique = buf.readUInt32LE(rec.dataStart + 4);
|
|
5058
|
-
} else if (rec.id === 19) {
|
|
5059
|
-
const cch = buf.readUInt32LE(rec.dataStart + 1);
|
|
5060
|
-
values.push(readUtf16(buf, rec.dataStart + 5, cch));
|
|
5061
|
-
} else if (rec.id === 160) {
|
|
5062
|
-
endSstOffset = rec.headerStart;
|
|
5063
|
-
}
|
|
5064
|
-
}
|
|
5065
|
-
return { values, total, unique, endSstOffset };
|
|
5066
|
-
}
|
|
5067
|
-
|
|
5068
|
-
// src/XlsbUpdater.ts
|
|
5069
5894
|
var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
|
5070
5895
|
var RK_INT_LOWER = -1 << 29;
|
|
5071
5896
|
var RK_INT_UPPER = (1 << 29) - 1;
|
|
@@ -5169,6 +5994,7 @@ var XlsbUpdater = class {
|
|
|
5169
5994
|
const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
|
|
5170
5995
|
const colLast = lastCol;
|
|
5171
5996
|
this._patchPivotCaches(sheetName, rwLast, colLast);
|
|
5997
|
+
this._removeCalcChain();
|
|
5172
5998
|
}
|
|
5173
5999
|
/** Replace worksheet rows from a one-pass source. */
|
|
5174
6000
|
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
@@ -5236,10 +6062,10 @@ var XlsbUpdater = class {
|
|
|
5236
6062
|
if (outputError) throw outputError;
|
|
5237
6063
|
writtenBytes += data.length;
|
|
5238
6064
|
if (rowOutput.write(data)) return;
|
|
5239
|
-
await new Promise((
|
|
6065
|
+
await new Promise((resolve6, reject) => {
|
|
5240
6066
|
const onDrain = () => {
|
|
5241
6067
|
cleanup();
|
|
5242
|
-
|
|
6068
|
+
resolve6();
|
|
5243
6069
|
};
|
|
5244
6070
|
const onError = (error) => {
|
|
5245
6071
|
cleanup();
|
|
@@ -5290,8 +6116,8 @@ var XlsbUpdater = class {
|
|
|
5290
6116
|
lastKeptEnd = writtenBytes;
|
|
5291
6117
|
}
|
|
5292
6118
|
}
|
|
5293
|
-
await new Promise((
|
|
5294
|
-
rowOutput.once("close",
|
|
6119
|
+
await new Promise((resolve6, reject) => {
|
|
6120
|
+
rowOutput.once("close", resolve6);
|
|
5295
6121
|
rowOutput.once("error", reject);
|
|
5296
6122
|
rowOutput.end();
|
|
5297
6123
|
});
|
|
@@ -5354,6 +6180,7 @@ var XlsbUpdater = class {
|
|
|
5354
6180
|
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
5355
6181
|
lastCol
|
|
5356
6182
|
);
|
|
6183
|
+
this._removeCalcChain();
|
|
5357
6184
|
if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
|
|
5358
6185
|
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
5359
6186
|
} catch (error) {
|
|
@@ -5424,7 +6251,7 @@ var XlsbUpdater = class {
|
|
|
5424
6251
|
}
|
|
5425
6252
|
_ensureTemporaryDirectory() {
|
|
5426
6253
|
if (this.temporaryDirectory === null) {
|
|
5427
|
-
this.temporaryDirectory = fs6.mkdtempSync(
|
|
6254
|
+
this.temporaryDirectory = fs6.mkdtempSync(path7.join(os2.tmpdir(), "spreadsheet-updater-"));
|
|
5428
6255
|
} else {
|
|
5429
6256
|
try {
|
|
5430
6257
|
fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
@@ -5434,7 +6261,7 @@ var XlsbUpdater = class {
|
|
|
5434
6261
|
return this.temporaryDirectory;
|
|
5435
6262
|
}
|
|
5436
6263
|
_temporaryPath(suffix) {
|
|
5437
|
-
return
|
|
6264
|
+
return path7.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
5438
6265
|
}
|
|
5439
6266
|
_removeTemporaryFile(filePath) {
|
|
5440
6267
|
try {
|
|
@@ -5474,10 +6301,15 @@ var XlsbUpdater = class {
|
|
|
5474
6301
|
const rIdToTarget = {};
|
|
5475
6302
|
if (relsEntry) {
|
|
5476
6303
|
const rels = relsEntry.getData().toString("utf8");
|
|
5477
|
-
const
|
|
5478
|
-
let
|
|
5479
|
-
while ((
|
|
5480
|
-
|
|
6304
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
6305
|
+
let tagMatch;
|
|
6306
|
+
while ((tagMatch = relTagRe.exec(rels)) !== null) {
|
|
6307
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
6308
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
6309
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
6310
|
+
if (id !== void 0 && target !== void 0) {
|
|
6311
|
+
rIdToTarget[id] = target;
|
|
6312
|
+
}
|
|
5481
6313
|
}
|
|
5482
6314
|
}
|
|
5483
6315
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
@@ -5497,12 +6329,24 @@ var XlsbUpdater = class {
|
|
|
5497
6329
|
o += 4;
|
|
5498
6330
|
const name = readUtf16(wbBuf, o, nameLen);
|
|
5499
6331
|
let target = rIdToTarget[rId] ?? "";
|
|
5500
|
-
|
|
5501
|
-
else if (!target.startsWith("xl/")) target = "xl/" + target;
|
|
6332
|
+
target = resolveRelTarget(target);
|
|
5502
6333
|
if (target) {
|
|
5503
6334
|
this.sheetNameToPath.set(name, target);
|
|
5504
6335
|
}
|
|
5505
6336
|
}
|
|
6337
|
+
try {
|
|
6338
|
+
const rec1904 = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
6339
|
+
let p1904 = 0;
|
|
6340
|
+
while (readRecord(wbBuf, p1904, rec1904)) {
|
|
6341
|
+
if (rec1904.id === 153 && rec1904.len >= 4) {
|
|
6342
|
+
const is1904 = (wbBuf[rec1904.dataStart] & 1) !== 0;
|
|
6343
|
+
this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
6344
|
+
break;
|
|
6345
|
+
}
|
|
6346
|
+
p1904 = rec1904.dataEnd;
|
|
6347
|
+
}
|
|
6348
|
+
} catch {
|
|
6349
|
+
}
|
|
5506
6350
|
}
|
|
5507
6351
|
for (const e of this.zip.getEntries()) {
|
|
5508
6352
|
if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
|
|
@@ -5611,27 +6455,15 @@ var XlsbUpdater = class {
|
|
|
5611
6455
|
_findDateXf() {
|
|
5612
6456
|
const entry = this.zip.getEntry("xl/styles.bin");
|
|
5613
6457
|
if (!entry) return null;
|
|
5614
|
-
const
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
if (
|
|
5622
|
-
|
|
5623
|
-
xfIndex = 0;
|
|
5624
|
-
continue;
|
|
5625
|
-
}
|
|
5626
|
-
if (rec.id === 618) {
|
|
5627
|
-
inCellXfs = false;
|
|
5628
|
-
continue;
|
|
5629
|
-
}
|
|
5630
|
-
if (inCellXfs && rec.id === 47 && rec.len >= 4) {
|
|
5631
|
-
const numFmtId = stylesBuf.readUInt16LE(rec.dataStart + 2);
|
|
5632
|
-
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47;
|
|
5633
|
-
if (isDate) return xfIndex;
|
|
5634
|
-
xfIndex++;
|
|
6458
|
+
const reader = new BiffReaderWriter(entry.getData());
|
|
6459
|
+
while (reader.readStyles()) {
|
|
6460
|
+
}
|
|
6461
|
+
const numFmtIds = reader._xfIndexToNumFmtId;
|
|
6462
|
+
const customDates = reader._customNumFmts;
|
|
6463
|
+
for (let i = 0; i < numFmtIds.length; i++) {
|
|
6464
|
+
const numFmtId = numFmtIds[i];
|
|
6465
|
+
if (numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || customDates.has(numFmtId)) {
|
|
6466
|
+
return i;
|
|
5635
6467
|
}
|
|
5636
6468
|
}
|
|
5637
6469
|
return null;
|
|
@@ -5711,7 +6543,7 @@ var XlsbUpdater = class {
|
|
|
5711
6543
|
return buildRecord(4, payload);
|
|
5712
6544
|
}
|
|
5713
6545
|
if (val instanceof Date) {
|
|
5714
|
-
const oaDate = (val.getTime()
|
|
6546
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
5715
6547
|
if (Number.isFinite(oaDate)) {
|
|
5716
6548
|
const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
|
|
5717
6549
|
const payload = Buffer.alloc(16);
|
|
@@ -5778,10 +6610,10 @@ var XlsbUpdater = class {
|
|
|
5778
6610
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5779
6611
|
let pos = 0;
|
|
5780
6612
|
while (readRecord(originalSheet, pos, rec)) {
|
|
5781
|
-
if (rec.id ===
|
|
6613
|
+
if (rec.id === 148 && rec.len >= 16) {
|
|
5782
6614
|
const shift = rec.dataStart >= rowsEnd ? delta : 0;
|
|
5783
|
-
patchInt32(rec.dataStart + shift +
|
|
5784
|
-
patchInt32(rec.dataStart + shift +
|
|
6615
|
+
patchInt32(rec.dataStart + shift + 4, lastRowIdx);
|
|
6616
|
+
patchInt32(rec.dataStart + shift + 12, lastCol);
|
|
5785
6617
|
break;
|
|
5786
6618
|
}
|
|
5787
6619
|
pos = rec.dataEnd;
|
|
@@ -5820,8 +6652,8 @@ var XlsbUpdater = class {
|
|
|
5820
6652
|
return { rowsStart, rowsEnd, autoFilterRanges };
|
|
5821
6653
|
}
|
|
5822
6654
|
/**
|
|
5823
|
-
* Keep the sheet dimension record (
|
|
5824
|
-
* Excel uses its rwLast (payload offset
|
|
6655
|
+
* Keep the sheet dimension record (0x94) in sync with the new data.
|
|
6656
|
+
* Excel uses its rwLast (payload offset 4) and colLast (payload offset 12)
|
|
5825
6657
|
* fields to read the full row range; stale values make the last written row
|
|
5826
6658
|
* invisible to Excel (and drop it from pivot refreshes).
|
|
5827
6659
|
*/
|
|
@@ -5829,9 +6661,9 @@ var XlsbUpdater = class {
|
|
|
5829
6661
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5830
6662
|
let pos = 0;
|
|
5831
6663
|
while (readRecord(sheetBuf, pos, rec)) {
|
|
5832
|
-
if (rec.id ===
|
|
5833
|
-
sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart +
|
|
5834
|
-
sheetBuf.writeInt32LE(lastCol, rec.dataStart +
|
|
6664
|
+
if (rec.id === 148 && rec.len >= 16) {
|
|
6665
|
+
sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 4);
|
|
6666
|
+
sheetBuf.writeInt32LE(lastCol, rec.dataStart + 12);
|
|
5835
6667
|
return;
|
|
5836
6668
|
}
|
|
5837
6669
|
pos = rec.dataEnd;
|
|
@@ -5841,8 +6673,8 @@ var XlsbUpdater = class {
|
|
|
5841
6673
|
// Pivot cache integration
|
|
5842
6674
|
// ------------------------------------------------------------------
|
|
5843
6675
|
_patchPivotCaches(sheetName, rwLast, colLast) {
|
|
5844
|
-
for (const
|
|
5845
|
-
const entry = this.zip.getEntry(
|
|
6676
|
+
for (const path9 of this.pivotCacheDefPaths) {
|
|
6677
|
+
const entry = this.zip.getEntry(path9);
|
|
5846
6678
|
if (!entry) continue;
|
|
5847
6679
|
const buf = Buffer.from(entry.getData());
|
|
5848
6680
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
@@ -5874,16 +6706,84 @@ var XlsbUpdater = class {
|
|
|
5874
6706
|
}
|
|
5875
6707
|
}
|
|
5876
6708
|
if (patched) {
|
|
5877
|
-
this.zip.updateFile(
|
|
6709
|
+
this.zip.updateFile(path9, buf);
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
}
|
|
6713
|
+
/**
|
|
6714
|
+
* Remove the stale calculation chain (xl/calcChain.bin). Excel rebuilds it
|
|
6715
|
+
* on open. Removes the part, its Content_Types override and its workbook
|
|
6716
|
+
* rels entry. XLSB tables are not patched (documented limitation).
|
|
6717
|
+
*/
|
|
6718
|
+
_removeCalcChain() {
|
|
6719
|
+
const calcPath = "xl/calcChain.bin";
|
|
6720
|
+
if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) return;
|
|
6721
|
+
try {
|
|
6722
|
+
this.zip.deleteFile(calcPath);
|
|
6723
|
+
} catch {
|
|
6724
|
+
}
|
|
6725
|
+
const stagedCalc = this.stagedParts.get(calcPath);
|
|
6726
|
+
if (stagedCalc) {
|
|
6727
|
+
this.stagedParts.delete(calcPath);
|
|
6728
|
+
try {
|
|
6729
|
+
fs6.rmSync(stagedCalc, { force: true });
|
|
6730
|
+
} catch {
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
const ctPath = "[Content_Types].xml";
|
|
6734
|
+
if (this.zip.getEntry(ctPath) || this.stagedParts.has(ctPath)) {
|
|
6735
|
+
try {
|
|
6736
|
+
const ct = this._entryData(ctPath).toString("utf8");
|
|
6737
|
+
const updated = ct.replace(
|
|
6738
|
+
/<Override\b[^>]*PartName="\/xl\/calcChain\.bin"[^>]*\/?>/gi,
|
|
6739
|
+
""
|
|
6740
|
+
);
|
|
6741
|
+
if (updated !== ct) this._writePartText(ctPath, updated);
|
|
6742
|
+
} catch {
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
const relsPath = "xl/_rels/workbook.bin.rels";
|
|
6746
|
+
if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
|
|
6747
|
+
try {
|
|
6748
|
+
const rels = this._entryData(relsPath).toString("utf8");
|
|
6749
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
6750
|
+
let m;
|
|
6751
|
+
let out = "";
|
|
6752
|
+
let last = 0;
|
|
6753
|
+
let changed = false;
|
|
6754
|
+
while ((m = relTagRe.exec(rels)) !== null) {
|
|
6755
|
+
const attrs = parseTagAttributes(m[0]);
|
|
6756
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
6757
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
6758
|
+
if (type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.bin") {
|
|
6759
|
+
out += rels.substring(last, m.index);
|
|
6760
|
+
last = m.index + m[0].length;
|
|
6761
|
+
changed = true;
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
if (changed) {
|
|
6765
|
+
out += rels.substring(last);
|
|
6766
|
+
this._writePartText(relsPath, out);
|
|
6767
|
+
}
|
|
6768
|
+
} catch {
|
|
5878
6769
|
}
|
|
5879
6770
|
}
|
|
5880
6771
|
}
|
|
6772
|
+
/** Write an XML/text part to its staged file when present, else into the ZIP. */
|
|
6773
|
+
_writePartText(name, text) {
|
|
6774
|
+
const stagedPath = this.stagedParts.get(name);
|
|
6775
|
+
if (stagedPath) {
|
|
6776
|
+
fs6.writeFileSync(stagedPath, text, "utf8");
|
|
6777
|
+
} else {
|
|
6778
|
+
this.zip.updateFile(name, Buffer.from(text, "utf8"));
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
5881
6781
|
};
|
|
5882
6782
|
|
|
5883
6783
|
// src/ExcelConverter.ts
|
|
5884
6784
|
var fs7 = __toESM(require("fs"));
|
|
5885
|
-
var
|
|
5886
|
-
var
|
|
6785
|
+
var path8 = __toESM(require("path"));
|
|
6786
|
+
var import_crypto5 = require("crypto");
|
|
5887
6787
|
var import_child_process = require("child_process");
|
|
5888
6788
|
var POWERSHELL_CONVERSION_SCRIPT = `
|
|
5889
6789
|
$ErrorActionPreference = 'Stop'
|
|
@@ -5929,14 +6829,14 @@ function errorMessage2(error) {
|
|
|
5929
6829
|
return error instanceof Error ? error.message : String(error);
|
|
5930
6830
|
}
|
|
5931
6831
|
function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
|
|
5932
|
-
const input =
|
|
5933
|
-
const output =
|
|
5934
|
-
if (
|
|
6832
|
+
const input = path8.resolve(inputPath);
|
|
6833
|
+
const output = path8.resolve(outputPath);
|
|
6834
|
+
if (path8.extname(input).toLowerCase() !== inputExtension) {
|
|
5935
6835
|
throw new Error(
|
|
5936
6836
|
`Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
|
|
5937
6837
|
);
|
|
5938
6838
|
}
|
|
5939
|
-
if (
|
|
6839
|
+
if (path8.extname(output).toLowerCase() !== outputExtension) {
|
|
5940
6840
|
throw new Error(
|
|
5941
6841
|
`Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
|
|
5942
6842
|
);
|
|
@@ -5947,8 +6847,8 @@ function validateConversionPaths(inputPath, outputPath, inputExtension, outputEx
|
|
|
5947
6847
|
if (!fs7.statSync(input).isFile()) {
|
|
5948
6848
|
throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
|
|
5949
6849
|
}
|
|
5950
|
-
if (!fs7.existsSync(
|
|
5951
|
-
throw new Error(`Excel conversion: destination directory not found: ${
|
|
6850
|
+
if (!fs7.existsSync(path8.dirname(output))) {
|
|
6851
|
+
throw new Error(`Excel conversion: destination directory not found: ${path8.dirname(output)}`);
|
|
5952
6852
|
}
|
|
5953
6853
|
if (fs7.existsSync(output)) {
|
|
5954
6854
|
if (fs7.statSync(output).isDirectory()) {
|
|
@@ -5973,7 +6873,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5973
6873
|
JUSTYBASE_EXCEL_OUTPUT: outputPath,
|
|
5974
6874
|
JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
|
|
5975
6875
|
};
|
|
5976
|
-
return new Promise((
|
|
6876
|
+
return new Promise((resolve6, reject) => {
|
|
5977
6877
|
var _a, _b, _c, _d;
|
|
5978
6878
|
let stdout = "";
|
|
5979
6879
|
let stderr = "";
|
|
@@ -6008,7 +6908,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
6008
6908
|
if (settled) return;
|
|
6009
6909
|
settled = true;
|
|
6010
6910
|
if (code === 0) {
|
|
6011
|
-
|
|
6911
|
+
resolve6();
|
|
6012
6912
|
return;
|
|
6013
6913
|
}
|
|
6014
6914
|
const details = (stderr || stdout).replace(/\s+/g, " ").trim();
|
|
@@ -6029,9 +6929,9 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
|
|
|
6029
6929
|
if (process.platform !== "win32") {
|
|
6030
6930
|
throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
|
|
6031
6931
|
}
|
|
6032
|
-
const temporaryOutput =
|
|
6033
|
-
|
|
6034
|
-
`.${
|
|
6932
|
+
const temporaryOutput = path8.join(
|
|
6933
|
+
path8.dirname(output),
|
|
6934
|
+
`.${path8.basename(output, outputExtension)}.${(0, import_crypto5.randomUUID)()}.conversion${outputExtension}`
|
|
6035
6935
|
);
|
|
6036
6936
|
try {
|
|
6037
6937
|
await runPowerShellConversion(input, temporaryOutput, fileFormat);
|
|
@@ -6063,6 +6963,9 @@ function convertXlsxToXlsb(inputPath, outputPath, options) {
|
|
|
6063
6963
|
ExcelReaderAbstract,
|
|
6064
6964
|
F,
|
|
6065
6965
|
INVALID_SHEET_NAME_CHARS,
|
|
6966
|
+
MAX_CELL_CHARS,
|
|
6967
|
+
MAX_COLS,
|
|
6968
|
+
MAX_ROWS,
|
|
6066
6969
|
ReaderFactory,
|
|
6067
6970
|
StreamingSheetState,
|
|
6068
6971
|
XlsbReader,
|
|
@@ -6073,13 +6976,27 @@ function convertXlsxToXlsb(inputPath, outputPath, options) {
|
|
|
6073
6976
|
XlsxUpdater,
|
|
6074
6977
|
XlsxWriter,
|
|
6075
6978
|
applyHeaderWidths,
|
|
6979
|
+
assertCellTextLength,
|
|
6980
|
+
assertColumnCount,
|
|
6981
|
+
assertRowCount,
|
|
6982
|
+
assertRowFitsColumns,
|
|
6983
|
+
columnIndexToLetter,
|
|
6984
|
+
columnLetterToIndex,
|
|
6076
6985
|
convertXlsbToXlsx,
|
|
6077
6986
|
convertXlsxToXlsb,
|
|
6078
6987
|
defaultColWidth,
|
|
6988
|
+
escapeXlsxText,
|
|
6989
|
+
escapeXmlText,
|
|
6079
6990
|
getFormat,
|
|
6080
6991
|
initColWidths,
|
|
6081
6992
|
isFormattedCell,
|
|
6993
|
+
parseSharedStringsXml,
|
|
6994
|
+
parseTagAttributes,
|
|
6995
|
+
resolveRelTarget,
|
|
6082
6996
|
sanitizeSheetName,
|
|
6997
|
+
trimTrailingEmptyRows,
|
|
6998
|
+
unescapeXml,
|
|
6999
|
+
unescapeXmlText,
|
|
6083
7000
|
unwrapCell,
|
|
6084
7001
|
updateColWidthsFromRows
|
|
6085
7002
|
});
|