@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/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
- const ms = oaDate * 864e5 + this._oaEpoch;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\r/g, "&#13;");
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 "&amp;":
648
+ return "&";
649
+ case "&lt;":
650
+ return "<";
651
+ case "&gt;":
652
+ return ">";
653
+ case "&quot;":
654
+ return '"';
655
+ case "&apos;":
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(path7, readSharedStrings = true) {
465
- this.zip = new import_adm_zip.default(path7);
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 relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
483
- let match;
872
+ const relTagRe = /<Relationship\b[^>]*>/g;
873
+ let tagMatch;
484
874
  const rIdToTarget = {};
485
- while ((match = relRegex.exec(xml)) !== null) {
486
- rIdToTarget[match[1]] = match[2];
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
- let target = rIdToTarget[sheet.rId];
884
+ const target = rIdToTarget[sheet.rId];
490
885
  if (target) {
491
- if (target.startsWith("/")) target = target.substring(1);
492
- if (!target.startsWith("xl/")) target = "xl/" + target;
493
- sheet.path = target;
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(path7) {
511
- const entry = this.zip.getEntry(path7);
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(path7) {
521
- const entry = this.zip.getEntry(path7);
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 false;
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
- // src/xmlUtils.ts
613
- function hasInvalidControlChars(str) {
614
- for (let i = 0; i < str.length; i++) {
615
- const c = str.charCodeAt(i);
616
- if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
617
- return true;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
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(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/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(path7, readSharedStrings = true) {
705
- return new Promise((resolve4, reject) => {
706
- import_yauzl.default.open(path7, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
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 relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
720
- let match;
721
- while ((match = relRegex.exec(wbRelsContent)) !== null) {
722
- rIdToTarget[match[1]] = match[2];
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 sheetRegex = /<sheet[^>]*name="([^"]*)"[^>]*sheetId="([^"]*)"[^>]*r:id="([^"]*)"/g;
728
- let match;
729
- while ((match = sheetRegex.exec(wbContent)) !== null) {
730
- const name = unescapeXml(match[1]);
731
- const sheetId = match[2];
732
- const rId = match[3];
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
- let fullPath = target;
735
- if (!fullPath.startsWith("xl/")) {
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
- resolve4();
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(path7) {
769
- const entry = this.entries.get(path7);
1178
+ async _readZipEntryContent(path9) {
1179
+ const entry = this.entries.get(path9);
770
1180
  if (!entry) return null;
771
- return new Promise((resolve4, reject) => {
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
- resolve4(Buffer.concat(chunks).toString("utf8"));
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) this._xmlPos = sd + 11;
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
- const rowStart = this._sheetXml.indexOf("<row", this._xmlPos);
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
- const tStart = xml.indexOf("<t", nextTagClose);
899
- if (tStart !== -1 && tStart < cellContentEnd) {
900
- const tContentStart = xml.indexOf(">", tStart) + 1;
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 !== -1 && tEnd < cellContentEnd) {
903
- val = xml.substring(tContentStart, tEnd);
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 (val !== null) {
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 !== -1) {
937
- this._currentRow[colIndex] = finalVal;
938
- if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
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
- constructor(filePath) {
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
- this.output = fs.createWriteStream(filePath);
1125
- this.archive = (0, import_archiver.default)("zip");
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() - this._oaEpoch) / 864e5;
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() - this._oaEpoch) / 864e5;
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() - this._oaEpoch) / 864e5;
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((resolve4, reject) => {
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.on("close", () => resolve4());
3482
- this.archive.on("error", (err) => reject(err));
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
- reject(err);
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
- constructor(filePath) {
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
- this.output = fs2.createWriteStream(filePath);
3528
- this.archive = (0, import_archiver2.default)("zip");
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 < COLUMN_LETTERS.length ? COLUMN_LETTERS[colIndex] : "A";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
4118
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\r/g, "&#13;").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], colLetters[c], this.stream.rowNum, 3);
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 colRef = this.currentColLetters[c];
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 r="${colRef}${this.stream.rowNum}"${styleAttr}><v>${val}</v></c>`);
4199
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
3653
4200
  } else {
3654
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
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(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4204
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3658
4205
  } else if (typeof val === "boolean") {
3659
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></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 r="${colRef}${this.stream.rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4211
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
3665
4212
  } else {
3666
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
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(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
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], colLetters[c], rowNum, 3);
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 colRef = colLetters[c];
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 r="${colRef}${rowNum}"${styleAttr}><v>${val}</v></c>`);
4322
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
3765
4323
  } else {
3766
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
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(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4327
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3770
4328
  } else if (typeof val === "boolean") {
3771
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></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 r="${colRef}${rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4334
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
3777
4335
  } else {
3778
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
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(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
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
- _writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
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 r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></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() - this._oaEpoch) / 864e5;
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((resolve4, reject) => {
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.on("close", () => {
3837
- resolve4();
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
- reject(err);
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 path4 = __toESM(require("path"));
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 path2 = __toESM(require("path"));
3982
- var import_crypto = require("crypto");
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, import_crypto.randomUUID)()}.replacement-backup`;
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 = path2.resolve(destinationPath);
4054
- fs3.mkdirSync(path2.dirname(target), { recursive: true });
4055
- const temporaryPath = path2.join(
4056
- path2.dirname(target),
4057
- `.${path2.basename(target)}.${(0, import_crypto.randomUUID)()}.tmp`
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 path3 = __toESM(require("path"));
4094
- var import_crypto2 = require("crypto");
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((resolve4, reject) => {
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
- resolve4();
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 = path3.resolve(destinationPath);
4140
- fs4.mkdirSync(path3.dirname(target), { recursive: true });
4141
- const temporaryPath = path3.join(
4142
- path3.dirname(target),
4143
- `.${path3.basename(target)}.${(0, import_crypto2.randomUUID)()}.tmp`
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
- archive = (0, import_archiver3.default)("zip");
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((resolve4, reject) => {
5005
+ await new Promise((resolve6, reject) => {
4357
5006
  const onDrain = () => {
4358
5007
  cleanup();
4359
- resolve4();
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((resolve4, reject) => {
4400
- rowOutput.once("close", resolve4);
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(path4.join(os.tmpdir(), "spreadsheet-updater-"));
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 path4.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
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 relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
4543
- let match2;
4544
- while ((match2 = relRegex.exec(rels)) !== null) {
4545
- rIdToTarget[match2[1]] = match2[2];
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 nameMatch = /name="([^"]*)"/.exec(tag);
4555
- const rIdMatch = /r:id="([^"]*)"/.exec(tag);
4556
- if (!nameMatch || !rIdMatch) continue;
4557
- const name = nameMatch[1].replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
4558
- const target = rIdToTarget[rIdMatch[1]] ?? "";
4559
- let fullPath = target;
4560
- if (fullPath.startsWith("/")) {
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 = escapeXmlText(txt);
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
- if (!rMatch) continue;
4657
- const col = columnLetterToIndex(rMatch[1]);
4658
- const row = parseInt(rMatch[2], 10);
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() - this._oaEpoch) / 864e5;
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 = escapeXmlText(text);
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 path7 of this.pivotTableDefPaths) {
4981
- const entry = this.zip.getEntry(path7);
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(path7, Buffer.from(xml, "utf8"));
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 path5 = __toESM(require("path"));
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((resolve4, reject) => {
6065
+ await new Promise((resolve6, reject) => {
5240
6066
  const onDrain = () => {
5241
6067
  cleanup();
5242
- resolve4();
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((resolve4, reject) => {
5294
- rowOutput.once("close", resolve4);
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(path5.join(os2.tmpdir(), "spreadsheet-updater-"));
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 path5.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
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 relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
5478
- let match;
5479
- while ((match = relRegex.exec(rels)) !== null) {
5480
- rIdToTarget[match[1]] = match[2];
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
- if (target.startsWith("/")) target = target.substring(1);
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 stylesBuf = entry.getData();
5615
- const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
5616
- let pos = 0;
5617
- let inCellXfs = false;
5618
- let xfIndex = 0;
5619
- while (readRecord(stylesBuf, pos, rec)) {
5620
- pos = rec.dataEnd;
5621
- if (rec.id === 617) {
5622
- inCellXfs = true;
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() - this._oaEpoch) / 864e5;
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 === 152 && rec.len >= 36) {
6613
+ if (rec.id === 148 && rec.len >= 16) {
5782
6614
  const shift = rec.dataStart >= rowsEnd ? delta : 0;
5783
- patchInt32(rec.dataStart + shift + 24, lastRowIdx);
5784
- patchInt32(rec.dataStart + shift + 32, lastCol);
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 (0x98) in sync with the new data.
5824
- * Excel uses its rwLast (payload offset 24) and colLast (payload offset 32)
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 === 152 && rec.len >= 36) {
5833
- sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 24);
5834
- sheetBuf.writeInt32LE(lastCol, rec.dataStart + 32);
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 path7 of this.pivotCacheDefPaths) {
5845
- const entry = this.zip.getEntry(path7);
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(path7, buf);
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 path6 = __toESM(require("path"));
5886
- var import_crypto3 = require("crypto");
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 = path6.resolve(inputPath);
5933
- const output = path6.resolve(outputPath);
5934
- if (path6.extname(input).toLowerCase() !== inputExtension) {
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 (path6.extname(output).toLowerCase() !== outputExtension) {
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(path6.dirname(output))) {
5951
- throw new Error(`Excel conversion: destination directory not found: ${path6.dirname(output)}`);
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((resolve4, reject) => {
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
- resolve4();
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 = path6.join(
6033
- path6.dirname(output),
6034
- `.${path6.basename(output, outputExtension)}.${(0, import_crypto3.randomUUID)()}.conversion${outputExtension}`
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
  });