@justybase/spreadsheet-tasks 2.2.0 → 3.0.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,143 @@ 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
+
115
252
  // src/ExcelReaderAbstract.ts
116
253
  var ExcelReaderAbstract = class {
117
254
  constructor() {
@@ -126,8 +263,7 @@ var ExcelReaderAbstract = class {
126
263
  dispose() {
127
264
  }
128
265
  getDateTimeFromOaDate(oaDate) {
129
- const ms = oaDate * 864e5 + this._oaEpoch;
130
- return new Date(ms);
266
+ return new Date(oaDateToTime(oaDate, this._oaEpoch));
131
267
  }
132
268
  };
133
269
 
@@ -387,6 +523,208 @@ var BiffReaderWriter = class {
387
523
 
388
524
  // src/XlsbReader.ts
389
525
  import AdmZip from "adm-zip";
526
+
527
+ // src/xmlUtils.ts
528
+ function hasInvalidControlChars(str) {
529
+ for (let i = 0; i < str.length; i++) {
530
+ const c = str.charCodeAt(i);
531
+ if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
532
+ return true;
533
+ }
534
+ }
535
+ return false;
536
+ }
537
+ function stripInvalidControlChars(str) {
538
+ let out = "";
539
+ for (let i = 0; i < str.length; i++) {
540
+ const c = str.charCodeAt(i);
541
+ if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
542
+ out += str[i];
543
+ }
544
+ }
545
+ return out;
546
+ }
547
+ function escapeXmlText(str) {
548
+ if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
549
+ return str;
550
+ }
551
+ let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
552
+ if (hasInvalidControlChars(out)) {
553
+ out = stripInvalidControlChars(out);
554
+ }
555
+ return out;
556
+ }
557
+ function unescapeXml(str) {
558
+ if (!str || str.indexOf("&") === -1) return str;
559
+ return str.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);/g, (entity) => {
560
+ switch (entity) {
561
+ case "&amp;":
562
+ return "&";
563
+ case "&lt;":
564
+ return "<";
565
+ case "&gt;":
566
+ return ">";
567
+ case "&quot;":
568
+ return '"';
569
+ case "&apos;":
570
+ return "'";
571
+ default: {
572
+ if (entity[1] === "#") {
573
+ const body = entity.slice(2, -1);
574
+ const code = body[0] === "x" || body[0] === "X" ? parseInt(body.slice(1), 16) : parseInt(body, 10);
575
+ if (Number.isFinite(code)) {
576
+ try {
577
+ return String.fromCodePoint(code);
578
+ } catch {
579
+ return entity;
580
+ }
581
+ }
582
+ }
583
+ return entity;
584
+ }
585
+ }
586
+ });
587
+ }
588
+ function parseTagAttributes(tag) {
589
+ const attrs = {};
590
+ const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
591
+ let m;
592
+ while ((m = re.exec(tag)) !== null) {
593
+ const name = m[1];
594
+ const value = m[3] !== void 0 ? m[3] : m[4];
595
+ attrs[name] = value;
596
+ const colon = name.indexOf(":");
597
+ if (colon !== -1) {
598
+ const local = name.slice(colon + 1);
599
+ if (!(local in attrs)) attrs[local] = value;
600
+ }
601
+ }
602
+ return attrs;
603
+ }
604
+ function resolveRelTarget(target) {
605
+ if (!target) return target;
606
+ if (target.startsWith("/")) return target.slice(1);
607
+ if (target.startsWith("xl/")) return target;
608
+ return "xl/" + target;
609
+ }
610
+ function columnIndexToLetter(colIndex) {
611
+ let n = colIndex + 1;
612
+ let result = "";
613
+ while (n > 0) {
614
+ const rem = (n - 1) % 26;
615
+ result = String.fromCharCode(65 + rem) + result;
616
+ n = Math.floor((n - 1) / 26);
617
+ }
618
+ return result;
619
+ }
620
+ function columnLetterToIndex(letter) {
621
+ let column = 0;
622
+ const length = letter.length;
623
+ for (let i = 0; i < length; i++) {
624
+ column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
625
+ }
626
+ return column - 1;
627
+ }
628
+ function trimTrailingEmptyRows(rows) {
629
+ let end = rows.length;
630
+ while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
631
+ end--;
632
+ }
633
+ return end === rows.length ? rows : rows.slice(0, end);
634
+ }
635
+ function parseSharedStringsXml(xml) {
636
+ const result = [];
637
+ const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>|<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*\/>/gi;
638
+ let itemMatch;
639
+ while ((itemMatch = itemRegex.exec(xml)) !== null) {
640
+ const full = itemMatch[0];
641
+ if (full.endsWith("/>") && itemMatch[1] === void 0) {
642
+ result.push("");
643
+ continue;
644
+ }
645
+ let content = itemMatch[1] ?? "";
646
+ content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*>[\s\S]*?<\/(?:(?:[A-Za-z_][\w.-]*):)?rPh\s*>/gi, "");
647
+ content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*\/>/gi, "");
648
+ const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
649
+ let value = "";
650
+ let textMatch;
651
+ while ((textMatch = textRegex.exec(content)) !== null) {
652
+ value += textMatch[1];
653
+ }
654
+ result.push(unescapeXml(value));
655
+ }
656
+ return result;
657
+ }
658
+
659
+ // src/biff12Utils.ts
660
+ function readVlq(buf, pos) {
661
+ let value = 0;
662
+ let shift = 0;
663
+ let byte;
664
+ do {
665
+ byte = buf[pos++];
666
+ value |= (byte & 127) << shift;
667
+ shift += 7;
668
+ } while ((byte & 128) !== 0);
669
+ return { value, nextPos: pos };
670
+ }
671
+ function vlqBytes(value) {
672
+ const out = [];
673
+ while (value >= 128) {
674
+ out.push(value & 127 | 128);
675
+ value >>>= 7;
676
+ }
677
+ out.push(value & 127);
678
+ return out;
679
+ }
680
+ function readRecord(buf, pos, rec) {
681
+ const idRes = readVlq(buf, pos);
682
+ if (idRes.nextPos > buf.length) return false;
683
+ const lenRes = readVlq(buf, idRes.nextPos);
684
+ if (lenRes.nextPos + lenRes.value > buf.length) return false;
685
+ rec.headerStart = pos;
686
+ rec.dataStart = lenRes.nextPos;
687
+ rec.dataEnd = lenRes.nextPos + lenRes.value;
688
+ rec.id = idRes.value;
689
+ rec.len = lenRes.value;
690
+ return true;
691
+ }
692
+ function buildRecord(id, payload) {
693
+ const idBytes = vlqBytes(id);
694
+ const lenBytes = vlqBytes(payload.length);
695
+ const header = Buffer.alloc(idBytes.length + lenBytes.length);
696
+ idBytes.forEach((b, i) => header[i] = b);
697
+ lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
698
+ return Buffer.concat([header, payload]);
699
+ }
700
+ function readUtf16(buf, start, charCount) {
701
+ const end = start + charCount * 2;
702
+ if (end > buf.length) return "";
703
+ return buf.toString("utf16le", start, end);
704
+ }
705
+ function parseSharedStringsBin(buf) {
706
+ const values = [];
707
+ let total = 0;
708
+ let unique = 0;
709
+ let endSstOffset = buf.length;
710
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
711
+ let pos = 0;
712
+ while (readRecord(buf, pos, rec)) {
713
+ pos = rec.dataEnd;
714
+ if (rec.id === 159) {
715
+ total = buf.readUInt32LE(rec.dataStart);
716
+ unique = buf.readUInt32LE(rec.dataStart + 4);
717
+ } else if (rec.id === 19) {
718
+ const cch = buf.readUInt32LE(rec.dataStart + 1);
719
+ values.push(readUtf16(buf, rec.dataStart + 5, cch));
720
+ } else if (rec.id === 160) {
721
+ endSstOffset = rec.headerStart;
722
+ }
723
+ }
724
+ return { values, total, unique, endSstOffset };
725
+ }
726
+
727
+ // src/XlsbReader.ts
390
728
  var XlsbReader = class extends ExcelReaderAbstract {
391
729
  constructor() {
392
730
  super();
@@ -398,12 +736,29 @@ var XlsbReader = class extends ExcelReaderAbstract {
398
736
  this._reader = null;
399
737
  this._currentRow = [];
400
738
  this._pendingRowIndex = -1;
739
+ this._nextExpectedRow = 0;
401
740
  this._eof = false;
402
741
  this.xfIdToNumFmtId = [];
403
742
  this.customDateFormats = null;
404
743
  }
405
- async open(path7, readSharedStrings = true) {
406
- this.zip = new AdmZip(path7);
744
+ async open(path9, readSharedStrings = true) {
745
+ await this.close();
746
+ this.sheetNames = [];
747
+ this.sheets = [];
748
+ this.sharedStrings = [];
749
+ this.xfIdToNumFmtId = [];
750
+ this.customDateFormats = /* @__PURE__ */ new Set();
751
+ this.fieldCount = 0;
752
+ this.resultsCount = 0;
753
+ this.actualSheetName = "";
754
+ this._currentSheetIndex = -1;
755
+ this._currentRow = [];
756
+ this._pendingRowIndex = -1;
757
+ this._nextExpectedRow = 0;
758
+ this._eof = false;
759
+ this._reader = null;
760
+ this._oaEpoch = Date.UTC(1899, 11, 30);
761
+ this.zip = new AdmZip(path9);
407
762
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
408
763
  if (wbEntry) {
409
764
  const buf = wbEntry.getData();
@@ -420,20 +775,39 @@ var XlsbReader = class extends ExcelReaderAbstract {
420
775
  const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
421
776
  if (relsEntry) {
422
777
  const xml = relsEntry.getData().toString("utf8");
423
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
424
- let match;
778
+ const relTagRe = /<Relationship\b[^>]*>/g;
779
+ let tagMatch;
425
780
  const rIdToTarget = {};
426
- while ((match = relRegex.exec(xml)) !== null) {
427
- rIdToTarget[match[1]] = match[2];
781
+ while ((tagMatch = relTagRe.exec(xml)) !== null) {
782
+ const attrs = parseTagAttributes(tagMatch[0]);
783
+ const id = attrs["Id"] ?? attrs["id"];
784
+ const target = attrs["Target"] ?? attrs["target"];
785
+ if (id !== void 0 && target !== void 0) {
786
+ rIdToTarget[id] = target;
787
+ }
428
788
  }
429
789
  for (const sheet of this.sheets) {
430
- let target = rIdToTarget[sheet.rId];
790
+ const target = rIdToTarget[sheet.rId];
431
791
  if (target) {
432
- if (target.startsWith("/")) target = target.substring(1);
433
- if (!target.startsWith("xl/")) target = "xl/" + target;
434
- sheet.path = target;
792
+ sheet.path = resolveRelTarget(target);
793
+ }
794
+ }
795
+ }
796
+ try {
797
+ const raw = wbEntry ? wbEntry.getData() : null;
798
+ if (raw) {
799
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
800
+ let pos = 0;
801
+ while (readRecord(raw, pos, rec)) {
802
+ if (rec.id === 153 && rec.len >= 4) {
803
+ const is1904 = (raw[rec.dataStart] & 1) !== 0;
804
+ this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
805
+ break;
806
+ }
807
+ pos = rec.dataEnd;
435
808
  }
436
809
  }
810
+ } catch {
437
811
  }
438
812
  if (readSharedStrings) {
439
813
  const ssPath = "xl/sharedStrings.bin";
@@ -448,8 +822,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
448
822
  this.resultsCount = this.sheets.length;
449
823
  this._currentSheetIndex = -1;
450
824
  }
451
- _readSharedStrings(path7) {
452
- const entry = this.zip.getEntry(path7);
825
+ _readSharedStrings(path9) {
826
+ const entry = this.zip.getEntry(path9);
453
827
  if (!entry) return;
454
828
  const reader = new BiffReaderWriter(entry.getData());
455
829
  while (reader.readSharedStrings()) {
@@ -458,8 +832,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
458
832
  }
459
833
  }
460
834
  }
461
- _readStyles(path7) {
462
- const entry = this.zip.getEntry(path7);
835
+ _readStyles(path9) {
836
+ const entry = this.zip.getEntry(path9);
463
837
  if (!entry) return;
464
838
  const reader = new BiffReaderWriter(entry.getData());
465
839
  while (reader.readStyles()) {
@@ -470,21 +844,54 @@ var XlsbReader = class extends ExcelReaderAbstract {
470
844
  getSheetNames() {
471
845
  return this.sheetNames;
472
846
  }
847
+ async selectSheet(nameOrIndex) {
848
+ let index = -1;
849
+ if (typeof nameOrIndex === "number") {
850
+ index = nameOrIndex;
851
+ } else {
852
+ index = this.sheetNames.indexOf(nameOrIndex);
853
+ if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
854
+ }
855
+ if (index < 0 || index >= this.sheets.length) {
856
+ throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
857
+ }
858
+ this._currentSheetIndex = index;
859
+ if (!this._initSheet(index)) {
860
+ throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[index]}'.`);
861
+ }
862
+ }
863
+ async nextResult() {
864
+ const next = this._currentSheetIndex + 1;
865
+ if (next >= this.sheets.length) return false;
866
+ this._currentSheetIndex = next;
867
+ if (!this._initSheet(next)) {
868
+ throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[next]}'.`);
869
+ }
870
+ return true;
871
+ }
473
872
  async read() {
474
873
  if (this._currentSheetIndex === -1) {
475
874
  this._currentSheetIndex = 0;
476
875
  if (!this._initSheet(0)) return false;
477
876
  }
478
877
  if (this._eof) return false;
878
+ if (this._pendingRowIndex > this._nextExpectedRow && this._pendingRowIndex < MAX_ROWS) {
879
+ this._currentRow = [];
880
+ this._nextExpectedRow++;
881
+ return true;
882
+ }
479
883
  this._currentRow = [];
480
884
  while (true) {
481
885
  const hasRecord = this._reader.readWorksheet();
482
886
  if (!hasRecord) {
483
887
  this._eof = true;
888
+ this._nextExpectedRow = this._pendingRowIndex + 1;
484
889
  return true;
485
890
  }
486
891
  if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
892
+ const completed = this._pendingRowIndex;
487
893
  this._pendingRowIndex = this._reader._rowIndex;
894
+ this._nextExpectedRow = completed + 1;
488
895
  return true;
489
896
  }
490
897
  if (this._reader._readCell) {
@@ -525,13 +932,17 @@ var XlsbReader = class extends ExcelReaderAbstract {
525
932
  }
526
933
  }
527
934
  _initSheet(index) {
528
- if (index >= this.sheets.length) return false;
935
+ if (index < 0 || index >= this.sheets.length) return false;
529
936
  const sheet = this.sheets[index];
530
- const entry = this.zip.getEntry(sheet.path);
937
+ const entry = sheet.path ? this.zip.getEntry(sheet.path) : void 0;
531
938
  if (!entry) return false;
532
939
  this._reader = new BiffReaderWriter(entry.getData());
533
940
  this._eof = false;
534
941
  this._pendingRowIndex = -1;
942
+ this._nextExpectedRow = 0;
943
+ this._currentRow = [];
944
+ this.fieldCount = 0;
945
+ this.actualSheetName = sheet.name;
535
946
  while (this._reader.readWorksheet()) {
536
947
  if (this._reader._rowIndex !== -1) {
537
948
  this._pendingRowIndex = this._reader._rowIndex;
@@ -539,7 +950,7 @@ var XlsbReader = class extends ExcelReaderAbstract {
539
950
  }
540
951
  }
541
952
  this._eof = true;
542
- return false;
953
+ return true;
543
954
  }
544
955
  getValue(i) {
545
956
  if (i < 0 || i >= this._currentRow.length) return null;
@@ -549,84 +960,15 @@ var XlsbReader = class extends ExcelReaderAbstract {
549
960
 
550
961
  // src/XlsxReader.ts
551
962
  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;
963
+ function parseIsoDateUtc(value) {
964
+ let text = value.trim().replace(" ", "T");
965
+ if (!text) return null;
966
+ if (!/z$|[+-]\d{2}:?\d{2}$/i.test(text) && text.includes("T")) {
967
+ text += "Z";
968
+ }
969
+ const date = new Date(text);
970
+ return Number.isNaN(date.getTime()) ? null : date;
627
971
  }
628
-
629
- // src/XlsxReader.ts
630
972
  var XlsxReader = class extends ExcelReaderAbstract {
631
973
  constructor() {
632
974
  super();
@@ -638,13 +980,34 @@ var XlsxReader = class extends ExcelReaderAbstract {
638
980
  this._currentSheetIndex = -1;
639
981
  this._sheetXml = null;
640
982
  this._xmlPos = 0;
983
+ /** End of the sheetData element; row tags must not be searched past it. */
984
+ this._xmlEnd = 0;
641
985
  this._currentRow = [];
986
+ this._expectedRowNum = 1;
987
+ this._pendingEmptyCount = 0;
642
988
  this.xfIdToNumFmtId = [];
643
989
  this.customDateFormats = /* @__PURE__ */ new Set();
644
990
  }
645
- async open(path7, readSharedStrings = true) {
646
- return new Promise((resolve4, reject) => {
647
- yauzl.open(path7, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
991
+ async open(path9, readSharedStrings = true) {
992
+ await this.close();
993
+ this.sheetNames = [];
994
+ this.sheets = [];
995
+ this.sharedStrings = [];
996
+ this.xfIdToNumFmtId = [];
997
+ this.customDateFormats = /* @__PURE__ */ new Set();
998
+ this.fieldCount = 0;
999
+ this.resultsCount = 0;
1000
+ this.actualSheetName = "";
1001
+ this._currentSheetIndex = -1;
1002
+ this._sheetXml = null;
1003
+ this._xmlPos = 0;
1004
+ this._xmlEnd = 0;
1005
+ this._currentRow = [];
1006
+ this._expectedRowNum = 1;
1007
+ this._pendingEmptyCount = 0;
1008
+ this._oaEpoch = Date.UTC(1899, 11, 30);
1009
+ return new Promise((resolve6, reject) => {
1010
+ yauzl.open(path9, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
648
1011
  if (err) return reject(err);
649
1012
  this.zipfile = zipfile;
650
1013
  this.entries = /* @__PURE__ */ new Map();
@@ -657,28 +1020,40 @@ var XlsxReader = class extends ExcelReaderAbstract {
657
1020
  const wbRelsContent = await this._readZipEntryContent("xl/_rels/workbook.xml.rels");
658
1021
  const rIdToTarget = {};
659
1022
  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];
1023
+ const relTagRe = /<Relationship\b[^>]*>/g;
1024
+ let tagMatch;
1025
+ while ((tagMatch = relTagRe.exec(wbRelsContent)) !== null) {
1026
+ const attrs = parseTagAttributes(tagMatch[0]);
1027
+ const id = attrs["Id"] ?? attrs["id"];
1028
+ const target = attrs["Target"] ?? attrs["target"];
1029
+ if (id !== void 0 && target !== void 0) {
1030
+ rIdToTarget[id] = target;
1031
+ }
664
1032
  }
665
1033
  }
666
1034
  const wbContent = await this._readZipEntryContent("xl/workbook.xml");
667
1035
  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];
1036
+ const sheetTagRe = /<sheet\b[^>]*>/g;
1037
+ let tagMatch;
1038
+ while ((tagMatch = sheetTagRe.exec(wbContent)) !== null) {
1039
+ const attrs = parseTagAttributes(tagMatch[0]);
1040
+ const rawName = attrs["name"];
1041
+ const sheetId = attrs["sheetId"] ?? "";
1042
+ const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
1043
+ if (rawName === void 0 || rId === void 0) continue;
1044
+ const name = unescapeXml(rawName);
674
1045
  const target = rIdToTarget[rId];
675
- let fullPath = target;
676
- if (!fullPath.startsWith("xl/")) {
677
- fullPath = "xl/" + fullPath;
678
- }
1046
+ if (!target) continue;
1047
+ const fullPath = resolveRelTarget(target);
679
1048
  this.sheetNames.push(name);
680
1049
  this.sheets.push({ name, sheetId, rId, path: fullPath });
681
1050
  }
1051
+ const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbContent);
1052
+ if (wbPrMatch) {
1053
+ const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
1054
+ const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
1055
+ this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
1056
+ }
682
1057
  }
683
1058
  if (readSharedStrings) {
684
1059
  const ssContent = await this._readZipEntryContent("xl/sharedStrings.xml");
@@ -692,7 +1067,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
692
1067
  }
693
1068
  this.resultsCount = this.sheets.length;
694
1069
  this._currentSheetIndex = -1;
695
- resolve4();
1070
+ resolve6();
696
1071
  } catch (e) {
697
1072
  reject(e);
698
1073
  }
@@ -706,16 +1081,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
706
1081
  this.zipfile.close();
707
1082
  }
708
1083
  }
709
- async _readZipEntryContent(path7) {
710
- const entry = this.entries.get(path7);
1084
+ async _readZipEntryContent(path9) {
1085
+ const entry = this.entries.get(path9);
711
1086
  if (!entry) return null;
712
- return new Promise((resolve4, reject) => {
1087
+ return new Promise((resolve6, reject) => {
713
1088
  this.zipfile.openReadStream(entry, (err, readStream) => {
714
1089
  if (err) return reject(err);
715
1090
  const chunks = [];
716
1091
  readStream.on("data", (chunk) => chunks.push(chunk));
717
1092
  readStream.on("end", () => {
718
- resolve4(Buffer.concat(chunks).toString("utf8"));
1093
+ resolve6(Buffer.concat(chunks).toString("utf8"));
719
1094
  });
720
1095
  readStream.on("error", reject);
721
1096
  });
@@ -763,6 +1138,27 @@ var XlsxReader = class extends ExcelReaderAbstract {
763
1138
  return false;
764
1139
  }
765
1140
  }
1141
+ async selectSheet(nameOrIndex) {
1142
+ let index = -1;
1143
+ if (typeof nameOrIndex === "number") {
1144
+ index = nameOrIndex;
1145
+ } else {
1146
+ index = this.sheetNames.indexOf(nameOrIndex);
1147
+ if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
1148
+ }
1149
+ if (index < 0 || index >= this.sheets.length) {
1150
+ throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
1151
+ }
1152
+ this._currentSheetIndex = index;
1153
+ await this._initSheet(index);
1154
+ }
1155
+ async nextResult() {
1156
+ const next = this._currentSheetIndex + 1;
1157
+ if (next >= this.sheets.length) return false;
1158
+ this._currentSheetIndex = next;
1159
+ await this._initSheet(next);
1160
+ return true;
1161
+ }
766
1162
  async _initSheet(index) {
767
1163
  if (index >= this.sheets.length) return;
768
1164
  const sheet = this.sheets[index];
@@ -772,23 +1168,106 @@ var XlsxReader = class extends ExcelReaderAbstract {
772
1168
  this._sheetXml = "";
773
1169
  }
774
1170
  this._xmlPos = 0;
1171
+ this._xmlEnd = this._sheetXml.length;
1172
+ this._expectedRowNum = 1;
1173
+ this._pendingEmptyCount = 0;
1174
+ this._currentRow = [];
1175
+ this.fieldCount = 0;
775
1176
  const sd = this._sheetXml.indexOf("<sheetData>");
776
- if (sd !== -1) this._xmlPos = sd + 11;
1177
+ if (sd !== -1) {
1178
+ this._xmlPos = sd + 11;
1179
+ const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
1180
+ if (se !== -1) this._xmlEnd = se;
1181
+ } else {
1182
+ const m = /<(?:[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(this._sheetXml);
1183
+ if (m && m.index !== void 0) {
1184
+ this._xmlPos = m.index + m[0].length;
1185
+ if (/\/>\s*$/.test(m[0])) {
1186
+ this._xmlEnd = this._xmlPos;
1187
+ } else {
1188
+ const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
1189
+ if (se !== -1) this._xmlEnd = se;
1190
+ }
1191
+ }
1192
+ }
777
1193
  }
778
1194
  _readNextRow() {
779
1195
  if (!this._sheetXml) return false;
780
- const rowStart = this._sheetXml.indexOf("<row", this._xmlPos);
1196
+ if (this._pendingEmptyCount > 0) {
1197
+ this._pendingEmptyCount--;
1198
+ this._currentRow = [];
1199
+ this._expectedRowNum++;
1200
+ return true;
1201
+ }
1202
+ const rowStart = this._findRowTagStart(this._xmlPos);
781
1203
  if (rowStart === -1) return false;
1204
+ const rowTagEnd = this._sheetXml.indexOf(">", rowStart);
1205
+ if (rowTagEnd === -1) return false;
1206
+ const rowTag = this._sheetXml.substring(rowStart, rowTagEnd + 1);
1207
+ const isSelfClosing = this._sheetXml.charCodeAt(rowTagEnd - 1) === 47;
1208
+ const rowAttrs = parseTagAttributes(rowTag);
1209
+ const rRaw = rowAttrs["r"];
1210
+ let rowNum = this._expectedRowNum;
1211
+ if (rRaw !== void 0) {
1212
+ const parsed = parseInt(rRaw, 10);
1213
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= MAX_ROWS) rowNum = parsed;
1214
+ }
1215
+ if (rowNum > this._expectedRowNum) {
1216
+ const gap = rowNum - this._expectedRowNum;
1217
+ this._pendingEmptyCount = gap - 1;
1218
+ this._currentRow = [];
1219
+ this._expectedRowNum++;
1220
+ return true;
1221
+ }
1222
+ if (rowNum < this._expectedRowNum) {
1223
+ if (isSelfClosing) {
1224
+ this._xmlPos = rowTagEnd + 1;
1225
+ this._currentRow = [];
1226
+ return true;
1227
+ }
1228
+ const rowEnd2 = this._sheetXml.indexOf("</row>", rowStart);
1229
+ if (rowEnd2 === -1) return false;
1230
+ this._xmlPos = rowEnd2 + 6;
1231
+ this._parseRowByIndex(rowStart, rowEnd2);
1232
+ this._expectedRowNum = Math.max(this._expectedRowNum + 1, rowNum + 1);
1233
+ return true;
1234
+ }
1235
+ if (isSelfClosing) {
1236
+ this._xmlPos = rowTagEnd + 1;
1237
+ this._currentRow = [];
1238
+ this._expectedRowNum = rowNum + 1;
1239
+ return true;
1240
+ }
782
1241
  const rowEnd = this._sheetXml.indexOf("</row>", rowStart);
783
1242
  if (rowEnd === -1) return false;
784
1243
  this._xmlPos = rowEnd + 6;
785
1244
  this._parseRowByIndex(rowStart, rowEnd);
1245
+ this._expectedRowNum = rowNum + 1;
786
1246
  return true;
787
1247
  }
1248
+ /**
1249
+ * Find the next `<row …>` start, ignoring similarly named elements such as
1250
+ * `<rowBreaks>` that follow `</sheetData>`.
1251
+ */
1252
+ _findRowTagStart(from) {
1253
+ const xml = this._sheetXml;
1254
+ let pos = from;
1255
+ while (pos < this._xmlEnd) {
1256
+ const idx = xml.indexOf("<row", pos);
1257
+ if (idx === -1 || idx >= this._xmlEnd) return -1;
1258
+ const next = xml.charCodeAt(idx + 4);
1259
+ if (next === 62 || next === 47 || next === 32 || next === 9 || next === 10 || next === 13) {
1260
+ return idx;
1261
+ }
1262
+ pos = idx + 4;
1263
+ }
1264
+ return -1;
1265
+ }
788
1266
  _parseRowByIndex(rowStart, rowEnd) {
789
1267
  this._currentRow = [];
790
1268
  const xml = this._sheetXml;
791
1269
  let pos = xml.indexOf(">", rowStart) + 1;
1270
+ let nextCol = 0;
792
1271
  while (pos < rowEnd) {
793
1272
  const cStart = xml.indexOf("<c", pos);
794
1273
  if (cStart === -1 || cStart >= rowEnd) break;
@@ -822,6 +1301,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
822
1301
  }
823
1302
  }
824
1303
  let val = null;
1304
+ let inlineVals = null;
825
1305
  if (xml.charCodeAt(nextTagClose - 1) === 47) {
826
1306
  pos = nextTagClose + 1;
827
1307
  } else {
@@ -836,20 +1316,28 @@ var XlsxReader = class extends ExcelReaderAbstract {
836
1316
  }
837
1317
  } else {
838
1318
  if (type === "inlineStr") {
839
- const tStart = xml.indexOf("<t", nextTagClose);
840
- if (tStart !== -1 && tStart < cellContentEnd) {
841
- const tContentStart = xml.indexOf(">", tStart) + 1;
1319
+ inlineVals = [];
1320
+ let tSearch = nextTagClose;
1321
+ while (true) {
1322
+ const tStart = xml.indexOf("<t", tSearch);
1323
+ if (tStart === -1 || tStart >= cellContentEnd) break;
1324
+ const tClose = xml.indexOf(">", tStart);
1325
+ if (tClose === -1 || tClose >= cellContentEnd) break;
1326
+ const tContentStart = tClose + 1;
842
1327
  const tEnd = xml.indexOf("</t>", tContentStart);
843
- if (tEnd !== -1 && tEnd < cellContentEnd) {
844
- val = xml.substring(tContentStart, tEnd);
845
- }
1328
+ if (tEnd === -1 || tEnd > cellContentEnd) break;
1329
+ inlineVals.push(xml.substring(tContentStart, tEnd));
1330
+ tSearch = tEnd + 4;
846
1331
  }
1332
+ if (inlineVals.length === 0) inlineVals = null;
847
1333
  }
848
1334
  }
849
1335
  pos = cellContentEnd + 4;
850
1336
  }
851
1337
  let finalVal = null;
852
- if (val !== null) {
1338
+ if (inlineVals !== null) {
1339
+ finalVal = unescapeXml(inlineVals.join(""));
1340
+ } else if (val !== null) {
853
1341
  if (type === "s") {
854
1342
  const idx = parseInt(val, 10);
855
1343
  finalVal = this.sharedStrings[idx];
@@ -857,6 +1345,12 @@ var XlsxReader = class extends ExcelReaderAbstract {
857
1345
  finalVal = val === "1" || val === "true";
858
1346
  } else if (type === "inlineStr") {
859
1347
  finalVal = unescapeXml(val);
1348
+ } else if (type === "str") {
1349
+ finalVal = unescapeXml(val);
1350
+ } else if (type === "e") {
1351
+ finalVal = unescapeXml(val);
1352
+ } else if (type === "d") {
1353
+ finalVal = parseIsoDateUtc(val) ?? unescapeXml(val);
860
1354
  } else if (type === "n" || type === "") {
861
1355
  finalVal = parseFloat(val);
862
1356
  if (styleIndex > 0 && this.xfIdToNumFmtId) {
@@ -874,10 +1368,10 @@ var XlsxReader = class extends ExcelReaderAbstract {
874
1368
  }
875
1369
  }
876
1370
  }
877
- if (colIndex !== -1) {
878
- this._currentRow[colIndex] = finalVal;
879
- if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
880
- }
1371
+ if (colIndex === -1) colIndex = nextCol;
1372
+ nextCol = colIndex + 1;
1373
+ this._currentRow[colIndex] = finalVal;
1374
+ if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
881
1375
  }
882
1376
  }
883
1377
  getValue(i) {
@@ -907,99 +1401,11 @@ var ReaderFactory = class {
907
1401
 
908
1402
  // src/XlsbWriter.ts
909
1403
  import * as fs from "fs";
1404
+ import * as path2 from "path";
1405
+ import { randomUUID } from "crypto";
910
1406
  import archiver from "archiver";
911
1407
  import { Readable } from "stream";
912
1408
 
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
1409
  // src/StreamingSheetState.ts
1004
1410
  var StreamingSheetState = class {
1005
1411
  constructor() {
@@ -1052,6 +1458,8 @@ var XlsbWriter = class _XlsbWriter {
1052
1458
  * @param filePath Destination `.xlsb` path on disk.
1053
1459
  */
1054
1460
  constructor(filePath) {
1461
+ this._outputError = null;
1462
+ this._finalized = false;
1055
1463
  this.sheetCount = 0;
1056
1464
  this.sheetList = [];
1057
1465
  this.sstDic = /* @__PURE__ */ new Map();
@@ -1062,8 +1470,20 @@ var XlsbWriter = class _XlsbWriter {
1062
1470
  this._nextXfIdx = 4;
1063
1471
  this._stFmtRecords = /* @__PURE__ */ new Map();
1064
1472
  this.stream = new StreamingSheetState();
1065
- this.output = fs.createWriteStream(filePath);
1473
+ this._targetPath = path2.resolve(filePath);
1474
+ fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
1475
+ this._tempPath = path2.join(
1476
+ path2.dirname(this._targetPath),
1477
+ `.${path2.basename(this._targetPath)}.${randomUUID()}.tmp`
1478
+ );
1479
+ this.output = fs.createWriteStream(this._tempPath);
1480
+ this.output.on("error", (err) => {
1481
+ this._outputError = err;
1482
+ });
1066
1483
  this.archive = archiver("zip");
1484
+ this.archive.on("error", (err) => {
1485
+ this._outputError = err;
1486
+ });
1067
1487
  this.archive.pipe(this.output);
1068
1488
  this._oaEpoch = Date.UTC(1899, 11, 30);
1069
1489
  this._sheet1Bytes = Buffer.from([
@@ -2933,6 +3353,11 @@ var XlsbWriter = class _XlsbWriter {
2933
3353
  * @param options Optional settings (hidden, doAutofilter)
2934
3354
  */
2935
3355
  startSheet(sheetName, columnCount, headers, options = {}) {
3356
+ assertColumnCount(columnCount);
3357
+ if (headers !== void 0) {
3358
+ assertRowFitsColumns("header", headers.length, columnCount);
3359
+ for (const h of headers) assertCellTextLength(h);
3360
+ }
2936
3361
  const { hidden = false, doAutofilter = true, sampleRows } = options;
2937
3362
  this.addSheet(sheetName, hidden);
2938
3363
  this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
@@ -2998,6 +3423,7 @@ var XlsbWriter = class _XlsbWriter {
2998
3423
  `Row length mismatch. Expected ${this.stream.endCol - this.stream.startCol} columns, got ${row.length}`
2999
3424
  );
3000
3425
  }
3426
+ assertRowCount(this.stream.rowNum + 1);
3001
3427
  this.createRowHeader(bigBuf, this.stream.rowNum, this.stream.startCol, this.stream.endCol);
3002
3428
  for (let c = 0; c < row.length; c++) {
3003
3429
  const raw = row[c];
@@ -3020,7 +3446,7 @@ var XlsbWriter = class _XlsbWriter {
3020
3446
  } else if (typeof val === "boolean") {
3021
3447
  this.writeBool(bigBuf, val, c);
3022
3448
  } else if (val instanceof Date) {
3023
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
3449
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
3024
3450
  this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
3025
3451
  } else if (val !== null && val !== void 0) {
3026
3452
  this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
@@ -3077,6 +3503,16 @@ var XlsbWriter = class _XlsbWriter {
3077
3503
  } else if (headers) {
3078
3504
  columnCount = headers.length;
3079
3505
  }
3506
+ if (columnCount > 0) assertColumnCount(columnCount);
3507
+ const totalRowsCheck = rows.length + (headers ? 1 : 0);
3508
+ assertRowCount(totalRowsCheck);
3509
+ if (headers) {
3510
+ assertRowFitsColumns("header", headers.length, columnCount);
3511
+ for (const h of headers) assertCellTextLength(h);
3512
+ }
3513
+ for (let r = 0; r < rows.length; r++) {
3514
+ assertRowFitsColumns("row", rows[r].length, columnCount);
3515
+ }
3080
3516
  const colWidths = initColWidths(columnCount);
3081
3517
  if (headers) {
3082
3518
  applyHeaderWidths(colWidths, headers, columnCount);
@@ -3152,7 +3588,7 @@ var XlsbWriter = class _XlsbWriter {
3152
3588
  } else if (typeof val === "boolean") {
3153
3589
  this.writeBool(bigBuf, val, c);
3154
3590
  } else if (val instanceof Date) {
3155
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
3591
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
3156
3592
  this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
3157
3593
  } else if (val !== null && val !== void 0) {
3158
3594
  this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
@@ -3235,13 +3671,14 @@ var XlsbWriter = class _XlsbWriter {
3235
3671
  bigBuf.writeUnsafeByte(val ? 1 : 0);
3236
3672
  }
3237
3673
  writeDateTime(bigBuf, date, colNum) {
3238
- const oaDate = (date.getTime() - this._oaEpoch) / 864e5;
3674
+ const oaDate = timeToOaDate(date.getTime(), this._oaEpoch);
3239
3675
  this.writeDouble(bigBuf, oaDate, colNum, 1);
3240
3676
  }
3241
3677
  _getDateWidth(_date) {
3242
3678
  return 19;
3243
3679
  }
3244
3680
  writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
3681
+ assertCellTextLength(val);
3245
3682
  let index;
3246
3683
  if (this.sstDic.has(val)) {
3247
3684
  index = this.sstDic.get(val);
@@ -3267,8 +3704,8 @@ var XlsbWriter = class _XlsbWriter {
3267
3704
  bigBuf.writeByte(159);
3268
3705
  bigBuf.writeByte(1);
3269
3706
  bigBuf.writeByte(8);
3270
- bigBuf.writeInt32LE(this.sstCntUnique);
3271
3707
  bigBuf.writeInt32LE(this.sstCntAll);
3708
+ bigBuf.writeInt32LE(this.sstCntUnique);
3272
3709
  for (const [txt] of this.sstDic) {
3273
3710
  const txtLen = txt.length;
3274
3711
  bigBuf.writeByte(19);
@@ -3321,8 +3758,61 @@ var XlsbWriter = class _XlsbWriter {
3321
3758
  * Must be awaited before the file is fully written.
3322
3759
  */
3323
3760
  finalize() {
3324
- return new Promise((resolve4, reject) => {
3761
+ return new Promise((resolve6, reject) => {
3762
+ if (this._finalized) {
3763
+ reject(new Error("XlsbWriter: already finalized"));
3764
+ return;
3765
+ }
3766
+ this._finalized = true;
3767
+ let settled = false;
3768
+ const cleanupTemp = () => {
3769
+ try {
3770
+ if (fs.existsSync(this._tempPath)) fs.rmSync(this._tempPath);
3771
+ } catch {
3772
+ }
3773
+ };
3774
+ const fail = (err) => {
3775
+ if (settled) return;
3776
+ settled = true;
3777
+ try {
3778
+ this.archive.abort();
3779
+ } catch {
3780
+ }
3781
+ try {
3782
+ this.output.destroy();
3783
+ } catch {
3784
+ }
3785
+ cleanupTemp();
3786
+ reject(err instanceof Error ? err : new Error(String(err)));
3787
+ };
3788
+ const succeed = () => {
3789
+ if (settled) return;
3790
+ if (this._outputError) {
3791
+ fail(this._outputError);
3792
+ return;
3793
+ }
3794
+ settled = true;
3795
+ try {
3796
+ const fd = fs.openSync(this._tempPath, "r+");
3797
+ try {
3798
+ fs.fsyncSync(fd);
3799
+ } finally {
3800
+ fs.closeSync(fd);
3801
+ }
3802
+ try {
3803
+ const existing = fs.statSync(this._targetPath);
3804
+ fs.chmodSync(this._tempPath, existing.mode & 511);
3805
+ } catch {
3806
+ }
3807
+ fs.renameSync(this._tempPath, this._targetPath);
3808
+ resolve6();
3809
+ } catch (err) {
3810
+ cleanupTemp();
3811
+ reject(err instanceof Error ? err : new Error(String(err)));
3812
+ }
3813
+ };
3325
3814
  try {
3815
+ if (this._outputError) throw this._outputError;
3326
3816
  this.saveSst();
3327
3817
  this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
3328
3818
  const wbBuffers = [];
@@ -3419,11 +3909,12 @@ var XlsbWriter = class _XlsbWriter {
3419
3909
  </Relationships>`;
3420
3910
  this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
3421
3911
  }
3422
- this.output.on("close", () => resolve4());
3423
- this.archive.on("error", (err) => reject(err));
3912
+ this.output.once("close", succeed);
3913
+ this.output.once("error", fail);
3914
+ this.archive.once("error", fail);
3424
3915
  this.archive.finalize();
3425
3916
  } catch (err) {
3426
- reject(err);
3917
+ fail(err);
3427
3918
  }
3428
3919
  });
3429
3920
  }
@@ -3431,27 +3922,17 @@ var XlsbWriter = class _XlsbWriter {
3431
3922
 
3432
3923
  // src/XlsxWriter.ts
3433
3924
  import * as fs2 from "fs";
3925
+ import * as path3 from "path";
3926
+ import { randomUUID as randomUUID2 } from "crypto";
3434
3927
  import archiver2 from "archiver";
3435
3928
  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
3929
  var XlsxWriter = class {
3451
3930
  /**
3452
3931
  * @param filePath Destination `.xlsx` path on disk.
3453
3932
  */
3454
3933
  constructor(filePath) {
3934
+ this._outputError = null;
3935
+ this._finalized = false;
3455
3936
  this.sheetCount = 0;
3456
3937
  this.sheetList = [];
3457
3938
  this.sstArray = [];
@@ -3465,13 +3946,25 @@ var XlsxWriter = class {
3465
3946
  this._nextXfIndex = 4;
3466
3947
  this.stream = new StreamingSheetState();
3467
3948
  this.currentColLetters = [];
3468
- this.output = fs2.createWriteStream(filePath);
3949
+ this._targetPath = path3.resolve(filePath);
3950
+ fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
3951
+ this._tempPath = path3.join(
3952
+ path3.dirname(this._targetPath),
3953
+ `.${path3.basename(this._targetPath)}.${randomUUID2()}.tmp`
3954
+ );
3955
+ this.output = fs2.createWriteStream(this._tempPath);
3956
+ this.output.on("error", (err) => {
3957
+ this._outputError = err;
3958
+ });
3469
3959
  this.archive = archiver2("zip");
3960
+ this.archive.on("error", (err) => {
3961
+ this._outputError = err;
3962
+ });
3470
3963
  this.archive.pipe(this.output);
3471
3964
  this._oaEpoch = Date.UTC(1899, 11, 30);
3472
3965
  }
3473
3966
  _getColumnLetter(colIndex) {
3474
- return colIndex < COLUMN_LETTERS.length ? COLUMN_LETTERS[colIndex] : "A";
3967
+ return columnIndexToLetter(colIndex);
3475
3968
  }
3476
3969
  /** @deprecated Internal; kept for unit-test access. Prefer sanitizeSheetName(). */
3477
3970
  _sanitizeSheetName(name) {
@@ -3523,6 +4016,11 @@ var XlsxWriter = class {
3523
4016
  * Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
3524
4017
  */
3525
4018
  startSheet(sheetName, columnCount, headers, options = {}) {
4019
+ assertColumnCount(columnCount);
4020
+ if (headers !== void 0) {
4021
+ assertRowFitsColumns("header", headers.length, columnCount);
4022
+ for (const h of headers) assertCellTextLength(h);
4023
+ }
3526
4024
  const { hidden = false, doAutofilter = true, sampleRows } = options;
3527
4025
  this.addSheet(sheetName, hidden);
3528
4026
  this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
@@ -3579,6 +4077,7 @@ var XlsxWriter = class {
3579
4077
  );
3580
4078
  }
3581
4079
  this.stream.rowNum++;
4080
+ assertRowCount(this.stream.rowNum);
3582
4081
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
3583
4082
  for (let c = 0; c < row.length; c++) {
3584
4083
  const raw = row[c];
@@ -3642,6 +4141,16 @@ var XlsxWriter = class {
3642
4141
  } else if (headers) {
3643
4142
  columnCount = headers.length;
3644
4143
  }
4144
+ if (columnCount > 0) assertColumnCount(columnCount);
4145
+ const totalRows = rows.length + (headers ? 1 : 0);
4146
+ assertRowCount(totalRows);
4147
+ if (headers) {
4148
+ assertRowFitsColumns("header", headers.length, columnCount);
4149
+ for (const h of headers) assertCellTextLength(h);
4150
+ }
4151
+ for (let r = 0; r < rows.length; r++) {
4152
+ assertRowFitsColumns("row", rows[r].length, columnCount);
4153
+ }
3645
4154
  const colWidths = initColWidths(columnCount);
3646
4155
  const colLetters = new Array(columnCount);
3647
4156
  for (let i = 0; i < columnCount; i++) {
@@ -3653,7 +4162,6 @@ var XlsxWriter = class {
3653
4162
  updateColWidthsFromRows(colWidths, rows);
3654
4163
  bigBuf.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
3655
4164
  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
4165
  if (totalRows > 0 && columnCount > 0) {
3658
4166
  bigBuf.writeString(`<dimension ref="A1:${colLetters[columnCount - 1]}${totalRows}"/>`);
3659
4167
  } else {
@@ -3739,6 +4247,7 @@ var XlsxWriter = class {
3739
4247
  });
3740
4248
  }
3741
4249
  _writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
4250
+ assertCellTextLength(val);
3742
4251
  let index = this.sstMap.get(val);
3743
4252
  if (index === void 0) {
3744
4253
  index = this.sstArray.length;
@@ -3760,29 +4269,79 @@ var XlsxWriter = class {
3760
4269
  return xfIndex;
3761
4270
  }
3762
4271
  _toOADate(date) {
3763
- return (date.getTime() - this._oaEpoch) / 864e5;
4272
+ return timeToOaDate(date.getTime(), this._oaEpoch);
3764
4273
  }
3765
4274
  _getDateWidth(_date) {
3766
4275
  return 19;
3767
4276
  }
3768
4277
  /** Finalize the ZIP package and close the output stream. */
3769
4278
  finalize() {
3770
- return new Promise((resolve4, reject) => {
4279
+ return new Promise((resolve6, reject) => {
4280
+ if (this._finalized) {
4281
+ reject(new Error("XlsxWriter: already finalized"));
4282
+ return;
4283
+ }
4284
+ this._finalized = true;
4285
+ let settled = false;
4286
+ const cleanupTemp = () => {
4287
+ try {
4288
+ if (fs2.existsSync(this._tempPath)) fs2.rmSync(this._tempPath);
4289
+ } catch {
4290
+ }
4291
+ };
4292
+ const fail = (err) => {
4293
+ if (settled) return;
4294
+ settled = true;
4295
+ try {
4296
+ this.archive.abort();
4297
+ } catch {
4298
+ }
4299
+ try {
4300
+ this.output.destroy();
4301
+ } catch {
4302
+ }
4303
+ cleanupTemp();
4304
+ reject(err instanceof Error ? err : new Error(String(err)));
4305
+ };
4306
+ const succeed = () => {
4307
+ if (settled) return;
4308
+ if (this._outputError) {
4309
+ fail(this._outputError);
4310
+ return;
4311
+ }
4312
+ settled = true;
4313
+ try {
4314
+ const fd = fs2.openSync(this._tempPath, "r+");
4315
+ try {
4316
+ fs2.fsyncSync(fd);
4317
+ } finally {
4318
+ fs2.closeSync(fd);
4319
+ }
4320
+ try {
4321
+ const existing = fs2.statSync(this._targetPath);
4322
+ fs2.chmodSync(this._tempPath, existing.mode & 511);
4323
+ } catch {
4324
+ }
4325
+ fs2.renameSync(this._tempPath, this._targetPath);
4326
+ resolve6();
4327
+ } catch (err) {
4328
+ cleanupTemp();
4329
+ reject(err instanceof Error ? err : new Error(String(err)));
4330
+ }
4331
+ };
3771
4332
  try {
4333
+ if (this._outputError) throw this._outputError;
3772
4334
  this._writeSharedStrings();
3773
4335
  this._writeStyles();
3774
4336
  this._writeWorkbook();
3775
4337
  this._writeContentTypes();
3776
4338
  this._writeRels();
3777
- this.output.on("close", () => {
3778
- resolve4();
3779
- });
3780
- this.archive.on("error", (err) => {
3781
- reject(err);
3782
- });
4339
+ this.output.once("close", succeed);
4340
+ this.output.once("error", fail);
4341
+ this.archive.once("error", fail);
3783
4342
  this.archive.finalize();
3784
4343
  } catch (err) {
3785
- reject(err);
4344
+ fail(err);
3786
4345
  }
3787
4346
  });
3788
4347
  }
@@ -3914,13 +4473,13 @@ ${xfEntries.join("\n")}
3914
4473
  // src/XlsxUpdater.ts
3915
4474
  import * as fs5 from "fs";
3916
4475
  import * as os from "os";
3917
- import * as path4 from "path";
4476
+ import * as path6 from "path";
3918
4477
  import AdmZip2 from "adm-zip";
3919
4478
 
3920
4479
  // src/atomicFile.ts
3921
4480
  import * as fs3 from "fs";
3922
- import * as path2 from "path";
3923
- import { randomUUID } from "crypto";
4481
+ import * as path4 from "path";
4482
+ import { randomUUID as randomUUID3 } from "crypto";
3924
4483
  var realFileOperations = {
3925
4484
  exists: (filePath) => fs3.existsSync(filePath),
3926
4485
  rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
@@ -3943,7 +4502,7 @@ function readDestinationMode(target) {
3943
4502
  }
3944
4503
  }
3945
4504
  function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
3946
- const backupPath = `${destinationPath}.${randomUUID()}.replacement-backup`;
4505
+ const backupPath = `${destinationPath}.${randomUUID3()}.replacement-backup`;
3947
4506
  let backupCreated = false;
3948
4507
  let installed = false;
3949
4508
  if (operations.exists(destinationPath)) {
@@ -3991,11 +4550,11 @@ function installTemporaryOutput(temporaryPath, destinationPath, overwrite, opera
3991
4550
  }
3992
4551
  }
3993
4552
  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`
4553
+ const target = path4.resolve(destinationPath);
4554
+ fs3.mkdirSync(path4.dirname(target), { recursive: true });
4555
+ const temporaryPath = path4.join(
4556
+ path4.dirname(target),
4557
+ `.${path4.basename(target)}.${randomUUID3()}.tmp`
3999
4558
  );
4000
4559
  const destinationMode = readDestinationMode(target);
4001
4560
  try {
@@ -4031,8 +4590,8 @@ function writeBufferAtomically(buffer, destinationPath) {
4031
4590
 
4032
4591
  // src/streamingZip.ts
4033
4592
  import * as fs4 from "fs";
4034
- import * as path3 from "path";
4035
- import { randomUUID as randomUUID2 } from "crypto";
4593
+ import * as path5 from "path";
4594
+ import { randomUUID as randomUUID4 } from "crypto";
4036
4595
  import { finished } from "stream/promises";
4037
4596
  import { Readable as Readable3 } from "stream";
4038
4597
  import archiver3 from "archiver";
@@ -4044,8 +4603,8 @@ function readDestinationMode2(target) {
4044
4603
  return void 0;
4045
4604
  }
4046
4605
  }
4047
- function appendAndWait(archive, source, name) {
4048
- return new Promise((resolve4, reject) => {
4606
+ function appendAndWait(archive, source, name, output) {
4607
+ return new Promise((resolve6, reject) => {
4049
4608
  const cleanup = () => {
4050
4609
  archive.off("entry", onEntry);
4051
4610
  archive.off("error", onError);
@@ -4053,10 +4612,13 @@ function appendAndWait(archive, source, name) {
4053
4612
  if (typeof maybeEmitter2.off === "function") {
4054
4613
  maybeEmitter2.off("error", onError);
4055
4614
  }
4615
+ if (output) {
4616
+ output.off("error", onError);
4617
+ }
4056
4618
  };
4057
4619
  const onEntry = () => {
4058
4620
  cleanup();
4059
- resolve4();
4621
+ resolve6();
4060
4622
  };
4061
4623
  const onError = (error) => {
4062
4624
  cleanup();
@@ -4064,6 +4626,9 @@ function appendAndWait(archive, source, name) {
4064
4626
  };
4065
4627
  archive.once("entry", onEntry);
4066
4628
  archive.once("error", onError);
4629
+ if (output) {
4630
+ output.once("error", onError);
4631
+ }
4067
4632
  const maybeEmitter = source;
4068
4633
  if (typeof maybeEmitter.once === "function") {
4069
4634
  maybeEmitter.once("error", onError);
@@ -4077,11 +4642,11 @@ function appendAndWait(archive, source, name) {
4077
4642
  });
4078
4643
  }
4079
4644
  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`
4645
+ const target = path5.resolve(destinationPath);
4646
+ fs4.mkdirSync(path5.dirname(target), { recursive: true });
4647
+ const temporaryPath = path5.join(
4648
+ path5.dirname(target),
4649
+ `.${path5.basename(target)}.${randomUUID4()}.tmp`
4085
4650
  );
4086
4651
  const destinationMode = readDestinationMode2(target);
4087
4652
  let output = null;
@@ -4091,18 +4656,32 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4091
4656
  temporaryPath,
4092
4657
  destinationMode !== void 0 ? { mode: destinationMode } : {}
4093
4658
  );
4659
+ let outputError = null;
4660
+ output.on("error", (err) => {
4661
+ outputError = err;
4662
+ try {
4663
+ archive == null ? void 0 : archive.abort();
4664
+ } catch {
4665
+ }
4666
+ });
4094
4667
  archive = archiver3("zip");
4668
+ archive.on("error", (err) => {
4669
+ outputError = err;
4670
+ });
4095
4671
  archive.pipe(output);
4096
4672
  for (const entry of zip.getEntries()) {
4673
+ if (outputError) throw outputError;
4097
4674
  const stagedPath = stagedParts.get(entry.entryName);
4098
4675
  if (stagedPath) {
4099
- await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName);
4676
+ await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName, output);
4100
4677
  } else {
4101
- await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName);
4678
+ await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName, output);
4102
4679
  }
4103
4680
  }
4681
+ if (outputError) throw outputError;
4104
4682
  await archive.finalize();
4105
4683
  await finished(output);
4684
+ if (outputError) throw outputError;
4106
4685
  if (destinationMode !== void 0) {
4107
4686
  try {
4108
4687
  fs4.chmodSync(temporaryPath, destinationMode);
@@ -4136,6 +4715,23 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4136
4715
  }
4137
4716
 
4138
4717
  // src/XlsxUpdater.ts
4718
+ function parseCellRange(ref) {
4719
+ const m = /^\$?([A-Za-z]+)\$?(\d+)(?::\$?([A-Za-z]+)\$?(\d+))?$/.exec(ref.trim());
4720
+ if (!m) return null;
4721
+ const c1 = columnLetterToIndex(m[1].toUpperCase());
4722
+ const r1 = parseInt(m[2], 10);
4723
+ const c2 = m[3] ? columnLetterToIndex(m[3].toUpperCase()) : c1;
4724
+ const r2 = m[4] ? parseInt(m[4], 10) : r1;
4725
+ if (!Number.isFinite(c1) || !Number.isFinite(r1) || !Number.isFinite(c2) || !Number.isFinite(r2)) {
4726
+ return null;
4727
+ }
4728
+ return {
4729
+ c1: Math.min(c1, c2),
4730
+ r1: Math.min(r1, r2),
4731
+ c2: Math.max(c1, c2),
4732
+ r2: Math.max(r1, r2)
4733
+ };
4734
+ }
4139
4735
  var XlsxUpdater = class _XlsxUpdater {
4140
4736
  /**
4141
4737
  * @param path Path to an existing `.xlsx` file.
@@ -4222,6 +4818,8 @@ var XlsxUpdater = class _XlsxUpdater {
4222
4818
  const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
4223
4819
  this._updatePivotCaches(sheetName, dimRef, rows.length);
4224
4820
  this._addRefreshOnLoadToPivotTables();
4821
+ this._removeCalcChain();
4822
+ this._updateTableRefs(sheetName, dimRef);
4225
4823
  }
4226
4824
  /**
4227
4825
  * Replace worksheet rows from a one-pass source without materialising the
@@ -4294,10 +4892,10 @@ var XlsxUpdater = class _XlsxUpdater {
4294
4892
  if (outputError) throw outputError;
4295
4893
  writtenBytes += data.length;
4296
4894
  if (rowOutput.write(data)) return;
4297
- await new Promise((resolve4, reject) => {
4895
+ await new Promise((resolve6, reject) => {
4298
4896
  const onDrain = () => {
4299
4897
  cleanup();
4300
- resolve4();
4898
+ resolve6();
4301
4899
  };
4302
4900
  const onError = (error) => {
4303
4901
  cleanup();
@@ -4337,8 +4935,8 @@ var XlsxUpdater = class _XlsxUpdater {
4337
4935
  lastKeptEnd = writtenBytes;
4338
4936
  }
4339
4937
  }
4340
- await new Promise((resolve4, reject) => {
4341
- rowOutput.once("close", resolve4);
4938
+ await new Promise((resolve6, reject) => {
4939
+ rowOutput.once("close", resolve6);
4342
4940
  rowOutput.once("error", reject);
4343
4941
  rowOutput.end();
4344
4942
  });
@@ -4357,6 +4955,8 @@ var XlsxUpdater = class _XlsxUpdater {
4357
4955
  this._commitSharedStrings();
4358
4956
  this._updatePivotCaches(sheetName, dimRef, keptDataRows);
4359
4957
  this._addRefreshOnLoadToPivotTables();
4958
+ this._removeCalcChain();
4959
+ this._updateTableRefs(sheetName, dimRef);
4360
4960
  if (oldStage && oldStage !== outputPath) {
4361
4961
  this._removeTemporaryFile(oldStage);
4362
4962
  }
@@ -4430,7 +5030,7 @@ var XlsxUpdater = class _XlsxUpdater {
4430
5030
  }
4431
5031
  _ensureTemporaryDirectory() {
4432
5032
  if (this.temporaryDirectory === null) {
4433
- this.temporaryDirectory = fs5.mkdtempSync(path4.join(os.tmpdir(), "spreadsheet-updater-"));
5033
+ this.temporaryDirectory = fs5.mkdtempSync(path6.join(os.tmpdir(), "spreadsheet-updater-"));
4434
5034
  } else {
4435
5035
  try {
4436
5036
  fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
@@ -4440,7 +5040,7 @@ var XlsxUpdater = class _XlsxUpdater {
4440
5040
  return this.temporaryDirectory;
4441
5041
  }
4442
5042
  _temporaryPath(suffix) {
4443
- return path4.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
5043
+ return path6.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
4444
5044
  }
4445
5045
  _removeTemporaryFile(filePath) {
4446
5046
  try {
@@ -4480,10 +5080,15 @@ var XlsxUpdater = class _XlsxUpdater {
4480
5080
  const rIdToTarget = {};
4481
5081
  if (relsEntry) {
4482
5082
  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];
5083
+ const relTagRe = /<Relationship\b[^>]*>/g;
5084
+ let tagMatch;
5085
+ while ((tagMatch = relTagRe.exec(rels)) !== null) {
5086
+ const attrs = parseTagAttributes(tagMatch[0]);
5087
+ const id = attrs["Id"] ?? attrs["id"];
5088
+ const target = attrs["Target"] ?? attrs["target"];
5089
+ if (id !== void 0 && target !== void 0) {
5090
+ rIdToTarget[id] = target;
5091
+ }
4487
5092
  }
4488
5093
  }
4489
5094
  const wbEntry = this.zip.getEntry("xl/workbook.xml");
@@ -4492,21 +5097,23 @@ var XlsxUpdater = class _XlsxUpdater {
4492
5097
  let match;
4493
5098
  while ((match = sheetRe.exec(wbXml)) !== null) {
4494
5099
  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
- }
5100
+ const attrs = parseTagAttributes(tag);
5101
+ const rawName = attrs["name"];
5102
+ const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
5103
+ if (rawName === void 0 || rId === void 0) continue;
5104
+ const name = unescapeXml(rawName);
5105
+ const target = rIdToTarget[rId] ?? "";
5106
+ const fullPath = resolveRelTarget(target);
4506
5107
  if (fullPath) {
4507
5108
  this.sheetNameToPath.set(name, fullPath);
4508
5109
  }
4509
5110
  }
5111
+ const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbXml);
5112
+ if (wbPrMatch) {
5113
+ const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
5114
+ const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
5115
+ this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
5116
+ }
4510
5117
  for (const entry of this.zip.getEntries()) {
4511
5118
  const name = entry.entryName;
4512
5119
  if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
@@ -4701,7 +5308,7 @@ var XlsxUpdater = class _XlsxUpdater {
4701
5308
  return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
4702
5309
  }
4703
5310
  if (val instanceof Date) {
4704
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
5311
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
4705
5312
  if (Number.isFinite(oaDate)) {
4706
5313
  const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
4707
5314
  return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
@@ -4918,8 +5525,8 @@ var XlsxUpdater = class _XlsxUpdater {
4918
5525
  }
4919
5526
  }
4920
5527
  _addRefreshOnLoadToPivotTables() {
4921
- for (const path7 of this.pivotTableDefPaths) {
4922
- const entry = this.zip.getEntry(path7);
5528
+ for (const path9 of this.pivotTableDefPaths) {
5529
+ const entry = this.zip.getEntry(path9);
4923
5530
  if (!entry) continue;
4924
5531
  let xml = entry.getData().toString("utf8");
4925
5532
  const rootRe = /<pivotTableDefinition\b[^>]*>/;
@@ -4927,7 +5534,233 @@ var XlsxUpdater = class _XlsxUpdater {
4927
5534
  if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
4928
5535
  const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
4929
5536
  xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
4930
- this.zip.updateFile(path7, Buffer.from(xml, "utf8"));
5537
+ this.zip.updateFile(path9, Buffer.from(xml, "utf8"));
5538
+ }
5539
+ }
5540
+ /**
5541
+ * Remove the stale calculation chain (xl/calcChain.xml). Excel rebuilds it
5542
+ * on open; keeping stale <c> entries for the replaced sheet would force a
5543
+ * repair prompt. Removes the part, its Content_Types override and its
5544
+ * workbook rels entry (xlspy parity).
5545
+ */
5546
+ _removeCalcChain() {
5547
+ const calcPath = "xl/calcChain.xml";
5548
+ if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) {
5549
+ this._ensureCalcPrFullCalc();
5550
+ return;
5551
+ }
5552
+ try {
5553
+ this.zip.deleteFile(calcPath);
5554
+ } catch {
5555
+ }
5556
+ const stagedCalc = this.stagedParts.get(calcPath);
5557
+ if (stagedCalc) {
5558
+ this.stagedParts.delete(calcPath);
5559
+ try {
5560
+ fs5.rmSync(stagedCalc, { force: true });
5561
+ } catch {
5562
+ }
5563
+ }
5564
+ const ctEntry = this.zip.getEntry("[Content_Types].xml");
5565
+ if (ctEntry || this.stagedParts.has("[Content_Types].xml")) {
5566
+ try {
5567
+ let ct = this._entryData("[Content_Types].xml").toString("utf8");
5568
+ const before = ct;
5569
+ ct = ct.replace(/<Override\b[^>]*PartName="\/xl\/calcChain\.xml"[^>]*\/?>/gi, "");
5570
+ if (ct !== before) {
5571
+ this._writePartText("[Content_Types].xml", ct);
5572
+ }
5573
+ } catch {
5574
+ }
5575
+ }
5576
+ const relsPath = "xl/_rels/workbook.xml.rels";
5577
+ if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
5578
+ try {
5579
+ const rels = this._entryData(relsPath).toString("utf8");
5580
+ const relTagRe = /<Relationship\b[^>]*>/g;
5581
+ let m;
5582
+ let out = "";
5583
+ let last = 0;
5584
+ let changed = false;
5585
+ while ((m = relTagRe.exec(rels)) !== null) {
5586
+ const attrs = parseTagAttributes(m[0]);
5587
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
5588
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
5589
+ const isCalcChain = type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.xml";
5590
+ if (isCalcChain) {
5591
+ out += rels.substring(last, m.index);
5592
+ last = m.index + m[0].length;
5593
+ changed = true;
5594
+ }
5595
+ }
5596
+ if (changed) {
5597
+ out += rels.substring(last);
5598
+ this._writePartText(relsPath, out);
5599
+ }
5600
+ } catch {
5601
+ }
5602
+ }
5603
+ this._ensureCalcPrFullCalc();
5604
+ }
5605
+ _writePartText(name, text) {
5606
+ if (this.stagedParts.has(name)) {
5607
+ fs5.writeFileSync(this.stagedParts.get(name), text, "utf8");
5608
+ } else {
5609
+ this.zip.updateFile(name, Buffer.from(text, "utf8"));
5610
+ }
5611
+ }
5612
+ _ensureCalcPrFullCalc() {
5613
+ try {
5614
+ const wbPath = "xl/workbook.xml";
5615
+ if (!this.zip.getEntry(wbPath) && !this.stagedParts.has(wbPath)) return;
5616
+ let xml = this._entryData(wbPath).toString("utf8");
5617
+ const calcPrRe = /<(?:[A-Za-z_][\w.-]*:)?calcPr\b[^>]*>/i;
5618
+ const match = calcPrRe.exec(xml);
5619
+ if (match && match.index !== void 0) {
5620
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(match[0], "fullCalcOnLoad", "1");
5621
+ xml = xml.substring(0, match.index) + newTag + xml.substring(match.index + match[0].length);
5622
+ } else {
5623
+ const insertAt = _XlsxUpdater._calcPrInsertPosition(xml);
5624
+ if (insertAt < 0) return;
5625
+ xml = xml.substring(0, insertAt) + '<calcPr fullCalcOnLoad="1"/>' + xml.substring(insertAt);
5626
+ }
5627
+ this._writePartText(wbPath, xml);
5628
+ } catch {
5629
+ }
5630
+ }
5631
+ /**
5632
+ * Insertion offset for a missing `<calcPr>`. The element follows
5633
+ * sheets/externalReferences/definedNames in the workbook schema, so insert
5634
+ * after the last of those that exists instead of right after `<workbook>`.
5635
+ */
5636
+ static _calcPrInsertPosition(xml) {
5637
+ const prefixed = "(?:[A-Za-z_][\\w.-]*:)?";
5638
+ const candidates = [
5639
+ new RegExp(`</${prefixed}sheets\\s*>`, "i"),
5640
+ new RegExp(`<${prefixed}externalReferences\\b[^>]*/>`, "i"),
5641
+ new RegExp(`<${prefixed}externalReferences\\b[^>]*>[\\s\\S]*?</${prefixed}externalReferences\\s*>`, "i"),
5642
+ new RegExp(`<${prefixed}definedNames\\b[^>]*/>`, "i"),
5643
+ new RegExp(`<${prefixed}definedNames\\b[^>]*>[\\s\\S]*?</${prefixed}definedNames\\s*>`, "i")
5644
+ ];
5645
+ let insertAt = -1;
5646
+ for (const re of candidates) {
5647
+ const m = re.exec(xml);
5648
+ if (m && m.index !== void 0) {
5649
+ insertAt = Math.max(insertAt, m.index + m[0].length);
5650
+ }
5651
+ }
5652
+ if (insertAt >= 0) return insertAt;
5653
+ const close = new RegExp(`</${prefixed}workbook\\s*>`, "i").exec(xml);
5654
+ if (close && close.index !== void 0) return close.index;
5655
+ const root = new RegExp(`<${prefixed}workbook\\b[^>]*>`, "i").exec(xml);
5656
+ if (!root || root.index === void 0) return -1;
5657
+ return root.index + root[0].length;
5658
+ }
5659
+ /**
5660
+ * Update ListObject table ranges (xl/tables/*.xml) for the replaced sheet.
5661
+ * Only tables anchored at A1 are updated to the new dimension; tables
5662
+ * elsewhere are left untouched (moving them would corrupt the sheet).
5663
+ * Empty replacements (dimRef "A1") do not shrink multi-column tables.
5664
+ */
5665
+ _updateTableRefs(sheetName, dimRef) {
5666
+ const sheetPath = this.sheetNameToPath.get(sheetName);
5667
+ if (!sheetPath) return;
5668
+ const slash = sheetPath.lastIndexOf("/");
5669
+ const dir = slash >= 0 ? sheetPath.substring(0, slash + 1) : "";
5670
+ const base = slash >= 0 ? sheetPath.substring(slash + 1) : sheetPath;
5671
+ const sheetRelsPath = `${dir}_rels/${base}.rels`;
5672
+ const sheetRelsEntry = this.zip.getEntry(sheetRelsPath);
5673
+ const hasStagedRels = this.stagedParts.has(sheetRelsPath);
5674
+ if (!sheetRelsEntry && !hasStagedRels) return;
5675
+ const tableTargets = [];
5676
+ try {
5677
+ const relsXml = this._entryData(sheetRelsPath).toString("utf8");
5678
+ const relTagRe = /<Relationship\b[^>]*>/g;
5679
+ let m;
5680
+ while ((m = relTagRe.exec(relsXml)) !== null) {
5681
+ const attrs = parseTagAttributes(m[0]);
5682
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
5683
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
5684
+ if (!type.includes("/table") || !target) continue;
5685
+ let tablePath;
5686
+ if (target.startsWith("/")) {
5687
+ tablePath = resolveRelTarget(target);
5688
+ } else {
5689
+ const combined = dir + target;
5690
+ const parts = [];
5691
+ for (const seg of combined.split("/")) {
5692
+ if (seg === "..") parts.pop();
5693
+ else if (seg !== "." && seg !== "") parts.push(seg);
5694
+ }
5695
+ tablePath = parts.join("/");
5696
+ }
5697
+ tableTargets.push(tablePath);
5698
+ }
5699
+ } catch {
5700
+ return;
5701
+ }
5702
+ if (tableTargets.length === 0) return;
5703
+ const tables = [];
5704
+ for (const tablePath of tableTargets) {
5705
+ try {
5706
+ const hasEntry = !!this.zip.getEntry(tablePath);
5707
+ const hasStaged = this.stagedParts.has(tablePath);
5708
+ if (!hasEntry && !hasStaged) continue;
5709
+ const xml = this._entryData(tablePath).toString("utf8");
5710
+ const tableTagRe = /<(?:[A-Za-z_][\w.-]*:)?table\b[^>]*>/i;
5711
+ const tableMatch = tableTagRe.exec(xml);
5712
+ if (!tableMatch || tableMatch.index === void 0) continue;
5713
+ const refMatch = /\bref\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tableMatch[0]);
5714
+ const ref = refMatch ? refMatch[2] ?? refMatch[3] ?? "" : "";
5715
+ tables.push({
5716
+ path: tablePath,
5717
+ xml,
5718
+ tag: tableMatch[0],
5719
+ tagIndex: tableMatch.index,
5720
+ ref,
5721
+ range: parseCellRange(ref),
5722
+ staged: hasStaged
5723
+ });
5724
+ } catch {
5725
+ }
5726
+ }
5727
+ if (tables.length === 0) return;
5728
+ const dimRange = parseCellRange(dimRef);
5729
+ if (!dimRange) return;
5730
+ for (const table of tables) {
5731
+ try {
5732
+ const origRange = table.range;
5733
+ if (!origRange) continue;
5734
+ if (origRange.r1 !== 1 || origRange.c1 !== 0) continue;
5735
+ if (dimRef === "A1" && !/^A1$/i.test(table.ref)) continue;
5736
+ const origCols = origRange.c2 - origRange.c1 + 1;
5737
+ const newCols = dimRange.c2 - dimRange.c1 + 1;
5738
+ if (newCols !== origCols) continue;
5739
+ let lastRow = dimRange.r2;
5740
+ for (const other of tables) {
5741
+ if (other === table || !other.range) continue;
5742
+ if (other.range.r1 <= 1) continue;
5743
+ if (other.range.c1 > origRange.c2 || other.range.c2 < origRange.c1) continue;
5744
+ lastRow = Math.min(lastRow, other.range.r1 - 1);
5745
+ }
5746
+ if (lastRow < 1) continue;
5747
+ const newRef = origRange.c2 === 0 && lastRow === 1 ? "A1" : `A1:${columnIndexToLetter(origRange.c2)}${lastRow}`;
5748
+ let xml = table.xml;
5749
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(table.tag, "ref", newRef);
5750
+ xml = xml.substring(0, table.tagIndex) + newTag + xml.substring(table.tagIndex + table.tag.length);
5751
+ const afRe = /<(?:[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i;
5752
+ const afMatch = afRe.exec(xml);
5753
+ if (afMatch && afMatch.index !== void 0) {
5754
+ const newAf = _XlsxUpdater._replaceOrAddXmlAttribute(afMatch[0], "ref", newRef);
5755
+ xml = xml.substring(0, afMatch.index) + newAf + xml.substring(afMatch.index + afMatch[0].length);
5756
+ }
5757
+ if (table.staged) {
5758
+ fs5.writeFileSync(this.stagedParts.get(table.path), xml, "utf8");
5759
+ } else {
5760
+ this.zip.updateFile(table.path, Buffer.from(xml, "utf8"));
5761
+ }
5762
+ } catch {
5763
+ }
4931
5764
  }
4932
5765
  }
4933
5766
  };
@@ -4935,78 +5768,8 @@ var XlsxUpdater = class _XlsxUpdater {
4935
5768
  // src/XlsbUpdater.ts
4936
5769
  import * as fs6 from "fs";
4937
5770
  import * as os2 from "os";
4938
- import * as path5 from "path";
5771
+ import * as path7 from "path";
4939
5772
  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
5773
  var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
5011
5774
  var RK_INT_LOWER = -1 << 29;
5012
5775
  var RK_INT_UPPER = (1 << 29) - 1;
@@ -5110,6 +5873,7 @@ var XlsbUpdater = class {
5110
5873
  const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
5111
5874
  const colLast = lastCol;
5112
5875
  this._patchPivotCaches(sheetName, rwLast, colLast);
5876
+ this._removeCalcChain();
5113
5877
  }
5114
5878
  /** Replace worksheet rows from a one-pass source. */
5115
5879
  async replaceSheetDataStream(sheetName, rows, options = {}) {
@@ -5177,10 +5941,10 @@ var XlsbUpdater = class {
5177
5941
  if (outputError) throw outputError;
5178
5942
  writtenBytes += data.length;
5179
5943
  if (rowOutput.write(data)) return;
5180
- await new Promise((resolve4, reject) => {
5944
+ await new Promise((resolve6, reject) => {
5181
5945
  const onDrain = () => {
5182
5946
  cleanup();
5183
- resolve4();
5947
+ resolve6();
5184
5948
  };
5185
5949
  const onError = (error) => {
5186
5950
  cleanup();
@@ -5231,8 +5995,8 @@ var XlsbUpdater = class {
5231
5995
  lastKeptEnd = writtenBytes;
5232
5996
  }
5233
5997
  }
5234
- await new Promise((resolve4, reject) => {
5235
- rowOutput.once("close", resolve4);
5998
+ await new Promise((resolve6, reject) => {
5999
+ rowOutput.once("close", resolve6);
5236
6000
  rowOutput.once("error", reject);
5237
6001
  rowOutput.end();
5238
6002
  });
@@ -5295,6 +6059,7 @@ var XlsbUpdater = class {
5295
6059
  Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
5296
6060
  lastCol
5297
6061
  );
6062
+ this._removeCalcChain();
5298
6063
  if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
5299
6064
  createdParts.splice(createdParts.indexOf(outputPath), 1);
5300
6065
  } catch (error) {
@@ -5365,7 +6130,7 @@ var XlsbUpdater = class {
5365
6130
  }
5366
6131
  _ensureTemporaryDirectory() {
5367
6132
  if (this.temporaryDirectory === null) {
5368
- this.temporaryDirectory = fs6.mkdtempSync(path5.join(os2.tmpdir(), "spreadsheet-updater-"));
6133
+ this.temporaryDirectory = fs6.mkdtempSync(path7.join(os2.tmpdir(), "spreadsheet-updater-"));
5369
6134
  } else {
5370
6135
  try {
5371
6136
  fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
@@ -5375,7 +6140,7 @@ var XlsbUpdater = class {
5375
6140
  return this.temporaryDirectory;
5376
6141
  }
5377
6142
  _temporaryPath(suffix) {
5378
- return path5.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
6143
+ return path7.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
5379
6144
  }
5380
6145
  _removeTemporaryFile(filePath) {
5381
6146
  try {
@@ -5415,10 +6180,15 @@ var XlsbUpdater = class {
5415
6180
  const rIdToTarget = {};
5416
6181
  if (relsEntry) {
5417
6182
  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];
6183
+ const relTagRe = /<Relationship\b[^>]*>/g;
6184
+ let tagMatch;
6185
+ while ((tagMatch = relTagRe.exec(rels)) !== null) {
6186
+ const attrs = parseTagAttributes(tagMatch[0]);
6187
+ const id = attrs["Id"] ?? attrs["id"];
6188
+ const target = attrs["Target"] ?? attrs["target"];
6189
+ if (id !== void 0 && target !== void 0) {
6190
+ rIdToTarget[id] = target;
6191
+ }
5422
6192
  }
5423
6193
  }
5424
6194
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
@@ -5438,12 +6208,24 @@ var XlsbUpdater = class {
5438
6208
  o += 4;
5439
6209
  const name = readUtf16(wbBuf, o, nameLen);
5440
6210
  let target = rIdToTarget[rId] ?? "";
5441
- if (target.startsWith("/")) target = target.substring(1);
5442
- else if (!target.startsWith("xl/")) target = "xl/" + target;
6211
+ target = resolveRelTarget(target);
5443
6212
  if (target) {
5444
6213
  this.sheetNameToPath.set(name, target);
5445
6214
  }
5446
6215
  }
6216
+ try {
6217
+ const rec1904 = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
6218
+ let p1904 = 0;
6219
+ while (readRecord(wbBuf, p1904, rec1904)) {
6220
+ if (rec1904.id === 153 && rec1904.len >= 4) {
6221
+ const is1904 = (wbBuf[rec1904.dataStart] & 1) !== 0;
6222
+ this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
6223
+ break;
6224
+ }
6225
+ p1904 = rec1904.dataEnd;
6226
+ }
6227
+ } catch {
6228
+ }
5447
6229
  }
5448
6230
  for (const e of this.zip.getEntries()) {
5449
6231
  if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
@@ -5552,27 +6334,15 @@ var XlsbUpdater = class {
5552
6334
  _findDateXf() {
5553
6335
  const entry = this.zip.getEntry("xl/styles.bin");
5554
6336
  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++;
6337
+ const reader = new BiffReaderWriter(entry.getData());
6338
+ while (reader.readStyles()) {
6339
+ }
6340
+ const numFmtIds = reader._xfIndexToNumFmtId;
6341
+ const customDates = reader._customNumFmts;
6342
+ for (let i = 0; i < numFmtIds.length; i++) {
6343
+ const numFmtId = numFmtIds[i];
6344
+ if (numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || customDates.has(numFmtId)) {
6345
+ return i;
5576
6346
  }
5577
6347
  }
5578
6348
  return null;
@@ -5652,7 +6422,7 @@ var XlsbUpdater = class {
5652
6422
  return buildRecord(4, payload);
5653
6423
  }
5654
6424
  if (val instanceof Date) {
5655
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
6425
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
5656
6426
  if (Number.isFinite(oaDate)) {
5657
6427
  const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
5658
6428
  const payload = Buffer.alloc(16);
@@ -5719,10 +6489,10 @@ var XlsbUpdater = class {
5719
6489
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
5720
6490
  let pos = 0;
5721
6491
  while (readRecord(originalSheet, pos, rec)) {
5722
- if (rec.id === 152 && rec.len >= 36) {
6492
+ if (rec.id === 148 && rec.len >= 16) {
5723
6493
  const shift = rec.dataStart >= rowsEnd ? delta : 0;
5724
- patchInt32(rec.dataStart + shift + 24, lastRowIdx);
5725
- patchInt32(rec.dataStart + shift + 32, lastCol);
6494
+ patchInt32(rec.dataStart + shift + 4, lastRowIdx);
6495
+ patchInt32(rec.dataStart + shift + 12, lastCol);
5726
6496
  break;
5727
6497
  }
5728
6498
  pos = rec.dataEnd;
@@ -5761,8 +6531,8 @@ var XlsbUpdater = class {
5761
6531
  return { rowsStart, rowsEnd, autoFilterRanges };
5762
6532
  }
5763
6533
  /**
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)
6534
+ * Keep the sheet dimension record (0x94) in sync with the new data.
6535
+ * Excel uses its rwLast (payload offset 4) and colLast (payload offset 12)
5766
6536
  * fields to read the full row range; stale values make the last written row
5767
6537
  * invisible to Excel (and drop it from pivot refreshes).
5768
6538
  */
@@ -5770,9 +6540,9 @@ var XlsbUpdater = class {
5770
6540
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
5771
6541
  let pos = 0;
5772
6542
  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);
6543
+ if (rec.id === 148 && rec.len >= 16) {
6544
+ sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 4);
6545
+ sheetBuf.writeInt32LE(lastCol, rec.dataStart + 12);
5776
6546
  return;
5777
6547
  }
5778
6548
  pos = rec.dataEnd;
@@ -5782,8 +6552,8 @@ var XlsbUpdater = class {
5782
6552
  // Pivot cache integration
5783
6553
  // ------------------------------------------------------------------
5784
6554
  _patchPivotCaches(sheetName, rwLast, colLast) {
5785
- for (const path7 of this.pivotCacheDefPaths) {
5786
- const entry = this.zip.getEntry(path7);
6555
+ for (const path9 of this.pivotCacheDefPaths) {
6556
+ const entry = this.zip.getEntry(path9);
5787
6557
  if (!entry) continue;
5788
6558
  const buf = Buffer.from(entry.getData());
5789
6559
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
@@ -5815,16 +6585,84 @@ var XlsbUpdater = class {
5815
6585
  }
5816
6586
  }
5817
6587
  if (patched) {
5818
- this.zip.updateFile(path7, buf);
6588
+ this.zip.updateFile(path9, buf);
6589
+ }
6590
+ }
6591
+ }
6592
+ /**
6593
+ * Remove the stale calculation chain (xl/calcChain.bin). Excel rebuilds it
6594
+ * on open. Removes the part, its Content_Types override and its workbook
6595
+ * rels entry. XLSB tables are not patched (documented limitation).
6596
+ */
6597
+ _removeCalcChain() {
6598
+ const calcPath = "xl/calcChain.bin";
6599
+ if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) return;
6600
+ try {
6601
+ this.zip.deleteFile(calcPath);
6602
+ } catch {
6603
+ }
6604
+ const stagedCalc = this.stagedParts.get(calcPath);
6605
+ if (stagedCalc) {
6606
+ this.stagedParts.delete(calcPath);
6607
+ try {
6608
+ fs6.rmSync(stagedCalc, { force: true });
6609
+ } catch {
6610
+ }
6611
+ }
6612
+ const ctPath = "[Content_Types].xml";
6613
+ if (this.zip.getEntry(ctPath) || this.stagedParts.has(ctPath)) {
6614
+ try {
6615
+ const ct = this._entryData(ctPath).toString("utf8");
6616
+ const updated = ct.replace(
6617
+ /<Override\b[^>]*PartName="\/xl\/calcChain\.bin"[^>]*\/?>/gi,
6618
+ ""
6619
+ );
6620
+ if (updated !== ct) this._writePartText(ctPath, updated);
6621
+ } catch {
6622
+ }
6623
+ }
6624
+ const relsPath = "xl/_rels/workbook.bin.rels";
6625
+ if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
6626
+ try {
6627
+ const rels = this._entryData(relsPath).toString("utf8");
6628
+ const relTagRe = /<Relationship\b[^>]*>/g;
6629
+ let m;
6630
+ let out = "";
6631
+ let last = 0;
6632
+ let changed = false;
6633
+ while ((m = relTagRe.exec(rels)) !== null) {
6634
+ const attrs = parseTagAttributes(m[0]);
6635
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
6636
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
6637
+ if (type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.bin") {
6638
+ out += rels.substring(last, m.index);
6639
+ last = m.index + m[0].length;
6640
+ changed = true;
6641
+ }
6642
+ }
6643
+ if (changed) {
6644
+ out += rels.substring(last);
6645
+ this._writePartText(relsPath, out);
6646
+ }
6647
+ } catch {
5819
6648
  }
5820
6649
  }
5821
6650
  }
6651
+ /** Write an XML/text part to its staged file when present, else into the ZIP. */
6652
+ _writePartText(name, text) {
6653
+ const stagedPath = this.stagedParts.get(name);
6654
+ if (stagedPath) {
6655
+ fs6.writeFileSync(stagedPath, text, "utf8");
6656
+ } else {
6657
+ this.zip.updateFile(name, Buffer.from(text, "utf8"));
6658
+ }
6659
+ }
5822
6660
  };
5823
6661
 
5824
6662
  // src/ExcelConverter.ts
5825
6663
  import * as fs7 from "fs";
5826
- import * as path6 from "path";
5827
- import { randomUUID as randomUUID3 } from "crypto";
6664
+ import * as path8 from "path";
6665
+ import { randomUUID as randomUUID5 } from "crypto";
5828
6666
  import { spawn } from "child_process";
5829
6667
  var POWERSHELL_CONVERSION_SCRIPT = `
5830
6668
  $ErrorActionPreference = 'Stop'
@@ -5870,14 +6708,14 @@ function errorMessage2(error) {
5870
6708
  return error instanceof Error ? error.message : String(error);
5871
6709
  }
5872
6710
  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) {
6711
+ const input = path8.resolve(inputPath);
6712
+ const output = path8.resolve(outputPath);
6713
+ if (path8.extname(input).toLowerCase() !== inputExtension) {
5876
6714
  throw new Error(
5877
6715
  `Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
5878
6716
  );
5879
6717
  }
5880
- if (path6.extname(output).toLowerCase() !== outputExtension) {
6718
+ if (path8.extname(output).toLowerCase() !== outputExtension) {
5881
6719
  throw new Error(
5882
6720
  `Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
5883
6721
  );
@@ -5888,8 +6726,8 @@ function validateConversionPaths(inputPath, outputPath, inputExtension, outputEx
5888
6726
  if (!fs7.statSync(input).isFile()) {
5889
6727
  throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
5890
6728
  }
5891
- if (!fs7.existsSync(path6.dirname(output))) {
5892
- throw new Error(`Excel conversion: destination directory not found: ${path6.dirname(output)}`);
6729
+ if (!fs7.existsSync(path8.dirname(output))) {
6730
+ throw new Error(`Excel conversion: destination directory not found: ${path8.dirname(output)}`);
5893
6731
  }
5894
6732
  if (fs7.existsSync(output)) {
5895
6733
  if (fs7.statSync(output).isDirectory()) {
@@ -5914,7 +6752,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5914
6752
  JUSTYBASE_EXCEL_OUTPUT: outputPath,
5915
6753
  JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
5916
6754
  };
5917
- return new Promise((resolve4, reject) => {
6755
+ return new Promise((resolve6, reject) => {
5918
6756
  var _a, _b, _c, _d;
5919
6757
  let stdout = "";
5920
6758
  let stderr = "";
@@ -5949,7 +6787,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5949
6787
  if (settled) return;
5950
6788
  settled = true;
5951
6789
  if (code === 0) {
5952
- resolve4();
6790
+ resolve6();
5953
6791
  return;
5954
6792
  }
5955
6793
  const details = (stderr || stdout).replace(/\s+/g, " ").trim();
@@ -5970,9 +6808,9 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
5970
6808
  if (process.platform !== "win32") {
5971
6809
  throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
5972
6810
  }
5973
- const temporaryOutput = path6.join(
5974
- path6.dirname(output),
5975
- `.${path6.basename(output, outputExtension)}.${randomUUID3()}.conversion${outputExtension}`
6811
+ const temporaryOutput = path8.join(
6812
+ path8.dirname(output),
6813
+ `.${path8.basename(output, outputExtension)}.${randomUUID5()}.conversion${outputExtension}`
5976
6814
  );
5977
6815
  try {
5978
6816
  await runPowerShellConversion(input, temporaryOutput, fileFormat);
@@ -6003,6 +6841,9 @@ export {
6003
6841
  ExcelReaderAbstract,
6004
6842
  F,
6005
6843
  INVALID_SHEET_NAME_CHARS,
6844
+ MAX_CELL_CHARS,
6845
+ MAX_COLS,
6846
+ MAX_ROWS,
6006
6847
  ReaderFactory,
6007
6848
  StreamingSheetState,
6008
6849
  XlsbReader,
@@ -6013,13 +6854,25 @@ export {
6013
6854
  XlsxUpdater,
6014
6855
  XlsxWriter,
6015
6856
  applyHeaderWidths,
6857
+ assertCellTextLength,
6858
+ assertColumnCount,
6859
+ assertRowCount,
6860
+ assertRowFitsColumns,
6861
+ columnIndexToLetter,
6862
+ columnLetterToIndex,
6016
6863
  convertXlsbToXlsx,
6017
6864
  convertXlsxToXlsb,
6018
6865
  defaultColWidth,
6866
+ escapeXmlText,
6019
6867
  getFormat,
6020
6868
  initColWidths,
6021
6869
  isFormattedCell,
6870
+ parseSharedStringsXml,
6871
+ parseTagAttributes,
6872
+ resolveRelTarget,
6022
6873
  sanitizeSheetName,
6874
+ trimTrailingEmptyRows,
6875
+ unescapeXml,
6023
6876
  unwrapCell,
6024
6877
  updateColWidthsFromRows
6025
6878
  };