@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/esm/index.js CHANGED
@@ -112,6 +112,153 @@ var BigBuffer = class {
112
112
  }
113
113
  };
114
114
 
115
+ // src/Formats.ts
116
+ var F = {
117
+ THOUSANDS_SEP: "#,##0",
118
+ CURRENCY_PLN: '#,##0.00 "z\u0142"',
119
+ CURRENCY_EUR: "#,##0.00 \u20AC",
120
+ PERCENTAGE: "0%",
121
+ SCIENTIFIC: "0.00E+00",
122
+ TWO_DECIMALS: "#,##0.00",
123
+ TEXT: "@",
124
+ LEADING_ZEROS: "000000000",
125
+ DATE_SHORT: "dd.mm.yyyy",
126
+ DATE_LONG: "d mmmm yyyy",
127
+ DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
128
+ DATE_ISO: "yyyy-mm-dd",
129
+ DATE_MONTH_YEAR: "mmmm yyyy",
130
+ DATE_WEEKDAY: "dddd, d mmmm yyyy",
131
+ DATE_DAY_MONTH: "d mmmm",
132
+ DATE_YEAR_ONLY: "yyyy",
133
+ DATETIME_SHORT: "dd.mm.yyyy hh:mm",
134
+ DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
135
+ TIME_HH_MM: "hh:mm",
136
+ TIME_HH_MM_SS: "hh:mm:ss",
137
+ TIME_12H: "h:mm AM/PM",
138
+ DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
139
+ DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
140
+ TIME_MS: "hh:mm:ss.000"
141
+ };
142
+ function isFormattedCell(val) {
143
+ return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
144
+ }
145
+ function unwrapCell(val) {
146
+ if (isFormattedCell(val)) {
147
+ return val.value;
148
+ }
149
+ return val;
150
+ }
151
+ function getFormat(val) {
152
+ if (isFormattedCell(val)) {
153
+ return val.format;
154
+ }
155
+ return null;
156
+ }
157
+
158
+ // src/writerHelpers.ts
159
+ var OA_EPOCH_1900 = Date.UTC(1899, 11, 30);
160
+ var MARCH_1_1900 = Date.UTC(1900, 2, 1);
161
+ function oaDateToTime(oaDate, epochMs) {
162
+ let serial = oaDate;
163
+ if (epochMs === OA_EPOCH_1900 && serial >= 1 && serial < 60) {
164
+ serial += 1;
165
+ }
166
+ return Math.round(serial * 864e5) + epochMs;
167
+ }
168
+ function timeToOaDate(timeMs, epochMs) {
169
+ let serial = (timeMs - epochMs) / 864e5;
170
+ if (epochMs === OA_EPOCH_1900 && timeMs < MARCH_1_1900) {
171
+ serial -= 1;
172
+ }
173
+ return serial;
174
+ }
175
+ var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
176
+ function sanitizeSheetName(name, sheetCount) {
177
+ if (!name || typeof name !== "string") {
178
+ return `Sheet${sheetCount + 1}`;
179
+ }
180
+ let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
181
+ if (sanitized.length > 31) {
182
+ sanitized = sanitized.substring(0, 31);
183
+ }
184
+ if (sanitized.trim().length === 0) {
185
+ sanitized = `Sheet${sheetCount + 1}`;
186
+ }
187
+ return sanitized;
188
+ }
189
+ var MAX_ROWS = 1048576;
190
+ var MAX_COLS = 16384;
191
+ var MAX_CELL_CHARS = 32767;
192
+ function assertCellTextLength(text) {
193
+ if (text.length > MAX_CELL_CHARS) {
194
+ throw new Error(
195
+ `Excel cell text exceeds ${MAX_CELL_CHARS} UTF-16 code units (got ${text.length}).`
196
+ );
197
+ }
198
+ }
199
+ function assertColumnCount(columnCount) {
200
+ if (!Number.isInteger(columnCount) || columnCount < 1 || columnCount > MAX_COLS) {
201
+ throw new Error(
202
+ `Excel column count must be 1..${MAX_COLS} (got ${columnCount}).`
203
+ );
204
+ }
205
+ }
206
+ function assertRowFitsColumns(kind, length, columnCount) {
207
+ if (length > columnCount) {
208
+ throw new Error(
209
+ `Excel ${kind} length ${length} exceeds columnCount ${columnCount}.`
210
+ );
211
+ }
212
+ }
213
+ function assertRowCount(totalRows) {
214
+ if (totalRows > MAX_ROWS) {
215
+ throw new Error(
216
+ `Excel row count exceeds ${MAX_ROWS} (got ${totalRows}).`
217
+ );
218
+ }
219
+ }
220
+ function initColWidths(columnCount) {
221
+ return new Array(columnCount).fill(-1);
222
+ }
223
+ function applyHeaderWidths(colWidths, headers, columnCount) {
224
+ for (let i = 0; i < columnCount; i++) {
225
+ const len = headers[i] ? headers[i].length + 1 : 0;
226
+ let width = 1.25 * len + 2;
227
+ if (width > 80) width = 80;
228
+ if (colWidths[i] < width) colWidths[i] = width;
229
+ }
230
+ }
231
+ function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
232
+ for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
233
+ const row = rows[r];
234
+ for (let c = 0; c < row.length; c++) {
235
+ const raw = row[c];
236
+ if (raw === null || raw === void 0) continue;
237
+ const val = isFormattedCell(raw) ? raw.value : raw;
238
+ const len = val instanceof Date ? 20 : String(val).length + 1;
239
+ let width = 1.25 * len + 2;
240
+ if (width > 80) width = 80;
241
+ if (colWidths[c] < width) colWidths[c] = width;
242
+ }
243
+ }
244
+ }
245
+ function defaultColWidth(width, floor = false) {
246
+ if (width > 0) {
247
+ return floor ? Math.floor(width) : width;
248
+ }
249
+ return 10;
250
+ }
251
+ function cellRefAttr(col, prevCol, colRef, rowNum) {
252
+ return col === prevCol + 1 ? "" : ` r="${colRef}${rowNum}"`;
253
+ }
254
+ var DEFAULT_COMPRESSION_LEVEL = 6;
255
+ function assertCompressionLevel(level) {
256
+ if (!Number.isInteger(level) || level < 0 || level > 9) {
257
+ throw new RangeError(`compressionLevel must be an integer from 0 to 9, got ${level}`);
258
+ }
259
+ return level;
260
+ }
261
+
115
262
  // src/ExcelReaderAbstract.ts
116
263
  var ExcelReaderAbstract = class {
117
264
  constructor() {
@@ -126,8 +273,7 @@ var ExcelReaderAbstract = class {
126
273
  dispose() {
127
274
  }
128
275
  getDateTimeFromOaDate(oaDate) {
129
- const ms = oaDate * 864e5 + this._oaEpoch;
130
- return new Date(ms);
276
+ return new Date(oaDateToTime(oaDate, this._oaEpoch));
131
277
  }
132
278
  };
133
279
 
@@ -387,6 +533,216 @@ var BiffReaderWriter = class {
387
533
 
388
534
  // src/XlsbReader.ts
389
535
  import AdmZip from "adm-zip";
536
+
537
+ // src/xmlUtils.ts
538
+ function hasInvalidControlChars(str) {
539
+ for (let i = 0; i < str.length; i++) {
540
+ const c = str.charCodeAt(i);
541
+ if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
542
+ return true;
543
+ }
544
+ }
545
+ return false;
546
+ }
547
+ function stripInvalidControlChars(str) {
548
+ let out = "";
549
+ for (let i = 0; i < str.length; i++) {
550
+ const c = str.charCodeAt(i);
551
+ if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
552
+ out += str[i];
553
+ }
554
+ }
555
+ return out;
556
+ }
557
+ function escapeXmlText(str) {
558
+ if (!/[&<>"'\r]/.test(str) && !hasInvalidControlChars(str)) {
559
+ return str;
560
+ }
561
+ let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;").replace(/\r/g, "&#13;");
562
+ if (hasInvalidControlChars(out)) {
563
+ out = stripInvalidControlChars(out);
564
+ }
565
+ return out;
566
+ }
567
+ function unescapeXml(str) {
568
+ if (!str || str.indexOf("&") === -1) return str;
569
+ return str.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);/g, (entity) => {
570
+ switch (entity) {
571
+ case "&amp;":
572
+ return "&";
573
+ case "&lt;":
574
+ return "<";
575
+ case "&gt;":
576
+ return ">";
577
+ case "&quot;":
578
+ return '"';
579
+ case "&apos;":
580
+ return "'";
581
+ default: {
582
+ if (entity[1] === "#") {
583
+ const body = entity.slice(2, -1);
584
+ const code = body[0] === "x" || body[0] === "X" ? parseInt(body.slice(1), 16) : parseInt(body, 10);
585
+ if (Number.isFinite(code)) {
586
+ try {
587
+ return String.fromCodePoint(code);
588
+ } catch {
589
+ return entity;
590
+ }
591
+ }
592
+ }
593
+ return entity;
594
+ }
595
+ }
596
+ });
597
+ }
598
+ function escapeXlsxText(str) {
599
+ return escapeXmlText(str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_"));
600
+ }
601
+ function unescapeXmlText(str) {
602
+ if (!str) return str;
603
+ const text = unescapeXml(str.indexOf("\r") === -1 ? str : str.replace(/\r\n?/g, "\n"));
604
+ return text.indexOf("_x") === -1 ? text : text.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
605
+ }
606
+ function parseTagAttributes(tag) {
607
+ const attrs = {};
608
+ const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
609
+ let m;
610
+ while ((m = re.exec(tag)) !== null) {
611
+ const name = m[1];
612
+ const value = m[3] !== void 0 ? m[3] : m[4];
613
+ attrs[name] = value;
614
+ const colon = name.indexOf(":");
615
+ if (colon !== -1) {
616
+ const local = name.slice(colon + 1);
617
+ if (!(local in attrs)) attrs[local] = value;
618
+ }
619
+ }
620
+ return attrs;
621
+ }
622
+ function resolveRelTarget(target) {
623
+ if (!target) return target;
624
+ if (target.startsWith("/")) return target.slice(1);
625
+ if (target.startsWith("xl/")) return target;
626
+ return "xl/" + target;
627
+ }
628
+ function columnIndexToLetter(colIndex) {
629
+ let n = colIndex + 1;
630
+ let result = "";
631
+ while (n > 0) {
632
+ const rem = (n - 1) % 26;
633
+ result = String.fromCharCode(65 + rem) + result;
634
+ n = Math.floor((n - 1) / 26);
635
+ }
636
+ return result;
637
+ }
638
+ function columnLetterToIndex(letter) {
639
+ let column = 0;
640
+ const length = letter.length;
641
+ for (let i = 0; i < length; i++) {
642
+ column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
643
+ }
644
+ return column - 1;
645
+ }
646
+ function trimTrailingEmptyRows(rows) {
647
+ let end = rows.length;
648
+ while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
649
+ end--;
650
+ }
651
+ return end === rows.length ? rows : rows.slice(0, end);
652
+ }
653
+ function parseSharedStringsXml(xml) {
654
+ const result = [];
655
+ const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>|<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*\/>/gi;
656
+ let itemMatch;
657
+ while ((itemMatch = itemRegex.exec(xml)) !== null) {
658
+ const full = itemMatch[0];
659
+ if (full.endsWith("/>") && itemMatch[1] === void 0) {
660
+ result.push("");
661
+ continue;
662
+ }
663
+ let content = itemMatch[1] ?? "";
664
+ content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*>[\s\S]*?<\/(?:(?:[A-Za-z_][\w.-]*):)?rPh\s*>/gi, "");
665
+ content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*\/>/gi, "");
666
+ const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
667
+ let value = "";
668
+ let textMatch;
669
+ while ((textMatch = textRegex.exec(content)) !== null) {
670
+ value += textMatch[1];
671
+ }
672
+ result.push(unescapeXmlText(value));
673
+ }
674
+ return result;
675
+ }
676
+
677
+ // src/biff12Utils.ts
678
+ function readVlq(buf, pos) {
679
+ let value = 0;
680
+ let shift = 0;
681
+ let byte;
682
+ do {
683
+ byte = buf[pos++];
684
+ value |= (byte & 127) << shift;
685
+ shift += 7;
686
+ } while ((byte & 128) !== 0);
687
+ return { value, nextPos: pos };
688
+ }
689
+ function vlqBytes(value) {
690
+ const out = [];
691
+ while (value >= 128) {
692
+ out.push(value & 127 | 128);
693
+ value >>>= 7;
694
+ }
695
+ out.push(value & 127);
696
+ return out;
697
+ }
698
+ function readRecord(buf, pos, rec) {
699
+ const idRes = readVlq(buf, pos);
700
+ if (idRes.nextPos > buf.length) return false;
701
+ const lenRes = readVlq(buf, idRes.nextPos);
702
+ if (lenRes.nextPos + lenRes.value > buf.length) return false;
703
+ rec.headerStart = pos;
704
+ rec.dataStart = lenRes.nextPos;
705
+ rec.dataEnd = lenRes.nextPos + lenRes.value;
706
+ rec.id = idRes.value;
707
+ rec.len = lenRes.value;
708
+ return true;
709
+ }
710
+ function buildRecord(id, payload) {
711
+ const idBytes = vlqBytes(id);
712
+ const lenBytes = vlqBytes(payload.length);
713
+ const header = Buffer.alloc(idBytes.length + lenBytes.length);
714
+ idBytes.forEach((b, i) => header[i] = b);
715
+ lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
716
+ return Buffer.concat([header, payload]);
717
+ }
718
+ function readUtf16(buf, start, charCount) {
719
+ const end = start + charCount * 2;
720
+ if (end > buf.length) return "";
721
+ return buf.toString("utf16le", start, end);
722
+ }
723
+ function parseSharedStringsBin(buf) {
724
+ const values = [];
725
+ let total = 0;
726
+ let unique = 0;
727
+ let endSstOffset = buf.length;
728
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
729
+ let pos = 0;
730
+ while (readRecord(buf, pos, rec)) {
731
+ pos = rec.dataEnd;
732
+ if (rec.id === 159) {
733
+ total = buf.readUInt32LE(rec.dataStart);
734
+ unique = buf.readUInt32LE(rec.dataStart + 4);
735
+ } else if (rec.id === 19) {
736
+ const cch = buf.readUInt32LE(rec.dataStart + 1);
737
+ values.push(readUtf16(buf, rec.dataStart + 5, cch));
738
+ } else if (rec.id === 160) {
739
+ endSstOffset = rec.headerStart;
740
+ }
741
+ }
742
+ return { values, total, unique, endSstOffset };
743
+ }
744
+
745
+ // src/XlsbReader.ts
390
746
  var XlsbReader = class extends ExcelReaderAbstract {
391
747
  constructor() {
392
748
  super();
@@ -398,12 +754,29 @@ var XlsbReader = class extends ExcelReaderAbstract {
398
754
  this._reader = null;
399
755
  this._currentRow = [];
400
756
  this._pendingRowIndex = -1;
757
+ this._nextExpectedRow = 0;
401
758
  this._eof = false;
402
759
  this.xfIdToNumFmtId = [];
403
760
  this.customDateFormats = null;
404
761
  }
405
- async open(path7, readSharedStrings = true) {
406
- this.zip = new AdmZip(path7);
762
+ async open(path9, readSharedStrings = true) {
763
+ await this.close();
764
+ this.sheetNames = [];
765
+ this.sheets = [];
766
+ this.sharedStrings = [];
767
+ this.xfIdToNumFmtId = [];
768
+ this.customDateFormats = /* @__PURE__ */ new Set();
769
+ this.fieldCount = 0;
770
+ this.resultsCount = 0;
771
+ this.actualSheetName = "";
772
+ this._currentSheetIndex = -1;
773
+ this._currentRow = [];
774
+ this._pendingRowIndex = -1;
775
+ this._nextExpectedRow = 0;
776
+ this._eof = false;
777
+ this._reader = null;
778
+ this._oaEpoch = Date.UTC(1899, 11, 30);
779
+ this.zip = new AdmZip(path9);
407
780
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
408
781
  if (wbEntry) {
409
782
  const buf = wbEntry.getData();
@@ -420,20 +793,39 @@ var XlsbReader = class extends ExcelReaderAbstract {
420
793
  const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
421
794
  if (relsEntry) {
422
795
  const xml = relsEntry.getData().toString("utf8");
423
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
424
- let match;
796
+ const relTagRe = /<Relationship\b[^>]*>/g;
797
+ let tagMatch;
425
798
  const rIdToTarget = {};
426
- while ((match = relRegex.exec(xml)) !== null) {
427
- rIdToTarget[match[1]] = match[2];
799
+ while ((tagMatch = relTagRe.exec(xml)) !== null) {
800
+ const attrs = parseTagAttributes(tagMatch[0]);
801
+ const id = attrs["Id"] ?? attrs["id"];
802
+ const target = attrs["Target"] ?? attrs["target"];
803
+ if (id !== void 0 && target !== void 0) {
804
+ rIdToTarget[id] = target;
805
+ }
428
806
  }
429
807
  for (const sheet of this.sheets) {
430
- let target = rIdToTarget[sheet.rId];
808
+ const target = rIdToTarget[sheet.rId];
431
809
  if (target) {
432
- if (target.startsWith("/")) target = target.substring(1);
433
- if (!target.startsWith("xl/")) target = "xl/" + target;
434
- sheet.path = target;
810
+ sheet.path = resolveRelTarget(target);
811
+ }
812
+ }
813
+ }
814
+ try {
815
+ const raw = wbEntry ? wbEntry.getData() : null;
816
+ if (raw) {
817
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
818
+ let pos = 0;
819
+ while (readRecord(raw, pos, rec)) {
820
+ if (rec.id === 153 && rec.len >= 4) {
821
+ const is1904 = (raw[rec.dataStart] & 1) !== 0;
822
+ this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
823
+ break;
824
+ }
825
+ pos = rec.dataEnd;
435
826
  }
436
827
  }
828
+ } catch {
437
829
  }
438
830
  if (readSharedStrings) {
439
831
  const ssPath = "xl/sharedStrings.bin";
@@ -448,8 +840,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
448
840
  this.resultsCount = this.sheets.length;
449
841
  this._currentSheetIndex = -1;
450
842
  }
451
- _readSharedStrings(path7) {
452
- const entry = this.zip.getEntry(path7);
843
+ _readSharedStrings(path9) {
844
+ const entry = this.zip.getEntry(path9);
453
845
  if (!entry) return;
454
846
  const reader = new BiffReaderWriter(entry.getData());
455
847
  while (reader.readSharedStrings()) {
@@ -458,8 +850,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
458
850
  }
459
851
  }
460
852
  }
461
- _readStyles(path7) {
462
- const entry = this.zip.getEntry(path7);
853
+ _readStyles(path9) {
854
+ const entry = this.zip.getEntry(path9);
463
855
  if (!entry) return;
464
856
  const reader = new BiffReaderWriter(entry.getData());
465
857
  while (reader.readStyles()) {
@@ -470,21 +862,54 @@ var XlsbReader = class extends ExcelReaderAbstract {
470
862
  getSheetNames() {
471
863
  return this.sheetNames;
472
864
  }
865
+ async selectSheet(nameOrIndex) {
866
+ let index = -1;
867
+ if (typeof nameOrIndex === "number") {
868
+ index = nameOrIndex;
869
+ } else {
870
+ index = this.sheetNames.indexOf(nameOrIndex);
871
+ if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
872
+ }
873
+ if (index < 0 || index >= this.sheets.length) {
874
+ throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
875
+ }
876
+ this._currentSheetIndex = index;
877
+ if (!this._initSheet(index)) {
878
+ throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[index]}'.`);
879
+ }
880
+ }
881
+ async nextResult() {
882
+ const next = this._currentSheetIndex + 1;
883
+ if (next >= this.sheets.length) return false;
884
+ this._currentSheetIndex = next;
885
+ if (!this._initSheet(next)) {
886
+ throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[next]}'.`);
887
+ }
888
+ return true;
889
+ }
473
890
  async read() {
474
891
  if (this._currentSheetIndex === -1) {
475
892
  this._currentSheetIndex = 0;
476
893
  if (!this._initSheet(0)) return false;
477
894
  }
478
895
  if (this._eof) return false;
896
+ if (this._pendingRowIndex > this._nextExpectedRow && this._pendingRowIndex < MAX_ROWS) {
897
+ this._currentRow = [];
898
+ this._nextExpectedRow++;
899
+ return true;
900
+ }
479
901
  this._currentRow = [];
480
902
  while (true) {
481
903
  const hasRecord = this._reader.readWorksheet();
482
904
  if (!hasRecord) {
483
905
  this._eof = true;
906
+ this._nextExpectedRow = this._pendingRowIndex + 1;
484
907
  return true;
485
908
  }
486
909
  if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
910
+ const completed = this._pendingRowIndex;
487
911
  this._pendingRowIndex = this._reader._rowIndex;
912
+ this._nextExpectedRow = completed + 1;
488
913
  return true;
489
914
  }
490
915
  if (this._reader._readCell) {
@@ -525,13 +950,17 @@ var XlsbReader = class extends ExcelReaderAbstract {
525
950
  }
526
951
  }
527
952
  _initSheet(index) {
528
- if (index >= this.sheets.length) return false;
953
+ if (index < 0 || index >= this.sheets.length) return false;
529
954
  const sheet = this.sheets[index];
530
- const entry = this.zip.getEntry(sheet.path);
955
+ const entry = sheet.path ? this.zip.getEntry(sheet.path) : void 0;
531
956
  if (!entry) return false;
532
957
  this._reader = new BiffReaderWriter(entry.getData());
533
958
  this._eof = false;
534
959
  this._pendingRowIndex = -1;
960
+ this._nextExpectedRow = 0;
961
+ this._currentRow = [];
962
+ this.fieldCount = 0;
963
+ this.actualSheetName = sheet.name;
535
964
  while (this._reader.readWorksheet()) {
536
965
  if (this._reader._rowIndex !== -1) {
537
966
  this._pendingRowIndex = this._reader._rowIndex;
@@ -539,7 +968,7 @@ var XlsbReader = class extends ExcelReaderAbstract {
539
968
  }
540
969
  }
541
970
  this._eof = true;
542
- return false;
971
+ return true;
543
972
  }
544
973
  getValue(i) {
545
974
  if (i < 0 || i >= this._currentRow.length) return null;
@@ -549,84 +978,15 @@ var XlsbReader = class extends ExcelReaderAbstract {
549
978
 
550
979
  // src/XlsxReader.ts
551
980
  import yauzl from "yauzl";
552
-
553
- // src/xmlUtils.ts
554
- function hasInvalidControlChars(str) {
555
- for (let i = 0; i < str.length; i++) {
556
- const c = str.charCodeAt(i);
557
- if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
558
- return true;
559
- }
560
- }
561
- return false;
562
- }
563
- function stripInvalidControlChars(str) {
564
- let out = "";
565
- for (let i = 0; i < str.length; i++) {
566
- const c = str.charCodeAt(i);
567
- if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
568
- out += str[i];
569
- }
570
- }
571
- return out;
572
- }
573
- function escapeXmlText(str) {
574
- if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
575
- return str;
576
- }
577
- let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
578
- if (hasInvalidControlChars(out)) {
579
- out = stripInvalidControlChars(out);
580
- }
581
- return out;
582
- }
583
- function unescapeXml(str) {
584
- if (!str || str.indexOf("&") === -1) return str;
585
- 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, "'");
586
- }
587
- function columnIndexToLetter(colIndex) {
588
- let n = colIndex + 1;
589
- let result = "";
590
- while (n > 0) {
591
- const rem = (n - 1) % 26;
592
- result = String.fromCharCode(65 + rem) + result;
593
- n = Math.floor((n - 1) / 26);
594
- }
595
- return result;
596
- }
597
- function columnLetterToIndex(letter) {
598
- let column = 0;
599
- const length = letter.length;
600
- for (let i = 0; i < length; i++) {
601
- column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
602
- }
603
- return column - 1;
604
- }
605
- function trimTrailingEmptyRows(rows) {
606
- let end = rows.length;
607
- while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
608
- end--;
609
- }
610
- return end === rows.length ? rows : rows.slice(0, end);
611
- }
612
- function parseSharedStringsXml(xml) {
613
- const result = [];
614
- const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>/gi;
615
- let itemMatch;
616
- while ((itemMatch = itemRegex.exec(xml)) !== null) {
617
- const content = itemMatch[1];
618
- const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
619
- let value = "";
620
- let textMatch;
621
- while ((textMatch = textRegex.exec(content)) !== null) {
622
- value += textMatch[1];
623
- }
624
- result.push(unescapeXml(value));
625
- }
626
- return result;
981
+ function parseIsoDateUtc(value) {
982
+ let text = value.trim().replace(" ", "T");
983
+ if (!text) return null;
984
+ if (!/z$|[+-]\d{2}:?\d{2}$/i.test(text) && text.includes("T")) {
985
+ text += "Z";
986
+ }
987
+ const date = new Date(text);
988
+ return Number.isNaN(date.getTime()) ? null : date;
627
989
  }
628
-
629
- // src/XlsxReader.ts
630
990
  var XlsxReader = class extends ExcelReaderAbstract {
631
991
  constructor() {
632
992
  super();
@@ -638,13 +998,34 @@ var XlsxReader = class extends ExcelReaderAbstract {
638
998
  this._currentSheetIndex = -1;
639
999
  this._sheetXml = null;
640
1000
  this._xmlPos = 0;
1001
+ /** End of the sheetData element; row tags must not be searched past it. */
1002
+ this._xmlEnd = 0;
641
1003
  this._currentRow = [];
1004
+ this._expectedRowNum = 1;
1005
+ this._pendingEmptyCount = 0;
642
1006
  this.xfIdToNumFmtId = [];
643
1007
  this.customDateFormats = /* @__PURE__ */ new Set();
644
1008
  }
645
- async open(path7, readSharedStrings = true) {
646
- return new Promise((resolve4, reject) => {
647
- yauzl.open(path7, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
1009
+ async open(path9, readSharedStrings = true) {
1010
+ await this.close();
1011
+ this.sheetNames = [];
1012
+ this.sheets = [];
1013
+ this.sharedStrings = [];
1014
+ this.xfIdToNumFmtId = [];
1015
+ this.customDateFormats = /* @__PURE__ */ new Set();
1016
+ this.fieldCount = 0;
1017
+ this.resultsCount = 0;
1018
+ this.actualSheetName = "";
1019
+ this._currentSheetIndex = -1;
1020
+ this._sheetXml = null;
1021
+ this._xmlPos = 0;
1022
+ this._xmlEnd = 0;
1023
+ this._currentRow = [];
1024
+ this._expectedRowNum = 1;
1025
+ this._pendingEmptyCount = 0;
1026
+ this._oaEpoch = Date.UTC(1899, 11, 30);
1027
+ return new Promise((resolve6, reject) => {
1028
+ yauzl.open(path9, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
648
1029
  if (err) return reject(err);
649
1030
  this.zipfile = zipfile;
650
1031
  this.entries = /* @__PURE__ */ new Map();
@@ -657,28 +1038,40 @@ var XlsxReader = class extends ExcelReaderAbstract {
657
1038
  const wbRelsContent = await this._readZipEntryContent("xl/_rels/workbook.xml.rels");
658
1039
  const rIdToTarget = {};
659
1040
  if (wbRelsContent) {
660
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
661
- let match;
662
- while ((match = relRegex.exec(wbRelsContent)) !== null) {
663
- rIdToTarget[match[1]] = match[2];
1041
+ const relTagRe = /<Relationship\b[^>]*>/g;
1042
+ let tagMatch;
1043
+ while ((tagMatch = relTagRe.exec(wbRelsContent)) !== null) {
1044
+ const attrs = parseTagAttributes(tagMatch[0]);
1045
+ const id = attrs["Id"] ?? attrs["id"];
1046
+ const target = attrs["Target"] ?? attrs["target"];
1047
+ if (id !== void 0 && target !== void 0) {
1048
+ rIdToTarget[id] = target;
1049
+ }
664
1050
  }
665
1051
  }
666
1052
  const wbContent = await this._readZipEntryContent("xl/workbook.xml");
667
1053
  if (wbContent) {
668
- const sheetRegex = /<sheet[^>]*name="([^"]*)"[^>]*sheetId="([^"]*)"[^>]*r:id="([^"]*)"/g;
669
- let match;
670
- while ((match = sheetRegex.exec(wbContent)) !== null) {
671
- const name = unescapeXml(match[1]);
672
- const sheetId = match[2];
673
- const rId = match[3];
1054
+ const sheetTagRe = /<sheet\b[^>]*>/g;
1055
+ let tagMatch;
1056
+ while ((tagMatch = sheetTagRe.exec(wbContent)) !== null) {
1057
+ const attrs = parseTagAttributes(tagMatch[0]);
1058
+ const rawName = attrs["name"];
1059
+ const sheetId = attrs["sheetId"] ?? "";
1060
+ const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
1061
+ if (rawName === void 0 || rId === void 0) continue;
1062
+ const name = unescapeXml(rawName);
674
1063
  const target = rIdToTarget[rId];
675
- let fullPath = target;
676
- if (!fullPath.startsWith("xl/")) {
677
- fullPath = "xl/" + fullPath;
678
- }
1064
+ if (!target) continue;
1065
+ const fullPath = resolveRelTarget(target);
679
1066
  this.sheetNames.push(name);
680
1067
  this.sheets.push({ name, sheetId, rId, path: fullPath });
681
1068
  }
1069
+ const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbContent);
1070
+ if (wbPrMatch) {
1071
+ const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
1072
+ const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
1073
+ this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
1074
+ }
682
1075
  }
683
1076
  if (readSharedStrings) {
684
1077
  const ssContent = await this._readZipEntryContent("xl/sharedStrings.xml");
@@ -692,7 +1085,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
692
1085
  }
693
1086
  this.resultsCount = this.sheets.length;
694
1087
  this._currentSheetIndex = -1;
695
- resolve4();
1088
+ resolve6();
696
1089
  } catch (e) {
697
1090
  reject(e);
698
1091
  }
@@ -706,16 +1099,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
706
1099
  this.zipfile.close();
707
1100
  }
708
1101
  }
709
- async _readZipEntryContent(path7) {
710
- const entry = this.entries.get(path7);
1102
+ async _readZipEntryContent(path9) {
1103
+ const entry = this.entries.get(path9);
711
1104
  if (!entry) return null;
712
- return new Promise((resolve4, reject) => {
1105
+ return new Promise((resolve6, reject) => {
713
1106
  this.zipfile.openReadStream(entry, (err, readStream) => {
714
1107
  if (err) return reject(err);
715
1108
  const chunks = [];
716
1109
  readStream.on("data", (chunk) => chunks.push(chunk));
717
1110
  readStream.on("end", () => {
718
- resolve4(Buffer.concat(chunks).toString("utf8"));
1111
+ resolve6(Buffer.concat(chunks).toString("utf8"));
719
1112
  });
720
1113
  readStream.on("error", reject);
721
1114
  });
@@ -763,6 +1156,27 @@ var XlsxReader = class extends ExcelReaderAbstract {
763
1156
  return false;
764
1157
  }
765
1158
  }
1159
+ async selectSheet(nameOrIndex) {
1160
+ let index = -1;
1161
+ if (typeof nameOrIndex === "number") {
1162
+ index = nameOrIndex;
1163
+ } else {
1164
+ index = this.sheetNames.indexOf(nameOrIndex);
1165
+ if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
1166
+ }
1167
+ if (index < 0 || index >= this.sheets.length) {
1168
+ throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
1169
+ }
1170
+ this._currentSheetIndex = index;
1171
+ await this._initSheet(index);
1172
+ }
1173
+ async nextResult() {
1174
+ const next = this._currentSheetIndex + 1;
1175
+ if (next >= this.sheets.length) return false;
1176
+ this._currentSheetIndex = next;
1177
+ await this._initSheet(next);
1178
+ return true;
1179
+ }
766
1180
  async _initSheet(index) {
767
1181
  if (index >= this.sheets.length) return;
768
1182
  const sheet = this.sheets[index];
@@ -772,23 +1186,106 @@ var XlsxReader = class extends ExcelReaderAbstract {
772
1186
  this._sheetXml = "";
773
1187
  }
774
1188
  this._xmlPos = 0;
1189
+ this._xmlEnd = this._sheetXml.length;
1190
+ this._expectedRowNum = 1;
1191
+ this._pendingEmptyCount = 0;
1192
+ this._currentRow = [];
1193
+ this.fieldCount = 0;
775
1194
  const sd = this._sheetXml.indexOf("<sheetData>");
776
- if (sd !== -1) this._xmlPos = sd + 11;
1195
+ if (sd !== -1) {
1196
+ this._xmlPos = sd + 11;
1197
+ const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
1198
+ if (se !== -1) this._xmlEnd = se;
1199
+ } else {
1200
+ const m = /<(?:[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(this._sheetXml);
1201
+ if (m && m.index !== void 0) {
1202
+ this._xmlPos = m.index + m[0].length;
1203
+ if (/\/>\s*$/.test(m[0])) {
1204
+ this._xmlEnd = this._xmlPos;
1205
+ } else {
1206
+ const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
1207
+ if (se !== -1) this._xmlEnd = se;
1208
+ }
1209
+ }
1210
+ }
777
1211
  }
778
1212
  _readNextRow() {
779
1213
  if (!this._sheetXml) return false;
780
- const rowStart = this._sheetXml.indexOf("<row", this._xmlPos);
1214
+ if (this._pendingEmptyCount > 0) {
1215
+ this._pendingEmptyCount--;
1216
+ this._currentRow = [];
1217
+ this._expectedRowNum++;
1218
+ return true;
1219
+ }
1220
+ const rowStart = this._findRowTagStart(this._xmlPos);
781
1221
  if (rowStart === -1) return false;
1222
+ const rowTagEnd = this._sheetXml.indexOf(">", rowStart);
1223
+ if (rowTagEnd === -1) return false;
1224
+ const rowTag = this._sheetXml.substring(rowStart, rowTagEnd + 1);
1225
+ const isSelfClosing = this._sheetXml.charCodeAt(rowTagEnd - 1) === 47;
1226
+ const rowAttrs = parseTagAttributes(rowTag);
1227
+ const rRaw = rowAttrs["r"];
1228
+ let rowNum = this._expectedRowNum;
1229
+ if (rRaw !== void 0) {
1230
+ const parsed = parseInt(rRaw, 10);
1231
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= MAX_ROWS) rowNum = parsed;
1232
+ }
1233
+ if (rowNum > this._expectedRowNum) {
1234
+ const gap = rowNum - this._expectedRowNum;
1235
+ this._pendingEmptyCount = gap - 1;
1236
+ this._currentRow = [];
1237
+ this._expectedRowNum++;
1238
+ return true;
1239
+ }
1240
+ if (rowNum < this._expectedRowNum) {
1241
+ if (isSelfClosing) {
1242
+ this._xmlPos = rowTagEnd + 1;
1243
+ this._currentRow = [];
1244
+ return true;
1245
+ }
1246
+ const rowEnd2 = this._sheetXml.indexOf("</row>", rowStart);
1247
+ if (rowEnd2 === -1) return false;
1248
+ this._xmlPos = rowEnd2 + 6;
1249
+ this._parseRowByIndex(rowStart, rowEnd2);
1250
+ this._expectedRowNum = Math.max(this._expectedRowNum + 1, rowNum + 1);
1251
+ return true;
1252
+ }
1253
+ if (isSelfClosing) {
1254
+ this._xmlPos = rowTagEnd + 1;
1255
+ this._currentRow = [];
1256
+ this._expectedRowNum = rowNum + 1;
1257
+ return true;
1258
+ }
782
1259
  const rowEnd = this._sheetXml.indexOf("</row>", rowStart);
783
1260
  if (rowEnd === -1) return false;
784
1261
  this._xmlPos = rowEnd + 6;
785
1262
  this._parseRowByIndex(rowStart, rowEnd);
1263
+ this._expectedRowNum = rowNum + 1;
786
1264
  return true;
787
1265
  }
1266
+ /**
1267
+ * Find the next `<row …>` start, ignoring similarly named elements such as
1268
+ * `<rowBreaks>` that follow `</sheetData>`.
1269
+ */
1270
+ _findRowTagStart(from) {
1271
+ const xml = this._sheetXml;
1272
+ let pos = from;
1273
+ while (pos < this._xmlEnd) {
1274
+ const idx = xml.indexOf("<row", pos);
1275
+ if (idx === -1 || idx >= this._xmlEnd) return -1;
1276
+ const next = xml.charCodeAt(idx + 4);
1277
+ if (next === 62 || next === 47 || next === 32 || next === 9 || next === 10 || next === 13) {
1278
+ return idx;
1279
+ }
1280
+ pos = idx + 4;
1281
+ }
1282
+ return -1;
1283
+ }
788
1284
  _parseRowByIndex(rowStart, rowEnd) {
789
1285
  this._currentRow = [];
790
1286
  const xml = this._sheetXml;
791
1287
  let pos = xml.indexOf(">", rowStart) + 1;
1288
+ let nextCol = 0;
792
1289
  while (pos < rowEnd) {
793
1290
  const cStart = xml.indexOf("<c", pos);
794
1291
  if (cStart === -1 || cStart >= rowEnd) break;
@@ -822,6 +1319,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
822
1319
  }
823
1320
  }
824
1321
  let val = null;
1322
+ let inlineVals = null;
825
1323
  if (xml.charCodeAt(nextTagClose - 1) === 47) {
826
1324
  pos = nextTagClose + 1;
827
1325
  } else {
@@ -836,27 +1334,41 @@ var XlsxReader = class extends ExcelReaderAbstract {
836
1334
  }
837
1335
  } else {
838
1336
  if (type === "inlineStr") {
839
- const tStart = xml.indexOf("<t", nextTagClose);
840
- if (tStart !== -1 && tStart < cellContentEnd) {
841
- const tContentStart = xml.indexOf(">", tStart) + 1;
1337
+ inlineVals = [];
1338
+ let tSearch = nextTagClose;
1339
+ while (true) {
1340
+ const tStart = xml.indexOf("<t", tSearch);
1341
+ if (tStart === -1 || tStart >= cellContentEnd) break;
1342
+ const tClose = xml.indexOf(">", tStart);
1343
+ if (tClose === -1 || tClose >= cellContentEnd) break;
1344
+ const tContentStart = tClose + 1;
842
1345
  const tEnd = xml.indexOf("</t>", tContentStart);
843
- if (tEnd !== -1 && tEnd < cellContentEnd) {
844
- val = xml.substring(tContentStart, tEnd);
845
- }
1346
+ if (tEnd === -1 || tEnd > cellContentEnd) break;
1347
+ inlineVals.push(xml.substring(tContentStart, tEnd));
1348
+ tSearch = tEnd + 4;
846
1349
  }
1350
+ if (inlineVals.length === 0) inlineVals = null;
847
1351
  }
848
1352
  }
849
1353
  pos = cellContentEnd + 4;
850
1354
  }
851
1355
  let finalVal = null;
852
- if (val !== null) {
1356
+ if (inlineVals !== null) {
1357
+ finalVal = unescapeXmlText(inlineVals.join(""));
1358
+ } else if (val !== null) {
853
1359
  if (type === "s") {
854
1360
  const idx = parseInt(val, 10);
855
1361
  finalVal = this.sharedStrings[idx];
856
1362
  } else if (type === "b") {
857
1363
  finalVal = val === "1" || val === "true";
858
1364
  } else if (type === "inlineStr") {
1365
+ finalVal = unescapeXmlText(val);
1366
+ } else if (type === "str") {
1367
+ finalVal = unescapeXmlText(val);
1368
+ } else if (type === "e") {
859
1369
  finalVal = unescapeXml(val);
1370
+ } else if (type === "d") {
1371
+ finalVal = parseIsoDateUtc(val) ?? unescapeXmlText(val);
860
1372
  } else if (type === "n" || type === "") {
861
1373
  finalVal = parseFloat(val);
862
1374
  if (styleIndex > 0 && this.xfIdToNumFmtId) {
@@ -874,10 +1386,10 @@ var XlsxReader = class extends ExcelReaderAbstract {
874
1386
  }
875
1387
  }
876
1388
  }
877
- if (colIndex !== -1) {
878
- this._currentRow[colIndex] = finalVal;
879
- if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
880
- }
1389
+ if (colIndex === -1) colIndex = nextCol;
1390
+ nextCol = colIndex + 1;
1391
+ this._currentRow[colIndex] = finalVal;
1392
+ if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
881
1393
  }
882
1394
  }
883
1395
  getValue(i) {
@@ -907,99 +1419,11 @@ var ReaderFactory = class {
907
1419
 
908
1420
  // src/XlsbWriter.ts
909
1421
  import * as fs from "fs";
1422
+ import * as path2 from "path";
1423
+ import { randomUUID } from "crypto";
910
1424
  import archiver from "archiver";
911
1425
  import { Readable } from "stream";
912
1426
 
913
- // src/Formats.ts
914
- var F = {
915
- THOUSANDS_SEP: "#,##0",
916
- CURRENCY_PLN: '#,##0.00 "z\u0142"',
917
- CURRENCY_EUR: "#,##0.00 \u20AC",
918
- PERCENTAGE: "0%",
919
- SCIENTIFIC: "0.00E+00",
920
- TWO_DECIMALS: "#,##0.00",
921
- TEXT: "@",
922
- LEADING_ZEROS: "000000000",
923
- DATE_SHORT: "dd.mm.yyyy",
924
- DATE_LONG: "d mmmm yyyy",
925
- DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
926
- DATE_ISO: "yyyy-mm-dd",
927
- DATE_MONTH_YEAR: "mmmm yyyy",
928
- DATE_WEEKDAY: "dddd, d mmmm yyyy",
929
- DATE_DAY_MONTH: "d mmmm",
930
- DATE_YEAR_ONLY: "yyyy",
931
- DATETIME_SHORT: "dd.mm.yyyy hh:mm",
932
- DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
933
- TIME_HH_MM: "hh:mm",
934
- TIME_HH_MM_SS: "hh:mm:ss",
935
- TIME_12H: "h:mm AM/PM",
936
- DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
937
- DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
938
- TIME_MS: "hh:mm:ss.000"
939
- };
940
- function isFormattedCell(val) {
941
- return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
942
- }
943
- function unwrapCell(val) {
944
- if (isFormattedCell(val)) {
945
- return val.value;
946
- }
947
- return val;
948
- }
949
- function getFormat(val) {
950
- if (isFormattedCell(val)) {
951
- return val.format;
952
- }
953
- return null;
954
- }
955
-
956
- // src/writerHelpers.ts
957
- var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
958
- function sanitizeSheetName(name, sheetCount) {
959
- if (!name || typeof name !== "string") {
960
- return `Sheet${sheetCount + 1}`;
961
- }
962
- let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
963
- if (sanitized.length > 31) {
964
- sanitized = sanitized.substring(0, 31);
965
- }
966
- if (sanitized.trim().length === 0) {
967
- sanitized = `Sheet${sheetCount + 1}`;
968
- }
969
- return sanitized;
970
- }
971
- function initColWidths(columnCount) {
972
- return new Array(columnCount).fill(-1);
973
- }
974
- function applyHeaderWidths(colWidths, headers, columnCount) {
975
- for (let i = 0; i < columnCount; i++) {
976
- const len = headers[i] ? headers[i].length + 1 : 0;
977
- let width = 1.25 * len + 2;
978
- if (width > 80) width = 80;
979
- if (colWidths[i] < width) colWidths[i] = width;
980
- }
981
- }
982
- function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
983
- for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
984
- const row = rows[r];
985
- for (let c = 0; c < row.length; c++) {
986
- const raw = row[c];
987
- if (raw === null || raw === void 0) continue;
988
- const val = isFormattedCell(raw) ? raw.value : raw;
989
- const len = val instanceof Date ? 20 : String(val).length + 1;
990
- let width = 1.25 * len + 2;
991
- if (width > 80) width = 80;
992
- if (colWidths[c] < width) colWidths[c] = width;
993
- }
994
- }
995
- }
996
- function defaultColWidth(width, floor = false) {
997
- if (width > 0) {
998
- return floor ? Math.floor(width) : width;
999
- }
1000
- return 10;
1001
- }
1002
-
1003
1427
  // src/StreamingSheetState.ts
1004
1428
  var StreamingSheetState = class {
1005
1429
  constructor() {
@@ -1051,7 +1475,13 @@ var XlsbWriter = class _XlsbWriter {
1051
1475
  /**
1052
1476
  * @param filePath Destination `.xlsb` path on disk.
1053
1477
  */
1054
- constructor(filePath) {
1478
+ /**
1479
+ * @param filePath Destination `.xlsb` path on disk.
1480
+ * @param options.compressionLevel Deflate level 0-9 (default 6).
1481
+ */
1482
+ constructor(filePath, options = {}) {
1483
+ this._outputError = null;
1484
+ this._finalized = false;
1055
1485
  this.sheetCount = 0;
1056
1486
  this.sheetList = [];
1057
1487
  this.sstDic = /* @__PURE__ */ new Map();
@@ -1062,8 +1492,21 @@ var XlsbWriter = class _XlsbWriter {
1062
1492
  this._nextXfIdx = 4;
1063
1493
  this._stFmtRecords = /* @__PURE__ */ new Map();
1064
1494
  this.stream = new StreamingSheetState();
1065
- this.output = fs.createWriteStream(filePath);
1066
- this.archive = archiver("zip");
1495
+ const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
1496
+ this._targetPath = path2.resolve(filePath);
1497
+ fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
1498
+ this._tempPath = path2.join(
1499
+ path2.dirname(this._targetPath),
1500
+ `.${path2.basename(this._targetPath)}.${randomUUID()}.tmp`
1501
+ );
1502
+ this.output = fs.createWriteStream(this._tempPath);
1503
+ this.output.on("error", (err) => {
1504
+ this._outputError = err;
1505
+ });
1506
+ this.archive = archiver("zip", { zlib: { level: compressionLevel } });
1507
+ this.archive.on("error", (err) => {
1508
+ this._outputError = err;
1509
+ });
1067
1510
  this.archive.pipe(this.output);
1068
1511
  this._oaEpoch = Date.UTC(1899, 11, 30);
1069
1512
  this._sheet1Bytes = Buffer.from([
@@ -2933,6 +3376,11 @@ var XlsbWriter = class _XlsbWriter {
2933
3376
  * @param options Optional settings (hidden, doAutofilter)
2934
3377
  */
2935
3378
  startSheet(sheetName, columnCount, headers, options = {}) {
3379
+ assertColumnCount(columnCount);
3380
+ if (headers !== void 0) {
3381
+ assertRowFitsColumns("header", headers.length, columnCount);
3382
+ for (const h of headers) assertCellTextLength(h);
3383
+ }
2936
3384
  const { hidden = false, doAutofilter = true, sampleRows } = options;
2937
3385
  this.addSheet(sheetName, hidden);
2938
3386
  this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
@@ -2998,6 +3446,7 @@ var XlsbWriter = class _XlsbWriter {
2998
3446
  `Row length mismatch. Expected ${this.stream.endCol - this.stream.startCol} columns, got ${row.length}`
2999
3447
  );
3000
3448
  }
3449
+ assertRowCount(this.stream.rowNum + 1);
3001
3450
  this.createRowHeader(bigBuf, this.stream.rowNum, this.stream.startCol, this.stream.endCol);
3002
3451
  for (let c = 0; c < row.length; c++) {
3003
3452
  const raw = row[c];
@@ -3020,7 +3469,7 @@ var XlsbWriter = class _XlsbWriter {
3020
3469
  } else if (typeof val === "boolean") {
3021
3470
  this.writeBool(bigBuf, val, c);
3022
3471
  } else if (val instanceof Date) {
3023
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
3472
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
3024
3473
  this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
3025
3474
  } else if (val !== null && val !== void 0) {
3026
3475
  this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
@@ -3077,6 +3526,16 @@ var XlsbWriter = class _XlsbWriter {
3077
3526
  } else if (headers) {
3078
3527
  columnCount = headers.length;
3079
3528
  }
3529
+ if (columnCount > 0) assertColumnCount(columnCount);
3530
+ const totalRowsCheck = rows.length + (headers ? 1 : 0);
3531
+ assertRowCount(totalRowsCheck);
3532
+ if (headers) {
3533
+ assertRowFitsColumns("header", headers.length, columnCount);
3534
+ for (const h of headers) assertCellTextLength(h);
3535
+ }
3536
+ for (let r = 0; r < rows.length; r++) {
3537
+ assertRowFitsColumns("row", rows[r].length, columnCount);
3538
+ }
3080
3539
  const colWidths = initColWidths(columnCount);
3081
3540
  if (headers) {
3082
3541
  applyHeaderWidths(colWidths, headers, columnCount);
@@ -3152,7 +3611,7 @@ var XlsbWriter = class _XlsbWriter {
3152
3611
  } else if (typeof val === "boolean") {
3153
3612
  this.writeBool(bigBuf, val, c);
3154
3613
  } else if (val instanceof Date) {
3155
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
3614
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
3156
3615
  this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
3157
3616
  } else if (val !== null && val !== void 0) {
3158
3617
  this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
@@ -3235,13 +3694,14 @@ var XlsbWriter = class _XlsbWriter {
3235
3694
  bigBuf.writeUnsafeByte(val ? 1 : 0);
3236
3695
  }
3237
3696
  writeDateTime(bigBuf, date, colNum) {
3238
- const oaDate = (date.getTime() - this._oaEpoch) / 864e5;
3697
+ const oaDate = timeToOaDate(date.getTime(), this._oaEpoch);
3239
3698
  this.writeDouble(bigBuf, oaDate, colNum, 1);
3240
3699
  }
3241
3700
  _getDateWidth(_date) {
3242
3701
  return 19;
3243
3702
  }
3244
3703
  writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
3704
+ assertCellTextLength(val);
3245
3705
  let index;
3246
3706
  if (this.sstDic.has(val)) {
3247
3707
  index = this.sstDic.get(val);
@@ -3267,8 +3727,8 @@ var XlsbWriter = class _XlsbWriter {
3267
3727
  bigBuf.writeByte(159);
3268
3728
  bigBuf.writeByte(1);
3269
3729
  bigBuf.writeByte(8);
3270
- bigBuf.writeInt32LE(this.sstCntUnique);
3271
3730
  bigBuf.writeInt32LE(this.sstCntAll);
3731
+ bigBuf.writeInt32LE(this.sstCntUnique);
3272
3732
  for (const [txt] of this.sstDic) {
3273
3733
  const txtLen = txt.length;
3274
3734
  bigBuf.writeByte(19);
@@ -3321,8 +3781,61 @@ var XlsbWriter = class _XlsbWriter {
3321
3781
  * Must be awaited before the file is fully written.
3322
3782
  */
3323
3783
  finalize() {
3324
- return new Promise((resolve4, reject) => {
3784
+ return new Promise((resolve6, reject) => {
3785
+ if (this._finalized) {
3786
+ reject(new Error("XlsbWriter: already finalized"));
3787
+ return;
3788
+ }
3789
+ this._finalized = true;
3790
+ let settled = false;
3791
+ const cleanupTemp = () => {
3792
+ try {
3793
+ if (fs.existsSync(this._tempPath)) fs.rmSync(this._tempPath);
3794
+ } catch {
3795
+ }
3796
+ };
3797
+ const fail = (err) => {
3798
+ if (settled) return;
3799
+ settled = true;
3800
+ try {
3801
+ this.archive.abort();
3802
+ } catch {
3803
+ }
3804
+ try {
3805
+ this.output.destroy();
3806
+ } catch {
3807
+ }
3808
+ cleanupTemp();
3809
+ reject(err instanceof Error ? err : new Error(String(err)));
3810
+ };
3811
+ const succeed = () => {
3812
+ if (settled) return;
3813
+ if (this._outputError) {
3814
+ fail(this._outputError);
3815
+ return;
3816
+ }
3817
+ settled = true;
3818
+ try {
3819
+ const fd = fs.openSync(this._tempPath, "r+");
3820
+ try {
3821
+ fs.fsyncSync(fd);
3822
+ } finally {
3823
+ fs.closeSync(fd);
3824
+ }
3825
+ try {
3826
+ const existing = fs.statSync(this._targetPath);
3827
+ fs.chmodSync(this._tempPath, existing.mode & 511);
3828
+ } catch {
3829
+ }
3830
+ fs.renameSync(this._tempPath, this._targetPath);
3831
+ resolve6();
3832
+ } catch (err) {
3833
+ cleanupTemp();
3834
+ reject(err instanceof Error ? err : new Error(String(err)));
3835
+ }
3836
+ };
3325
3837
  try {
3838
+ if (this._outputError) throw this._outputError;
3326
3839
  this.saveSst();
3327
3840
  this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
3328
3841
  const wbBuffers = [];
@@ -3419,11 +3932,12 @@ var XlsbWriter = class _XlsbWriter {
3419
3932
  </Relationships>`;
3420
3933
  this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
3421
3934
  }
3422
- this.output.on("close", () => resolve4());
3423
- this.archive.on("error", (err) => reject(err));
3935
+ this.output.once("close", succeed);
3936
+ this.output.once("error", fail);
3937
+ this.archive.once("error", fail);
3424
3938
  this.archive.finalize();
3425
3939
  } catch (err) {
3426
- reject(err);
3940
+ fail(err);
3427
3941
  }
3428
3942
  });
3429
3943
  }
@@ -3431,27 +3945,21 @@ var XlsbWriter = class _XlsbWriter {
3431
3945
 
3432
3946
  // src/XlsxWriter.ts
3433
3947
  import * as fs2 from "fs";
3948
+ import * as path3 from "path";
3949
+ import { randomUUID as randomUUID2 } from "crypto";
3434
3950
  import archiver2 from "archiver";
3435
3951
  import { Readable as Readable2 } from "stream";
3436
- var COLUMN_LETTERS = (() => {
3437
- const letters = [];
3438
- for (let i = 65; i < 91; i++) {
3439
- letters.push(String.fromCharCode(i));
3440
- }
3441
- const temp = [];
3442
- for (const p of letters) {
3443
- for (let i = 65; i < 91; i++) {
3444
- temp.push(p + String.fromCharCode(i));
3445
- }
3446
- }
3447
- letters.push(...temp);
3448
- return letters;
3449
- })();
3450
3952
  var XlsxWriter = class {
3451
3953
  /**
3452
3954
  * @param filePath Destination `.xlsx` path on disk.
3453
3955
  */
3454
- constructor(filePath) {
3956
+ /**
3957
+ * @param filePath Destination `.xlsx` path on disk.
3958
+ * @param options.compressionLevel Deflate level 0-9 (default 6).
3959
+ */
3960
+ constructor(filePath, options = {}) {
3961
+ this._outputError = null;
3962
+ this._finalized = false;
3455
3963
  this.sheetCount = 0;
3456
3964
  this.sheetList = [];
3457
3965
  this.sstArray = [];
@@ -3465,13 +3973,26 @@ var XlsxWriter = class {
3465
3973
  this._nextXfIndex = 4;
3466
3974
  this.stream = new StreamingSheetState();
3467
3975
  this.currentColLetters = [];
3468
- this.output = fs2.createWriteStream(filePath);
3469
- this.archive = archiver2("zip");
3976
+ const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
3977
+ this._targetPath = path3.resolve(filePath);
3978
+ fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
3979
+ this._tempPath = path3.join(
3980
+ path3.dirname(this._targetPath),
3981
+ `.${path3.basename(this._targetPath)}.${randomUUID2()}.tmp`
3982
+ );
3983
+ this.output = fs2.createWriteStream(this._tempPath);
3984
+ this.output.on("error", (err) => {
3985
+ this._outputError = err;
3986
+ });
3987
+ this.archive = archiver2("zip", { zlib: { level: compressionLevel } });
3988
+ this.archive.on("error", (err) => {
3989
+ this._outputError = err;
3990
+ });
3470
3991
  this.archive.pipe(this.output);
3471
3992
  this._oaEpoch = Date.UTC(1899, 11, 30);
3472
3993
  }
3473
3994
  _getColumnLetter(colIndex) {
3474
- return colIndex < COLUMN_LETTERS.length ? COLUMN_LETTERS[colIndex] : "A";
3995
+ return columnIndexToLetter(colIndex);
3475
3996
  }
3476
3997
  /** @deprecated Internal; kept for unit-test access. Prefer sanitizeSheetName(). */
3477
3998
  _sanitizeSheetName(name) {
@@ -3508,7 +4029,7 @@ var XlsxWriter = class {
3508
4029
  _needsEscape(str) {
3509
4030
  for (let i = 0; i < str.length; i++) {
3510
4031
  const c = str.charCodeAt(i);
3511
- if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
4032
+ 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) {
3512
4033
  return true;
3513
4034
  }
3514
4035
  }
@@ -3516,13 +4037,19 @@ var XlsxWriter = class {
3516
4037
  }
3517
4038
  _escape(str) {
3518
4039
  if (typeof str !== "string") return str;
4040
+ str = str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_");
3519
4041
  if (!this._needsEscape(str)) return str;
3520
- 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, "");
4042
+ 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, "");
3521
4043
  }
3522
4044
  /**
3523
4045
  * Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
3524
4046
  */
3525
4047
  startSheet(sheetName, columnCount, headers, options = {}) {
4048
+ assertColumnCount(columnCount);
4049
+ if (headers !== void 0) {
4050
+ assertRowFitsColumns("header", headers.length, columnCount);
4051
+ for (const h of headers) assertCellTextLength(h);
4052
+ }
3526
4053
  const { hidden = false, doAutofilter = true, sampleRows } = options;
3527
4054
  this.addSheet(sheetName, hidden);
3528
4055
  this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
@@ -3565,7 +4092,7 @@ var XlsxWriter = class {
3565
4092
  this.stream.rowNum++;
3566
4093
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
3567
4094
  for (let c = 0; c < headers.length; c++) {
3568
- this._writeStringCell(bigBuf, headers[c], colLetters[c], this.stream.rowNum, 3);
4095
+ this._writeStringCell(bigBuf, headers[c], "", 3);
3569
4096
  }
3570
4097
  bigBuf.writeString("</row>");
3571
4098
  }
@@ -3579,7 +4106,9 @@ var XlsxWriter = class {
3579
4106
  );
3580
4107
  }
3581
4108
  this.stream.rowNum++;
4109
+ assertRowCount(this.stream.rowNum);
3582
4110
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
4111
+ let prevCol = -1;
3583
4112
  for (let c = 0; c < row.length; c++) {
3584
4113
  const raw = row[c];
3585
4114
  if (raw === null || raw === void 0) continue;
@@ -3587,27 +4116,28 @@ var XlsxWriter = class {
3587
4116
  const val = fmtString !== null ? unwrapCell(raw) : raw;
3588
4117
  const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
3589
4118
  const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
3590
- const colRef = this.currentColLetters[c];
4119
+ const ref = cellRefAttr(c, prevCol, this.currentColLetters[c], this.stream.rowNum);
4120
+ prevCol = c;
3591
4121
  if (typeof val === "number") {
3592
4122
  if (Number.isFinite(val)) {
3593
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}"${styleAttr}><v>${val}</v></c>`);
4123
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
3594
4124
  } else {
3595
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4125
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3596
4126
  }
3597
4127
  } else if (typeof val === "bigint") {
3598
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4128
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3599
4129
  } else if (typeof val === "boolean") {
3600
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4130
+ bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
3601
4131
  } else if (val instanceof Date) {
3602
4132
  const oaDate = this._toOADate(val);
3603
4133
  if (Number.isFinite(oaDate)) {
3604
4134
  const finalStyle = xfIdx >= 0 ? xfIdx : 1;
3605
- bigBuf.writeString(`<c r="${colRef}${this.stream.rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4135
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
3606
4136
  } else {
3607
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4137
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3608
4138
  }
3609
4139
  } else if (val !== null && val !== void 0) {
3610
- this._writeStringCell(bigBuf, val.toString(), colRef, this.stream.rowNum, xfIdx >= 0 ? xfIdx : void 0);
4140
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3611
4141
  }
3612
4142
  }
3613
4143
  bigBuf.writeString("</row>");
@@ -3642,6 +4172,16 @@ var XlsxWriter = class {
3642
4172
  } else if (headers) {
3643
4173
  columnCount = headers.length;
3644
4174
  }
4175
+ if (columnCount > 0) assertColumnCount(columnCount);
4176
+ const totalRows = rows.length + (headers ? 1 : 0);
4177
+ assertRowCount(totalRows);
4178
+ if (headers) {
4179
+ assertRowFitsColumns("header", headers.length, columnCount);
4180
+ for (const h of headers) assertCellTextLength(h);
4181
+ }
4182
+ for (let r = 0; r < rows.length; r++) {
4183
+ assertRowFitsColumns("row", rows[r].length, columnCount);
4184
+ }
3645
4185
  const colWidths = initColWidths(columnCount);
3646
4186
  const colLetters = new Array(columnCount);
3647
4187
  for (let i = 0; i < columnCount; i++) {
@@ -3653,7 +4193,6 @@ var XlsxWriter = class {
3653
4193
  updateColWidthsFromRows(colWidths, rows);
3654
4194
  bigBuf.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
3655
4195
  bigBuf.writeString('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
3656
- const totalRows = rows.length + (headers ? 1 : 0);
3657
4196
  if (totalRows > 0 && columnCount > 0) {
3658
4197
  bigBuf.writeString(`<dimension ref="A1:${colLetters[columnCount - 1]}${totalRows}"/>`);
3659
4198
  } else {
@@ -3684,7 +4223,7 @@ var XlsxWriter = class {
3684
4223
  rowNum++;
3685
4224
  bigBuf.writeString(`<row r="${rowNum}">`);
3686
4225
  for (let c = 0; c < headers.length; c++) {
3687
- this._writeStringCell(bigBuf, headers[c], colLetters[c], rowNum, 3);
4226
+ this._writeStringCell(bigBuf, headers[c], "", 3);
3688
4227
  }
3689
4228
  bigBuf.writeString("</row>");
3690
4229
  }
@@ -3692,6 +4231,7 @@ var XlsxWriter = class {
3692
4231
  rowNum++;
3693
4232
  bigBuf.writeString(`<row r="${rowNum}">`);
3694
4233
  const row = rows[r];
4234
+ let prevCol = -1;
3695
4235
  for (let c = 0; c < row.length; c++) {
3696
4236
  const raw = row[c];
3697
4237
  if (raw === null || raw === void 0) continue;
@@ -3699,27 +4239,28 @@ var XlsxWriter = class {
3699
4239
  const val = fmtString !== null ? unwrapCell(raw) : raw;
3700
4240
  const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
3701
4241
  const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
3702
- const colRef = colLetters[c];
4242
+ const ref = cellRefAttr(c, prevCol, colLetters[c], rowNum);
4243
+ prevCol = c;
3703
4244
  if (typeof val === "number") {
3704
4245
  if (Number.isFinite(val)) {
3705
- bigBuf.writeString(`<c r="${colRef}${rowNum}"${styleAttr}><v>${val}</v></c>`);
4246
+ bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
3706
4247
  } else {
3707
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4248
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3708
4249
  }
3709
4250
  } else if (typeof val === "bigint") {
3710
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4251
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3711
4252
  } else if (typeof val === "boolean") {
3712
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
4253
+ bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
3713
4254
  } else if (val instanceof Date) {
3714
4255
  const oaDate = this._toOADate(val);
3715
4256
  if (Number.isFinite(oaDate)) {
3716
4257
  const finalStyle = xfIdx >= 0 ? xfIdx : 1;
3717
- bigBuf.writeString(`<c r="${colRef}${rowNum}" s="${finalStyle}"><v>${oaDate}</v></c>`);
4258
+ bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
3718
4259
  } else {
3719
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4260
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3720
4261
  }
3721
4262
  } else if (val !== null && val !== void 0) {
3722
- this._writeStringCell(bigBuf, val.toString(), colRef, rowNum, xfIdx >= 0 ? xfIdx : void 0);
4263
+ this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
3723
4264
  }
3724
4265
  }
3725
4266
  bigBuf.writeString("</row>");
@@ -3738,7 +4279,9 @@ var XlsxWriter = class {
3738
4279
  name: this.sheetList[this.sheetCount - 1].pathInArchive
3739
4280
  });
3740
4281
  }
3741
- _writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
4282
+ /** `ref` is the cell's `r` attribute from `cellRefAttr`, or '' when omitted. */
4283
+ _writeStringCell(bigBuf, val, ref, styleOverride) {
4284
+ assertCellTextLength(val);
3742
4285
  let index = this.sstMap.get(val);
3743
4286
  if (index === void 0) {
3744
4287
  index = this.sstArray.length;
@@ -3747,7 +4290,7 @@ var XlsxWriter = class {
3747
4290
  }
3748
4291
  this.sstCntAll++;
3749
4292
  const styleAttr = styleOverride !== void 0 ? ` s="${styleOverride}"` : "";
3750
- bigBuf.writeString(`<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`);
4293
+ bigBuf.writeString(`<c${ref} t="s"${styleAttr}><v>${index}</v></c>`);
3751
4294
  }
3752
4295
  _registerFormat(fmtString) {
3753
4296
  if (this._formatXfMap.has(fmtString)) {
@@ -3760,29 +4303,79 @@ var XlsxWriter = class {
3760
4303
  return xfIndex;
3761
4304
  }
3762
4305
  _toOADate(date) {
3763
- return (date.getTime() - this._oaEpoch) / 864e5;
4306
+ return timeToOaDate(date.getTime(), this._oaEpoch);
3764
4307
  }
3765
4308
  _getDateWidth(_date) {
3766
4309
  return 19;
3767
4310
  }
3768
4311
  /** Finalize the ZIP package and close the output stream. */
3769
4312
  finalize() {
3770
- return new Promise((resolve4, reject) => {
4313
+ return new Promise((resolve6, reject) => {
4314
+ if (this._finalized) {
4315
+ reject(new Error("XlsxWriter: already finalized"));
4316
+ return;
4317
+ }
4318
+ this._finalized = true;
4319
+ let settled = false;
4320
+ const cleanupTemp = () => {
4321
+ try {
4322
+ if (fs2.existsSync(this._tempPath)) fs2.rmSync(this._tempPath);
4323
+ } catch {
4324
+ }
4325
+ };
4326
+ const fail = (err) => {
4327
+ if (settled) return;
4328
+ settled = true;
4329
+ try {
4330
+ this.archive.abort();
4331
+ } catch {
4332
+ }
4333
+ try {
4334
+ this.output.destroy();
4335
+ } catch {
4336
+ }
4337
+ cleanupTemp();
4338
+ reject(err instanceof Error ? err : new Error(String(err)));
4339
+ };
4340
+ const succeed = () => {
4341
+ if (settled) return;
4342
+ if (this._outputError) {
4343
+ fail(this._outputError);
4344
+ return;
4345
+ }
4346
+ settled = true;
4347
+ try {
4348
+ const fd = fs2.openSync(this._tempPath, "r+");
4349
+ try {
4350
+ fs2.fsyncSync(fd);
4351
+ } finally {
4352
+ fs2.closeSync(fd);
4353
+ }
4354
+ try {
4355
+ const existing = fs2.statSync(this._targetPath);
4356
+ fs2.chmodSync(this._tempPath, existing.mode & 511);
4357
+ } catch {
4358
+ }
4359
+ fs2.renameSync(this._tempPath, this._targetPath);
4360
+ resolve6();
4361
+ } catch (err) {
4362
+ cleanupTemp();
4363
+ reject(err instanceof Error ? err : new Error(String(err)));
4364
+ }
4365
+ };
3771
4366
  try {
4367
+ if (this._outputError) throw this._outputError;
3772
4368
  this._writeSharedStrings();
3773
4369
  this._writeStyles();
3774
4370
  this._writeWorkbook();
3775
4371
  this._writeContentTypes();
3776
4372
  this._writeRels();
3777
- this.output.on("close", () => {
3778
- resolve4();
3779
- });
3780
- this.archive.on("error", (err) => {
3781
- reject(err);
3782
- });
4373
+ this.output.once("close", succeed);
4374
+ this.output.once("error", fail);
4375
+ this.archive.once("error", fail);
3783
4376
  this.archive.finalize();
3784
4377
  } catch (err) {
3785
- reject(err);
4378
+ fail(err);
3786
4379
  }
3787
4380
  });
3788
4381
  }
@@ -3914,13 +4507,13 @@ ${xfEntries.join("\n")}
3914
4507
  // src/XlsxUpdater.ts
3915
4508
  import * as fs5 from "fs";
3916
4509
  import * as os from "os";
3917
- import * as path4 from "path";
4510
+ import * as path6 from "path";
3918
4511
  import AdmZip2 from "adm-zip";
3919
4512
 
3920
4513
  // src/atomicFile.ts
3921
4514
  import * as fs3 from "fs";
3922
- import * as path2 from "path";
3923
- import { randomUUID } from "crypto";
4515
+ import * as path4 from "path";
4516
+ import { randomUUID as randomUUID3 } from "crypto";
3924
4517
  var realFileOperations = {
3925
4518
  exists: (filePath) => fs3.existsSync(filePath),
3926
4519
  rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
@@ -3943,7 +4536,7 @@ function readDestinationMode(target) {
3943
4536
  }
3944
4537
  }
3945
4538
  function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
3946
- const backupPath = `${destinationPath}.${randomUUID()}.replacement-backup`;
4539
+ const backupPath = `${destinationPath}.${randomUUID3()}.replacement-backup`;
3947
4540
  let backupCreated = false;
3948
4541
  let installed = false;
3949
4542
  if (operations.exists(destinationPath)) {
@@ -3991,11 +4584,11 @@ function installTemporaryOutput(temporaryPath, destinationPath, overwrite, opera
3991
4584
  }
3992
4585
  }
3993
4586
  function writeBufferAtomically(buffer, destinationPath) {
3994
- const target = path2.resolve(destinationPath);
3995
- fs3.mkdirSync(path2.dirname(target), { recursive: true });
3996
- const temporaryPath = path2.join(
3997
- path2.dirname(target),
3998
- `.${path2.basename(target)}.${randomUUID()}.tmp`
4587
+ const target = path4.resolve(destinationPath);
4588
+ fs3.mkdirSync(path4.dirname(target), { recursive: true });
4589
+ const temporaryPath = path4.join(
4590
+ path4.dirname(target),
4591
+ `.${path4.basename(target)}.${randomUUID3()}.tmp`
3999
4592
  );
4000
4593
  const destinationMode = readDestinationMode(target);
4001
4594
  try {
@@ -4031,8 +4624,8 @@ function writeBufferAtomically(buffer, destinationPath) {
4031
4624
 
4032
4625
  // src/streamingZip.ts
4033
4626
  import * as fs4 from "fs";
4034
- import * as path3 from "path";
4035
- import { randomUUID as randomUUID2 } from "crypto";
4627
+ import * as path5 from "path";
4628
+ import { randomUUID as randomUUID4 } from "crypto";
4036
4629
  import { finished } from "stream/promises";
4037
4630
  import { Readable as Readable3 } from "stream";
4038
4631
  import archiver3 from "archiver";
@@ -4044,8 +4637,8 @@ function readDestinationMode2(target) {
4044
4637
  return void 0;
4045
4638
  }
4046
4639
  }
4047
- function appendAndWait(archive, source, name) {
4048
- return new Promise((resolve4, reject) => {
4640
+ function appendAndWait(archive, source, name, output) {
4641
+ return new Promise((resolve6, reject) => {
4049
4642
  const cleanup = () => {
4050
4643
  archive.off("entry", onEntry);
4051
4644
  archive.off("error", onError);
@@ -4053,10 +4646,13 @@ function appendAndWait(archive, source, name) {
4053
4646
  if (typeof maybeEmitter2.off === "function") {
4054
4647
  maybeEmitter2.off("error", onError);
4055
4648
  }
4649
+ if (output) {
4650
+ output.off("error", onError);
4651
+ }
4056
4652
  };
4057
4653
  const onEntry = () => {
4058
4654
  cleanup();
4059
- resolve4();
4655
+ resolve6();
4060
4656
  };
4061
4657
  const onError = (error) => {
4062
4658
  cleanup();
@@ -4064,6 +4660,9 @@ function appendAndWait(archive, source, name) {
4064
4660
  };
4065
4661
  archive.once("entry", onEntry);
4066
4662
  archive.once("error", onError);
4663
+ if (output) {
4664
+ output.once("error", onError);
4665
+ }
4067
4666
  const maybeEmitter = source;
4068
4667
  if (typeof maybeEmitter.once === "function") {
4069
4668
  maybeEmitter.once("error", onError);
@@ -4077,11 +4676,11 @@ function appendAndWait(archive, source, name) {
4077
4676
  });
4078
4677
  }
4079
4678
  async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4080
- const target = path3.resolve(destinationPath);
4081
- fs4.mkdirSync(path3.dirname(target), { recursive: true });
4082
- const temporaryPath = path3.join(
4083
- path3.dirname(target),
4084
- `.${path3.basename(target)}.${randomUUID2()}.tmp`
4679
+ const target = path5.resolve(destinationPath);
4680
+ fs4.mkdirSync(path5.dirname(target), { recursive: true });
4681
+ const temporaryPath = path5.join(
4682
+ path5.dirname(target),
4683
+ `.${path5.basename(target)}.${randomUUID4()}.tmp`
4085
4684
  );
4086
4685
  const destinationMode = readDestinationMode2(target);
4087
4686
  let output = null;
@@ -4091,18 +4690,32 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4091
4690
  temporaryPath,
4092
4691
  destinationMode !== void 0 ? { mode: destinationMode } : {}
4093
4692
  );
4094
- archive = archiver3("zip");
4693
+ let outputError = null;
4694
+ output.on("error", (err) => {
4695
+ outputError = err;
4696
+ try {
4697
+ archive == null ? void 0 : archive.abort();
4698
+ } catch {
4699
+ }
4700
+ });
4701
+ archive = archiver3("zip", { zlib: { level: DEFAULT_COMPRESSION_LEVEL } });
4702
+ archive.on("error", (err) => {
4703
+ outputError = err;
4704
+ });
4095
4705
  archive.pipe(output);
4096
4706
  for (const entry of zip.getEntries()) {
4707
+ if (outputError) throw outputError;
4097
4708
  const stagedPath = stagedParts.get(entry.entryName);
4098
4709
  if (stagedPath) {
4099
- await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName);
4710
+ await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName, output);
4100
4711
  } else {
4101
- await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName);
4712
+ await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName, output);
4102
4713
  }
4103
4714
  }
4715
+ if (outputError) throw outputError;
4104
4716
  await archive.finalize();
4105
4717
  await finished(output);
4718
+ if (outputError) throw outputError;
4106
4719
  if (destinationMode !== void 0) {
4107
4720
  try {
4108
4721
  fs4.chmodSync(temporaryPath, destinationMode);
@@ -4136,6 +4749,23 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4136
4749
  }
4137
4750
 
4138
4751
  // src/XlsxUpdater.ts
4752
+ function parseCellRange(ref) {
4753
+ const m = /^\$?([A-Za-z]+)\$?(\d+)(?::\$?([A-Za-z]+)\$?(\d+))?$/.exec(ref.trim());
4754
+ if (!m) return null;
4755
+ const c1 = columnLetterToIndex(m[1].toUpperCase());
4756
+ const r1 = parseInt(m[2], 10);
4757
+ const c2 = m[3] ? columnLetterToIndex(m[3].toUpperCase()) : c1;
4758
+ const r2 = m[4] ? parseInt(m[4], 10) : r1;
4759
+ if (!Number.isFinite(c1) || !Number.isFinite(r1) || !Number.isFinite(c2) || !Number.isFinite(r2)) {
4760
+ return null;
4761
+ }
4762
+ return {
4763
+ c1: Math.min(c1, c2),
4764
+ r1: Math.min(r1, r2),
4765
+ c2: Math.max(c1, c2),
4766
+ r2: Math.max(r1, r2)
4767
+ };
4768
+ }
4139
4769
  var XlsxUpdater = class _XlsxUpdater {
4140
4770
  /**
4141
4771
  * @param path Path to an existing `.xlsx` file.
@@ -4222,6 +4852,8 @@ var XlsxUpdater = class _XlsxUpdater {
4222
4852
  const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
4223
4853
  this._updatePivotCaches(sheetName, dimRef, rows.length);
4224
4854
  this._addRefreshOnLoadToPivotTables();
4855
+ this._removeCalcChain();
4856
+ this._updateTableRefs(sheetName, dimRef);
4225
4857
  }
4226
4858
  /**
4227
4859
  * Replace worksheet rows from a one-pass source without materialising the
@@ -4294,10 +4926,10 @@ var XlsxUpdater = class _XlsxUpdater {
4294
4926
  if (outputError) throw outputError;
4295
4927
  writtenBytes += data.length;
4296
4928
  if (rowOutput.write(data)) return;
4297
- await new Promise((resolve4, reject) => {
4929
+ await new Promise((resolve6, reject) => {
4298
4930
  const onDrain = () => {
4299
4931
  cleanup();
4300
- resolve4();
4932
+ resolve6();
4301
4933
  };
4302
4934
  const onError = (error) => {
4303
4935
  cleanup();
@@ -4337,8 +4969,8 @@ var XlsxUpdater = class _XlsxUpdater {
4337
4969
  lastKeptEnd = writtenBytes;
4338
4970
  }
4339
4971
  }
4340
- await new Promise((resolve4, reject) => {
4341
- rowOutput.once("close", resolve4);
4972
+ await new Promise((resolve6, reject) => {
4973
+ rowOutput.once("close", resolve6);
4342
4974
  rowOutput.once("error", reject);
4343
4975
  rowOutput.end();
4344
4976
  });
@@ -4357,6 +4989,8 @@ var XlsxUpdater = class _XlsxUpdater {
4357
4989
  this._commitSharedStrings();
4358
4990
  this._updatePivotCaches(sheetName, dimRef, keptDataRows);
4359
4991
  this._addRefreshOnLoadToPivotTables();
4992
+ this._removeCalcChain();
4993
+ this._updateTableRefs(sheetName, dimRef);
4360
4994
  if (oldStage && oldStage !== outputPath) {
4361
4995
  this._removeTemporaryFile(oldStage);
4362
4996
  }
@@ -4430,7 +5064,7 @@ var XlsxUpdater = class _XlsxUpdater {
4430
5064
  }
4431
5065
  _ensureTemporaryDirectory() {
4432
5066
  if (this.temporaryDirectory === null) {
4433
- this.temporaryDirectory = fs5.mkdtempSync(path4.join(os.tmpdir(), "spreadsheet-updater-"));
5067
+ this.temporaryDirectory = fs5.mkdtempSync(path6.join(os.tmpdir(), "spreadsheet-updater-"));
4434
5068
  } else {
4435
5069
  try {
4436
5070
  fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
@@ -4440,7 +5074,7 @@ var XlsxUpdater = class _XlsxUpdater {
4440
5074
  return this.temporaryDirectory;
4441
5075
  }
4442
5076
  _temporaryPath(suffix) {
4443
- return path4.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
5077
+ return path6.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
4444
5078
  }
4445
5079
  _removeTemporaryFile(filePath) {
4446
5080
  try {
@@ -4480,10 +5114,15 @@ var XlsxUpdater = class _XlsxUpdater {
4480
5114
  const rIdToTarget = {};
4481
5115
  if (relsEntry) {
4482
5116
  const rels = relsEntry.getData().toString("utf8");
4483
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
4484
- let match2;
4485
- while ((match2 = relRegex.exec(rels)) !== null) {
4486
- rIdToTarget[match2[1]] = match2[2];
5117
+ const relTagRe = /<Relationship\b[^>]*>/g;
5118
+ let tagMatch;
5119
+ while ((tagMatch = relTagRe.exec(rels)) !== null) {
5120
+ const attrs = parseTagAttributes(tagMatch[0]);
5121
+ const id = attrs["Id"] ?? attrs["id"];
5122
+ const target = attrs["Target"] ?? attrs["target"];
5123
+ if (id !== void 0 && target !== void 0) {
5124
+ rIdToTarget[id] = target;
5125
+ }
4487
5126
  }
4488
5127
  }
4489
5128
  const wbEntry = this.zip.getEntry("xl/workbook.xml");
@@ -4492,21 +5131,23 @@ var XlsxUpdater = class _XlsxUpdater {
4492
5131
  let match;
4493
5132
  while ((match = sheetRe.exec(wbXml)) !== null) {
4494
5133
  const tag = match[0];
4495
- const nameMatch = /name="([^"]*)"/.exec(tag);
4496
- const rIdMatch = /r:id="([^"]*)"/.exec(tag);
4497
- if (!nameMatch || !rIdMatch) continue;
4498
- const name = nameMatch[1].replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
4499
- const target = rIdToTarget[rIdMatch[1]] ?? "";
4500
- let fullPath = target;
4501
- if (fullPath.startsWith("/")) {
4502
- fullPath = fullPath.substring(1);
4503
- } else if (!fullPath.startsWith("xl/")) {
4504
- fullPath = "xl/" + fullPath;
4505
- }
5134
+ const attrs = parseTagAttributes(tag);
5135
+ const rawName = attrs["name"];
5136
+ const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
5137
+ if (rawName === void 0 || rId === void 0) continue;
5138
+ const name = unescapeXml(rawName);
5139
+ const target = rIdToTarget[rId] ?? "";
5140
+ const fullPath = resolveRelTarget(target);
4506
5141
  if (fullPath) {
4507
5142
  this.sheetNameToPath.set(name, fullPath);
4508
5143
  }
4509
5144
  }
5145
+ const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbXml);
5146
+ if (wbPrMatch) {
5147
+ const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
5148
+ const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
5149
+ this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
5150
+ }
4510
5151
  for (const entry of this.zip.getEntries()) {
4511
5152
  const name = entry.entryName;
4512
5153
  if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
@@ -4566,7 +5207,7 @@ var XlsxUpdater = class _XlsxUpdater {
4566
5207
  throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
4567
5208
  }
4568
5209
  const appended = this.sharedStringsPending.map((txt) => {
4569
- const clean = escapeXmlText(txt);
5210
+ const clean = escapeXlsxText(txt);
4570
5211
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
4571
5212
  const tAttr = preserve ? ' xml:space="preserve"' : "";
4572
5213
  return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
@@ -4589,14 +5230,25 @@ var XlsxUpdater = class _XlsxUpdater {
4589
5230
  const sdEnd = sheetXml.indexOf("</sheetData>", sdStart);
4590
5231
  if (sdEnd === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
4591
5232
  const region = sheetXml.substring(sdStart, sdEnd);
4592
- const cellRe = /<c\b[^>]*>/g;
5233
+ const cellRe = /<row\b[^>]*>|<c\b[^>]*>/g;
4593
5234
  let m;
5235
+ let row = 0;
5236
+ let nextCol = 0;
4594
5237
  while ((m = cellRe.exec(region)) !== null) {
4595
5238
  const tag = m[0];
5239
+ if (tag.startsWith("<row")) {
5240
+ const rowAttr = /\br="(\d+)"/.exec(tag);
5241
+ row = rowAttr ? parseInt(rowAttr[1], 10) : row + 1;
5242
+ nextCol = 0;
5243
+ continue;
5244
+ }
4596
5245
  const rMatch = /r="([A-Z]+)(\d+)"/.exec(tag);
4597
- if (!rMatch) continue;
4598
- const col = columnLetterToIndex(rMatch[1]);
4599
- const row = parseInt(rMatch[2], 10);
5246
+ let col = nextCol;
5247
+ if (rMatch) {
5248
+ col = columnLetterToIndex(rMatch[1]);
5249
+ row = parseInt(rMatch[2], 10);
5250
+ }
5251
+ nextCol = col + 1;
4600
5252
  const sMatch = /s="(\d+)"/.exec(tag);
4601
5253
  if (!sMatch) continue;
4602
5254
  const s = parseInt(sMatch[1], 10);
@@ -4701,7 +5353,7 @@ var XlsxUpdater = class _XlsxUpdater {
4701
5353
  return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
4702
5354
  }
4703
5355
  if (val instanceof Date) {
4704
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
5356
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
4705
5357
  if (Number.isFinite(oaDate)) {
4706
5358
  const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
4707
5359
  return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
@@ -4717,7 +5369,7 @@ var XlsxUpdater = class _XlsxUpdater {
4717
5369
  const colRef = columnIndexToLetter(col);
4718
5370
  const styleAttr = style > 0 ? ` s="${style}"` : "";
4719
5371
  if (this.sharedStringsXml === null) {
4720
- const clean = escapeXmlText(text);
5372
+ const clean = escapeXlsxText(text);
4721
5373
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
4722
5374
  const tAttr = preserve ? ' xml:space="preserve"' : "";
4723
5375
  return `<c r="${colRef}${rowNum}" t="inlineStr"${styleAttr}><is><t${tAttr}>${clean}</t></is></c>`;
@@ -4918,8 +5570,8 @@ var XlsxUpdater = class _XlsxUpdater {
4918
5570
  }
4919
5571
  }
4920
5572
  _addRefreshOnLoadToPivotTables() {
4921
- for (const path7 of this.pivotTableDefPaths) {
4922
- const entry = this.zip.getEntry(path7);
5573
+ for (const path9 of this.pivotTableDefPaths) {
5574
+ const entry = this.zip.getEntry(path9);
4923
5575
  if (!entry) continue;
4924
5576
  let xml = entry.getData().toString("utf8");
4925
5577
  const rootRe = /<pivotTableDefinition\b[^>]*>/;
@@ -4927,7 +5579,233 @@ var XlsxUpdater = class _XlsxUpdater {
4927
5579
  if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
4928
5580
  const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
4929
5581
  xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
4930
- this.zip.updateFile(path7, Buffer.from(xml, "utf8"));
5582
+ this.zip.updateFile(path9, Buffer.from(xml, "utf8"));
5583
+ }
5584
+ }
5585
+ /**
5586
+ * Remove the stale calculation chain (xl/calcChain.xml). Excel rebuilds it
5587
+ * on open; keeping stale <c> entries for the replaced sheet would force a
5588
+ * repair prompt. Removes the part, its Content_Types override and its
5589
+ * workbook rels entry (xlspy parity).
5590
+ */
5591
+ _removeCalcChain() {
5592
+ const calcPath = "xl/calcChain.xml";
5593
+ if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) {
5594
+ this._ensureCalcPrFullCalc();
5595
+ return;
5596
+ }
5597
+ try {
5598
+ this.zip.deleteFile(calcPath);
5599
+ } catch {
5600
+ }
5601
+ const stagedCalc = this.stagedParts.get(calcPath);
5602
+ if (stagedCalc) {
5603
+ this.stagedParts.delete(calcPath);
5604
+ try {
5605
+ fs5.rmSync(stagedCalc, { force: true });
5606
+ } catch {
5607
+ }
5608
+ }
5609
+ const ctEntry = this.zip.getEntry("[Content_Types].xml");
5610
+ if (ctEntry || this.stagedParts.has("[Content_Types].xml")) {
5611
+ try {
5612
+ let ct = this._entryData("[Content_Types].xml").toString("utf8");
5613
+ const before = ct;
5614
+ ct = ct.replace(/<Override\b[^>]*PartName="\/xl\/calcChain\.xml"[^>]*\/?>/gi, "");
5615
+ if (ct !== before) {
5616
+ this._writePartText("[Content_Types].xml", ct);
5617
+ }
5618
+ } catch {
5619
+ }
5620
+ }
5621
+ const relsPath = "xl/_rels/workbook.xml.rels";
5622
+ if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
5623
+ try {
5624
+ const rels = this._entryData(relsPath).toString("utf8");
5625
+ const relTagRe = /<Relationship\b[^>]*>/g;
5626
+ let m;
5627
+ let out = "";
5628
+ let last = 0;
5629
+ let changed = false;
5630
+ while ((m = relTagRe.exec(rels)) !== null) {
5631
+ const attrs = parseTagAttributes(m[0]);
5632
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
5633
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
5634
+ const isCalcChain = type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.xml";
5635
+ if (isCalcChain) {
5636
+ out += rels.substring(last, m.index);
5637
+ last = m.index + m[0].length;
5638
+ changed = true;
5639
+ }
5640
+ }
5641
+ if (changed) {
5642
+ out += rels.substring(last);
5643
+ this._writePartText(relsPath, out);
5644
+ }
5645
+ } catch {
5646
+ }
5647
+ }
5648
+ this._ensureCalcPrFullCalc();
5649
+ }
5650
+ _writePartText(name, text) {
5651
+ if (this.stagedParts.has(name)) {
5652
+ fs5.writeFileSync(this.stagedParts.get(name), text, "utf8");
5653
+ } else {
5654
+ this.zip.updateFile(name, Buffer.from(text, "utf8"));
5655
+ }
5656
+ }
5657
+ _ensureCalcPrFullCalc() {
5658
+ try {
5659
+ const wbPath = "xl/workbook.xml";
5660
+ if (!this.zip.getEntry(wbPath) && !this.stagedParts.has(wbPath)) return;
5661
+ let xml = this._entryData(wbPath).toString("utf8");
5662
+ const calcPrRe = /<(?:[A-Za-z_][\w.-]*:)?calcPr\b[^>]*>/i;
5663
+ const match = calcPrRe.exec(xml);
5664
+ if (match && match.index !== void 0) {
5665
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(match[0], "fullCalcOnLoad", "1");
5666
+ xml = xml.substring(0, match.index) + newTag + xml.substring(match.index + match[0].length);
5667
+ } else {
5668
+ const insertAt = _XlsxUpdater._calcPrInsertPosition(xml);
5669
+ if (insertAt < 0) return;
5670
+ xml = xml.substring(0, insertAt) + '<calcPr fullCalcOnLoad="1"/>' + xml.substring(insertAt);
5671
+ }
5672
+ this._writePartText(wbPath, xml);
5673
+ } catch {
5674
+ }
5675
+ }
5676
+ /**
5677
+ * Insertion offset for a missing `<calcPr>`. The element follows
5678
+ * sheets/externalReferences/definedNames in the workbook schema, so insert
5679
+ * after the last of those that exists instead of right after `<workbook>`.
5680
+ */
5681
+ static _calcPrInsertPosition(xml) {
5682
+ const prefixed = "(?:[A-Za-z_][\\w.-]*:)?";
5683
+ const candidates = [
5684
+ new RegExp(`</${prefixed}sheets\\s*>`, "i"),
5685
+ new RegExp(`<${prefixed}externalReferences\\b[^>]*/>`, "i"),
5686
+ new RegExp(`<${prefixed}externalReferences\\b[^>]*>[\\s\\S]*?</${prefixed}externalReferences\\s*>`, "i"),
5687
+ new RegExp(`<${prefixed}definedNames\\b[^>]*/>`, "i"),
5688
+ new RegExp(`<${prefixed}definedNames\\b[^>]*>[\\s\\S]*?</${prefixed}definedNames\\s*>`, "i")
5689
+ ];
5690
+ let insertAt = -1;
5691
+ for (const re of candidates) {
5692
+ const m = re.exec(xml);
5693
+ if (m && m.index !== void 0) {
5694
+ insertAt = Math.max(insertAt, m.index + m[0].length);
5695
+ }
5696
+ }
5697
+ if (insertAt >= 0) return insertAt;
5698
+ const close = new RegExp(`</${prefixed}workbook\\s*>`, "i").exec(xml);
5699
+ if (close && close.index !== void 0) return close.index;
5700
+ const root = new RegExp(`<${prefixed}workbook\\b[^>]*>`, "i").exec(xml);
5701
+ if (!root || root.index === void 0) return -1;
5702
+ return root.index + root[0].length;
5703
+ }
5704
+ /**
5705
+ * Update ListObject table ranges (xl/tables/*.xml) for the replaced sheet.
5706
+ * Only tables anchored at A1 are updated to the new dimension; tables
5707
+ * elsewhere are left untouched (moving them would corrupt the sheet).
5708
+ * Empty replacements (dimRef "A1") do not shrink multi-column tables.
5709
+ */
5710
+ _updateTableRefs(sheetName, dimRef) {
5711
+ const sheetPath = this.sheetNameToPath.get(sheetName);
5712
+ if (!sheetPath) return;
5713
+ const slash = sheetPath.lastIndexOf("/");
5714
+ const dir = slash >= 0 ? sheetPath.substring(0, slash + 1) : "";
5715
+ const base = slash >= 0 ? sheetPath.substring(slash + 1) : sheetPath;
5716
+ const sheetRelsPath = `${dir}_rels/${base}.rels`;
5717
+ const sheetRelsEntry = this.zip.getEntry(sheetRelsPath);
5718
+ const hasStagedRels = this.stagedParts.has(sheetRelsPath);
5719
+ if (!sheetRelsEntry && !hasStagedRels) return;
5720
+ const tableTargets = [];
5721
+ try {
5722
+ const relsXml = this._entryData(sheetRelsPath).toString("utf8");
5723
+ const relTagRe = /<Relationship\b[^>]*>/g;
5724
+ let m;
5725
+ while ((m = relTagRe.exec(relsXml)) !== null) {
5726
+ const attrs = parseTagAttributes(m[0]);
5727
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
5728
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
5729
+ if (!type.includes("/table") || !target) continue;
5730
+ let tablePath;
5731
+ if (target.startsWith("/")) {
5732
+ tablePath = resolveRelTarget(target);
5733
+ } else {
5734
+ const combined = dir + target;
5735
+ const parts = [];
5736
+ for (const seg of combined.split("/")) {
5737
+ if (seg === "..") parts.pop();
5738
+ else if (seg !== "." && seg !== "") parts.push(seg);
5739
+ }
5740
+ tablePath = parts.join("/");
5741
+ }
5742
+ tableTargets.push(tablePath);
5743
+ }
5744
+ } catch {
5745
+ return;
5746
+ }
5747
+ if (tableTargets.length === 0) return;
5748
+ const tables = [];
5749
+ for (const tablePath of tableTargets) {
5750
+ try {
5751
+ const hasEntry = !!this.zip.getEntry(tablePath);
5752
+ const hasStaged = this.stagedParts.has(tablePath);
5753
+ if (!hasEntry && !hasStaged) continue;
5754
+ const xml = this._entryData(tablePath).toString("utf8");
5755
+ const tableTagRe = /<(?:[A-Za-z_][\w.-]*:)?table\b[^>]*>/i;
5756
+ const tableMatch = tableTagRe.exec(xml);
5757
+ if (!tableMatch || tableMatch.index === void 0) continue;
5758
+ const refMatch = /\bref\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tableMatch[0]);
5759
+ const ref = refMatch ? refMatch[2] ?? refMatch[3] ?? "" : "";
5760
+ tables.push({
5761
+ path: tablePath,
5762
+ xml,
5763
+ tag: tableMatch[0],
5764
+ tagIndex: tableMatch.index,
5765
+ ref,
5766
+ range: parseCellRange(ref),
5767
+ staged: hasStaged
5768
+ });
5769
+ } catch {
5770
+ }
5771
+ }
5772
+ if (tables.length === 0) return;
5773
+ const dimRange = parseCellRange(dimRef);
5774
+ if (!dimRange) return;
5775
+ for (const table of tables) {
5776
+ try {
5777
+ const origRange = table.range;
5778
+ if (!origRange) continue;
5779
+ if (origRange.r1 !== 1 || origRange.c1 !== 0) continue;
5780
+ if (dimRef === "A1" && !/^A1$/i.test(table.ref)) continue;
5781
+ const origCols = origRange.c2 - origRange.c1 + 1;
5782
+ const newCols = dimRange.c2 - dimRange.c1 + 1;
5783
+ if (newCols !== origCols) continue;
5784
+ let lastRow = dimRange.r2;
5785
+ for (const other of tables) {
5786
+ if (other === table || !other.range) continue;
5787
+ if (other.range.r1 <= 1) continue;
5788
+ if (other.range.c1 > origRange.c2 || other.range.c2 < origRange.c1) continue;
5789
+ lastRow = Math.min(lastRow, other.range.r1 - 1);
5790
+ }
5791
+ if (lastRow < 1) continue;
5792
+ const newRef = origRange.c2 === 0 && lastRow === 1 ? "A1" : `A1:${columnIndexToLetter(origRange.c2)}${lastRow}`;
5793
+ let xml = table.xml;
5794
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(table.tag, "ref", newRef);
5795
+ xml = xml.substring(0, table.tagIndex) + newTag + xml.substring(table.tagIndex + table.tag.length);
5796
+ const afRe = /<(?:[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i;
5797
+ const afMatch = afRe.exec(xml);
5798
+ if (afMatch && afMatch.index !== void 0) {
5799
+ const newAf = _XlsxUpdater._replaceOrAddXmlAttribute(afMatch[0], "ref", newRef);
5800
+ xml = xml.substring(0, afMatch.index) + newAf + xml.substring(afMatch.index + afMatch[0].length);
5801
+ }
5802
+ if (table.staged) {
5803
+ fs5.writeFileSync(this.stagedParts.get(table.path), xml, "utf8");
5804
+ } else {
5805
+ this.zip.updateFile(table.path, Buffer.from(xml, "utf8"));
5806
+ }
5807
+ } catch {
5808
+ }
4931
5809
  }
4932
5810
  }
4933
5811
  };
@@ -4935,78 +5813,8 @@ var XlsxUpdater = class _XlsxUpdater {
4935
5813
  // src/XlsbUpdater.ts
4936
5814
  import * as fs6 from "fs";
4937
5815
  import * as os2 from "os";
4938
- import * as path5 from "path";
5816
+ import * as path7 from "path";
4939
5817
  import AdmZip3 from "adm-zip";
4940
-
4941
- // src/biff12Utils.ts
4942
- function readVlq(buf, pos) {
4943
- let value = 0;
4944
- let shift = 0;
4945
- let byte;
4946
- do {
4947
- byte = buf[pos++];
4948
- value |= (byte & 127) << shift;
4949
- shift += 7;
4950
- } while ((byte & 128) !== 0);
4951
- return { value, nextPos: pos };
4952
- }
4953
- function vlqBytes(value) {
4954
- const out = [];
4955
- while (value >= 128) {
4956
- out.push(value & 127 | 128);
4957
- value >>>= 7;
4958
- }
4959
- out.push(value & 127);
4960
- return out;
4961
- }
4962
- function readRecord(buf, pos, rec) {
4963
- const idRes = readVlq(buf, pos);
4964
- if (idRes.nextPos > buf.length) return false;
4965
- const lenRes = readVlq(buf, idRes.nextPos);
4966
- if (lenRes.nextPos + lenRes.value > buf.length) return false;
4967
- rec.headerStart = pos;
4968
- rec.dataStart = lenRes.nextPos;
4969
- rec.dataEnd = lenRes.nextPos + lenRes.value;
4970
- rec.id = idRes.value;
4971
- rec.len = lenRes.value;
4972
- return true;
4973
- }
4974
- function buildRecord(id, payload) {
4975
- const idBytes = vlqBytes(id);
4976
- const lenBytes = vlqBytes(payload.length);
4977
- const header = Buffer.alloc(idBytes.length + lenBytes.length);
4978
- idBytes.forEach((b, i) => header[i] = b);
4979
- lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
4980
- return Buffer.concat([header, payload]);
4981
- }
4982
- function readUtf16(buf, start, charCount) {
4983
- const end = start + charCount * 2;
4984
- if (end > buf.length) return "";
4985
- return buf.toString("utf16le", start, end);
4986
- }
4987
- function parseSharedStringsBin(buf) {
4988
- const values = [];
4989
- let total = 0;
4990
- let unique = 0;
4991
- let endSstOffset = buf.length;
4992
- const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4993
- let pos = 0;
4994
- while (readRecord(buf, pos, rec)) {
4995
- pos = rec.dataEnd;
4996
- if (rec.id === 159) {
4997
- total = buf.readUInt32LE(rec.dataStart);
4998
- unique = buf.readUInt32LE(rec.dataStart + 4);
4999
- } else if (rec.id === 19) {
5000
- const cch = buf.readUInt32LE(rec.dataStart + 1);
5001
- values.push(readUtf16(buf, rec.dataStart + 5, cch));
5002
- } else if (rec.id === 160) {
5003
- endSstOffset = rec.headerStart;
5004
- }
5005
- }
5006
- return { values, total, unique, endSstOffset };
5007
- }
5008
-
5009
- // src/XlsbUpdater.ts
5010
5818
  var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
5011
5819
  var RK_INT_LOWER = -1 << 29;
5012
5820
  var RK_INT_UPPER = (1 << 29) - 1;
@@ -5110,6 +5918,7 @@ var XlsbUpdater = class {
5110
5918
  const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
5111
5919
  const colLast = lastCol;
5112
5920
  this._patchPivotCaches(sheetName, rwLast, colLast);
5921
+ this._removeCalcChain();
5113
5922
  }
5114
5923
  /** Replace worksheet rows from a one-pass source. */
5115
5924
  async replaceSheetDataStream(sheetName, rows, options = {}) {
@@ -5177,10 +5986,10 @@ var XlsbUpdater = class {
5177
5986
  if (outputError) throw outputError;
5178
5987
  writtenBytes += data.length;
5179
5988
  if (rowOutput.write(data)) return;
5180
- await new Promise((resolve4, reject) => {
5989
+ await new Promise((resolve6, reject) => {
5181
5990
  const onDrain = () => {
5182
5991
  cleanup();
5183
- resolve4();
5992
+ resolve6();
5184
5993
  };
5185
5994
  const onError = (error) => {
5186
5995
  cleanup();
@@ -5231,8 +6040,8 @@ var XlsbUpdater = class {
5231
6040
  lastKeptEnd = writtenBytes;
5232
6041
  }
5233
6042
  }
5234
- await new Promise((resolve4, reject) => {
5235
- rowOutput.once("close", resolve4);
6043
+ await new Promise((resolve6, reject) => {
6044
+ rowOutput.once("close", resolve6);
5236
6045
  rowOutput.once("error", reject);
5237
6046
  rowOutput.end();
5238
6047
  });
@@ -5295,6 +6104,7 @@ var XlsbUpdater = class {
5295
6104
  Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
5296
6105
  lastCol
5297
6106
  );
6107
+ this._removeCalcChain();
5298
6108
  if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
5299
6109
  createdParts.splice(createdParts.indexOf(outputPath), 1);
5300
6110
  } catch (error) {
@@ -5365,7 +6175,7 @@ var XlsbUpdater = class {
5365
6175
  }
5366
6176
  _ensureTemporaryDirectory() {
5367
6177
  if (this.temporaryDirectory === null) {
5368
- this.temporaryDirectory = fs6.mkdtempSync(path5.join(os2.tmpdir(), "spreadsheet-updater-"));
6178
+ this.temporaryDirectory = fs6.mkdtempSync(path7.join(os2.tmpdir(), "spreadsheet-updater-"));
5369
6179
  } else {
5370
6180
  try {
5371
6181
  fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
@@ -5375,7 +6185,7 @@ var XlsbUpdater = class {
5375
6185
  return this.temporaryDirectory;
5376
6186
  }
5377
6187
  _temporaryPath(suffix) {
5378
- return path5.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
6188
+ return path7.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
5379
6189
  }
5380
6190
  _removeTemporaryFile(filePath) {
5381
6191
  try {
@@ -5415,10 +6225,15 @@ var XlsbUpdater = class {
5415
6225
  const rIdToTarget = {};
5416
6226
  if (relsEntry) {
5417
6227
  const rels = relsEntry.getData().toString("utf8");
5418
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
5419
- let match;
5420
- while ((match = relRegex.exec(rels)) !== null) {
5421
- rIdToTarget[match[1]] = match[2];
6228
+ const relTagRe = /<Relationship\b[^>]*>/g;
6229
+ let tagMatch;
6230
+ while ((tagMatch = relTagRe.exec(rels)) !== null) {
6231
+ const attrs = parseTagAttributes(tagMatch[0]);
6232
+ const id = attrs["Id"] ?? attrs["id"];
6233
+ const target = attrs["Target"] ?? attrs["target"];
6234
+ if (id !== void 0 && target !== void 0) {
6235
+ rIdToTarget[id] = target;
6236
+ }
5422
6237
  }
5423
6238
  }
5424
6239
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
@@ -5438,12 +6253,24 @@ var XlsbUpdater = class {
5438
6253
  o += 4;
5439
6254
  const name = readUtf16(wbBuf, o, nameLen);
5440
6255
  let target = rIdToTarget[rId] ?? "";
5441
- if (target.startsWith("/")) target = target.substring(1);
5442
- else if (!target.startsWith("xl/")) target = "xl/" + target;
6256
+ target = resolveRelTarget(target);
5443
6257
  if (target) {
5444
6258
  this.sheetNameToPath.set(name, target);
5445
6259
  }
5446
6260
  }
6261
+ try {
6262
+ const rec1904 = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
6263
+ let p1904 = 0;
6264
+ while (readRecord(wbBuf, p1904, rec1904)) {
6265
+ if (rec1904.id === 153 && rec1904.len >= 4) {
6266
+ const is1904 = (wbBuf[rec1904.dataStart] & 1) !== 0;
6267
+ this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
6268
+ break;
6269
+ }
6270
+ p1904 = rec1904.dataEnd;
6271
+ }
6272
+ } catch {
6273
+ }
5447
6274
  }
5448
6275
  for (const e of this.zip.getEntries()) {
5449
6276
  if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
@@ -5552,27 +6379,15 @@ var XlsbUpdater = class {
5552
6379
  _findDateXf() {
5553
6380
  const entry = this.zip.getEntry("xl/styles.bin");
5554
6381
  if (!entry) return null;
5555
- const stylesBuf = entry.getData();
5556
- const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
5557
- let pos = 0;
5558
- let inCellXfs = false;
5559
- let xfIndex = 0;
5560
- while (readRecord(stylesBuf, pos, rec)) {
5561
- pos = rec.dataEnd;
5562
- if (rec.id === 617) {
5563
- inCellXfs = true;
5564
- xfIndex = 0;
5565
- continue;
5566
- }
5567
- if (rec.id === 618) {
5568
- inCellXfs = false;
5569
- continue;
5570
- }
5571
- if (inCellXfs && rec.id === 47 && rec.len >= 4) {
5572
- const numFmtId = stylesBuf.readUInt16LE(rec.dataStart + 2);
5573
- const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47;
5574
- if (isDate) return xfIndex;
5575
- xfIndex++;
6382
+ const reader = new BiffReaderWriter(entry.getData());
6383
+ while (reader.readStyles()) {
6384
+ }
6385
+ const numFmtIds = reader._xfIndexToNumFmtId;
6386
+ const customDates = reader._customNumFmts;
6387
+ for (let i = 0; i < numFmtIds.length; i++) {
6388
+ const numFmtId = numFmtIds[i];
6389
+ if (numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || customDates.has(numFmtId)) {
6390
+ return i;
5576
6391
  }
5577
6392
  }
5578
6393
  return null;
@@ -5652,7 +6467,7 @@ var XlsbUpdater = class {
5652
6467
  return buildRecord(4, payload);
5653
6468
  }
5654
6469
  if (val instanceof Date) {
5655
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
6470
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
5656
6471
  if (Number.isFinite(oaDate)) {
5657
6472
  const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
5658
6473
  const payload = Buffer.alloc(16);
@@ -5719,10 +6534,10 @@ var XlsbUpdater = class {
5719
6534
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
5720
6535
  let pos = 0;
5721
6536
  while (readRecord(originalSheet, pos, rec)) {
5722
- if (rec.id === 152 && rec.len >= 36) {
6537
+ if (rec.id === 148 && rec.len >= 16) {
5723
6538
  const shift = rec.dataStart >= rowsEnd ? delta : 0;
5724
- patchInt32(rec.dataStart + shift + 24, lastRowIdx);
5725
- patchInt32(rec.dataStart + shift + 32, lastCol);
6539
+ patchInt32(rec.dataStart + shift + 4, lastRowIdx);
6540
+ patchInt32(rec.dataStart + shift + 12, lastCol);
5726
6541
  break;
5727
6542
  }
5728
6543
  pos = rec.dataEnd;
@@ -5761,8 +6576,8 @@ var XlsbUpdater = class {
5761
6576
  return { rowsStart, rowsEnd, autoFilterRanges };
5762
6577
  }
5763
6578
  /**
5764
- * Keep the sheet dimension record (0x98) in sync with the new data.
5765
- * Excel uses its rwLast (payload offset 24) and colLast (payload offset 32)
6579
+ * Keep the sheet dimension record (0x94) in sync with the new data.
6580
+ * Excel uses its rwLast (payload offset 4) and colLast (payload offset 12)
5766
6581
  * fields to read the full row range; stale values make the last written row
5767
6582
  * invisible to Excel (and drop it from pivot refreshes).
5768
6583
  */
@@ -5770,9 +6585,9 @@ var XlsbUpdater = class {
5770
6585
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
5771
6586
  let pos = 0;
5772
6587
  while (readRecord(sheetBuf, pos, rec)) {
5773
- if (rec.id === 152 && rec.len >= 36) {
5774
- sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 24);
5775
- sheetBuf.writeInt32LE(lastCol, rec.dataStart + 32);
6588
+ if (rec.id === 148 && rec.len >= 16) {
6589
+ sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 4);
6590
+ sheetBuf.writeInt32LE(lastCol, rec.dataStart + 12);
5776
6591
  return;
5777
6592
  }
5778
6593
  pos = rec.dataEnd;
@@ -5782,8 +6597,8 @@ var XlsbUpdater = class {
5782
6597
  // Pivot cache integration
5783
6598
  // ------------------------------------------------------------------
5784
6599
  _patchPivotCaches(sheetName, rwLast, colLast) {
5785
- for (const path7 of this.pivotCacheDefPaths) {
5786
- const entry = this.zip.getEntry(path7);
6600
+ for (const path9 of this.pivotCacheDefPaths) {
6601
+ const entry = this.zip.getEntry(path9);
5787
6602
  if (!entry) continue;
5788
6603
  const buf = Buffer.from(entry.getData());
5789
6604
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
@@ -5815,16 +6630,84 @@ var XlsbUpdater = class {
5815
6630
  }
5816
6631
  }
5817
6632
  if (patched) {
5818
- this.zip.updateFile(path7, buf);
6633
+ this.zip.updateFile(path9, buf);
6634
+ }
6635
+ }
6636
+ }
6637
+ /**
6638
+ * Remove the stale calculation chain (xl/calcChain.bin). Excel rebuilds it
6639
+ * on open. Removes the part, its Content_Types override and its workbook
6640
+ * rels entry. XLSB tables are not patched (documented limitation).
6641
+ */
6642
+ _removeCalcChain() {
6643
+ const calcPath = "xl/calcChain.bin";
6644
+ if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) return;
6645
+ try {
6646
+ this.zip.deleteFile(calcPath);
6647
+ } catch {
6648
+ }
6649
+ const stagedCalc = this.stagedParts.get(calcPath);
6650
+ if (stagedCalc) {
6651
+ this.stagedParts.delete(calcPath);
6652
+ try {
6653
+ fs6.rmSync(stagedCalc, { force: true });
6654
+ } catch {
6655
+ }
6656
+ }
6657
+ const ctPath = "[Content_Types].xml";
6658
+ if (this.zip.getEntry(ctPath) || this.stagedParts.has(ctPath)) {
6659
+ try {
6660
+ const ct = this._entryData(ctPath).toString("utf8");
6661
+ const updated = ct.replace(
6662
+ /<Override\b[^>]*PartName="\/xl\/calcChain\.bin"[^>]*\/?>/gi,
6663
+ ""
6664
+ );
6665
+ if (updated !== ct) this._writePartText(ctPath, updated);
6666
+ } catch {
6667
+ }
6668
+ }
6669
+ const relsPath = "xl/_rels/workbook.bin.rels";
6670
+ if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
6671
+ try {
6672
+ const rels = this._entryData(relsPath).toString("utf8");
6673
+ const relTagRe = /<Relationship\b[^>]*>/g;
6674
+ let m;
6675
+ let out = "";
6676
+ let last = 0;
6677
+ let changed = false;
6678
+ while ((m = relTagRe.exec(rels)) !== null) {
6679
+ const attrs = parseTagAttributes(m[0]);
6680
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
6681
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
6682
+ if (type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.bin") {
6683
+ out += rels.substring(last, m.index);
6684
+ last = m.index + m[0].length;
6685
+ changed = true;
6686
+ }
6687
+ }
6688
+ if (changed) {
6689
+ out += rels.substring(last);
6690
+ this._writePartText(relsPath, out);
6691
+ }
6692
+ } catch {
5819
6693
  }
5820
6694
  }
5821
6695
  }
6696
+ /** Write an XML/text part to its staged file when present, else into the ZIP. */
6697
+ _writePartText(name, text) {
6698
+ const stagedPath = this.stagedParts.get(name);
6699
+ if (stagedPath) {
6700
+ fs6.writeFileSync(stagedPath, text, "utf8");
6701
+ } else {
6702
+ this.zip.updateFile(name, Buffer.from(text, "utf8"));
6703
+ }
6704
+ }
5822
6705
  };
5823
6706
 
5824
6707
  // src/ExcelConverter.ts
5825
6708
  import * as fs7 from "fs";
5826
- import * as path6 from "path";
5827
- import { randomUUID as randomUUID3 } from "crypto";
6709
+ import * as path8 from "path";
6710
+ import { randomUUID as randomUUID5 } from "crypto";
5828
6711
  import { spawn } from "child_process";
5829
6712
  var POWERSHELL_CONVERSION_SCRIPT = `
5830
6713
  $ErrorActionPreference = 'Stop'
@@ -5870,14 +6753,14 @@ function errorMessage2(error) {
5870
6753
  return error instanceof Error ? error.message : String(error);
5871
6754
  }
5872
6755
  function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
5873
- const input = path6.resolve(inputPath);
5874
- const output = path6.resolve(outputPath);
5875
- if (path6.extname(input).toLowerCase() !== inputExtension) {
6756
+ const input = path8.resolve(inputPath);
6757
+ const output = path8.resolve(outputPath);
6758
+ if (path8.extname(input).toLowerCase() !== inputExtension) {
5876
6759
  throw new Error(
5877
6760
  `Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
5878
6761
  );
5879
6762
  }
5880
- if (path6.extname(output).toLowerCase() !== outputExtension) {
6763
+ if (path8.extname(output).toLowerCase() !== outputExtension) {
5881
6764
  throw new Error(
5882
6765
  `Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
5883
6766
  );
@@ -5888,8 +6771,8 @@ function validateConversionPaths(inputPath, outputPath, inputExtension, outputEx
5888
6771
  if (!fs7.statSync(input).isFile()) {
5889
6772
  throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
5890
6773
  }
5891
- if (!fs7.existsSync(path6.dirname(output))) {
5892
- throw new Error(`Excel conversion: destination directory not found: ${path6.dirname(output)}`);
6774
+ if (!fs7.existsSync(path8.dirname(output))) {
6775
+ throw new Error(`Excel conversion: destination directory not found: ${path8.dirname(output)}`);
5893
6776
  }
5894
6777
  if (fs7.existsSync(output)) {
5895
6778
  if (fs7.statSync(output).isDirectory()) {
@@ -5914,7 +6797,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5914
6797
  JUSTYBASE_EXCEL_OUTPUT: outputPath,
5915
6798
  JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
5916
6799
  };
5917
- return new Promise((resolve4, reject) => {
6800
+ return new Promise((resolve6, reject) => {
5918
6801
  var _a, _b, _c, _d;
5919
6802
  let stdout = "";
5920
6803
  let stderr = "";
@@ -5949,7 +6832,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5949
6832
  if (settled) return;
5950
6833
  settled = true;
5951
6834
  if (code === 0) {
5952
- resolve4();
6835
+ resolve6();
5953
6836
  return;
5954
6837
  }
5955
6838
  const details = (stderr || stdout).replace(/\s+/g, " ").trim();
@@ -5970,9 +6853,9 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
5970
6853
  if (process.platform !== "win32") {
5971
6854
  throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
5972
6855
  }
5973
- const temporaryOutput = path6.join(
5974
- path6.dirname(output),
5975
- `.${path6.basename(output, outputExtension)}.${randomUUID3()}.conversion${outputExtension}`
6856
+ const temporaryOutput = path8.join(
6857
+ path8.dirname(output),
6858
+ `.${path8.basename(output, outputExtension)}.${randomUUID5()}.conversion${outputExtension}`
5976
6859
  );
5977
6860
  try {
5978
6861
  await runPowerShellConversion(input, temporaryOutput, fileFormat);
@@ -6003,6 +6886,9 @@ export {
6003
6886
  ExcelReaderAbstract,
6004
6887
  F,
6005
6888
  INVALID_SHEET_NAME_CHARS,
6889
+ MAX_CELL_CHARS,
6890
+ MAX_COLS,
6891
+ MAX_ROWS,
6006
6892
  ReaderFactory,
6007
6893
  StreamingSheetState,
6008
6894
  XlsbReader,
@@ -6013,13 +6899,27 @@ export {
6013
6899
  XlsxUpdater,
6014
6900
  XlsxWriter,
6015
6901
  applyHeaderWidths,
6902
+ assertCellTextLength,
6903
+ assertColumnCount,
6904
+ assertRowCount,
6905
+ assertRowFitsColumns,
6906
+ columnIndexToLetter,
6907
+ columnLetterToIndex,
6016
6908
  convertXlsbToXlsx,
6017
6909
  convertXlsxToXlsb,
6018
6910
  defaultColWidth,
6911
+ escapeXlsxText,
6912
+ escapeXmlText,
6019
6913
  getFormat,
6020
6914
  initColWidths,
6021
6915
  isFormattedCell,
6916
+ parseSharedStringsXml,
6917
+ parseTagAttributes,
6918
+ resolveRelTarget,
6022
6919
  sanitizeSheetName,
6920
+ trimTrailingEmptyRows,
6921
+ unescapeXml,
6922
+ unescapeXmlText,
6023
6923
  unwrapCell,
6024
6924
  updateColWidthsFromRows
6025
6925
  };