@justybase/spreadsheet-tasks 2.1.1 → 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/cjs/index.js CHANGED
@@ -35,22 +35,38 @@ __export(index_exports, {
35
35
  ExcelReaderAbstract: () => ExcelReaderAbstract,
36
36
  F: () => F,
37
37
  INVALID_SHEET_NAME_CHARS: () => INVALID_SHEET_NAME_CHARS,
38
+ MAX_CELL_CHARS: () => MAX_CELL_CHARS,
39
+ MAX_COLS: () => MAX_COLS,
40
+ MAX_ROWS: () => MAX_ROWS,
38
41
  ReaderFactory: () => ReaderFactory,
39
42
  StreamingSheetState: () => StreamingSheetState,
40
43
  XlsbReader: () => XlsbReader,
41
44
  XlsbUpdater: () => XlsbUpdater,
42
45
  XlsbWriter: () => XlsbWriter,
46
+ XlsmUpdater: () => XlsxUpdater,
43
47
  XlsxReader: () => XlsxReader,
44
48
  XlsxUpdater: () => XlsxUpdater,
45
49
  XlsxWriter: () => XlsxWriter,
46
50
  applyHeaderWidths: () => applyHeaderWidths,
51
+ assertCellTextLength: () => assertCellTextLength,
52
+ assertColumnCount: () => assertColumnCount,
53
+ assertRowCount: () => assertRowCount,
54
+ assertRowFitsColumns: () => assertRowFitsColumns,
55
+ columnIndexToLetter: () => columnIndexToLetter,
56
+ columnLetterToIndex: () => columnLetterToIndex,
47
57
  convertXlsbToXlsx: () => convertXlsbToXlsx,
48
58
  convertXlsxToXlsb: () => convertXlsxToXlsb,
49
59
  defaultColWidth: () => defaultColWidth,
60
+ escapeXmlText: () => escapeXmlText,
50
61
  getFormat: () => getFormat,
51
62
  initColWidths: () => initColWidths,
52
63
  isFormattedCell: () => isFormattedCell,
64
+ parseSharedStringsXml: () => parseSharedStringsXml,
65
+ parseTagAttributes: () => parseTagAttributes,
66
+ resolveRelTarget: () => resolveRelTarget,
53
67
  sanitizeSheetName: () => sanitizeSheetName,
68
+ trimTrailingEmptyRows: () => trimTrailingEmptyRows,
69
+ unescapeXml: () => unescapeXml,
54
70
  unwrapCell: () => unwrapCell,
55
71
  updateColWidthsFromRows: () => updateColWidthsFromRows
56
72
  });
@@ -170,6 +186,143 @@ var BigBuffer = class {
170
186
  }
171
187
  };
172
188
 
189
+ // src/Formats.ts
190
+ var F = {
191
+ THOUSANDS_SEP: "#,##0",
192
+ CURRENCY_PLN: '#,##0.00 "z\u0142"',
193
+ CURRENCY_EUR: "#,##0.00 \u20AC",
194
+ PERCENTAGE: "0%",
195
+ SCIENTIFIC: "0.00E+00",
196
+ TWO_DECIMALS: "#,##0.00",
197
+ TEXT: "@",
198
+ LEADING_ZEROS: "000000000",
199
+ DATE_SHORT: "dd.mm.yyyy",
200
+ DATE_LONG: "d mmmm yyyy",
201
+ DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
202
+ DATE_ISO: "yyyy-mm-dd",
203
+ DATE_MONTH_YEAR: "mmmm yyyy",
204
+ DATE_WEEKDAY: "dddd, d mmmm yyyy",
205
+ DATE_DAY_MONTH: "d mmmm",
206
+ DATE_YEAR_ONLY: "yyyy",
207
+ DATETIME_SHORT: "dd.mm.yyyy hh:mm",
208
+ DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
209
+ TIME_HH_MM: "hh:mm",
210
+ TIME_HH_MM_SS: "hh:mm:ss",
211
+ TIME_12H: "h:mm AM/PM",
212
+ DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
213
+ DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
214
+ TIME_MS: "hh:mm:ss.000"
215
+ };
216
+ function isFormattedCell(val) {
217
+ return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
218
+ }
219
+ function unwrapCell(val) {
220
+ if (isFormattedCell(val)) {
221
+ return val.value;
222
+ }
223
+ return val;
224
+ }
225
+ function getFormat(val) {
226
+ if (isFormattedCell(val)) {
227
+ return val.format;
228
+ }
229
+ return null;
230
+ }
231
+
232
+ // src/writerHelpers.ts
233
+ var OA_EPOCH_1900 = Date.UTC(1899, 11, 30);
234
+ var MARCH_1_1900 = Date.UTC(1900, 2, 1);
235
+ function oaDateToTime(oaDate, epochMs) {
236
+ let serial = oaDate;
237
+ if (epochMs === OA_EPOCH_1900 && serial >= 1 && serial < 60) {
238
+ serial += 1;
239
+ }
240
+ return Math.round(serial * 864e5) + epochMs;
241
+ }
242
+ function timeToOaDate(timeMs, epochMs) {
243
+ let serial = (timeMs - epochMs) / 864e5;
244
+ if (epochMs === OA_EPOCH_1900 && timeMs < MARCH_1_1900) {
245
+ serial -= 1;
246
+ }
247
+ return serial;
248
+ }
249
+ var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
250
+ function sanitizeSheetName(name, sheetCount) {
251
+ if (!name || typeof name !== "string") {
252
+ return `Sheet${sheetCount + 1}`;
253
+ }
254
+ let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
255
+ if (sanitized.length > 31) {
256
+ sanitized = sanitized.substring(0, 31);
257
+ }
258
+ if (sanitized.trim().length === 0) {
259
+ sanitized = `Sheet${sheetCount + 1}`;
260
+ }
261
+ return sanitized;
262
+ }
263
+ var MAX_ROWS = 1048576;
264
+ var MAX_COLS = 16384;
265
+ var MAX_CELL_CHARS = 32767;
266
+ function assertCellTextLength(text) {
267
+ if (text.length > MAX_CELL_CHARS) {
268
+ throw new Error(
269
+ `Excel cell text exceeds ${MAX_CELL_CHARS} UTF-16 code units (got ${text.length}).`
270
+ );
271
+ }
272
+ }
273
+ function assertColumnCount(columnCount) {
274
+ if (!Number.isInteger(columnCount) || columnCount < 1 || columnCount > MAX_COLS) {
275
+ throw new Error(
276
+ `Excel column count must be 1..${MAX_COLS} (got ${columnCount}).`
277
+ );
278
+ }
279
+ }
280
+ function assertRowFitsColumns(kind, length, columnCount) {
281
+ if (length > columnCount) {
282
+ throw new Error(
283
+ `Excel ${kind} length ${length} exceeds columnCount ${columnCount}.`
284
+ );
285
+ }
286
+ }
287
+ function assertRowCount(totalRows) {
288
+ if (totalRows > MAX_ROWS) {
289
+ throw new Error(
290
+ `Excel row count exceeds ${MAX_ROWS} (got ${totalRows}).`
291
+ );
292
+ }
293
+ }
294
+ function initColWidths(columnCount) {
295
+ return new Array(columnCount).fill(-1);
296
+ }
297
+ function applyHeaderWidths(colWidths, headers, columnCount) {
298
+ for (let i = 0; i < columnCount; i++) {
299
+ const len = headers[i] ? headers[i].length + 1 : 0;
300
+ let width = 1.25 * len + 2;
301
+ if (width > 80) width = 80;
302
+ if (colWidths[i] < width) colWidths[i] = width;
303
+ }
304
+ }
305
+ function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
306
+ for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
307
+ const row = rows[r];
308
+ for (let c = 0; c < row.length; c++) {
309
+ const raw = row[c];
310
+ if (raw === null || raw === void 0) continue;
311
+ const val = isFormattedCell(raw) ? raw.value : raw;
312
+ const len = val instanceof Date ? 20 : String(val).length + 1;
313
+ let width = 1.25 * len + 2;
314
+ if (width > 80) width = 80;
315
+ if (colWidths[c] < width) colWidths[c] = width;
316
+ }
317
+ }
318
+ }
319
+ function defaultColWidth(width, floor = false) {
320
+ if (width > 0) {
321
+ return floor ? Math.floor(width) : width;
322
+ }
323
+ return 10;
324
+ }
325
+
173
326
  // src/ExcelReaderAbstract.ts
174
327
  var ExcelReaderAbstract = class {
175
328
  constructor() {
@@ -184,8 +337,7 @@ var ExcelReaderAbstract = class {
184
337
  dispose() {
185
338
  }
186
339
  getDateTimeFromOaDate(oaDate) {
187
- const ms = oaDate * 864e5 + this._oaEpoch;
188
- return new Date(ms);
340
+ return new Date(oaDateToTime(oaDate, this._oaEpoch));
189
341
  }
190
342
  };
191
343
 
@@ -445,168 +597,6 @@ var BiffReaderWriter = class {
445
597
 
446
598
  // src/XlsbReader.ts
447
599
  var import_adm_zip = __toESM(require("adm-zip"));
448
- var XlsbReader = class extends ExcelReaderAbstract {
449
- constructor() {
450
- super();
451
- this.zip = null;
452
- this.sharedStrings = [];
453
- this.sheetNames = [];
454
- this.sheets = [];
455
- this._currentSheetIndex = -1;
456
- this._reader = null;
457
- this._currentRow = [];
458
- this._pendingRowIndex = -1;
459
- this._eof = false;
460
- this.xfIdToNumFmtId = [];
461
- this.customDateFormats = null;
462
- }
463
- async open(path3, readSharedStrings = true) {
464
- this.zip = new import_adm_zip.default(path3);
465
- const wbEntry = this.zip.getEntry("xl/workbook.bin");
466
- if (wbEntry) {
467
- const buf = wbEntry.getData();
468
- const reader = new BiffReaderWriter(buf);
469
- while (reader.readWorkbook()) {
470
- if (reader._isSheet) {
471
- const name = reader._workbookName;
472
- const rId = reader._recId;
473
- this.sheetNames.push(name);
474
- this.sheets.push({ name, rId, path: null });
475
- }
476
- }
477
- }
478
- const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
479
- if (relsEntry) {
480
- const xml = relsEntry.getData().toString("utf8");
481
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
482
- let match;
483
- const rIdToTarget = {};
484
- while ((match = relRegex.exec(xml)) !== null) {
485
- rIdToTarget[match[1]] = match[2];
486
- }
487
- for (const sheet of this.sheets) {
488
- let target = rIdToTarget[sheet.rId];
489
- if (target) {
490
- if (target.startsWith("/")) target = target.substring(1);
491
- if (!target.startsWith("xl/")) target = "xl/" + target;
492
- sheet.path = target;
493
- }
494
- }
495
- }
496
- if (readSharedStrings) {
497
- const ssPath = "xl/sharedStrings.bin";
498
- if (this.zip.getEntry(ssPath)) {
499
- this._readSharedStrings(ssPath);
500
- }
501
- }
502
- const stylesPath = "xl/styles.bin";
503
- if (this.zip.getEntry(stylesPath)) {
504
- this._readStyles(stylesPath);
505
- }
506
- this.resultsCount = this.sheets.length;
507
- this._currentSheetIndex = -1;
508
- }
509
- _readSharedStrings(path3) {
510
- const entry = this.zip.getEntry(path3);
511
- if (!entry) return;
512
- const reader = new BiffReaderWriter(entry.getData());
513
- while (reader.readSharedStrings()) {
514
- if (reader._sharedStringValue !== null) {
515
- this.sharedStrings.push(reader._sharedStringValue);
516
- }
517
- }
518
- }
519
- _readStyles(path3) {
520
- const entry = this.zip.getEntry(path3);
521
- if (!entry) return;
522
- const reader = new BiffReaderWriter(entry.getData());
523
- while (reader.readStyles()) {
524
- }
525
- this.xfIdToNumFmtId = reader._xfIndexToNumFmtId;
526
- this.customDateFormats = reader._customNumFmts;
527
- }
528
- getSheetNames() {
529
- return this.sheetNames;
530
- }
531
- async read() {
532
- if (this._currentSheetIndex === -1) {
533
- this._currentSheetIndex = 0;
534
- if (!this._initSheet(0)) return false;
535
- }
536
- if (this._eof) return false;
537
- this._currentRow = [];
538
- while (true) {
539
- const hasRecord = this._reader.readWorksheet();
540
- if (!hasRecord) {
541
- this._eof = true;
542
- return true;
543
- }
544
- if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
545
- this._pendingRowIndex = this._reader._rowIndex;
546
- return true;
547
- }
548
- if (this._reader._readCell) {
549
- const col = this._reader._columnNum;
550
- let val = null;
551
- switch (this._reader._cellType) {
552
- case 2:
553
- val = this.sharedStrings[this._reader._intValue];
554
- break;
555
- case 3: {
556
- val = this._reader._doubleVal;
557
- const xfIndex = this._reader._xfIndex;
558
- let numFmtId = 0;
559
- if (this.xfIdToNumFmtId && xfIndex < this.xfIdToNumFmtId.length) {
560
- numFmtId = this.xfIdToNumFmtId[xfIndex];
561
- }
562
- const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
563
- if (isDate) {
564
- try {
565
- val = this.getDateTimeFromOaDate(val);
566
- } catch {
567
- }
568
- }
569
- break;
570
- }
571
- case 4:
572
- val = this._reader._boolValue;
573
- break;
574
- case 5:
575
- val = this._reader._stringValue;
576
- break;
577
- default:
578
- val = null;
579
- }
580
- this._currentRow[col] = val;
581
- if (col >= this.fieldCount) this.fieldCount = col + 1;
582
- }
583
- }
584
- }
585
- _initSheet(index) {
586
- if (index >= this.sheets.length) return false;
587
- const sheet = this.sheets[index];
588
- const entry = this.zip.getEntry(sheet.path);
589
- if (!entry) return false;
590
- this._reader = new BiffReaderWriter(entry.getData());
591
- this._eof = false;
592
- this._pendingRowIndex = -1;
593
- while (this._reader.readWorksheet()) {
594
- if (this._reader._rowIndex !== -1) {
595
- this._pendingRowIndex = this._reader._rowIndex;
596
- return true;
597
- }
598
- }
599
- this._eof = true;
600
- return false;
601
- }
602
- getValue(i) {
603
- if (i < 0 || i >= this._currentRow.length) return null;
604
- return this._currentRow[i];
605
- }
606
- };
607
-
608
- // src/XlsxReader.ts
609
- var import_yauzl = __toESM(require("yauzl"));
610
600
 
611
601
  // src/xmlUtils.ts
612
602
  function hasInvalidControlChars(str) {
@@ -640,8 +630,57 @@ function escapeXmlText(str) {
640
630
  }
641
631
  function unescapeXml(str) {
642
632
  if (!str || str.indexOf("&") === -1) return str;
643
- return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
644
- }
633
+ return str.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);/g, (entity) => {
634
+ switch (entity) {
635
+ case "&amp;":
636
+ return "&";
637
+ case "&lt;":
638
+ return "<";
639
+ case "&gt;":
640
+ return ">";
641
+ case "&quot;":
642
+ return '"';
643
+ case "&apos;":
644
+ return "'";
645
+ default: {
646
+ if (entity[1] === "#") {
647
+ const body = entity.slice(2, -1);
648
+ const code = body[0] === "x" || body[0] === "X" ? parseInt(body.slice(1), 16) : parseInt(body, 10);
649
+ if (Number.isFinite(code)) {
650
+ try {
651
+ return String.fromCodePoint(code);
652
+ } catch {
653
+ return entity;
654
+ }
655
+ }
656
+ }
657
+ return entity;
658
+ }
659
+ }
660
+ });
661
+ }
662
+ function parseTagAttributes(tag) {
663
+ const attrs = {};
664
+ const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
665
+ let m;
666
+ while ((m = re.exec(tag)) !== null) {
667
+ const name = m[1];
668
+ const value = m[3] !== void 0 ? m[3] : m[4];
669
+ attrs[name] = value;
670
+ const colon = name.indexOf(":");
671
+ if (colon !== -1) {
672
+ const local = name.slice(colon + 1);
673
+ if (!(local in attrs)) attrs[local] = value;
674
+ }
675
+ }
676
+ return attrs;
677
+ }
678
+ function resolveRelTarget(target) {
679
+ if (!target) return target;
680
+ if (target.startsWith("/")) return target.slice(1);
681
+ if (target.startsWith("xl/")) return target;
682
+ return "xl/" + target;
683
+ }
645
684
  function columnIndexToLetter(colIndex) {
646
685
  let n = colIndex + 1;
647
686
  let result = "";
@@ -669,31 +708,341 @@ function trimTrailingEmptyRows(rows) {
669
708
  }
670
709
  function parseSharedStringsXml(xml) {
671
710
  const result = [];
711
+ const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>|<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*\/>/gi;
712
+ let itemMatch;
713
+ while ((itemMatch = itemRegex.exec(xml)) !== null) {
714
+ const full = itemMatch[0];
715
+ if (full.endsWith("/>") && itemMatch[1] === void 0) {
716
+ result.push("");
717
+ continue;
718
+ }
719
+ let content = itemMatch[1] ?? "";
720
+ content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*>[\s\S]*?<\/(?:(?:[A-Za-z_][\w.-]*):)?rPh\s*>/gi, "");
721
+ content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*\/>/gi, "");
722
+ const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
723
+ let value = "";
724
+ let textMatch;
725
+ while ((textMatch = textRegex.exec(content)) !== null) {
726
+ value += textMatch[1];
727
+ }
728
+ result.push(unescapeXml(value));
729
+ }
730
+ return result;
731
+ }
732
+
733
+ // src/biff12Utils.ts
734
+ function readVlq(buf, pos) {
735
+ let value = 0;
736
+ let shift = 0;
737
+ let byte;
738
+ do {
739
+ byte = buf[pos++];
740
+ value |= (byte & 127) << shift;
741
+ shift += 7;
742
+ } while ((byte & 128) !== 0);
743
+ return { value, nextPos: pos };
744
+ }
745
+ function vlqBytes(value) {
746
+ const out = [];
747
+ while (value >= 128) {
748
+ out.push(value & 127 | 128);
749
+ value >>>= 7;
750
+ }
751
+ out.push(value & 127);
752
+ return out;
753
+ }
754
+ function readRecord(buf, pos, rec) {
755
+ const idRes = readVlq(buf, pos);
756
+ if (idRes.nextPos > buf.length) return false;
757
+ const lenRes = readVlq(buf, idRes.nextPos);
758
+ if (lenRes.nextPos + lenRes.value > buf.length) return false;
759
+ rec.headerStart = pos;
760
+ rec.dataStart = lenRes.nextPos;
761
+ rec.dataEnd = lenRes.nextPos + lenRes.value;
762
+ rec.id = idRes.value;
763
+ rec.len = lenRes.value;
764
+ return true;
765
+ }
766
+ function buildRecord(id, payload) {
767
+ const idBytes = vlqBytes(id);
768
+ const lenBytes = vlqBytes(payload.length);
769
+ const header = Buffer.alloc(idBytes.length + lenBytes.length);
770
+ idBytes.forEach((b, i) => header[i] = b);
771
+ lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
772
+ return Buffer.concat([header, payload]);
773
+ }
774
+ function readUtf16(buf, start, charCount) {
775
+ const end = start + charCount * 2;
776
+ if (end > buf.length) return "";
777
+ return buf.toString("utf16le", start, end);
778
+ }
779
+ function parseSharedStringsBin(buf) {
780
+ const values = [];
781
+ let total = 0;
782
+ let unique = 0;
783
+ let endSstOffset = buf.length;
784
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
672
785
  let pos = 0;
673
- while (true) {
674
- const siStart = xml.indexOf("<si>", pos);
675
- if (siStart === -1) break;
676
- const siEnd = xml.indexOf("</si>", siStart);
677
- if (siEnd === -1) break;
678
- const content = xml.substring(siStart, siEnd);
679
- let val = "";
680
- let tPos = 0;
681
- while (true) {
682
- const tStart = content.indexOf("<t", tPos);
683
- if (tStart === -1) break;
684
- const tagEnd = content.indexOf(">", tStart);
685
- const tEnd = content.indexOf("</t>", tagEnd);
686
- if (tEnd === -1) break;
687
- val += content.substring(tagEnd + 1, tEnd);
688
- tPos = tEnd + 4;
786
+ while (readRecord(buf, pos, rec)) {
787
+ pos = rec.dataEnd;
788
+ if (rec.id === 159) {
789
+ total = buf.readUInt32LE(rec.dataStart);
790
+ unique = buf.readUInt32LE(rec.dataStart + 4);
791
+ } else if (rec.id === 19) {
792
+ const cch = buf.readUInt32LE(rec.dataStart + 1);
793
+ values.push(readUtf16(buf, rec.dataStart + 5, cch));
794
+ } else if (rec.id === 160) {
795
+ endSstOffset = rec.headerStart;
689
796
  }
690
- result.push(unescapeXml(val));
691
- pos = siEnd + 5;
692
797
  }
693
- return result;
798
+ return { values, total, unique, endSstOffset };
694
799
  }
695
800
 
801
+ // src/XlsbReader.ts
802
+ var XlsbReader = class extends ExcelReaderAbstract {
803
+ constructor() {
804
+ super();
805
+ this.zip = null;
806
+ this.sharedStrings = [];
807
+ this.sheetNames = [];
808
+ this.sheets = [];
809
+ this._currentSheetIndex = -1;
810
+ this._reader = null;
811
+ this._currentRow = [];
812
+ this._pendingRowIndex = -1;
813
+ this._nextExpectedRow = 0;
814
+ this._eof = false;
815
+ this.xfIdToNumFmtId = [];
816
+ this.customDateFormats = null;
817
+ }
818
+ async open(path9, readSharedStrings = true) {
819
+ await this.close();
820
+ this.sheetNames = [];
821
+ this.sheets = [];
822
+ this.sharedStrings = [];
823
+ this.xfIdToNumFmtId = [];
824
+ this.customDateFormats = /* @__PURE__ */ new Set();
825
+ this.fieldCount = 0;
826
+ this.resultsCount = 0;
827
+ this.actualSheetName = "";
828
+ this._currentSheetIndex = -1;
829
+ this._currentRow = [];
830
+ this._pendingRowIndex = -1;
831
+ this._nextExpectedRow = 0;
832
+ this._eof = false;
833
+ this._reader = null;
834
+ this._oaEpoch = Date.UTC(1899, 11, 30);
835
+ this.zip = new import_adm_zip.default(path9);
836
+ const wbEntry = this.zip.getEntry("xl/workbook.bin");
837
+ if (wbEntry) {
838
+ const buf = wbEntry.getData();
839
+ const reader = new BiffReaderWriter(buf);
840
+ while (reader.readWorkbook()) {
841
+ if (reader._isSheet) {
842
+ const name = reader._workbookName;
843
+ const rId = reader._recId;
844
+ this.sheetNames.push(name);
845
+ this.sheets.push({ name, rId, path: null });
846
+ }
847
+ }
848
+ }
849
+ const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
850
+ if (relsEntry) {
851
+ const xml = relsEntry.getData().toString("utf8");
852
+ const relTagRe = /<Relationship\b[^>]*>/g;
853
+ let tagMatch;
854
+ const rIdToTarget = {};
855
+ while ((tagMatch = relTagRe.exec(xml)) !== null) {
856
+ const attrs = parseTagAttributes(tagMatch[0]);
857
+ const id = attrs["Id"] ?? attrs["id"];
858
+ const target = attrs["Target"] ?? attrs["target"];
859
+ if (id !== void 0 && target !== void 0) {
860
+ rIdToTarget[id] = target;
861
+ }
862
+ }
863
+ for (const sheet of this.sheets) {
864
+ const target = rIdToTarget[sheet.rId];
865
+ if (target) {
866
+ sheet.path = resolveRelTarget(target);
867
+ }
868
+ }
869
+ }
870
+ try {
871
+ const raw = wbEntry ? wbEntry.getData() : null;
872
+ if (raw) {
873
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
874
+ let pos = 0;
875
+ while (readRecord(raw, pos, rec)) {
876
+ if (rec.id === 153 && rec.len >= 4) {
877
+ const is1904 = (raw[rec.dataStart] & 1) !== 0;
878
+ this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
879
+ break;
880
+ }
881
+ pos = rec.dataEnd;
882
+ }
883
+ }
884
+ } catch {
885
+ }
886
+ if (readSharedStrings) {
887
+ const ssPath = "xl/sharedStrings.bin";
888
+ if (this.zip.getEntry(ssPath)) {
889
+ this._readSharedStrings(ssPath);
890
+ }
891
+ }
892
+ const stylesPath = "xl/styles.bin";
893
+ if (this.zip.getEntry(stylesPath)) {
894
+ this._readStyles(stylesPath);
895
+ }
896
+ this.resultsCount = this.sheets.length;
897
+ this._currentSheetIndex = -1;
898
+ }
899
+ _readSharedStrings(path9) {
900
+ const entry = this.zip.getEntry(path9);
901
+ if (!entry) return;
902
+ const reader = new BiffReaderWriter(entry.getData());
903
+ while (reader.readSharedStrings()) {
904
+ if (reader._sharedStringValue !== null) {
905
+ this.sharedStrings.push(reader._sharedStringValue);
906
+ }
907
+ }
908
+ }
909
+ _readStyles(path9) {
910
+ const entry = this.zip.getEntry(path9);
911
+ if (!entry) return;
912
+ const reader = new BiffReaderWriter(entry.getData());
913
+ while (reader.readStyles()) {
914
+ }
915
+ this.xfIdToNumFmtId = reader._xfIndexToNumFmtId;
916
+ this.customDateFormats = reader._customNumFmts;
917
+ }
918
+ getSheetNames() {
919
+ return this.sheetNames;
920
+ }
921
+ async selectSheet(nameOrIndex) {
922
+ let index = -1;
923
+ if (typeof nameOrIndex === "number") {
924
+ index = nameOrIndex;
925
+ } else {
926
+ index = this.sheetNames.indexOf(nameOrIndex);
927
+ if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
928
+ }
929
+ if (index < 0 || index >= this.sheets.length) {
930
+ throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
931
+ }
932
+ this._currentSheetIndex = index;
933
+ if (!this._initSheet(index)) {
934
+ throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[index]}'.`);
935
+ }
936
+ }
937
+ async nextResult() {
938
+ const next = this._currentSheetIndex + 1;
939
+ if (next >= this.sheets.length) return false;
940
+ this._currentSheetIndex = next;
941
+ if (!this._initSheet(next)) {
942
+ throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[next]}'.`);
943
+ }
944
+ return true;
945
+ }
946
+ async read() {
947
+ if (this._currentSheetIndex === -1) {
948
+ this._currentSheetIndex = 0;
949
+ if (!this._initSheet(0)) return false;
950
+ }
951
+ if (this._eof) return false;
952
+ if (this._pendingRowIndex > this._nextExpectedRow && this._pendingRowIndex < MAX_ROWS) {
953
+ this._currentRow = [];
954
+ this._nextExpectedRow++;
955
+ return true;
956
+ }
957
+ this._currentRow = [];
958
+ while (true) {
959
+ const hasRecord = this._reader.readWorksheet();
960
+ if (!hasRecord) {
961
+ this._eof = true;
962
+ this._nextExpectedRow = this._pendingRowIndex + 1;
963
+ return true;
964
+ }
965
+ if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
966
+ const completed = this._pendingRowIndex;
967
+ this._pendingRowIndex = this._reader._rowIndex;
968
+ this._nextExpectedRow = completed + 1;
969
+ return true;
970
+ }
971
+ if (this._reader._readCell) {
972
+ const col = this._reader._columnNum;
973
+ let val = null;
974
+ switch (this._reader._cellType) {
975
+ case 2:
976
+ val = this.sharedStrings[this._reader._intValue];
977
+ break;
978
+ case 3: {
979
+ val = this._reader._doubleVal;
980
+ const xfIndex = this._reader._xfIndex;
981
+ let numFmtId = 0;
982
+ if (this.xfIdToNumFmtId && xfIndex < this.xfIdToNumFmtId.length) {
983
+ numFmtId = this.xfIdToNumFmtId[xfIndex];
984
+ }
985
+ const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
986
+ if (isDate) {
987
+ try {
988
+ val = this.getDateTimeFromOaDate(val);
989
+ } catch {
990
+ }
991
+ }
992
+ break;
993
+ }
994
+ case 4:
995
+ val = this._reader._boolValue;
996
+ break;
997
+ case 5:
998
+ val = this._reader._stringValue;
999
+ break;
1000
+ default:
1001
+ val = null;
1002
+ }
1003
+ this._currentRow[col] = val;
1004
+ if (col >= this.fieldCount) this.fieldCount = col + 1;
1005
+ }
1006
+ }
1007
+ }
1008
+ _initSheet(index) {
1009
+ if (index < 0 || index >= this.sheets.length) return false;
1010
+ const sheet = this.sheets[index];
1011
+ const entry = sheet.path ? this.zip.getEntry(sheet.path) : void 0;
1012
+ if (!entry) return false;
1013
+ this._reader = new BiffReaderWriter(entry.getData());
1014
+ this._eof = false;
1015
+ this._pendingRowIndex = -1;
1016
+ this._nextExpectedRow = 0;
1017
+ this._currentRow = [];
1018
+ this.fieldCount = 0;
1019
+ this.actualSheetName = sheet.name;
1020
+ while (this._reader.readWorksheet()) {
1021
+ if (this._reader._rowIndex !== -1) {
1022
+ this._pendingRowIndex = this._reader._rowIndex;
1023
+ return true;
1024
+ }
1025
+ }
1026
+ this._eof = true;
1027
+ return true;
1028
+ }
1029
+ getValue(i) {
1030
+ if (i < 0 || i >= this._currentRow.length) return null;
1031
+ return this._currentRow[i];
1032
+ }
1033
+ };
1034
+
696
1035
  // src/XlsxReader.ts
1036
+ var import_yauzl = __toESM(require("yauzl"));
1037
+ function parseIsoDateUtc(value) {
1038
+ let text = value.trim().replace(" ", "T");
1039
+ if (!text) return null;
1040
+ if (!/z$|[+-]\d{2}:?\d{2}$/i.test(text) && text.includes("T")) {
1041
+ text += "Z";
1042
+ }
1043
+ const date = new Date(text);
1044
+ return Number.isNaN(date.getTime()) ? null : date;
1045
+ }
697
1046
  var XlsxReader = class extends ExcelReaderAbstract {
698
1047
  constructor() {
699
1048
  super();
@@ -705,13 +1054,34 @@ var XlsxReader = class extends ExcelReaderAbstract {
705
1054
  this._currentSheetIndex = -1;
706
1055
  this._sheetXml = null;
707
1056
  this._xmlPos = 0;
1057
+ /** End of the sheetData element; row tags must not be searched past it. */
1058
+ this._xmlEnd = 0;
708
1059
  this._currentRow = [];
1060
+ this._expectedRowNum = 1;
1061
+ this._pendingEmptyCount = 0;
709
1062
  this.xfIdToNumFmtId = [];
710
1063
  this.customDateFormats = /* @__PURE__ */ new Set();
711
1064
  }
712
- async open(path3, readSharedStrings = true) {
713
- return new Promise((resolve2, reject) => {
714
- import_yauzl.default.open(path3, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
1065
+ async open(path9, readSharedStrings = true) {
1066
+ await this.close();
1067
+ this.sheetNames = [];
1068
+ this.sheets = [];
1069
+ this.sharedStrings = [];
1070
+ this.xfIdToNumFmtId = [];
1071
+ this.customDateFormats = /* @__PURE__ */ new Set();
1072
+ this.fieldCount = 0;
1073
+ this.resultsCount = 0;
1074
+ this.actualSheetName = "";
1075
+ this._currentSheetIndex = -1;
1076
+ this._sheetXml = null;
1077
+ this._xmlPos = 0;
1078
+ this._xmlEnd = 0;
1079
+ this._currentRow = [];
1080
+ this._expectedRowNum = 1;
1081
+ this._pendingEmptyCount = 0;
1082
+ this._oaEpoch = Date.UTC(1899, 11, 30);
1083
+ return new Promise((resolve6, reject) => {
1084
+ import_yauzl.default.open(path9, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
715
1085
  if (err) return reject(err);
716
1086
  this.zipfile = zipfile;
717
1087
  this.entries = /* @__PURE__ */ new Map();
@@ -724,28 +1094,40 @@ var XlsxReader = class extends ExcelReaderAbstract {
724
1094
  const wbRelsContent = await this._readZipEntryContent("xl/_rels/workbook.xml.rels");
725
1095
  const rIdToTarget = {};
726
1096
  if (wbRelsContent) {
727
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
728
- let match;
729
- while ((match = relRegex.exec(wbRelsContent)) !== null) {
730
- rIdToTarget[match[1]] = match[2];
1097
+ const relTagRe = /<Relationship\b[^>]*>/g;
1098
+ let tagMatch;
1099
+ while ((tagMatch = relTagRe.exec(wbRelsContent)) !== null) {
1100
+ const attrs = parseTagAttributes(tagMatch[0]);
1101
+ const id = attrs["Id"] ?? attrs["id"];
1102
+ const target = attrs["Target"] ?? attrs["target"];
1103
+ if (id !== void 0 && target !== void 0) {
1104
+ rIdToTarget[id] = target;
1105
+ }
731
1106
  }
732
1107
  }
733
1108
  const wbContent = await this._readZipEntryContent("xl/workbook.xml");
734
1109
  if (wbContent) {
735
- const sheetRegex = /<sheet[^>]*name="([^"]*)"[^>]*sheetId="([^"]*)"[^>]*r:id="([^"]*)"/g;
736
- let match;
737
- while ((match = sheetRegex.exec(wbContent)) !== null) {
738
- const name = unescapeXml(match[1]);
739
- const sheetId = match[2];
740
- const rId = match[3];
1110
+ const sheetTagRe = /<sheet\b[^>]*>/g;
1111
+ let tagMatch;
1112
+ while ((tagMatch = sheetTagRe.exec(wbContent)) !== null) {
1113
+ const attrs = parseTagAttributes(tagMatch[0]);
1114
+ const rawName = attrs["name"];
1115
+ const sheetId = attrs["sheetId"] ?? "";
1116
+ const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
1117
+ if (rawName === void 0 || rId === void 0) continue;
1118
+ const name = unescapeXml(rawName);
741
1119
  const target = rIdToTarget[rId];
742
- let fullPath = target;
743
- if (!fullPath.startsWith("xl/")) {
744
- fullPath = "xl/" + fullPath;
745
- }
1120
+ if (!target) continue;
1121
+ const fullPath = resolveRelTarget(target);
746
1122
  this.sheetNames.push(name);
747
1123
  this.sheets.push({ name, sheetId, rId, path: fullPath });
748
1124
  }
1125
+ const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbContent);
1126
+ if (wbPrMatch) {
1127
+ const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
1128
+ const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
1129
+ this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
1130
+ }
749
1131
  }
750
1132
  if (readSharedStrings) {
751
1133
  const ssContent = await this._readZipEntryContent("xl/sharedStrings.xml");
@@ -759,7 +1141,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
759
1141
  }
760
1142
  this.resultsCount = this.sheets.length;
761
1143
  this._currentSheetIndex = -1;
762
- resolve2();
1144
+ resolve6();
763
1145
  } catch (e) {
764
1146
  reject(e);
765
1147
  }
@@ -773,16 +1155,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
773
1155
  this.zipfile.close();
774
1156
  }
775
1157
  }
776
- async _readZipEntryContent(path3) {
777
- const entry = this.entries.get(path3);
1158
+ async _readZipEntryContent(path9) {
1159
+ const entry = this.entries.get(path9);
778
1160
  if (!entry) return null;
779
- return new Promise((resolve2, reject) => {
1161
+ return new Promise((resolve6, reject) => {
780
1162
  this.zipfile.openReadStream(entry, (err, readStream) => {
781
1163
  if (err) return reject(err);
782
1164
  const chunks = [];
783
1165
  readStream.on("data", (chunk) => chunks.push(chunk));
784
1166
  readStream.on("end", () => {
785
- resolve2(Buffer.concat(chunks).toString("utf8"));
1167
+ resolve6(Buffer.concat(chunks).toString("utf8"));
786
1168
  });
787
1169
  readStream.on("error", reject);
788
1170
  });
@@ -830,6 +1212,27 @@ var XlsxReader = class extends ExcelReaderAbstract {
830
1212
  return false;
831
1213
  }
832
1214
  }
1215
+ async selectSheet(nameOrIndex) {
1216
+ let index = -1;
1217
+ if (typeof nameOrIndex === "number") {
1218
+ index = nameOrIndex;
1219
+ } else {
1220
+ index = this.sheetNames.indexOf(nameOrIndex);
1221
+ if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
1222
+ }
1223
+ if (index < 0 || index >= this.sheets.length) {
1224
+ throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
1225
+ }
1226
+ this._currentSheetIndex = index;
1227
+ await this._initSheet(index);
1228
+ }
1229
+ async nextResult() {
1230
+ const next = this._currentSheetIndex + 1;
1231
+ if (next >= this.sheets.length) return false;
1232
+ this._currentSheetIndex = next;
1233
+ await this._initSheet(next);
1234
+ return true;
1235
+ }
833
1236
  async _initSheet(index) {
834
1237
  if (index >= this.sheets.length) return;
835
1238
  const sheet = this.sheets[index];
@@ -839,23 +1242,106 @@ var XlsxReader = class extends ExcelReaderAbstract {
839
1242
  this._sheetXml = "";
840
1243
  }
841
1244
  this._xmlPos = 0;
1245
+ this._xmlEnd = this._sheetXml.length;
1246
+ this._expectedRowNum = 1;
1247
+ this._pendingEmptyCount = 0;
1248
+ this._currentRow = [];
1249
+ this.fieldCount = 0;
842
1250
  const sd = this._sheetXml.indexOf("<sheetData>");
843
- if (sd !== -1) this._xmlPos = sd + 11;
1251
+ if (sd !== -1) {
1252
+ this._xmlPos = sd + 11;
1253
+ const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
1254
+ if (se !== -1) this._xmlEnd = se;
1255
+ } else {
1256
+ const m = /<(?:[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(this._sheetXml);
1257
+ if (m && m.index !== void 0) {
1258
+ this._xmlPos = m.index + m[0].length;
1259
+ if (/\/>\s*$/.test(m[0])) {
1260
+ this._xmlEnd = this._xmlPos;
1261
+ } else {
1262
+ const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
1263
+ if (se !== -1) this._xmlEnd = se;
1264
+ }
1265
+ }
1266
+ }
844
1267
  }
845
1268
  _readNextRow() {
846
1269
  if (!this._sheetXml) return false;
847
- const rowStart = this._sheetXml.indexOf("<row", this._xmlPos);
1270
+ if (this._pendingEmptyCount > 0) {
1271
+ this._pendingEmptyCount--;
1272
+ this._currentRow = [];
1273
+ this._expectedRowNum++;
1274
+ return true;
1275
+ }
1276
+ const rowStart = this._findRowTagStart(this._xmlPos);
848
1277
  if (rowStart === -1) return false;
1278
+ const rowTagEnd = this._sheetXml.indexOf(">", rowStart);
1279
+ if (rowTagEnd === -1) return false;
1280
+ const rowTag = this._sheetXml.substring(rowStart, rowTagEnd + 1);
1281
+ const isSelfClosing = this._sheetXml.charCodeAt(rowTagEnd - 1) === 47;
1282
+ const rowAttrs = parseTagAttributes(rowTag);
1283
+ const rRaw = rowAttrs["r"];
1284
+ let rowNum = this._expectedRowNum;
1285
+ if (rRaw !== void 0) {
1286
+ const parsed = parseInt(rRaw, 10);
1287
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= MAX_ROWS) rowNum = parsed;
1288
+ }
1289
+ if (rowNum > this._expectedRowNum) {
1290
+ const gap = rowNum - this._expectedRowNum;
1291
+ this._pendingEmptyCount = gap - 1;
1292
+ this._currentRow = [];
1293
+ this._expectedRowNum++;
1294
+ return true;
1295
+ }
1296
+ if (rowNum < this._expectedRowNum) {
1297
+ if (isSelfClosing) {
1298
+ this._xmlPos = rowTagEnd + 1;
1299
+ this._currentRow = [];
1300
+ return true;
1301
+ }
1302
+ const rowEnd2 = this._sheetXml.indexOf("</row>", rowStart);
1303
+ if (rowEnd2 === -1) return false;
1304
+ this._xmlPos = rowEnd2 + 6;
1305
+ this._parseRowByIndex(rowStart, rowEnd2);
1306
+ this._expectedRowNum = Math.max(this._expectedRowNum + 1, rowNum + 1);
1307
+ return true;
1308
+ }
1309
+ if (isSelfClosing) {
1310
+ this._xmlPos = rowTagEnd + 1;
1311
+ this._currentRow = [];
1312
+ this._expectedRowNum = rowNum + 1;
1313
+ return true;
1314
+ }
849
1315
  const rowEnd = this._sheetXml.indexOf("</row>", rowStart);
850
1316
  if (rowEnd === -1) return false;
851
1317
  this._xmlPos = rowEnd + 6;
852
1318
  this._parseRowByIndex(rowStart, rowEnd);
1319
+ this._expectedRowNum = rowNum + 1;
853
1320
  return true;
854
1321
  }
1322
+ /**
1323
+ * Find the next `<row …>` start, ignoring similarly named elements such as
1324
+ * `<rowBreaks>` that follow `</sheetData>`.
1325
+ */
1326
+ _findRowTagStart(from) {
1327
+ const xml = this._sheetXml;
1328
+ let pos = from;
1329
+ while (pos < this._xmlEnd) {
1330
+ const idx = xml.indexOf("<row", pos);
1331
+ if (idx === -1 || idx >= this._xmlEnd) return -1;
1332
+ const next = xml.charCodeAt(idx + 4);
1333
+ if (next === 62 || next === 47 || next === 32 || next === 9 || next === 10 || next === 13) {
1334
+ return idx;
1335
+ }
1336
+ pos = idx + 4;
1337
+ }
1338
+ return -1;
1339
+ }
855
1340
  _parseRowByIndex(rowStart, rowEnd) {
856
1341
  this._currentRow = [];
857
1342
  const xml = this._sheetXml;
858
1343
  let pos = xml.indexOf(">", rowStart) + 1;
1344
+ let nextCol = 0;
859
1345
  while (pos < rowEnd) {
860
1346
  const cStart = xml.indexOf("<c", pos);
861
1347
  if (cStart === -1 || cStart >= rowEnd) break;
@@ -889,6 +1375,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
889
1375
  }
890
1376
  }
891
1377
  let val = null;
1378
+ let inlineVals = null;
892
1379
  if (xml.charCodeAt(nextTagClose - 1) === 47) {
893
1380
  pos = nextTagClose + 1;
894
1381
  } else {
@@ -903,20 +1390,28 @@ var XlsxReader = class extends ExcelReaderAbstract {
903
1390
  }
904
1391
  } else {
905
1392
  if (type === "inlineStr") {
906
- const tStart = xml.indexOf("<t", nextTagClose);
907
- if (tStart !== -1 && tStart < cellContentEnd) {
908
- const tContentStart = xml.indexOf(">", tStart) + 1;
1393
+ inlineVals = [];
1394
+ let tSearch = nextTagClose;
1395
+ while (true) {
1396
+ const tStart = xml.indexOf("<t", tSearch);
1397
+ if (tStart === -1 || tStart >= cellContentEnd) break;
1398
+ const tClose = xml.indexOf(">", tStart);
1399
+ if (tClose === -1 || tClose >= cellContentEnd) break;
1400
+ const tContentStart = tClose + 1;
909
1401
  const tEnd = xml.indexOf("</t>", tContentStart);
910
- if (tEnd !== -1 && tEnd < cellContentEnd) {
911
- val = xml.substring(tContentStart, tEnd);
912
- }
1402
+ if (tEnd === -1 || tEnd > cellContentEnd) break;
1403
+ inlineVals.push(xml.substring(tContentStart, tEnd));
1404
+ tSearch = tEnd + 4;
913
1405
  }
1406
+ if (inlineVals.length === 0) inlineVals = null;
914
1407
  }
915
1408
  }
916
1409
  pos = cellContentEnd + 4;
917
1410
  }
918
1411
  let finalVal = null;
919
- if (val !== null) {
1412
+ if (inlineVals !== null) {
1413
+ finalVal = unescapeXml(inlineVals.join(""));
1414
+ } else if (val !== null) {
920
1415
  if (type === "s") {
921
1416
  const idx = parseInt(val, 10);
922
1417
  finalVal = this.sharedStrings[idx];
@@ -924,148 +1419,66 @@ var XlsxReader = class extends ExcelReaderAbstract {
924
1419
  finalVal = val === "1" || val === "true";
925
1420
  } else if (type === "inlineStr") {
926
1421
  finalVal = unescapeXml(val);
1422
+ } else if (type === "str") {
1423
+ finalVal = unescapeXml(val);
1424
+ } else if (type === "e") {
1425
+ finalVal = unescapeXml(val);
1426
+ } else if (type === "d") {
1427
+ finalVal = parseIsoDateUtc(val) ?? unescapeXml(val);
927
1428
  } else if (type === "n" || type === "") {
928
1429
  finalVal = parseFloat(val);
929
1430
  if (styleIndex > 0 && this.xfIdToNumFmtId) {
930
1431
  let numFmtId = 0;
931
- if (styleIndex < this.xfIdToNumFmtId.length) {
932
- numFmtId = this.xfIdToNumFmtId[styleIndex];
933
- }
934
- const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
935
- if (isDate) {
936
- try {
937
- finalVal = this.getDateTimeFromOaDate(finalVal);
938
- } catch {
939
- }
940
- }
941
- }
942
- }
943
- }
944
- if (colIndex !== -1) {
945
- this._currentRow[colIndex] = finalVal;
946
- if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
947
- }
948
- }
949
- }
950
- getValue(i) {
951
- if (i < 0 || i >= this._currentRow.length) return null;
952
- return this._currentRow[i];
953
- }
954
- };
955
-
956
- // src/ReaderFactory.ts
957
- var path = __toESM(require("path"));
958
- var ReaderFactory = class {
959
- /**
960
- * @param filePath Path ending in `.xlsb` or `.xlsx`
961
- * @throws if the extension is unsupported
962
- */
963
- static create(filePath) {
964
- const ext = path.extname(filePath).toLowerCase();
965
- if (ext === ".xlsx") {
966
- return new XlsxReader();
967
- } else if (ext === ".xlsb") {
968
- return new XlsbReader();
969
- } else {
970
- throw new Error(`Unsupported extension: ${ext}`);
971
- }
972
- }
973
- };
974
-
975
- // src/XlsbWriter.ts
976
- var fs = __toESM(require("fs"));
977
- var import_archiver = __toESM(require("archiver"));
978
- var import_stream = require("stream");
979
-
980
- // src/Formats.ts
981
- var F = {
982
- THOUSANDS_SEP: "#,##0",
983
- CURRENCY_PLN: '#,##0.00 "z\u0142"',
984
- CURRENCY_EUR: "#,##0.00 \u20AC",
985
- PERCENTAGE: "0%",
986
- SCIENTIFIC: "0.00E+00",
987
- TWO_DECIMALS: "#,##0.00",
988
- TEXT: "@",
989
- LEADING_ZEROS: "000000000",
990
- DATE_SHORT: "dd.mm.yyyy",
991
- DATE_LONG: "d mmmm yyyy",
992
- DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
993
- DATE_ISO: "yyyy-mm-dd",
994
- DATE_MONTH_YEAR: "mmmm yyyy",
995
- DATE_WEEKDAY: "dddd, d mmmm yyyy",
996
- DATE_DAY_MONTH: "d mmmm",
997
- DATE_YEAR_ONLY: "yyyy",
998
- DATETIME_SHORT: "dd.mm.yyyy hh:mm",
999
- DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
1000
- TIME_HH_MM: "hh:mm",
1001
- TIME_HH_MM_SS: "hh:mm:ss",
1002
- TIME_12H: "h:mm AM/PM",
1003
- DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
1004
- DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
1005
- TIME_MS: "hh:mm:ss.000"
1006
- };
1007
- function isFormattedCell(val) {
1008
- return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
1009
- }
1010
- function unwrapCell(val) {
1011
- if (isFormattedCell(val)) {
1012
- return val.value;
1013
- }
1014
- return val;
1015
- }
1016
- function getFormat(val) {
1017
- if (isFormattedCell(val)) {
1018
- return val.format;
1019
- }
1020
- return null;
1021
- }
1022
-
1023
- // src/writerHelpers.ts
1024
- var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
1025
- function sanitizeSheetName(name, sheetCount) {
1026
- if (!name || typeof name !== "string") {
1027
- return `Sheet${sheetCount + 1}`;
1028
- }
1029
- let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
1030
- if (sanitized.length > 31) {
1031
- sanitized = sanitized.substring(0, 31);
1032
- }
1033
- if (sanitized.trim().length === 0) {
1034
- sanitized = `Sheet${sheetCount + 1}`;
1035
- }
1036
- return sanitized;
1037
- }
1038
- function initColWidths(columnCount) {
1039
- return new Array(columnCount).fill(-1);
1040
- }
1041
- function applyHeaderWidths(colWidths, headers, columnCount) {
1042
- for (let i = 0; i < columnCount; i++) {
1043
- const len = headers[i] ? headers[i].length + 1 : 0;
1044
- let width = 1.25 * len + 2;
1045
- if (width > 80) width = 80;
1046
- if (colWidths[i] < width) colWidths[i] = width;
1047
- }
1048
- }
1049
- function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
1050
- for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
1051
- const row = rows[r];
1052
- for (let c = 0; c < row.length; c++) {
1053
- const raw = row[c];
1054
- if (raw === null || raw === void 0) continue;
1055
- const val = isFormattedCell(raw) ? raw.value : raw;
1056
- const len = val instanceof Date ? 20 : String(val).length + 1;
1057
- let width = 1.25 * len + 2;
1058
- if (width > 80) width = 80;
1059
- if (colWidths[c] < width) colWidths[c] = width;
1432
+ if (styleIndex < this.xfIdToNumFmtId.length) {
1433
+ numFmtId = this.xfIdToNumFmtId[styleIndex];
1434
+ }
1435
+ const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
1436
+ if (isDate) {
1437
+ try {
1438
+ finalVal = this.getDateTimeFromOaDate(finalVal);
1439
+ } catch {
1440
+ }
1441
+ }
1442
+ }
1443
+ }
1444
+ }
1445
+ if (colIndex === -1) colIndex = nextCol;
1446
+ nextCol = colIndex + 1;
1447
+ this._currentRow[colIndex] = finalVal;
1448
+ if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
1060
1449
  }
1061
1450
  }
1062
- }
1063
- function defaultColWidth(width, floor = false) {
1064
- if (width > 0) {
1065
- return floor ? Math.floor(width) : width;
1451
+ getValue(i) {
1452
+ if (i < 0 || i >= this._currentRow.length) return null;
1453
+ return this._currentRow[i];
1066
1454
  }
1067
- return 10;
1068
- }
1455
+ };
1456
+
1457
+ // src/ReaderFactory.ts
1458
+ var path = __toESM(require("path"));
1459
+ var ReaderFactory = class {
1460
+ /**
1461
+ * @param filePath Path ending in `.xlsb` or `.xlsx`
1462
+ * @throws if the extension is unsupported
1463
+ */
1464
+ static create(filePath) {
1465
+ const ext = path.extname(filePath).toLowerCase();
1466
+ if (ext === ".xlsx") {
1467
+ return new XlsxReader();
1468
+ } else if (ext === ".xlsb") {
1469
+ return new XlsbReader();
1470
+ } else {
1471
+ throw new Error(`Unsupported extension: ${ext}`);
1472
+ }
1473
+ }
1474
+ };
1475
+
1476
+ // src/XlsbWriter.ts
1477
+ var fs = __toESM(require("fs"));
1478
+ var path2 = __toESM(require("path"));
1479
+ var import_crypto = require("crypto");
1480
+ var import_archiver = __toESM(require("archiver"));
1481
+ var import_stream = require("stream");
1069
1482
 
1070
1483
  // src/StreamingSheetState.ts
1071
1484
  var StreamingSheetState = class {
@@ -1119,6 +1532,8 @@ var XlsbWriter = class _XlsbWriter {
1119
1532
  * @param filePath Destination `.xlsb` path on disk.
1120
1533
  */
1121
1534
  constructor(filePath) {
1535
+ this._outputError = null;
1536
+ this._finalized = false;
1122
1537
  this.sheetCount = 0;
1123
1538
  this.sheetList = [];
1124
1539
  this.sstDic = /* @__PURE__ */ new Map();
@@ -1129,8 +1544,20 @@ var XlsbWriter = class _XlsbWriter {
1129
1544
  this._nextXfIdx = 4;
1130
1545
  this._stFmtRecords = /* @__PURE__ */ new Map();
1131
1546
  this.stream = new StreamingSheetState();
1132
- this.output = fs.createWriteStream(filePath);
1547
+ this._targetPath = path2.resolve(filePath);
1548
+ fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
1549
+ this._tempPath = path2.join(
1550
+ path2.dirname(this._targetPath),
1551
+ `.${path2.basename(this._targetPath)}.${(0, import_crypto.randomUUID)()}.tmp`
1552
+ );
1553
+ this.output = fs.createWriteStream(this._tempPath);
1554
+ this.output.on("error", (err) => {
1555
+ this._outputError = err;
1556
+ });
1133
1557
  this.archive = (0, import_archiver.default)("zip");
1558
+ this.archive.on("error", (err) => {
1559
+ this._outputError = err;
1560
+ });
1134
1561
  this.archive.pipe(this.output);
1135
1562
  this._oaEpoch = Date.UTC(1899, 11, 30);
1136
1563
  this._sheet1Bytes = Buffer.from([
@@ -3000,6 +3427,11 @@ var XlsbWriter = class _XlsbWriter {
3000
3427
  * @param options Optional settings (hidden, doAutofilter)
3001
3428
  */
3002
3429
  startSheet(sheetName, columnCount, headers, options = {}) {
3430
+ assertColumnCount(columnCount);
3431
+ if (headers !== void 0) {
3432
+ assertRowFitsColumns("header", headers.length, columnCount);
3433
+ for (const h of headers) assertCellTextLength(h);
3434
+ }
3003
3435
  const { hidden = false, doAutofilter = true, sampleRows } = options;
3004
3436
  this.addSheet(sheetName, hidden);
3005
3437
  this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
@@ -3065,6 +3497,7 @@ var XlsbWriter = class _XlsbWriter {
3065
3497
  `Row length mismatch. Expected ${this.stream.endCol - this.stream.startCol} columns, got ${row.length}`
3066
3498
  );
3067
3499
  }
3500
+ assertRowCount(this.stream.rowNum + 1);
3068
3501
  this.createRowHeader(bigBuf, this.stream.rowNum, this.stream.startCol, this.stream.endCol);
3069
3502
  for (let c = 0; c < row.length; c++) {
3070
3503
  const raw = row[c];
@@ -3087,7 +3520,7 @@ var XlsbWriter = class _XlsbWriter {
3087
3520
  } else if (typeof val === "boolean") {
3088
3521
  this.writeBool(bigBuf, val, c);
3089
3522
  } else if (val instanceof Date) {
3090
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
3523
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
3091
3524
  this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
3092
3525
  } else if (val !== null && val !== void 0) {
3093
3526
  this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
@@ -3144,6 +3577,16 @@ var XlsbWriter = class _XlsbWriter {
3144
3577
  } else if (headers) {
3145
3578
  columnCount = headers.length;
3146
3579
  }
3580
+ if (columnCount > 0) assertColumnCount(columnCount);
3581
+ const totalRowsCheck = rows.length + (headers ? 1 : 0);
3582
+ assertRowCount(totalRowsCheck);
3583
+ if (headers) {
3584
+ assertRowFitsColumns("header", headers.length, columnCount);
3585
+ for (const h of headers) assertCellTextLength(h);
3586
+ }
3587
+ for (let r = 0; r < rows.length; r++) {
3588
+ assertRowFitsColumns("row", rows[r].length, columnCount);
3589
+ }
3147
3590
  const colWidths = initColWidths(columnCount);
3148
3591
  if (headers) {
3149
3592
  applyHeaderWidths(colWidths, headers, columnCount);
@@ -3219,7 +3662,7 @@ var XlsbWriter = class _XlsbWriter {
3219
3662
  } else if (typeof val === "boolean") {
3220
3663
  this.writeBool(bigBuf, val, c);
3221
3664
  } else if (val instanceof Date) {
3222
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
3665
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
3223
3666
  this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
3224
3667
  } else if (val !== null && val !== void 0) {
3225
3668
  this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
@@ -3302,13 +3745,14 @@ var XlsbWriter = class _XlsbWriter {
3302
3745
  bigBuf.writeUnsafeByte(val ? 1 : 0);
3303
3746
  }
3304
3747
  writeDateTime(bigBuf, date, colNum) {
3305
- const oaDate = (date.getTime() - this._oaEpoch) / 864e5;
3748
+ const oaDate = timeToOaDate(date.getTime(), this._oaEpoch);
3306
3749
  this.writeDouble(bigBuf, oaDate, colNum, 1);
3307
3750
  }
3308
3751
  _getDateWidth(_date) {
3309
3752
  return 19;
3310
3753
  }
3311
3754
  writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
3755
+ assertCellTextLength(val);
3312
3756
  let index;
3313
3757
  if (this.sstDic.has(val)) {
3314
3758
  index = this.sstDic.get(val);
@@ -3334,8 +3778,8 @@ var XlsbWriter = class _XlsbWriter {
3334
3778
  bigBuf.writeByte(159);
3335
3779
  bigBuf.writeByte(1);
3336
3780
  bigBuf.writeByte(8);
3337
- bigBuf.writeInt32LE(this.sstCntUnique);
3338
3781
  bigBuf.writeInt32LE(this.sstCntAll);
3782
+ bigBuf.writeInt32LE(this.sstCntUnique);
3339
3783
  for (const [txt] of this.sstDic) {
3340
3784
  const txtLen = txt.length;
3341
3785
  bigBuf.writeByte(19);
@@ -3388,8 +3832,61 @@ var XlsbWriter = class _XlsbWriter {
3388
3832
  * Must be awaited before the file is fully written.
3389
3833
  */
3390
3834
  finalize() {
3391
- return new Promise((resolve2, reject) => {
3835
+ return new Promise((resolve6, reject) => {
3836
+ if (this._finalized) {
3837
+ reject(new Error("XlsbWriter: already finalized"));
3838
+ return;
3839
+ }
3840
+ this._finalized = true;
3841
+ let settled = false;
3842
+ const cleanupTemp = () => {
3843
+ try {
3844
+ if (fs.existsSync(this._tempPath)) fs.rmSync(this._tempPath);
3845
+ } catch {
3846
+ }
3847
+ };
3848
+ const fail = (err) => {
3849
+ if (settled) return;
3850
+ settled = true;
3851
+ try {
3852
+ this.archive.abort();
3853
+ } catch {
3854
+ }
3855
+ try {
3856
+ this.output.destroy();
3857
+ } catch {
3858
+ }
3859
+ cleanupTemp();
3860
+ reject(err instanceof Error ? err : new Error(String(err)));
3861
+ };
3862
+ const succeed = () => {
3863
+ if (settled) return;
3864
+ if (this._outputError) {
3865
+ fail(this._outputError);
3866
+ return;
3867
+ }
3868
+ settled = true;
3869
+ try {
3870
+ const fd = fs.openSync(this._tempPath, "r+");
3871
+ try {
3872
+ fs.fsyncSync(fd);
3873
+ } finally {
3874
+ fs.closeSync(fd);
3875
+ }
3876
+ try {
3877
+ const existing = fs.statSync(this._targetPath);
3878
+ fs.chmodSync(this._tempPath, existing.mode & 511);
3879
+ } catch {
3880
+ }
3881
+ fs.renameSync(this._tempPath, this._targetPath);
3882
+ resolve6();
3883
+ } catch (err) {
3884
+ cleanupTemp();
3885
+ reject(err instanceof Error ? err : new Error(String(err)));
3886
+ }
3887
+ };
3392
3888
  try {
3889
+ if (this._outputError) throw this._outputError;
3393
3890
  this.saveSst();
3394
3891
  this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
3395
3892
  const wbBuffers = [];
@@ -3486,11 +3983,12 @@ var XlsbWriter = class _XlsbWriter {
3486
3983
  </Relationships>`;
3487
3984
  this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
3488
3985
  }
3489
- this.output.on("close", () => resolve2());
3490
- this.archive.on("error", (err) => reject(err));
3986
+ this.output.once("close", succeed);
3987
+ this.output.once("error", fail);
3988
+ this.archive.once("error", fail);
3491
3989
  this.archive.finalize();
3492
3990
  } catch (err) {
3493
- reject(err);
3991
+ fail(err);
3494
3992
  }
3495
3993
  });
3496
3994
  }
@@ -3498,27 +3996,17 @@ var XlsbWriter = class _XlsbWriter {
3498
3996
 
3499
3997
  // src/XlsxWriter.ts
3500
3998
  var fs2 = __toESM(require("fs"));
3999
+ var path3 = __toESM(require("path"));
4000
+ var import_crypto2 = require("crypto");
3501
4001
  var import_archiver2 = __toESM(require("archiver"));
3502
4002
  var import_stream2 = require("stream");
3503
- var COLUMN_LETTERS = (() => {
3504
- const letters = [];
3505
- for (let i = 65; i < 91; i++) {
3506
- letters.push(String.fromCharCode(i));
3507
- }
3508
- const temp = [];
3509
- for (const p of letters) {
3510
- for (let i = 65; i < 91; i++) {
3511
- temp.push(p + String.fromCharCode(i));
3512
- }
3513
- }
3514
- letters.push(...temp);
3515
- return letters;
3516
- })();
3517
4003
  var XlsxWriter = class {
3518
4004
  /**
3519
4005
  * @param filePath Destination `.xlsx` path on disk.
3520
4006
  */
3521
4007
  constructor(filePath) {
4008
+ this._outputError = null;
4009
+ this._finalized = false;
3522
4010
  this.sheetCount = 0;
3523
4011
  this.sheetList = [];
3524
4012
  this.sstArray = [];
@@ -3532,13 +4020,25 @@ var XlsxWriter = class {
3532
4020
  this._nextXfIndex = 4;
3533
4021
  this.stream = new StreamingSheetState();
3534
4022
  this.currentColLetters = [];
3535
- this.output = fs2.createWriteStream(filePath);
4023
+ this._targetPath = path3.resolve(filePath);
4024
+ fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
4025
+ this._tempPath = path3.join(
4026
+ path3.dirname(this._targetPath),
4027
+ `.${path3.basename(this._targetPath)}.${(0, import_crypto2.randomUUID)()}.tmp`
4028
+ );
4029
+ this.output = fs2.createWriteStream(this._tempPath);
4030
+ this.output.on("error", (err) => {
4031
+ this._outputError = err;
4032
+ });
3536
4033
  this.archive = (0, import_archiver2.default)("zip");
4034
+ this.archive.on("error", (err) => {
4035
+ this._outputError = err;
4036
+ });
3537
4037
  this.archive.pipe(this.output);
3538
4038
  this._oaEpoch = Date.UTC(1899, 11, 30);
3539
4039
  }
3540
4040
  _getColumnLetter(colIndex) {
3541
- return colIndex < COLUMN_LETTERS.length ? COLUMN_LETTERS[colIndex] : "A";
4041
+ return columnIndexToLetter(colIndex);
3542
4042
  }
3543
4043
  /** @deprecated Internal; kept for unit-test access. Prefer sanitizeSheetName(). */
3544
4044
  _sanitizeSheetName(name) {
@@ -3590,6 +4090,11 @@ var XlsxWriter = class {
3590
4090
  * Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
3591
4091
  */
3592
4092
  startSheet(sheetName, columnCount, headers, options = {}) {
4093
+ assertColumnCount(columnCount);
4094
+ if (headers !== void 0) {
4095
+ assertRowFitsColumns("header", headers.length, columnCount);
4096
+ for (const h of headers) assertCellTextLength(h);
4097
+ }
3593
4098
  const { hidden = false, doAutofilter = true, sampleRows } = options;
3594
4099
  this.addSheet(sheetName, hidden);
3595
4100
  this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
@@ -3646,6 +4151,7 @@ var XlsxWriter = class {
3646
4151
  );
3647
4152
  }
3648
4153
  this.stream.rowNum++;
4154
+ assertRowCount(this.stream.rowNum);
3649
4155
  bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
3650
4156
  for (let c = 0; c < row.length; c++) {
3651
4157
  const raw = row[c];
@@ -3709,6 +4215,16 @@ var XlsxWriter = class {
3709
4215
  } else if (headers) {
3710
4216
  columnCount = headers.length;
3711
4217
  }
4218
+ if (columnCount > 0) assertColumnCount(columnCount);
4219
+ const totalRows = rows.length + (headers ? 1 : 0);
4220
+ assertRowCount(totalRows);
4221
+ if (headers) {
4222
+ assertRowFitsColumns("header", headers.length, columnCount);
4223
+ for (const h of headers) assertCellTextLength(h);
4224
+ }
4225
+ for (let r = 0; r < rows.length; r++) {
4226
+ assertRowFitsColumns("row", rows[r].length, columnCount);
4227
+ }
3712
4228
  const colWidths = initColWidths(columnCount);
3713
4229
  const colLetters = new Array(columnCount);
3714
4230
  for (let i = 0; i < columnCount; i++) {
@@ -3720,7 +4236,6 @@ var XlsxWriter = class {
3720
4236
  updateColWidthsFromRows(colWidths, rows);
3721
4237
  bigBuf.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
3722
4238
  bigBuf.writeString('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
3723
- const totalRows = rows.length + (headers ? 1 : 0);
3724
4239
  if (totalRows > 0 && columnCount > 0) {
3725
4240
  bigBuf.writeString(`<dimension ref="A1:${colLetters[columnCount - 1]}${totalRows}"/>`);
3726
4241
  } else {
@@ -3806,6 +4321,7 @@ var XlsxWriter = class {
3806
4321
  });
3807
4322
  }
3808
4323
  _writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
4324
+ assertCellTextLength(val);
3809
4325
  let index = this.sstMap.get(val);
3810
4326
  if (index === void 0) {
3811
4327
  index = this.sstArray.length;
@@ -3827,29 +4343,79 @@ var XlsxWriter = class {
3827
4343
  return xfIndex;
3828
4344
  }
3829
4345
  _toOADate(date) {
3830
- return (date.getTime() - this._oaEpoch) / 864e5;
4346
+ return timeToOaDate(date.getTime(), this._oaEpoch);
3831
4347
  }
3832
4348
  _getDateWidth(_date) {
3833
4349
  return 19;
3834
4350
  }
3835
4351
  /** Finalize the ZIP package and close the output stream. */
3836
4352
  finalize() {
3837
- return new Promise((resolve2, reject) => {
4353
+ return new Promise((resolve6, reject) => {
4354
+ if (this._finalized) {
4355
+ reject(new Error("XlsxWriter: already finalized"));
4356
+ return;
4357
+ }
4358
+ this._finalized = true;
4359
+ let settled = false;
4360
+ const cleanupTemp = () => {
4361
+ try {
4362
+ if (fs2.existsSync(this._tempPath)) fs2.rmSync(this._tempPath);
4363
+ } catch {
4364
+ }
4365
+ };
4366
+ const fail = (err) => {
4367
+ if (settled) return;
4368
+ settled = true;
4369
+ try {
4370
+ this.archive.abort();
4371
+ } catch {
4372
+ }
4373
+ try {
4374
+ this.output.destroy();
4375
+ } catch {
4376
+ }
4377
+ cleanupTemp();
4378
+ reject(err instanceof Error ? err : new Error(String(err)));
4379
+ };
4380
+ const succeed = () => {
4381
+ if (settled) return;
4382
+ if (this._outputError) {
4383
+ fail(this._outputError);
4384
+ return;
4385
+ }
4386
+ settled = true;
4387
+ try {
4388
+ const fd = fs2.openSync(this._tempPath, "r+");
4389
+ try {
4390
+ fs2.fsyncSync(fd);
4391
+ } finally {
4392
+ fs2.closeSync(fd);
4393
+ }
4394
+ try {
4395
+ const existing = fs2.statSync(this._targetPath);
4396
+ fs2.chmodSync(this._tempPath, existing.mode & 511);
4397
+ } catch {
4398
+ }
4399
+ fs2.renameSync(this._tempPath, this._targetPath);
4400
+ resolve6();
4401
+ } catch (err) {
4402
+ cleanupTemp();
4403
+ reject(err instanceof Error ? err : new Error(String(err)));
4404
+ }
4405
+ };
3838
4406
  try {
4407
+ if (this._outputError) throw this._outputError;
3839
4408
  this._writeSharedStrings();
3840
4409
  this._writeStyles();
3841
4410
  this._writeWorkbook();
3842
4411
  this._writeContentTypes();
3843
4412
  this._writeRels();
3844
- this.output.on("close", () => {
3845
- resolve2();
3846
- });
3847
- this.archive.on("error", (err) => {
3848
- reject(err);
3849
- });
4413
+ this.output.once("close", succeed);
4414
+ this.output.once("error", fail);
4415
+ this.archive.once("error", fail);
3850
4416
  this.archive.finalize();
3851
4417
  } catch (err) {
3852
- reject(err);
4418
+ fail(err);
3853
4419
  }
3854
4420
  });
3855
4421
  }
@@ -3929,63 +4495,324 @@ ${xfEntries.join("\n")}
3929
4495
  const escapedName = this._escapeSheetNameForXml(sheet.name);
3930
4496
  xml += `<sheet name="${escapedName}" sheetId="${sheet.sheetId}"${state} r:id="${sheet.rId}"/>`;
3931
4497
  }
3932
- xml += `</sheets>`;
3933
- if (this._autofilterIsOn) {
3934
- xml += "<definedNames>";
3935
- for (const sheet of this.sheetList) {
3936
- if (sheet.filterHeaderRange) {
3937
- const localSheetId = sheet.sheetId - 1;
3938
- const escapedRange = this._escape(sheet.filterHeaderRange);
3939
- xml += `<definedName name="_xlnm._FilterDatabase" localSheetId="${localSheetId}" hidden="1">${escapedRange}</definedName>`;
3940
- }
4498
+ xml += `</sheets>`;
4499
+ if (this._autofilterIsOn) {
4500
+ xml += "<definedNames>";
4501
+ for (const sheet of this.sheetList) {
4502
+ if (sheet.filterHeaderRange) {
4503
+ const localSheetId = sheet.sheetId - 1;
4504
+ const escapedRange = this._escape(sheet.filterHeaderRange);
4505
+ xml += `<definedName name="_xlnm._FilterDatabase" localSheetId="${localSheetId}" hidden="1">${escapedRange}</definedName>`;
4506
+ }
4507
+ }
4508
+ xml += "</definedNames>";
4509
+ }
4510
+ xml += '<calcPr calcId="124519" fullCalcOnLoad="1"/>';
4511
+ xml += `</workbook>`;
4512
+ this.archive.append(xml, { name: "xl/workbook.xml" });
4513
+ }
4514
+ _writeContentTypes() {
4515
+ let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4516
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
4517
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
4518
+ <Default Extension="xml" ContentType="application/xml"/>
4519
+ <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
4520
+ <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
4521
+ <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>`;
4522
+ for (const sheet of this.sheetList) {
4523
+ xml += `<Override PartName="/${sheet.pathInArchive}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
4524
+ }
4525
+ xml += `</Types>`;
4526
+ this.archive.append(xml, { name: "[Content_Types].xml" });
4527
+ }
4528
+ _writeRels() {
4529
+ const globalRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4530
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
4531
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
4532
+ </Relationships>`;
4533
+ this.archive.append(globalRels, { name: "_rels/.rels" });
4534
+ let wbRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4535
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`;
4536
+ for (const sheet of this.sheetList) {
4537
+ wbRels += `<Relationship Id="${sheet.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/${sheet.nameInArchive}"/>`;
4538
+ }
4539
+ let nextId = this.sheetList.length + 1;
4540
+ wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
4541
+ wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`;
4542
+ wbRels += `</Relationships>`;
4543
+ this.archive.append(wbRels, { name: "xl/_rels/workbook.xml.rels" });
4544
+ }
4545
+ };
4546
+
4547
+ // src/XlsxUpdater.ts
4548
+ var fs5 = __toESM(require("fs"));
4549
+ var os = __toESM(require("os"));
4550
+ var path6 = __toESM(require("path"));
4551
+ var import_adm_zip2 = __toESM(require("adm-zip"));
4552
+
4553
+ // src/atomicFile.ts
4554
+ var fs3 = __toESM(require("fs"));
4555
+ var path4 = __toESM(require("path"));
4556
+ var import_crypto3 = require("crypto");
4557
+ var realFileOperations = {
4558
+ exists: (filePath) => fs3.existsSync(filePath),
4559
+ rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
4560
+ remove: (filePath) => fs3.rmSync(filePath)
4561
+ };
4562
+ function errorMessage(error) {
4563
+ return error instanceof Error ? error.message : String(error);
4564
+ }
4565
+ function destinationExistsError(destinationPath) {
4566
+ return new Error(
4567
+ `Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
4568
+ );
4569
+ }
4570
+ function readDestinationMode(target) {
4571
+ try {
4572
+ if (!fs3.existsSync(target)) return void 0;
4573
+ return fs3.statSync(target).mode & 511;
4574
+ } catch {
4575
+ return void 0;
4576
+ }
4577
+ }
4578
+ function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
4579
+ const backupPath = `${destinationPath}.${(0, import_crypto3.randomUUID)()}.replacement-backup`;
4580
+ let backupCreated = false;
4581
+ let installed = false;
4582
+ if (operations.exists(destinationPath)) {
4583
+ if (!overwrite) {
4584
+ throw destinationExistsError(destinationPath);
4585
+ }
4586
+ operations.rename(destinationPath, backupPath);
4587
+ backupCreated = true;
4588
+ }
4589
+ try {
4590
+ try {
4591
+ operations.rename(temporaryPath, destinationPath);
4592
+ installed = true;
4593
+ } catch (error) {
4594
+ if (overwrite && !backupCreated && operations.exists(destinationPath)) {
4595
+ operations.rename(destinationPath, backupPath);
4596
+ backupCreated = true;
4597
+ operations.rename(temporaryPath, destinationPath);
4598
+ installed = true;
4599
+ } else {
4600
+ throw error;
4601
+ }
4602
+ }
4603
+ if (backupCreated) {
4604
+ operations.remove(backupPath);
4605
+ }
4606
+ } catch (error) {
4607
+ if (backupCreated && !installed) {
4608
+ if (!operations.exists(destinationPath)) {
4609
+ try {
4610
+ operations.rename(backupPath, destinationPath);
4611
+ backupCreated = false;
4612
+ } catch (rollbackError) {
4613
+ throw new Error(
4614
+ `Excel conversion: final destination install failed (${errorMessage(error)}), and restoring the original destination also failed (${errorMessage(rollbackError)}). Original file remains at ${backupPath}.`
4615
+ );
4616
+ }
4617
+ } else {
4618
+ throw new Error(
4619
+ `Excel conversion: final destination install failed (${errorMessage(error)}). A concurrent destination was preserved; the original file remains at ${backupPath}.`
4620
+ );
4621
+ }
4622
+ }
4623
+ throw error;
4624
+ }
4625
+ }
4626
+ function writeBufferAtomically(buffer, destinationPath) {
4627
+ const target = path4.resolve(destinationPath);
4628
+ fs3.mkdirSync(path4.dirname(target), { recursive: true });
4629
+ const temporaryPath = path4.join(
4630
+ path4.dirname(target),
4631
+ `.${path4.basename(target)}.${(0, import_crypto3.randomUUID)()}.tmp`
4632
+ );
4633
+ const destinationMode = readDestinationMode(target);
4634
+ try {
4635
+ const fd = fs3.openSync(temporaryPath, "w", destinationMode ?? 438);
4636
+ try {
4637
+ if (destinationMode !== void 0) {
4638
+ try {
4639
+ fs3.fchmodSync(fd, destinationMode);
4640
+ } catch {
4641
+ }
4642
+ }
4643
+ fs3.writeFileSync(fd, buffer);
4644
+ fs3.fsyncSync(fd);
4645
+ } finally {
4646
+ fs3.closeSync(fd);
4647
+ }
4648
+ if (destinationMode !== void 0) {
4649
+ try {
4650
+ fs3.chmodSync(temporaryPath, destinationMode);
4651
+ } catch {
4652
+ }
4653
+ }
4654
+ fs3.renameSync(temporaryPath, target);
4655
+ } finally {
4656
+ if (fs3.existsSync(temporaryPath)) {
4657
+ try {
4658
+ fs3.rmSync(temporaryPath);
4659
+ } catch {
4660
+ }
4661
+ }
4662
+ }
4663
+ }
4664
+
4665
+ // src/streamingZip.ts
4666
+ var fs4 = __toESM(require("fs"));
4667
+ var path5 = __toESM(require("path"));
4668
+ var import_crypto4 = require("crypto");
4669
+ var import_promises = require("stream/promises");
4670
+ var import_stream3 = require("stream");
4671
+ var import_archiver3 = __toESM(require("archiver"));
4672
+ function readDestinationMode2(target) {
4673
+ try {
4674
+ if (!fs4.existsSync(target)) return void 0;
4675
+ return fs4.statSync(target).mode & 511;
4676
+ } catch {
4677
+ return void 0;
4678
+ }
4679
+ }
4680
+ function appendAndWait(archive, source, name, output) {
4681
+ return new Promise((resolve6, reject) => {
4682
+ const cleanup = () => {
4683
+ archive.off("entry", onEntry);
4684
+ archive.off("error", onError);
4685
+ const maybeEmitter2 = source;
4686
+ if (typeof maybeEmitter2.off === "function") {
4687
+ maybeEmitter2.off("error", onError);
4688
+ }
4689
+ if (output) {
4690
+ output.off("error", onError);
4691
+ }
4692
+ };
4693
+ const onEntry = () => {
4694
+ cleanup();
4695
+ resolve6();
4696
+ };
4697
+ const onError = (error) => {
4698
+ cleanup();
4699
+ reject(error);
4700
+ };
4701
+ archive.once("entry", onEntry);
4702
+ archive.once("error", onError);
4703
+ if (output) {
4704
+ output.once("error", onError);
4705
+ }
4706
+ const maybeEmitter = source;
4707
+ if (typeof maybeEmitter.once === "function") {
4708
+ maybeEmitter.once("error", onError);
4709
+ }
4710
+ try {
4711
+ archive.append(source, { name });
4712
+ } catch (error) {
4713
+ cleanup();
4714
+ reject(error);
4715
+ }
4716
+ });
4717
+ }
4718
+ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
4719
+ const target = path5.resolve(destinationPath);
4720
+ fs4.mkdirSync(path5.dirname(target), { recursive: true });
4721
+ const temporaryPath = path5.join(
4722
+ path5.dirname(target),
4723
+ `.${path5.basename(target)}.${(0, import_crypto4.randomUUID)()}.tmp`
4724
+ );
4725
+ const destinationMode = readDestinationMode2(target);
4726
+ let output = null;
4727
+ let archive = null;
4728
+ try {
4729
+ output = fs4.createWriteStream(
4730
+ temporaryPath,
4731
+ destinationMode !== void 0 ? { mode: destinationMode } : {}
4732
+ );
4733
+ let outputError = null;
4734
+ output.on("error", (err) => {
4735
+ outputError = err;
4736
+ try {
4737
+ archive == null ? void 0 : archive.abort();
4738
+ } catch {
4739
+ }
4740
+ });
4741
+ archive = (0, import_archiver3.default)("zip");
4742
+ archive.on("error", (err) => {
4743
+ outputError = err;
4744
+ });
4745
+ archive.pipe(output);
4746
+ for (const entry of zip.getEntries()) {
4747
+ if (outputError) throw outputError;
4748
+ const stagedPath = stagedParts.get(entry.entryName);
4749
+ if (stagedPath) {
4750
+ await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName, output);
4751
+ } else {
4752
+ await appendAndWait(archive, import_stream3.Readable.from([entry.getData()]), entry.entryName, output);
3941
4753
  }
3942
- xml += "</definedNames>";
3943
4754
  }
3944
- xml += '<calcPr calcId="124519" fullCalcOnLoad="1"/>';
3945
- xml += `</workbook>`;
3946
- this.archive.append(xml, { name: "xl/workbook.xml" });
3947
- }
3948
- _writeContentTypes() {
3949
- let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3950
- <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
3951
- <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
3952
- <Default Extension="xml" ContentType="application/xml"/>
3953
- <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
3954
- <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
3955
- <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>`;
3956
- for (const sheet of this.sheetList) {
3957
- xml += `<Override PartName="/${sheet.pathInArchive}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
4755
+ if (outputError) throw outputError;
4756
+ await archive.finalize();
4757
+ await (0, import_promises.finished)(output);
4758
+ if (outputError) throw outputError;
4759
+ if (destinationMode !== void 0) {
4760
+ try {
4761
+ fs4.chmodSync(temporaryPath, destinationMode);
4762
+ } catch {
4763
+ }
3958
4764
  }
3959
- xml += `</Types>`;
3960
- this.archive.append(xml, { name: "[Content_Types].xml" });
3961
- }
3962
- _writeRels() {
3963
- const globalRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3964
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
3965
- <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
3966
- </Relationships>`;
3967
- this.archive.append(globalRels, { name: "_rels/.rels" });
3968
- let wbRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3969
- <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`;
3970
- for (const sheet of this.sheetList) {
3971
- wbRels += `<Relationship Id="${sheet.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/${sheet.nameInArchive}"/>`;
4765
+ const fd = fs4.openSync(temporaryPath, "r+");
4766
+ try {
4767
+ fs4.fsyncSync(fd);
4768
+ } finally {
4769
+ fs4.closeSync(fd);
4770
+ }
4771
+ fs4.renameSync(temporaryPath, target);
4772
+ } catch (error) {
4773
+ try {
4774
+ archive == null ? void 0 : archive.abort();
4775
+ } catch {
4776
+ }
4777
+ throw error;
4778
+ } finally {
4779
+ if (output && !output.closed) {
4780
+ output.destroy();
4781
+ }
4782
+ if (fs4.existsSync(temporaryPath)) {
4783
+ try {
4784
+ fs4.rmSync(temporaryPath);
4785
+ } catch {
4786
+ }
3972
4787
  }
3973
- let nextId = this.sheetList.length + 1;
3974
- wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
3975
- wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`;
3976
- wbRels += `</Relationships>`;
3977
- this.archive.append(wbRels, { name: "xl/_rels/workbook.xml.rels" });
3978
4788
  }
3979
- };
4789
+ }
3980
4790
 
3981
4791
  // src/XlsxUpdater.ts
3982
- var fs3 = __toESM(require("fs"));
3983
- var import_adm_zip2 = __toESM(require("adm-zip"));
3984
- var XlsxUpdater = class {
4792
+ function parseCellRange(ref) {
4793
+ const m = /^\$?([A-Za-z]+)\$?(\d+)(?::\$?([A-Za-z]+)\$?(\d+))?$/.exec(ref.trim());
4794
+ if (!m) return null;
4795
+ const c1 = columnLetterToIndex(m[1].toUpperCase());
4796
+ const r1 = parseInt(m[2], 10);
4797
+ const c2 = m[3] ? columnLetterToIndex(m[3].toUpperCase()) : c1;
4798
+ const r2 = m[4] ? parseInt(m[4], 10) : r1;
4799
+ if (!Number.isFinite(c1) || !Number.isFinite(r1) || !Number.isFinite(c2) || !Number.isFinite(r2)) {
4800
+ return null;
4801
+ }
4802
+ return {
4803
+ c1: Math.min(c1, c2),
4804
+ r1: Math.min(r1, r2),
4805
+ c2: Math.max(c1, c2),
4806
+ r2: Math.max(r1, r2)
4807
+ };
4808
+ }
4809
+ var XlsxUpdater = class _XlsxUpdater {
3985
4810
  /**
3986
4811
  * @param path Path to an existing `.xlsx` file.
3987
4812
  */
3988
- constructor(path3) {
4813
+ constructor(filePath) {
4814
+ this.temporaryDirectory = null;
4815
+ this.stagedParts = /* @__PURE__ */ new Map();
3989
4816
  this.sheetNameToPath = /* @__PURE__ */ new Map();
3990
4817
  this.pivotCacheDefs = [];
3991
4818
  this.pivotTableDefPaths = [];
@@ -3993,13 +4820,15 @@ var XlsxUpdater = class {
3993
4820
  this.sharedStringsValues = [];
3994
4821
  this.sharedStringsOriginalLength = 0;
3995
4822
  this.sharedStringsCount = 0;
4823
+ this.sharedStringsPending = [];
4824
+ this.sharedStringsDirty = false;
3996
4825
  this.stringIndexMap = /* @__PURE__ */ new Map();
3997
- this.sourcePath = path3;
4826
+ this.sourcePath = filePath;
3998
4827
  this._oaEpoch = Date.UTC(1899, 11, 30);
3999
- if (!fs3.existsSync(path3)) {
4000
- throw new Error(`XlsxUpdater: file not found: ${path3}`);
4828
+ if (!fs5.existsSync(filePath)) {
4829
+ throw new Error(`XlsxUpdater: file not found: ${filePath}`);
4001
4830
  }
4002
- this.zip = new import_adm_zip2.default(path3);
4831
+ this.zip = new import_adm_zip2.default(filePath);
4003
4832
  if (this.zip.getEntry("xl/workbook.bin")) {
4004
4833
  throw new Error(
4005
4834
  "XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
@@ -4033,8 +4862,15 @@ var XlsxUpdater = class {
4033
4862
  if (!entry) {
4034
4863
  throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
4035
4864
  }
4036
- const sheetXml = entry.getData().toString("utf8");
4865
+ const sheetXml = this._entryData(sheetPath).toString("utf8");
4037
4866
  this._ensureSharedStringsLoaded();
4867
+ if (this.sharedStringsXml !== null) {
4868
+ this.sharedStringsCount = Math.max(
4869
+ 0,
4870
+ this.sharedStringsCount - this._sharedStringRefCount(sheetXml)
4871
+ );
4872
+ this.sharedStringsDirty = true;
4873
+ }
4038
4874
  let colStyles = /* @__PURE__ */ new Map();
4039
4875
  let headerStyles = /* @__PURE__ */ new Map();
4040
4876
  let dateStyle = null;
@@ -4049,12 +4885,177 @@ var XlsxUpdater = class {
4049
4885
  const newSheetData = this._buildSheetData(rows, headers, colStyles, headerStyles, dateStyle);
4050
4886
  const newXml = this._patchSheetXml(sheetXml, newSheetData, totalRows, lastCol);
4051
4887
  this.zip.updateFile(sheetPath, Buffer.from(newXml, "utf8"));
4888
+ this._discardStagedPart(sheetPath);
4052
4889
  if (this.sharedStringsXml !== null) {
4053
4890
  this._commitSharedStrings();
4054
4891
  }
4055
4892
  const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
4056
4893
  this._updatePivotCaches(sheetName, dimRef, rows.length);
4057
4894
  this._addRefreshOnLoadToPivotTables();
4895
+ this._removeCalcChain();
4896
+ this._updateTableRefs(sheetName, dimRef);
4897
+ }
4898
+ /**
4899
+ * Replace worksheet rows from a one-pass source without materialising the
4900
+ * complete result set. Use {@link saveStreaming} to keep the final ZIP
4901
+ * write streaming as well.
4902
+ */
4903
+ async replaceSheetDataStream(sheetName, rows, options = {}) {
4904
+ const { headers, styleFallback = "inherit" } = options;
4905
+ if (styleFallback !== "inherit" && styleFallback !== "general") {
4906
+ throw new Error("XlsxUpdater: styleFallback must be 'inherit' or 'general'.");
4907
+ }
4908
+ const sheetPath = this.sheetNameToPath.get(sheetName);
4909
+ if (!sheetPath) {
4910
+ throw new Error(`XlsxUpdater: sheet "${sheetName}" not found in the workbook.`);
4911
+ }
4912
+ const entry = this.zip.getEntry(sheetPath);
4913
+ if (!entry) {
4914
+ throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
4915
+ }
4916
+ const oldStage = this.stagedParts.get(sheetPath);
4917
+ const beforeValuesLength = this.sharedStringsValues.length;
4918
+ const beforePendingLength = this.sharedStringsPending.length;
4919
+ const beforeSharedStringsCount = this.sharedStringsCount;
4920
+ const beforeSharedStringsDirty = this.sharedStringsDirty;
4921
+ const beforeSharedStringsXml = this.sharedStringsXml;
4922
+ const beforeSharedStringsOriginalLength = this.sharedStringsOriginalLength;
4923
+ const beforeStringIndexMap = new Map(this.stringIndexMap);
4924
+ const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.xml");
4925
+ const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.xml") ? Buffer.from(this._entryData("xl/sharedStrings.xml")) : null;
4926
+ const createdParts = [];
4927
+ const oldSheetXml = this._entryData(sheetPath).toString("utf8");
4928
+ const rowsPath = this._temporaryPath(".rows");
4929
+ const outputPath = this._temporaryPath(".xml");
4930
+ createdParts.push(rowsPath, outputPath);
4931
+ let output = null;
4932
+ try {
4933
+ this._ensureSharedStringsLoaded();
4934
+ if (this.sharedStringsXml !== null) {
4935
+ this.sharedStringsCount = Math.max(
4936
+ 0,
4937
+ this.sharedStringsCount - this._sharedStringRefCount(oldSheetXml)
4938
+ );
4939
+ this.sharedStringsDirty = true;
4940
+ }
4941
+ let colStyles = /* @__PURE__ */ new Map();
4942
+ let headerStyles = /* @__PURE__ */ new Map();
4943
+ let dateStyle = null;
4944
+ if (styleFallback !== "general") {
4945
+ const collected = this._collectExistingStyles(oldSheetXml);
4946
+ colStyles = collected.dataStyles;
4947
+ headerStyles = collected.headerStyles;
4948
+ dateStyle = this._findDateStyleIndex();
4949
+ }
4950
+ const prefixMatch = /<([A-Za-z_][\w.-]*:)?sheetData\b/.exec(oldSheetXml);
4951
+ const prefix = (prefixMatch == null ? void 0 : prefixMatch[1]) ?? "";
4952
+ const rowOutput = fs5.createWriteStream(rowsPath);
4953
+ output = rowOutput;
4954
+ let outputError = null;
4955
+ rowOutput.on("error", (error) => {
4956
+ outputError = error;
4957
+ });
4958
+ let nextRow = 1;
4959
+ let width = (headers == null ? void 0 : headers.length) ?? 0;
4960
+ let dataRowsSeen = 0;
4961
+ let keptDataRows = 0;
4962
+ let lastKeptEnd = 0;
4963
+ let pendingEmptyWidth = 0;
4964
+ let writtenBytes = 0;
4965
+ const write = async (data) => {
4966
+ if (outputError) throw outputError;
4967
+ writtenBytes += data.length;
4968
+ if (rowOutput.write(data)) return;
4969
+ await new Promise((resolve6, reject) => {
4970
+ const onDrain = () => {
4971
+ cleanup();
4972
+ resolve6();
4973
+ };
4974
+ const onError = (error) => {
4975
+ cleanup();
4976
+ reject(error);
4977
+ };
4978
+ const cleanup = () => {
4979
+ rowOutput.off("drain", onDrain);
4980
+ rowOutput.off("error", onError);
4981
+ };
4982
+ rowOutput.once("drain", onDrain);
4983
+ rowOutput.once("error", onError);
4984
+ });
4985
+ };
4986
+ if (headers !== void 0) {
4987
+ const cells = headers.map((value, column) => this._valueCell(value, column, nextRow, headerStyles, dateStyle));
4988
+ const header = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
4989
+ await write(Buffer.from(header, "utf8"));
4990
+ lastKeptEnd = writtenBytes;
4991
+ nextRow++;
4992
+ }
4993
+ for await (const sourceRow of rows) {
4994
+ if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
4995
+ throw new TypeError("Each row must be an iterable of cells, not text");
4996
+ }
4997
+ const row = Array.from(sourceRow);
4998
+ const cells = row.map((value, column) => value === null || value === void 0 ? "" : this._valueCell(value, column, nextRow, colStyles, dateStyle));
4999
+ const rowXml = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
5000
+ await write(Buffer.from(rowXml, "utf8"));
5001
+ dataRowsSeen++;
5002
+ nextRow++;
5003
+ if (row.every((value) => value === null || value === void 0)) {
5004
+ pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
5005
+ } else {
5006
+ width = Math.max(width, pendingEmptyWidth, row.length);
5007
+ pendingEmptyWidth = 0;
5008
+ keptDataRows = dataRowsSeen;
5009
+ lastKeptEnd = writtenBytes;
5010
+ }
5011
+ }
5012
+ await new Promise((resolve6, reject) => {
5013
+ rowOutput.once("close", resolve6);
5014
+ rowOutput.once("error", reject);
5015
+ rowOutput.end();
5016
+ });
5017
+ if (outputError) throw outputError;
5018
+ const rowsFd = fs5.openSync(rowsPath, "r+");
5019
+ try {
5020
+ fs5.ftruncateSync(rowsFd, lastKeptEnd);
5021
+ } finally {
5022
+ fs5.closeSync(rowsFd);
5023
+ }
5024
+ const totalRows = keptDataRows + (headers !== void 0 ? 1 : 0);
5025
+ const lastCol = width - 1;
5026
+ const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
5027
+ this._patchSheetFile(oldSheetXml, rowsPath, outputPath, dimRef);
5028
+ this._stagePart(sheetPath, outputPath);
5029
+ this._commitSharedStrings();
5030
+ this._updatePivotCaches(sheetName, dimRef, keptDataRows);
5031
+ this._addRefreshOnLoadToPivotTables();
5032
+ this._removeCalcChain();
5033
+ this._updateTableRefs(sheetName, dimRef);
5034
+ if (oldStage && oldStage !== outputPath) {
5035
+ this._removeTemporaryFile(oldStage);
5036
+ }
5037
+ createdParts.splice(createdParts.indexOf(outputPath), 1);
5038
+ } catch (error) {
5039
+ this.sharedStringsValues.length = beforeValuesLength;
5040
+ this.sharedStringsPending.length = beforePendingLength;
5041
+ this.sharedStringsCount = beforeSharedStringsCount;
5042
+ this.sharedStringsDirty = beforeSharedStringsDirty;
5043
+ this.sharedStringsXml = beforeSharedStringsXml;
5044
+ this.sharedStringsOriginalLength = beforeSharedStringsOriginalLength;
5045
+ this.stringIndexMap.clear();
5046
+ for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
5047
+ if (beforeSharedStringsBytes) {
5048
+ this.zip.updateFile("xl/sharedStrings.xml", beforeSharedStringsBytes);
5049
+ }
5050
+ if (oldStage) this.stagedParts.set(sheetPath, oldStage);
5051
+ else this.stagedParts.delete(sheetPath);
5052
+ throw error;
5053
+ } finally {
5054
+ if (output && !output.closed) output.destroy();
5055
+ for (const temporaryPath of createdParts) {
5056
+ this._removeTemporaryFile(temporaryPath);
5057
+ }
5058
+ }
4058
5059
  }
4059
5060
  /**
4060
5061
  * Write the updated workbook to disk.
@@ -4062,12 +5063,89 @@ var XlsxUpdater = class {
4062
5063
  */
4063
5064
  save(outputPath) {
4064
5065
  const target = outputPath ?? this.sourcePath;
4065
- fs3.writeFileSync(target, this.zip.toBuffer());
5066
+ this._materializeStagedParts();
5067
+ writeBufferAtomically(this.zip.toBuffer(), target);
5068
+ }
5069
+ /** Save staged streaming replacements without materialising the ZIP. */
5070
+ async saveStreaming(outputPath) {
5071
+ const target = outputPath ?? this.sourcePath;
5072
+ await writeAdmZipStreaming(this.zip, this.stagedParts, target);
5073
+ this._materializeStagedParts();
4066
5074
  }
4067
5075
  /** The updated workbook as an in-memory ZIP buffer. */
4068
5076
  toBuffer() {
5077
+ this._materializeStagedParts();
4069
5078
  return this.zip.toBuffer();
4070
5079
  }
5080
+ /**
5081
+ * Release the staging directory and any unsaved staged parts.
5082
+ * The updater remains usable; a new staging directory is created lazily
5083
+ * on the next streaming replacement.
5084
+ */
5085
+ dispose() {
5086
+ for (const stagedPath of this.stagedParts.values()) {
5087
+ try {
5088
+ fs5.rmSync(stagedPath, { force: true });
5089
+ } catch {
5090
+ }
5091
+ }
5092
+ this.stagedParts.clear();
5093
+ if (this.temporaryDirectory !== null) {
5094
+ try {
5095
+ fs5.rmSync(this.temporaryDirectory, { recursive: true, force: true });
5096
+ } catch {
5097
+ }
5098
+ this.temporaryDirectory = null;
5099
+ }
5100
+ }
5101
+ /** Support `using` declarations for automatic staging cleanup. */
5102
+ [Symbol.dispose]() {
5103
+ this.dispose();
5104
+ }
5105
+ _ensureTemporaryDirectory() {
5106
+ if (this.temporaryDirectory === null) {
5107
+ this.temporaryDirectory = fs5.mkdtempSync(path6.join(os.tmpdir(), "spreadsheet-updater-"));
5108
+ } else {
5109
+ try {
5110
+ fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
5111
+ } catch {
5112
+ }
5113
+ }
5114
+ return this.temporaryDirectory;
5115
+ }
5116
+ _temporaryPath(suffix) {
5117
+ return path6.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
5118
+ }
5119
+ _removeTemporaryFile(filePath) {
5120
+ try {
5121
+ fs5.rmSync(filePath);
5122
+ } catch (error) {
5123
+ if (error.code !== "ENOENT") throw error;
5124
+ }
5125
+ }
5126
+ _entryData(name) {
5127
+ const stagedPath = this.stagedParts.get(name);
5128
+ if (stagedPath) return fs5.readFileSync(stagedPath);
5129
+ const entry = this.zip.getEntry(name);
5130
+ if (!entry) throw new Error(`XlsxUpdater: ZIP member missing: ${name}`);
5131
+ return entry.getData();
5132
+ }
5133
+ _stagePart(name, filePath) {
5134
+ this.stagedParts.set(name, filePath);
5135
+ }
5136
+ _discardStagedPart(name) {
5137
+ const stagedPath = this.stagedParts.get(name);
5138
+ if (!stagedPath) return;
5139
+ this._removeTemporaryFile(stagedPath);
5140
+ this.stagedParts.delete(name);
5141
+ }
5142
+ _materializeStagedParts() {
5143
+ for (const [name, stagedPath] of this.stagedParts) {
5144
+ this.zip.updateFile(name, fs5.readFileSync(stagedPath));
5145
+ this._removeTemporaryFile(stagedPath);
5146
+ }
5147
+ this.stagedParts.clear();
5148
+ }
4071
5149
  // ------------------------------------------------------------------
4072
5150
  // Workbook structure
4073
5151
  // ------------------------------------------------------------------
@@ -4076,10 +5154,15 @@ var XlsxUpdater = class {
4076
5154
  const rIdToTarget = {};
4077
5155
  if (relsEntry) {
4078
5156
  const rels = relsEntry.getData().toString("utf8");
4079
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
4080
- let match2;
4081
- while ((match2 = relRegex.exec(rels)) !== null) {
4082
- rIdToTarget[match2[1]] = match2[2];
5157
+ const relTagRe = /<Relationship\b[^>]*>/g;
5158
+ let tagMatch;
5159
+ while ((tagMatch = relTagRe.exec(rels)) !== null) {
5160
+ const attrs = parseTagAttributes(tagMatch[0]);
5161
+ const id = attrs["Id"] ?? attrs["id"];
5162
+ const target = attrs["Target"] ?? attrs["target"];
5163
+ if (id !== void 0 && target !== void 0) {
5164
+ rIdToTarget[id] = target;
5165
+ }
4083
5166
  }
4084
5167
  }
4085
5168
  const wbEntry = this.zip.getEntry("xl/workbook.xml");
@@ -4088,21 +5171,23 @@ var XlsxUpdater = class {
4088
5171
  let match;
4089
5172
  while ((match = sheetRe.exec(wbXml)) !== null) {
4090
5173
  const tag = match[0];
4091
- const nameMatch = /name="([^"]*)"/.exec(tag);
4092
- const rIdMatch = /r:id="([^"]*)"/.exec(tag);
4093
- if (!nameMatch || !rIdMatch) continue;
4094
- const name = nameMatch[1].replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
4095
- const target = rIdToTarget[rIdMatch[1]] ?? "";
4096
- let fullPath = target;
4097
- if (fullPath.startsWith("/")) {
4098
- fullPath = fullPath.substring(1);
4099
- } else if (!fullPath.startsWith("xl/")) {
4100
- fullPath = "xl/" + fullPath;
4101
- }
5174
+ const attrs = parseTagAttributes(tag);
5175
+ const rawName = attrs["name"];
5176
+ const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
5177
+ if (rawName === void 0 || rId === void 0) continue;
5178
+ const name = unescapeXml(rawName);
5179
+ const target = rIdToTarget[rId] ?? "";
5180
+ const fullPath = resolveRelTarget(target);
4102
5181
  if (fullPath) {
4103
5182
  this.sheetNameToPath.set(name, fullPath);
4104
5183
  }
4105
5184
  }
5185
+ const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbXml);
5186
+ if (wbPrMatch) {
5187
+ const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
5188
+ const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
5189
+ this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
5190
+ }
4106
5191
  for (const entry of this.zip.getEntries()) {
4107
5192
  const name = entry.entryName;
4108
5193
  if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
@@ -4120,47 +5205,59 @@ var XlsxUpdater = class {
4120
5205
  return;
4121
5206
  }
4122
5207
  const entry = this.zip.getEntry("xl/sharedStrings.xml");
4123
- if (!entry) {
5208
+ if (!entry && !this.stagedParts.has("xl/sharedStrings.xml")) {
4124
5209
  this.sharedStringsXml = null;
4125
5210
  return;
4126
5211
  }
4127
- this.sharedStringsXml = entry.getData().toString("utf8");
5212
+ this.sharedStringsXml = this._entryData("xl/sharedStrings.xml").toString("utf8");
4128
5213
  this.sharedStringsValues = parseSharedStringsXml(this.sharedStringsXml);
4129
- const countMatch = /<sst\b[^>]*>/.exec(this.sharedStringsXml);
4130
- const countAttr = countMatch ? /count="(\d+)"/.exec(countMatch[0]) : null;
4131
- this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) : 0;
5214
+ const countMatch = /<(?:[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(this.sharedStringsXml);
5215
+ const countAttr = countMatch ? /(?:^|\s)count\s*=\s*["'](\d+)["']/.exec(countMatch[0]) : null;
5216
+ this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) : this.sharedStringsValues.length;
4132
5217
  this.sharedStringsValues.forEach((value, index) => {
4133
5218
  if (!this.stringIndexMap.has(value)) {
4134
5219
  this.stringIndexMap.set(value, index);
4135
5220
  }
4136
5221
  });
4137
5222
  this.sharedStringsOriginalLength = this.sharedStringsValues.length;
5223
+ this.sharedStringsPending = [];
5224
+ this.sharedStringsDirty = false;
4138
5225
  }
4139
5226
  _commitSharedStrings() {
4140
- if (this.sharedStringsXml === null || this.sharedStringsValues.length === this.sharedStringsOriginalLength) {
5227
+ var _a;
5228
+ if (this.sharedStringsXml === null || !this.sharedStringsDirty) {
4141
5229
  return;
4142
5230
  }
4143
5231
  let xml = this.sharedStringsXml;
4144
- const sstStart = xml.indexOf("<sst");
4145
- const sstTagEnd = xml.indexOf(">", sstStart);
4146
- if (sstStart !== -1 && sstTagEnd !== -1) {
4147
- const tag = xml.substring(sstStart, sstTagEnd + 1);
4148
- const newTag = tag.replace(/count="\d+"/, `count="${this.sharedStringsCount}"`).replace(/uniqueCount="\d+"/, `uniqueCount="${this.sharedStringsValues.length}"`);
4149
- xml = xml.substring(0, sstStart) + newTag + xml.substring(sstTagEnd + 1);
4150
- }
4151
- const sstEnd = xml.lastIndexOf("</sst>");
4152
- if (sstEnd === -1) {
4153
- return;
5232
+ const sstMatch = /<(?<prefix>[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(xml);
5233
+ if (!sstMatch || sstMatch.index === void 0) {
5234
+ throw new Error("XlsxUpdater: sharedStrings.xml has no sst element.");
5235
+ }
5236
+ const tag = sstMatch[0];
5237
+ const prefix = ((_a = sstMatch.groups) == null ? void 0 : _a.prefix) ?? "";
5238
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(
5239
+ _XlsxUpdater._replaceOrAddXmlAttribute(tag, "count", String(this.sharedStringsCount)),
5240
+ "uniqueCount",
5241
+ String(this.sharedStringsValues.length)
5242
+ );
5243
+ xml = xml.substring(0, sstMatch.index) + newTag + xml.substring(sstMatch.index + tag.length);
5244
+ const closingRe = new RegExp(`</${prefix}sst\\s*>\\s*$`, "i");
5245
+ const closingMatch = closingRe.exec(xml);
5246
+ if (!closingMatch || closingMatch.index === void 0) {
5247
+ throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
4154
5248
  }
4155
- let appended = "";
4156
- for (let i = this.sharedStringsOriginalLength; i < this.sharedStringsValues.length; i++) {
4157
- const txt = this.sharedStringsValues[i];
5249
+ const appended = this.sharedStringsPending.map((txt) => {
4158
5250
  const clean = escapeXmlText(txt);
4159
5251
  const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
4160
- appended += preserve ? `<si><t xml:space="preserve">${clean}</t></si>` : `<si><t>${clean}</t></si>`;
4161
- }
4162
- xml = xml.substring(0, sstEnd) + appended + xml.substring(sstEnd);
5252
+ const tAttr = preserve ? ' xml:space="preserve"' : "";
5253
+ return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
5254
+ }).join("");
5255
+ xml = xml.substring(0, closingMatch.index) + appended + xml.substring(closingMatch.index);
4163
5256
  this.zip.updateFile("xl/sharedStrings.xml", Buffer.from(xml, "utf8"));
5257
+ this.sharedStringsXml = xml;
5258
+ this.sharedStringsOriginalLength = this.sharedStringsValues.length;
5259
+ this.sharedStringsPending = [];
5260
+ this.sharedStringsDirty = false;
4164
5261
  }
4165
5262
  // ------------------------------------------------------------------
4166
5263
  // Styles
@@ -4285,7 +5382,7 @@ var XlsxUpdater = class {
4285
5382
  return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
4286
5383
  }
4287
5384
  if (val instanceof Date) {
4288
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
5385
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
4289
5386
  if (Number.isFinite(oaDate)) {
4290
5387
  const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
4291
5388
  return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
@@ -4311,10 +5408,25 @@ var XlsxUpdater = class {
4311
5408
  index = this.sharedStringsValues.length;
4312
5409
  this.sharedStringsValues.push(text);
4313
5410
  this.stringIndexMap.set(text, index);
5411
+ this.sharedStringsPending.push(text);
4314
5412
  }
4315
5413
  this.sharedStringsCount++;
5414
+ this.sharedStringsDirty = true;
4316
5415
  return `<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`;
4317
5416
  }
5417
+ static _replaceOrAddXmlAttribute(tag, name, value) {
5418
+ const escaped = escapeXmlText(value);
5419
+ const attribute = new RegExp(`(\\s${name}\\s*=\\s*["'])[^"']*(["'])`, "i");
5420
+ if (attribute.test(tag)) {
5421
+ return tag.replace(attribute, `$1${escaped}$2`);
5422
+ }
5423
+ const insertion = ` ${name}="${escaped}"`;
5424
+ return tag.endsWith("/>") ? `${tag.slice(0, -2)}${insertion}/>` : `${tag.slice(0, -1)}${insertion}>`;
5425
+ }
5426
+ _sharedStringRefCount(sheetXml) {
5427
+ const cellTags = sheetXml.match(/<(?:(?:[A-Za-z_][\w.-]*):)?c\b[^>]*>/gi) ?? [];
5428
+ return cellTags.filter((tag) => /(?:^|\s)t\s*=\s*["']s["']/i.test(tag)).length;
5429
+ }
4318
5430
  // ------------------------------------------------------------------
4319
5431
  // Worksheet XML patching
4320
5432
  // ------------------------------------------------------------------
@@ -4356,6 +5468,109 @@ var XlsxUpdater = class {
4356
5468
  }
4357
5469
  return xml;
4358
5470
  }
5471
+ _patchSheetFile(sheetXml, rowsPath, outputPath, dimensionRef) {
5472
+ var _a, _b, _c, _d;
5473
+ const source = Buffer.from(sheetXml, "utf8");
5474
+ const text = sheetXml;
5475
+ const replacements = [];
5476
+ const emptySheetData = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b(?<attrs>[^>]*)\/>/i.exec(text);
5477
+ if (emptySheetData && emptySheetData.index !== void 0) {
5478
+ const prefix = ((_a = emptySheetData.groups) == null ? void 0 : _a.prefix) ?? "";
5479
+ replacements.push({
5480
+ start: emptySheetData.index,
5481
+ end: emptySheetData.index + emptySheetData[0].length,
5482
+ data: Buffer.from(`<${prefix}sheetData${((_b = emptySheetData.groups) == null ? void 0 : _b.attrs) ?? ""}>`, "utf8"),
5483
+ rows: true,
5484
+ suffix: Buffer.from(`</${prefix}sheetData>`, "utf8")
5485
+ });
5486
+ } else {
5487
+ const opening = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(text);
5488
+ if (!opening || opening.index === void 0) {
5489
+ throw new Error("XlsxUpdater: <sheetData> element not found in worksheet XML.");
5490
+ }
5491
+ const prefix = ((_c = opening.groups) == null ? void 0 : _c.prefix) ?? "";
5492
+ const closeRe = new RegExp(`</${prefix}sheetData\\s*>`, "i");
5493
+ const closing = closeRe.exec(text.slice(opening.index + opening[0].length));
5494
+ if (!closing || closing.index === void 0) {
5495
+ throw new Error("XlsxUpdater: <sheetData> element not closed in worksheet XML.");
5496
+ }
5497
+ const closingStart = opening.index + opening[0].length + closing.index;
5498
+ replacements.push({
5499
+ start: opening.index + opening[0].length,
5500
+ end: closingStart,
5501
+ rows: true
5502
+ });
5503
+ }
5504
+ const dimension = /<(?<prefix>[A-Za-z_][\w.-]*:)?dimension\b[^>]*>/i.exec(text);
5505
+ if (dimension && dimension.index !== void 0) {
5506
+ replacements.push({
5507
+ start: dimension.index,
5508
+ end: dimension.index + dimension[0].length,
5509
+ data: Buffer.from(
5510
+ _XlsxUpdater._replaceOrAddXmlAttribute(dimension[0], "ref", dimensionRef)
5511
+ )
5512
+ });
5513
+ } else {
5514
+ const root = /<(?<prefix>[A-Za-z_][\w.-]*:)?worksheet\b[^>]*>/i.exec(text);
5515
+ if (root && root.index !== void 0) {
5516
+ const prefix = ((_d = root.groups) == null ? void 0 : _d.prefix) ?? "";
5517
+ const insertAt = root.index + root[0].length;
5518
+ replacements.push({
5519
+ start: insertAt,
5520
+ end: insertAt,
5521
+ data: Buffer.from(`<${prefix}dimension ref="${dimensionRef}"/>`, "utf8")
5522
+ });
5523
+ }
5524
+ }
5525
+ const autoFilter = /<(?<prefix>[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i.exec(text);
5526
+ if (autoFilter && autoFilter.index !== void 0) {
5527
+ replacements.push({
5528
+ start: autoFilter.index,
5529
+ end: autoFilter.index + autoFilter[0].length,
5530
+ data: Buffer.from(
5531
+ _XlsxUpdater._replaceOrAddXmlAttribute(autoFilter[0], "ref", dimensionRef),
5532
+ "utf8"
5533
+ )
5534
+ });
5535
+ }
5536
+ replacements.sort((a, b) => a.start - b.start || a.end - b.end);
5537
+ const fd = fs5.openSync(outputPath, "w");
5538
+ let cursor = 0;
5539
+ const writeBuffer = (data) => {
5540
+ fs5.writeSync(fd, data);
5541
+ };
5542
+ const copyRows = () => {
5543
+ const rowsFd = fs5.openSync(rowsPath, "r");
5544
+ try {
5545
+ const chunk = Buffer.alloc(1024 * 1024);
5546
+ const length = fs5.statSync(rowsPath).size;
5547
+ let position = 0;
5548
+ while (position < length) {
5549
+ const count = fs5.readSync(rowsFd, chunk, 0, Math.min(chunk.length, length - position), position);
5550
+ if (count <= 0) throw new Error("XlsxUpdater: failed to read staged rows.");
5551
+ fs5.writeSync(fd, chunk, 0, count);
5552
+ position += count;
5553
+ }
5554
+ } finally {
5555
+ fs5.closeSync(rowsFd);
5556
+ }
5557
+ };
5558
+ try {
5559
+ for (const replacement of replacements) {
5560
+ const startByte = Buffer.byteLength(text.slice(0, replacement.start), "utf8");
5561
+ const endByte = Buffer.byteLength(text.slice(0, replacement.end), "utf8");
5562
+ if (startByte < cursor) throw new Error("XlsxUpdater: overlapping worksheet replacements.");
5563
+ writeBuffer(source.subarray(cursor, startByte));
5564
+ if (replacement.data) writeBuffer(replacement.data);
5565
+ if (replacement.rows) copyRows();
5566
+ if (replacement.suffix) writeBuffer(replacement.suffix);
5567
+ cursor = endByte;
5568
+ }
5569
+ writeBuffer(source.subarray(cursor));
5570
+ } finally {
5571
+ fs5.closeSync(fd);
5572
+ }
5573
+ }
4359
5574
  // ------------------------------------------------------------------
4360
5575
  // Pivot table integration
4361
5576
  // ------------------------------------------------------------------
@@ -4384,93 +5599,251 @@ var XlsxUpdater = class {
4384
5599
  }
4385
5600
  }
4386
5601
  _addRefreshOnLoadToPivotTables() {
4387
- for (const path3 of this.pivotTableDefPaths) {
4388
- const entry = this.zip.getEntry(path3);
5602
+ for (const path9 of this.pivotTableDefPaths) {
5603
+ const entry = this.zip.getEntry(path9);
4389
5604
  if (!entry) continue;
4390
5605
  let xml = entry.getData().toString("utf8");
4391
5606
  const rootRe = /<pivotTableDefinition\b[^>]*>/;
4392
5607
  const rootMatch = rootRe.exec(xml);
4393
- if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
4394
- const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
4395
- xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
4396
- this.zip.updateFile(path3, Buffer.from(xml, "utf8"));
4397
- }
4398
- }
4399
- };
4400
-
4401
- // src/XlsbUpdater.ts
4402
- var fs4 = __toESM(require("fs"));
4403
- var import_adm_zip3 = __toESM(require("adm-zip"));
4404
-
4405
- // src/biff12Utils.ts
4406
- function readVlq(buf, pos) {
4407
- let value = 0;
4408
- let shift = 0;
4409
- let byte;
4410
- do {
4411
- byte = buf[pos++];
4412
- value |= (byte & 127) << shift;
4413
- shift += 7;
4414
- } while ((byte & 128) !== 0);
4415
- return { value, nextPos: pos };
4416
- }
4417
- function vlqBytes(value) {
4418
- const out = [];
4419
- while (value >= 128) {
4420
- out.push(value & 127 | 128);
4421
- value >>>= 7;
4422
- }
4423
- out.push(value & 127);
4424
- return out;
4425
- }
4426
- function readRecord(buf, pos, rec) {
4427
- const idRes = readVlq(buf, pos);
4428
- if (idRes.nextPos > buf.length) return false;
4429
- const lenRes = readVlq(buf, idRes.nextPos);
4430
- if (lenRes.nextPos + lenRes.value > buf.length) return false;
4431
- rec.headerStart = pos;
4432
- rec.dataStart = lenRes.nextPos;
4433
- rec.dataEnd = lenRes.nextPos + lenRes.value;
4434
- rec.id = idRes.value;
4435
- rec.len = lenRes.value;
4436
- return true;
4437
- }
4438
- function buildRecord(id, payload) {
4439
- const idBytes = vlqBytes(id);
4440
- const lenBytes = vlqBytes(payload.length);
4441
- const header = Buffer.alloc(idBytes.length + lenBytes.length);
4442
- idBytes.forEach((b, i) => header[i] = b);
4443
- lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
4444
- return Buffer.concat([header, payload]);
4445
- }
4446
- function readUtf16(buf, start, charCount) {
4447
- const end = start + charCount * 2;
4448
- if (end > buf.length) return "";
4449
- return buf.toString("utf16le", start, end);
4450
- }
4451
- function parseSharedStringsBin(buf) {
4452
- const values = [];
4453
- let total = 0;
4454
- let unique = 0;
4455
- let endSstOffset = buf.length;
4456
- const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4457
- let pos = 0;
4458
- while (readRecord(buf, pos, rec)) {
4459
- pos = rec.dataEnd;
4460
- if (rec.id === 159) {
4461
- total = buf.readUInt32LE(rec.dataStart);
4462
- unique = buf.readUInt32LE(rec.dataStart + 4);
4463
- } else if (rec.id === 19) {
4464
- const cch = buf.readUInt32LE(rec.dataStart + 1);
4465
- values.push(readUtf16(buf, rec.dataStart + 5, cch));
4466
- } else if (rec.id === 160) {
4467
- endSstOffset = rec.headerStart;
5608
+ if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
5609
+ const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
5610
+ xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
5611
+ this.zip.updateFile(path9, Buffer.from(xml, "utf8"));
4468
5612
  }
4469
5613
  }
4470
- return { values, total, unique, endSstOffset };
4471
- }
5614
+ /**
5615
+ * Remove the stale calculation chain (xl/calcChain.xml). Excel rebuilds it
5616
+ * on open; keeping stale <c> entries for the replaced sheet would force a
5617
+ * repair prompt. Removes the part, its Content_Types override and its
5618
+ * workbook rels entry (xlspy parity).
5619
+ */
5620
+ _removeCalcChain() {
5621
+ const calcPath = "xl/calcChain.xml";
5622
+ if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) {
5623
+ this._ensureCalcPrFullCalc();
5624
+ return;
5625
+ }
5626
+ try {
5627
+ this.zip.deleteFile(calcPath);
5628
+ } catch {
5629
+ }
5630
+ const stagedCalc = this.stagedParts.get(calcPath);
5631
+ if (stagedCalc) {
5632
+ this.stagedParts.delete(calcPath);
5633
+ try {
5634
+ fs5.rmSync(stagedCalc, { force: true });
5635
+ } catch {
5636
+ }
5637
+ }
5638
+ const ctEntry = this.zip.getEntry("[Content_Types].xml");
5639
+ if (ctEntry || this.stagedParts.has("[Content_Types].xml")) {
5640
+ try {
5641
+ let ct = this._entryData("[Content_Types].xml").toString("utf8");
5642
+ const before = ct;
5643
+ ct = ct.replace(/<Override\b[^>]*PartName="\/xl\/calcChain\.xml"[^>]*\/?>/gi, "");
5644
+ if (ct !== before) {
5645
+ this._writePartText("[Content_Types].xml", ct);
5646
+ }
5647
+ } catch {
5648
+ }
5649
+ }
5650
+ const relsPath = "xl/_rels/workbook.xml.rels";
5651
+ if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
5652
+ try {
5653
+ const rels = this._entryData(relsPath).toString("utf8");
5654
+ const relTagRe = /<Relationship\b[^>]*>/g;
5655
+ let m;
5656
+ let out = "";
5657
+ let last = 0;
5658
+ let changed = false;
5659
+ while ((m = relTagRe.exec(rels)) !== null) {
5660
+ const attrs = parseTagAttributes(m[0]);
5661
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
5662
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
5663
+ const isCalcChain = type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.xml";
5664
+ if (isCalcChain) {
5665
+ out += rels.substring(last, m.index);
5666
+ last = m.index + m[0].length;
5667
+ changed = true;
5668
+ }
5669
+ }
5670
+ if (changed) {
5671
+ out += rels.substring(last);
5672
+ this._writePartText(relsPath, out);
5673
+ }
5674
+ } catch {
5675
+ }
5676
+ }
5677
+ this._ensureCalcPrFullCalc();
5678
+ }
5679
+ _writePartText(name, text) {
5680
+ if (this.stagedParts.has(name)) {
5681
+ fs5.writeFileSync(this.stagedParts.get(name), text, "utf8");
5682
+ } else {
5683
+ this.zip.updateFile(name, Buffer.from(text, "utf8"));
5684
+ }
5685
+ }
5686
+ _ensureCalcPrFullCalc() {
5687
+ try {
5688
+ const wbPath = "xl/workbook.xml";
5689
+ if (!this.zip.getEntry(wbPath) && !this.stagedParts.has(wbPath)) return;
5690
+ let xml = this._entryData(wbPath).toString("utf8");
5691
+ const calcPrRe = /<(?:[A-Za-z_][\w.-]*:)?calcPr\b[^>]*>/i;
5692
+ const match = calcPrRe.exec(xml);
5693
+ if (match && match.index !== void 0) {
5694
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(match[0], "fullCalcOnLoad", "1");
5695
+ xml = xml.substring(0, match.index) + newTag + xml.substring(match.index + match[0].length);
5696
+ } else {
5697
+ const insertAt = _XlsxUpdater._calcPrInsertPosition(xml);
5698
+ if (insertAt < 0) return;
5699
+ xml = xml.substring(0, insertAt) + '<calcPr fullCalcOnLoad="1"/>' + xml.substring(insertAt);
5700
+ }
5701
+ this._writePartText(wbPath, xml);
5702
+ } catch {
5703
+ }
5704
+ }
5705
+ /**
5706
+ * Insertion offset for a missing `<calcPr>`. The element follows
5707
+ * sheets/externalReferences/definedNames in the workbook schema, so insert
5708
+ * after the last of those that exists instead of right after `<workbook>`.
5709
+ */
5710
+ static _calcPrInsertPosition(xml) {
5711
+ const prefixed = "(?:[A-Za-z_][\\w.-]*:)?";
5712
+ const candidates = [
5713
+ new RegExp(`</${prefixed}sheets\\s*>`, "i"),
5714
+ new RegExp(`<${prefixed}externalReferences\\b[^>]*/>`, "i"),
5715
+ new RegExp(`<${prefixed}externalReferences\\b[^>]*>[\\s\\S]*?</${prefixed}externalReferences\\s*>`, "i"),
5716
+ new RegExp(`<${prefixed}definedNames\\b[^>]*/>`, "i"),
5717
+ new RegExp(`<${prefixed}definedNames\\b[^>]*>[\\s\\S]*?</${prefixed}definedNames\\s*>`, "i")
5718
+ ];
5719
+ let insertAt = -1;
5720
+ for (const re of candidates) {
5721
+ const m = re.exec(xml);
5722
+ if (m && m.index !== void 0) {
5723
+ insertAt = Math.max(insertAt, m.index + m[0].length);
5724
+ }
5725
+ }
5726
+ if (insertAt >= 0) return insertAt;
5727
+ const close = new RegExp(`</${prefixed}workbook\\s*>`, "i").exec(xml);
5728
+ if (close && close.index !== void 0) return close.index;
5729
+ const root = new RegExp(`<${prefixed}workbook\\b[^>]*>`, "i").exec(xml);
5730
+ if (!root || root.index === void 0) return -1;
5731
+ return root.index + root[0].length;
5732
+ }
5733
+ /**
5734
+ * Update ListObject table ranges (xl/tables/*.xml) for the replaced sheet.
5735
+ * Only tables anchored at A1 are updated to the new dimension; tables
5736
+ * elsewhere are left untouched (moving them would corrupt the sheet).
5737
+ * Empty replacements (dimRef "A1") do not shrink multi-column tables.
5738
+ */
5739
+ _updateTableRefs(sheetName, dimRef) {
5740
+ const sheetPath = this.sheetNameToPath.get(sheetName);
5741
+ if (!sheetPath) return;
5742
+ const slash = sheetPath.lastIndexOf("/");
5743
+ const dir = slash >= 0 ? sheetPath.substring(0, slash + 1) : "";
5744
+ const base = slash >= 0 ? sheetPath.substring(slash + 1) : sheetPath;
5745
+ const sheetRelsPath = `${dir}_rels/${base}.rels`;
5746
+ const sheetRelsEntry = this.zip.getEntry(sheetRelsPath);
5747
+ const hasStagedRels = this.stagedParts.has(sheetRelsPath);
5748
+ if (!sheetRelsEntry && !hasStagedRels) return;
5749
+ const tableTargets = [];
5750
+ try {
5751
+ const relsXml = this._entryData(sheetRelsPath).toString("utf8");
5752
+ const relTagRe = /<Relationship\b[^>]*>/g;
5753
+ let m;
5754
+ while ((m = relTagRe.exec(relsXml)) !== null) {
5755
+ const attrs = parseTagAttributes(m[0]);
5756
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
5757
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
5758
+ if (!type.includes("/table") || !target) continue;
5759
+ let tablePath;
5760
+ if (target.startsWith("/")) {
5761
+ tablePath = resolveRelTarget(target);
5762
+ } else {
5763
+ const combined = dir + target;
5764
+ const parts = [];
5765
+ for (const seg of combined.split("/")) {
5766
+ if (seg === "..") parts.pop();
5767
+ else if (seg !== "." && seg !== "") parts.push(seg);
5768
+ }
5769
+ tablePath = parts.join("/");
5770
+ }
5771
+ tableTargets.push(tablePath);
5772
+ }
5773
+ } catch {
5774
+ return;
5775
+ }
5776
+ if (tableTargets.length === 0) return;
5777
+ const tables = [];
5778
+ for (const tablePath of tableTargets) {
5779
+ try {
5780
+ const hasEntry = !!this.zip.getEntry(tablePath);
5781
+ const hasStaged = this.stagedParts.has(tablePath);
5782
+ if (!hasEntry && !hasStaged) continue;
5783
+ const xml = this._entryData(tablePath).toString("utf8");
5784
+ const tableTagRe = /<(?:[A-Za-z_][\w.-]*:)?table\b[^>]*>/i;
5785
+ const tableMatch = tableTagRe.exec(xml);
5786
+ if (!tableMatch || tableMatch.index === void 0) continue;
5787
+ const refMatch = /\bref\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tableMatch[0]);
5788
+ const ref = refMatch ? refMatch[2] ?? refMatch[3] ?? "" : "";
5789
+ tables.push({
5790
+ path: tablePath,
5791
+ xml,
5792
+ tag: tableMatch[0],
5793
+ tagIndex: tableMatch.index,
5794
+ ref,
5795
+ range: parseCellRange(ref),
5796
+ staged: hasStaged
5797
+ });
5798
+ } catch {
5799
+ }
5800
+ }
5801
+ if (tables.length === 0) return;
5802
+ const dimRange = parseCellRange(dimRef);
5803
+ if (!dimRange) return;
5804
+ for (const table of tables) {
5805
+ try {
5806
+ const origRange = table.range;
5807
+ if (!origRange) continue;
5808
+ if (origRange.r1 !== 1 || origRange.c1 !== 0) continue;
5809
+ if (dimRef === "A1" && !/^A1$/i.test(table.ref)) continue;
5810
+ const origCols = origRange.c2 - origRange.c1 + 1;
5811
+ const newCols = dimRange.c2 - dimRange.c1 + 1;
5812
+ if (newCols !== origCols) continue;
5813
+ let lastRow = dimRange.r2;
5814
+ for (const other of tables) {
5815
+ if (other === table || !other.range) continue;
5816
+ if (other.range.r1 <= 1) continue;
5817
+ if (other.range.c1 > origRange.c2 || other.range.c2 < origRange.c1) continue;
5818
+ lastRow = Math.min(lastRow, other.range.r1 - 1);
5819
+ }
5820
+ if (lastRow < 1) continue;
5821
+ const newRef = origRange.c2 === 0 && lastRow === 1 ? "A1" : `A1:${columnIndexToLetter(origRange.c2)}${lastRow}`;
5822
+ let xml = table.xml;
5823
+ const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(table.tag, "ref", newRef);
5824
+ xml = xml.substring(0, table.tagIndex) + newTag + xml.substring(table.tagIndex + table.tag.length);
5825
+ const afRe = /<(?:[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i;
5826
+ const afMatch = afRe.exec(xml);
5827
+ if (afMatch && afMatch.index !== void 0) {
5828
+ const newAf = _XlsxUpdater._replaceOrAddXmlAttribute(afMatch[0], "ref", newRef);
5829
+ xml = xml.substring(0, afMatch.index) + newAf + xml.substring(afMatch.index + afMatch[0].length);
5830
+ }
5831
+ if (table.staged) {
5832
+ fs5.writeFileSync(this.stagedParts.get(table.path), xml, "utf8");
5833
+ } else {
5834
+ this.zip.updateFile(table.path, Buffer.from(xml, "utf8"));
5835
+ }
5836
+ } catch {
5837
+ }
5838
+ }
5839
+ }
5840
+ };
4472
5841
 
4473
5842
  // src/XlsbUpdater.ts
5843
+ var fs6 = __toESM(require("fs"));
5844
+ var os2 = __toESM(require("os"));
5845
+ var path7 = __toESM(require("path"));
5846
+ var import_adm_zip3 = __toESM(require("adm-zip"));
4474
5847
  var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
4475
5848
  var RK_INT_LOWER = -1 << 29;
4476
5849
  var RK_INT_UPPER = (1 << 29) - 1;
@@ -4478,7 +5851,9 @@ var XlsbUpdater = class {
4478
5851
  /**
4479
5852
  * @param path Path to an existing `.xlsb` file.
4480
5853
  */
4481
- constructor(path3) {
5854
+ constructor(filePath) {
5855
+ this.temporaryDirectory = null;
5856
+ this.stagedParts = /* @__PURE__ */ new Map();
4482
5857
  this.sheetNameToPath = /* @__PURE__ */ new Map();
4483
5858
  this.pivotCacheDefPaths = [];
4484
5859
  this.sharedStringsBuf = null;
@@ -4487,16 +5862,17 @@ var XlsbUpdater = class {
4487
5862
  this.sharedStringsUnique = 0;
4488
5863
  this.sharedStringsEndSst = 0;
4489
5864
  this.stringIndexMap = /* @__PURE__ */ new Map();
5865
+ this.sharedStringsDirty = false;
4490
5866
  // ------------------------------------------------------------------
4491
5867
  // Shared strings
4492
5868
  // ------------------------------------------------------------------
4493
5869
  this._appendCount = 0;
4494
- this.sourcePath = path3;
5870
+ this.sourcePath = filePath;
4495
5871
  this._oaEpoch = Date.UTC(1899, 11, 30);
4496
- if (!fs4.existsSync(path3)) {
4497
- throw new Error(`XlsbUpdater: file not found: ${path3}`);
5872
+ if (!fs6.existsSync(filePath)) {
5873
+ throw new Error(`XlsbUpdater: file not found: ${filePath}`);
4498
5874
  }
4499
- this.zip = new import_adm_zip3.default(path3);
5875
+ this.zip = new import_adm_zip3.default(filePath);
4500
5876
  if (!this.zip.getEntry("xl/workbook.bin")) {
4501
5877
  throw new Error(
4502
5878
  "XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
@@ -4527,8 +5903,15 @@ var XlsbUpdater = class {
4527
5903
  if (!entry) {
4528
5904
  throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
4529
5905
  }
4530
- const sheetBuf = entry.getData();
5906
+ const sheetBuf = this._entryData(sheetPath);
4531
5907
  this._ensureSharedStringsLoaded();
5908
+ if (this.sharedStringsBuf !== null) {
5909
+ this.sharedStringsTotal = Math.max(
5910
+ 0,
5911
+ this.sharedStringsTotal - this._sharedStringRefCount(sheetBuf)
5912
+ );
5913
+ this.sharedStringsDirty = true;
5914
+ }
4532
5915
  let dataStyles = /* @__PURE__ */ new Map();
4533
5916
  let headerStyles = /* @__PURE__ */ new Map();
4534
5917
  let dateXf = null;
@@ -4557,12 +5940,222 @@ var XlsbUpdater = class {
4557
5940
  }
4558
5941
  this._patchDimension(newSheet, lastRowIdx, lastCol);
4559
5942
  this.zip.updateFile(sheetPath, newSheet);
4560
- if (this.sharedStringsBuf !== null && this._appendCount > 0) {
5943
+ this._discardStagedPart(sheetPath);
5944
+ if (this.sharedStringsBuf !== null) {
4561
5945
  this._commitSharedStrings();
4562
5946
  }
4563
5947
  const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
4564
5948
  const colLast = lastCol;
4565
5949
  this._patchPivotCaches(sheetName, rwLast, colLast);
5950
+ this._removeCalcChain();
5951
+ }
5952
+ /** Replace worksheet rows from a one-pass source. */
5953
+ async replaceSheetDataStream(sheetName, rows, options = {}) {
5954
+ const { headers, styleFallback = "inherit" } = options;
5955
+ if (styleFallback !== "inherit" && styleFallback !== "general") {
5956
+ throw new Error("XlsbUpdater: styleFallback must be 'inherit' or 'general'.");
5957
+ }
5958
+ const sheetPath = this.sheetNameToPath.get(sheetName);
5959
+ if (!sheetPath) {
5960
+ throw new Error(`XlsbUpdater: sheet "${sheetName}" not found in the workbook.`);
5961
+ }
5962
+ const entry = this.zip.getEntry(sheetPath);
5963
+ if (!entry) {
5964
+ throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
5965
+ }
5966
+ const oldStage = this.stagedParts.get(sheetPath);
5967
+ const beforeValuesLength = this.sharedStringsValues.length;
5968
+ const beforeAppendCount = this._appendCount;
5969
+ const beforeSharedStringsTotal = this.sharedStringsTotal;
5970
+ const beforeSharedStringsUnique = this.sharedStringsUnique;
5971
+ const beforeSharedStringsEndSst = this.sharedStringsEndSst;
5972
+ const beforeSharedStringsDirty = this.sharedStringsDirty;
5973
+ const beforeStringIndexMap = new Map(this.stringIndexMap);
5974
+ const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.bin");
5975
+ const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.bin") ? Buffer.from(this._entryData("xl/sharedStrings.bin")) : null;
5976
+ const beforeSharedStringsBuf = this.sharedStringsBuf ? Buffer.from(this.sharedStringsBuf) : null;
5977
+ const oldSheet = this._entryData(sheetPath);
5978
+ const rowsPath = this._temporaryPath(".rows");
5979
+ const outputPath = this._temporaryPath(".bin");
5980
+ const createdParts = [rowsPath, outputPath];
5981
+ let output = null;
5982
+ try {
5983
+ this._ensureSharedStringsLoaded();
5984
+ if (this.sharedStringsBuf !== null) {
5985
+ this.sharedStringsTotal = Math.max(
5986
+ 0,
5987
+ this.sharedStringsTotal - this._sharedStringRefCount(oldSheet)
5988
+ );
5989
+ this.sharedStringsDirty = true;
5990
+ }
5991
+ let dataStyles = /* @__PURE__ */ new Map();
5992
+ let headerStyles = /* @__PURE__ */ new Map();
5993
+ let dateXf = null;
5994
+ if (styleFallback !== "general") {
5995
+ const collected = this._collectStyles(oldSheet);
5996
+ dataStyles = collected.dataStyles;
5997
+ headerStyles = collected.headerStyles;
5998
+ dateXf = this._findDateXf();
5999
+ }
6000
+ const rowOutput = fs6.createWriteStream(rowsPath);
6001
+ output = rowOutput;
6002
+ let outputError = null;
6003
+ rowOutput.on("error", (error) => {
6004
+ outputError = error;
6005
+ });
6006
+ let writtenBytes = 0;
6007
+ let lastKeptEnd = 0;
6008
+ let width = (headers == null ? void 0 : headers.length) ?? 0;
6009
+ let dataRowsSeen = 0;
6010
+ let keptDataRows = 0;
6011
+ let pendingEmptyWidth = 0;
6012
+ let nextRow = 0;
6013
+ const rowHeaderOffsets = [];
6014
+ const write = async (data) => {
6015
+ if (outputError) throw outputError;
6016
+ writtenBytes += data.length;
6017
+ if (rowOutput.write(data)) return;
6018
+ await new Promise((resolve6, reject) => {
6019
+ const onDrain = () => {
6020
+ cleanup();
6021
+ resolve6();
6022
+ };
6023
+ const onError = (error) => {
6024
+ cleanup();
6025
+ reject(error);
6026
+ };
6027
+ const cleanup = () => {
6028
+ rowOutput.off("drain", onDrain);
6029
+ rowOutput.off("error", onError);
6030
+ };
6031
+ rowOutput.once("drain", onDrain);
6032
+ rowOutput.once("error", onError);
6033
+ });
6034
+ };
6035
+ const writeRow = async (rowNumber, cells) => {
6036
+ rowHeaderOffsets.push(writtenBytes);
6037
+ const header = Buffer.alloc(27);
6038
+ header[0] = 0;
6039
+ header[1] = 25;
6040
+ header.writeInt32LE(rowNumber, 2);
6041
+ header[10] = 44;
6042
+ header[11] = 1;
6043
+ header[15] = 1;
6044
+ header.writeInt32LE(0, 19);
6045
+ header.writeInt32LE(0, 23);
6046
+ await write(header);
6047
+ for (const cell of cells) await write(cell);
6048
+ };
6049
+ if (headers !== void 0) {
6050
+ await writeRow(0, headers.map((value, column) => this._stringCellBytes(column, styleFallback === "general" ? 0 : headerStyles.get(column) ?? 0, value)));
6051
+ lastKeptEnd = writtenBytes;
6052
+ nextRow = 1;
6053
+ }
6054
+ for await (const sourceRow of rows) {
6055
+ if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
6056
+ throw new TypeError("Each row must be an iterable of cells, not text");
6057
+ }
6058
+ const row = Array.from(sourceRow);
6059
+ const cells = row.map((value, column) => value === null || value === void 0 ? null : this._valueCellBytes(value, column, dataStyles, dateXf)).filter((cell) => cell !== null);
6060
+ await writeRow(nextRow, cells);
6061
+ dataRowsSeen++;
6062
+ nextRow++;
6063
+ if (row.every((value) => value === null || value === void 0)) {
6064
+ pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
6065
+ } else {
6066
+ width = Math.max(width, pendingEmptyWidth, row.length);
6067
+ pendingEmptyWidth = 0;
6068
+ keptDataRows = dataRowsSeen;
6069
+ lastKeptEnd = writtenBytes;
6070
+ }
6071
+ }
6072
+ await new Promise((resolve6, reject) => {
6073
+ rowOutput.once("close", resolve6);
6074
+ rowOutput.once("error", reject);
6075
+ rowOutput.end();
6076
+ });
6077
+ if (outputError) throw outputError;
6078
+ const rowsFd = fs6.openSync(rowsPath, "r+");
6079
+ try {
6080
+ fs6.ftruncateSync(rowsFd, lastKeptEnd);
6081
+ } finally {
6082
+ fs6.closeSync(rowsFd);
6083
+ }
6084
+ const lastCol = Math.max(0, width - 1);
6085
+ const rowPatchFd = fs6.openSync(rowsPath, "r+");
6086
+ try {
6087
+ const lastColBuffer = Buffer.alloc(4);
6088
+ lastColBuffer.writeInt32LE(lastCol, 0);
6089
+ for (const offset of rowHeaderOffsets) {
6090
+ if (offset >= lastKeptEnd) continue;
6091
+ fs6.writeSync(rowPatchFd, lastColBuffer, 0, lastColBuffer.length, offset + 23);
6092
+ }
6093
+ } finally {
6094
+ fs6.closeSync(rowPatchFd);
6095
+ }
6096
+ const { rowsStart, rowsEnd, autoFilterRanges } = this._findRowsRegion(oldSheet);
6097
+ const oldLength = oldSheet.length;
6098
+ const newRowsLength = fs6.statSync(rowsPath).size;
6099
+ const delta = newRowsLength - (rowsEnd - rowsStart);
6100
+ const outputFd = fs6.openSync(outputPath, "w");
6101
+ try {
6102
+ fs6.writeSync(outputFd, oldSheet, 0, rowsStart, null);
6103
+ const rowsFdRead = fs6.openSync(rowsPath, "r");
6104
+ try {
6105
+ const chunk = Buffer.alloc(1024 * 1024);
6106
+ let position = 0;
6107
+ while (position < newRowsLength) {
6108
+ const count = fs6.readSync(rowsFdRead, chunk, 0, Math.min(chunk.length, newRowsLength - position), position);
6109
+ if (count <= 0) throw new Error("XlsbUpdater: failed to read staged rows.");
6110
+ fs6.writeSync(outputFd, chunk, 0, count);
6111
+ position += count;
6112
+ }
6113
+ } finally {
6114
+ fs6.closeSync(rowsFdRead);
6115
+ }
6116
+ fs6.writeSync(outputFd, oldSheet, rowsEnd, oldLength - rowsEnd, rowsStart + newRowsLength);
6117
+ } finally {
6118
+ fs6.closeSync(outputFd);
6119
+ }
6120
+ this._patchSheetMetadataFile(
6121
+ outputPath,
6122
+ oldSheet,
6123
+ rowsEnd,
6124
+ delta,
6125
+ autoFilterRanges,
6126
+ Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
6127
+ lastCol
6128
+ );
6129
+ this._stagePart(sheetPath, outputPath);
6130
+ this._commitSharedStrings();
6131
+ this._patchPivotCaches(
6132
+ sheetName,
6133
+ Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
6134
+ lastCol
6135
+ );
6136
+ this._removeCalcChain();
6137
+ if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
6138
+ createdParts.splice(createdParts.indexOf(outputPath), 1);
6139
+ } catch (error) {
6140
+ this.sharedStringsValues.length = beforeValuesLength;
6141
+ this._appendCount = beforeAppendCount;
6142
+ this.sharedStringsTotal = beforeSharedStringsTotal;
6143
+ this.sharedStringsUnique = beforeSharedStringsUnique;
6144
+ this.sharedStringsEndSst = beforeSharedStringsEndSst;
6145
+ this.sharedStringsDirty = beforeSharedStringsDirty;
6146
+ this.sharedStringsBuf = beforeSharedStringsBuf;
6147
+ this.stringIndexMap.clear();
6148
+ for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
6149
+ if (beforeSharedStringsBytes) {
6150
+ this.zip.updateFile("xl/sharedStrings.bin", beforeSharedStringsBytes);
6151
+ }
6152
+ if (oldStage) this.stagedParts.set(sheetPath, oldStage);
6153
+ else this.stagedParts.delete(sheetPath);
6154
+ throw error;
6155
+ } finally {
6156
+ if (output && !output.closed) output.destroy();
6157
+ for (const temporaryPath of createdParts) this._removeTemporaryFile(temporaryPath);
6158
+ }
4566
6159
  }
4567
6160
  /**
4568
6161
  * Write the updated workbook to disk.
@@ -4570,12 +6163,89 @@ var XlsbUpdater = class {
4570
6163
  */
4571
6164
  save(outputPath) {
4572
6165
  const target = outputPath ?? this.sourcePath;
4573
- fs4.writeFileSync(target, this.zip.toBuffer());
6166
+ this._materializeStagedParts();
6167
+ writeBufferAtomically(this.zip.toBuffer(), target);
6168
+ }
6169
+ /** Save staged streaming replacements without materialising the ZIP. */
6170
+ async saveStreaming(outputPath) {
6171
+ const target = outputPath ?? this.sourcePath;
6172
+ await writeAdmZipStreaming(this.zip, this.stagedParts, target);
6173
+ this._materializeStagedParts();
4574
6174
  }
4575
6175
  /** The updated workbook as an in-memory ZIP buffer. */
4576
6176
  toBuffer() {
6177
+ this._materializeStagedParts();
4577
6178
  return this.zip.toBuffer();
4578
6179
  }
6180
+ /**
6181
+ * Release the staging directory and any unsaved staged parts.
6182
+ * The updater remains usable; a new staging directory is created lazily
6183
+ * on the next streaming replacement.
6184
+ */
6185
+ dispose() {
6186
+ for (const stagedPath of this.stagedParts.values()) {
6187
+ try {
6188
+ fs6.rmSync(stagedPath, { force: true });
6189
+ } catch {
6190
+ }
6191
+ }
6192
+ this.stagedParts.clear();
6193
+ if (this.temporaryDirectory !== null) {
6194
+ try {
6195
+ fs6.rmSync(this.temporaryDirectory, { recursive: true, force: true });
6196
+ } catch {
6197
+ }
6198
+ this.temporaryDirectory = null;
6199
+ }
6200
+ }
6201
+ /** Support `using` declarations for automatic staging cleanup. */
6202
+ [Symbol.dispose]() {
6203
+ this.dispose();
6204
+ }
6205
+ _ensureTemporaryDirectory() {
6206
+ if (this.temporaryDirectory === null) {
6207
+ this.temporaryDirectory = fs6.mkdtempSync(path7.join(os2.tmpdir(), "spreadsheet-updater-"));
6208
+ } else {
6209
+ try {
6210
+ fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
6211
+ } catch {
6212
+ }
6213
+ }
6214
+ return this.temporaryDirectory;
6215
+ }
6216
+ _temporaryPath(suffix) {
6217
+ return path7.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
6218
+ }
6219
+ _removeTemporaryFile(filePath) {
6220
+ try {
6221
+ fs6.rmSync(filePath);
6222
+ } catch (error) {
6223
+ if (error.code !== "ENOENT") throw error;
6224
+ }
6225
+ }
6226
+ _entryData(name) {
6227
+ const stagedPath = this.stagedParts.get(name);
6228
+ if (stagedPath) return fs6.readFileSync(stagedPath);
6229
+ const entry = this.zip.getEntry(name);
6230
+ if (!entry) throw new Error(`XlsbUpdater: ZIP member missing: ${name}`);
6231
+ return entry.getData();
6232
+ }
6233
+ _stagePart(name, filePath) {
6234
+ this.stagedParts.set(name, filePath);
6235
+ }
6236
+ _discardStagedPart(name) {
6237
+ const stagedPath = this.stagedParts.get(name);
6238
+ if (!stagedPath) return;
6239
+ this._removeTemporaryFile(stagedPath);
6240
+ this.stagedParts.delete(name);
6241
+ }
6242
+ _materializeStagedParts() {
6243
+ for (const [name, stagedPath] of this.stagedParts) {
6244
+ this.zip.updateFile(name, fs6.readFileSync(stagedPath));
6245
+ this._removeTemporaryFile(stagedPath);
6246
+ }
6247
+ this.stagedParts.clear();
6248
+ }
4579
6249
  // ------------------------------------------------------------------
4580
6250
  // Workbook structure
4581
6251
  // ------------------------------------------------------------------
@@ -4584,10 +6254,15 @@ var XlsbUpdater = class {
4584
6254
  const rIdToTarget = {};
4585
6255
  if (relsEntry) {
4586
6256
  const rels = relsEntry.getData().toString("utf8");
4587
- const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
4588
- let match;
4589
- while ((match = relRegex.exec(rels)) !== null) {
4590
- rIdToTarget[match[1]] = match[2];
6257
+ const relTagRe = /<Relationship\b[^>]*>/g;
6258
+ let tagMatch;
6259
+ while ((tagMatch = relTagRe.exec(rels)) !== null) {
6260
+ const attrs = parseTagAttributes(tagMatch[0]);
6261
+ const id = attrs["Id"] ?? attrs["id"];
6262
+ const target = attrs["Target"] ?? attrs["target"];
6263
+ if (id !== void 0 && target !== void 0) {
6264
+ rIdToTarget[id] = target;
6265
+ }
4591
6266
  }
4592
6267
  }
4593
6268
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
@@ -4607,12 +6282,24 @@ var XlsbUpdater = class {
4607
6282
  o += 4;
4608
6283
  const name = readUtf16(wbBuf, o, nameLen);
4609
6284
  let target = rIdToTarget[rId] ?? "";
4610
- if (target.startsWith("/")) target = target.substring(1);
4611
- else if (!target.startsWith("xl/")) target = "xl/" + target;
6285
+ target = resolveRelTarget(target);
4612
6286
  if (target) {
4613
6287
  this.sheetNameToPath.set(name, target);
4614
6288
  }
4615
6289
  }
6290
+ try {
6291
+ const rec1904 = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
6292
+ let p1904 = 0;
6293
+ while (readRecord(wbBuf, p1904, rec1904)) {
6294
+ if (rec1904.id === 153 && rec1904.len >= 4) {
6295
+ const is1904 = (wbBuf[rec1904.dataStart] & 1) !== 0;
6296
+ this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
6297
+ break;
6298
+ }
6299
+ p1904 = rec1904.dataEnd;
6300
+ }
6301
+ } catch {
6302
+ }
4616
6303
  }
4617
6304
  for (const e of this.zip.getEntries()) {
4618
6305
  if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
@@ -4625,11 +6312,11 @@ var XlsbUpdater = class {
4625
6312
  return;
4626
6313
  }
4627
6314
  const entry = this.zip.getEntry("xl/sharedStrings.bin");
4628
- if (!entry) {
6315
+ if (!entry && !this.stagedParts.has("xl/sharedStrings.bin")) {
4629
6316
  this.sharedStringsBuf = null;
4630
6317
  return;
4631
6318
  }
4632
- this.sharedStringsBuf = entry.getData();
6319
+ this.sharedStringsBuf = this._entryData("xl/sharedStrings.bin");
4633
6320
  const parsed = parseSharedStringsBin(this.sharedStringsBuf);
4634
6321
  this.sharedStringsValues = parsed.values;
4635
6322
  this.sharedStringsTotal = parsed.total;
@@ -4641,9 +6328,10 @@ var XlsbUpdater = class {
4641
6328
  }
4642
6329
  });
4643
6330
  this._appendCount = 0;
6331
+ this.sharedStringsDirty = false;
4644
6332
  }
4645
6333
  _commitSharedStrings() {
4646
- if (this.sharedStringsBuf === null || this._appendCount === 0) return;
6334
+ if (this.sharedStringsBuf === null || !this.sharedStringsDirty) return;
4647
6335
  const originalValuesLength = this.sharedStringsValues.length - this._appendCount;
4648
6336
  let newItems = Buffer.alloc(0);
4649
6337
  for (let i = originalValuesLength; i < this.sharedStringsValues.length; i++) {
@@ -4669,6 +6357,9 @@ var XlsbUpdater = class {
4669
6357
  pos = rec.dataEnd;
4670
6358
  }
4671
6359
  this.zip.updateFile("xl/sharedStrings.bin", out);
6360
+ this.sharedStringsBuf = out;
6361
+ this._appendCount = 0;
6362
+ this.sharedStringsDirty = false;
4672
6363
  }
4673
6364
  // ------------------------------------------------------------------
4674
6365
  // Styles
@@ -4717,27 +6408,15 @@ var XlsbUpdater = class {
4717
6408
  _findDateXf() {
4718
6409
  const entry = this.zip.getEntry("xl/styles.bin");
4719
6410
  if (!entry) return null;
4720
- const stylesBuf = entry.getData();
4721
- const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4722
- let pos = 0;
4723
- let inCellXfs = false;
4724
- let xfIndex = 0;
4725
- while (readRecord(stylesBuf, pos, rec)) {
4726
- pos = rec.dataEnd;
4727
- if (rec.id === 617) {
4728
- inCellXfs = true;
4729
- xfIndex = 0;
4730
- continue;
4731
- }
4732
- if (rec.id === 618) {
4733
- inCellXfs = false;
4734
- continue;
4735
- }
4736
- if (inCellXfs && rec.id === 47 && rec.len >= 4) {
4737
- const numFmtId = stylesBuf.readUInt16LE(rec.dataStart + 2);
4738
- const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47;
4739
- if (isDate) return xfIndex;
4740
- xfIndex++;
6411
+ const reader = new BiffReaderWriter(entry.getData());
6412
+ while (reader.readStyles()) {
6413
+ }
6414
+ const numFmtIds = reader._xfIndexToNumFmtId;
6415
+ const customDates = reader._customNumFmts;
6416
+ for (let i = 0; i < numFmtIds.length; i++) {
6417
+ const numFmtId = numFmtIds[i];
6418
+ if (numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || customDates.has(numFmtId)) {
6419
+ return i;
4741
6420
  }
4742
6421
  }
4743
6422
  return null;
@@ -4817,7 +6496,7 @@ var XlsbUpdater = class {
4817
6496
  return buildRecord(4, payload);
4818
6497
  }
4819
6498
  if (val instanceof Date) {
4820
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
6499
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
4821
6500
  if (Number.isFinite(oaDate)) {
4822
6501
  const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
4823
6502
  const payload = Buffer.alloc(16);
@@ -4851,12 +6530,51 @@ var XlsbUpdater = class {
4851
6530
  this._appendCount++;
4852
6531
  }
4853
6532
  this.sharedStringsTotal++;
6533
+ this.sharedStringsDirty = true;
4854
6534
  const payload = Buffer.alloc(12);
4855
6535
  payload.writeUInt32LE(col, 0);
4856
6536
  payload.writeUInt32LE(style, 4);
4857
6537
  payload.writeUInt32LE(index, 8);
4858
6538
  return buildRecord(7, payload);
4859
6539
  }
6540
+ _sharedStringRefCount(sheetBuf) {
6541
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
6542
+ let pos = 0;
6543
+ let count = 0;
6544
+ while (readRecord(sheetBuf, pos, rec)) {
6545
+ if (rec.id === 7) count++;
6546
+ pos = rec.dataEnd;
6547
+ }
6548
+ return count;
6549
+ }
6550
+ _patchSheetMetadataFile(filePath, originalSheet, rowsEnd, delta, autoFilterRanges, lastRowIdx, lastCol) {
6551
+ const fd = fs6.openSync(filePath, "r+");
6552
+ try {
6553
+ const patchInt32 = (offset, value) => {
6554
+ const bytes = Buffer.alloc(4);
6555
+ bytes.writeInt32LE(value, 0);
6556
+ fs6.writeSync(fd, bytes, 0, bytes.length, offset);
6557
+ };
6558
+ for (const filterOffset of autoFilterRanges) {
6559
+ const newOffset = filterOffset >= rowsEnd ? filterOffset + delta : filterOffset;
6560
+ patchInt32(newOffset + 4, lastRowIdx);
6561
+ patchInt32(newOffset + 12, lastCol);
6562
+ }
6563
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
6564
+ let pos = 0;
6565
+ while (readRecord(originalSheet, pos, rec)) {
6566
+ if (rec.id === 148 && rec.len >= 16) {
6567
+ const shift = rec.dataStart >= rowsEnd ? delta : 0;
6568
+ patchInt32(rec.dataStart + shift + 4, lastRowIdx);
6569
+ patchInt32(rec.dataStart + shift + 12, lastCol);
6570
+ break;
6571
+ }
6572
+ pos = rec.dataEnd;
6573
+ }
6574
+ } finally {
6575
+ fs6.closeSync(fd);
6576
+ }
6577
+ }
4860
6578
  // ------------------------------------------------------------------
4861
6579
  // Rows region detection
4862
6580
  // ------------------------------------------------------------------
@@ -4887,8 +6605,8 @@ var XlsbUpdater = class {
4887
6605
  return { rowsStart, rowsEnd, autoFilterRanges };
4888
6606
  }
4889
6607
  /**
4890
- * Keep the sheet dimension record (0x98) in sync with the new data.
4891
- * Excel uses its rwLast (payload offset 24) and colLast (payload offset 32)
6608
+ * Keep the sheet dimension record (0x94) in sync with the new data.
6609
+ * Excel uses its rwLast (payload offset 4) and colLast (payload offset 12)
4892
6610
  * fields to read the full row range; stale values make the last written row
4893
6611
  * invisible to Excel (and drop it from pivot refreshes).
4894
6612
  */
@@ -4896,9 +6614,9 @@ var XlsbUpdater = class {
4896
6614
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4897
6615
  let pos = 0;
4898
6616
  while (readRecord(sheetBuf, pos, rec)) {
4899
- if (rec.id === 152 && rec.len >= 36) {
4900
- sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 24);
4901
- sheetBuf.writeInt32LE(lastCol, rec.dataStart + 32);
6617
+ if (rec.id === 148 && rec.len >= 16) {
6618
+ sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 4);
6619
+ sheetBuf.writeInt32LE(lastCol, rec.dataStart + 12);
4902
6620
  return;
4903
6621
  }
4904
6622
  pos = rec.dataEnd;
@@ -4908,8 +6626,8 @@ var XlsbUpdater = class {
4908
6626
  // Pivot cache integration
4909
6627
  // ------------------------------------------------------------------
4910
6628
  _patchPivotCaches(sheetName, rwLast, colLast) {
4911
- for (const path3 of this.pivotCacheDefPaths) {
4912
- const entry = this.zip.getEntry(path3);
6629
+ for (const path9 of this.pivotCacheDefPaths) {
6630
+ const entry = this.zip.getEntry(path9);
4913
6631
  if (!entry) continue;
4914
6632
  const buf = Buffer.from(entry.getData());
4915
6633
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
@@ -4941,84 +6659,85 @@ var XlsbUpdater = class {
4941
6659
  }
4942
6660
  }
4943
6661
  if (patched) {
4944
- this.zip.updateFile(path3, buf);
6662
+ this.zip.updateFile(path9, buf);
4945
6663
  }
4946
6664
  }
4947
6665
  }
4948
- };
4949
-
4950
- // src/ExcelConverter.ts
4951
- var fs6 = __toESM(require("fs"));
4952
- var path2 = __toESM(require("path"));
4953
- var import_crypto2 = require("crypto");
4954
- var import_child_process = require("child_process");
4955
-
4956
- // src/atomicFile.ts
4957
- var fs5 = __toESM(require("fs"));
4958
- var import_crypto = require("crypto");
4959
- var realFileOperations = {
4960
- exists: (filePath) => fs5.existsSync(filePath),
4961
- rename: (sourcePath, destinationPath) => fs5.renameSync(sourcePath, destinationPath),
4962
- remove: (filePath) => fs5.rmSync(filePath)
4963
- };
4964
- function errorMessage(error) {
4965
- return error instanceof Error ? error.message : String(error);
4966
- }
4967
- function destinationExistsError(destinationPath) {
4968
- return new Error(
4969
- `Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
4970
- );
4971
- }
4972
- function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
4973
- const backupPath = `${destinationPath}.${(0, import_crypto.randomUUID)()}.replacement-backup`;
4974
- let backupCreated = false;
4975
- let installed = false;
4976
- if (operations.exists(destinationPath)) {
4977
- if (!overwrite) {
4978
- throw destinationExistsError(destinationPath);
4979
- }
4980
- operations.rename(destinationPath, backupPath);
4981
- backupCreated = true;
4982
- }
4983
- try {
6666
+ /**
6667
+ * Remove the stale calculation chain (xl/calcChain.bin). Excel rebuilds it
6668
+ * on open. Removes the part, its Content_Types override and its workbook
6669
+ * rels entry. XLSB tables are not patched (documented limitation).
6670
+ */
6671
+ _removeCalcChain() {
6672
+ const calcPath = "xl/calcChain.bin";
6673
+ if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) return;
4984
6674
  try {
4985
- operations.rename(temporaryPath, destinationPath);
4986
- installed = true;
4987
- } catch (error) {
4988
- if (overwrite && !backupCreated && operations.exists(destinationPath)) {
4989
- operations.rename(destinationPath, backupPath);
4990
- backupCreated = true;
4991
- operations.rename(temporaryPath, destinationPath);
4992
- installed = true;
4993
- } else {
4994
- throw error;
6675
+ this.zip.deleteFile(calcPath);
6676
+ } catch {
6677
+ }
6678
+ const stagedCalc = this.stagedParts.get(calcPath);
6679
+ if (stagedCalc) {
6680
+ this.stagedParts.delete(calcPath);
6681
+ try {
6682
+ fs6.rmSync(stagedCalc, { force: true });
6683
+ } catch {
4995
6684
  }
4996
6685
  }
4997
- if (backupCreated) {
4998
- operations.remove(backupPath);
6686
+ const ctPath = "[Content_Types].xml";
6687
+ if (this.zip.getEntry(ctPath) || this.stagedParts.has(ctPath)) {
6688
+ try {
6689
+ const ct = this._entryData(ctPath).toString("utf8");
6690
+ const updated = ct.replace(
6691
+ /<Override\b[^>]*PartName="\/xl\/calcChain\.bin"[^>]*\/?>/gi,
6692
+ ""
6693
+ );
6694
+ if (updated !== ct) this._writePartText(ctPath, updated);
6695
+ } catch {
6696
+ }
4999
6697
  }
5000
- } catch (error) {
5001
- if (backupCreated && !installed) {
5002
- if (!operations.exists(destinationPath)) {
5003
- try {
5004
- operations.rename(backupPath, destinationPath);
5005
- backupCreated = false;
5006
- } catch (rollbackError) {
5007
- throw new Error(
5008
- `Excel conversion: final destination install failed (${errorMessage(error)}), and restoring the original destination also failed (${errorMessage(rollbackError)}). Original file remains at ${backupPath}.`
5009
- );
6698
+ const relsPath = "xl/_rels/workbook.bin.rels";
6699
+ if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
6700
+ try {
6701
+ const rels = this._entryData(relsPath).toString("utf8");
6702
+ const relTagRe = /<Relationship\b[^>]*>/g;
6703
+ let m;
6704
+ let out = "";
6705
+ let last = 0;
6706
+ let changed = false;
6707
+ while ((m = relTagRe.exec(rels)) !== null) {
6708
+ const attrs = parseTagAttributes(m[0]);
6709
+ const type = attrs["Type"] ?? attrs["type"] ?? "";
6710
+ const target = attrs["Target"] ?? attrs["target"] ?? "";
6711
+ if (type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.bin") {
6712
+ out += rels.substring(last, m.index);
6713
+ last = m.index + m[0].length;
6714
+ changed = true;
6715
+ }
5010
6716
  }
5011
- } else {
5012
- throw new Error(
5013
- `Excel conversion: final destination install failed (${errorMessage(error)}). A concurrent destination was preserved; the original file remains at ${backupPath}.`
5014
- );
6717
+ if (changed) {
6718
+ out += rels.substring(last);
6719
+ this._writePartText(relsPath, out);
6720
+ }
6721
+ } catch {
5015
6722
  }
5016
6723
  }
5017
- throw error;
5018
6724
  }
5019
- }
6725
+ /** Write an XML/text part to its staged file when present, else into the ZIP. */
6726
+ _writePartText(name, text) {
6727
+ const stagedPath = this.stagedParts.get(name);
6728
+ if (stagedPath) {
6729
+ fs6.writeFileSync(stagedPath, text, "utf8");
6730
+ } else {
6731
+ this.zip.updateFile(name, Buffer.from(text, "utf8"));
6732
+ }
6733
+ }
6734
+ };
5020
6735
 
5021
6736
  // src/ExcelConverter.ts
6737
+ var fs7 = __toESM(require("fs"));
6738
+ var path8 = __toESM(require("path"));
6739
+ var import_crypto5 = require("crypto");
6740
+ var import_child_process = require("child_process");
5022
6741
  var POWERSHELL_CONVERSION_SCRIPT = `
5023
6742
  $ErrorActionPreference = 'Stop'
5024
6743
  $inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
@@ -5063,29 +6782,29 @@ function errorMessage2(error) {
5063
6782
  return error instanceof Error ? error.message : String(error);
5064
6783
  }
5065
6784
  function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
5066
- const input = path2.resolve(inputPath);
5067
- const output = path2.resolve(outputPath);
5068
- if (path2.extname(input).toLowerCase() !== inputExtension) {
6785
+ const input = path8.resolve(inputPath);
6786
+ const output = path8.resolve(outputPath);
6787
+ if (path8.extname(input).toLowerCase() !== inputExtension) {
5069
6788
  throw new Error(
5070
6789
  `Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
5071
6790
  );
5072
6791
  }
5073
- if (path2.extname(output).toLowerCase() !== outputExtension) {
6792
+ if (path8.extname(output).toLowerCase() !== outputExtension) {
5074
6793
  throw new Error(
5075
6794
  `Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
5076
6795
  );
5077
6796
  }
5078
- if (!fs6.existsSync(input)) {
6797
+ if (!fs7.existsSync(input)) {
5079
6798
  throw new Error(`Excel conversion: source file not found: ${inputPath}`);
5080
6799
  }
5081
- if (!fs6.statSync(input).isFile()) {
6800
+ if (!fs7.statSync(input).isFile()) {
5082
6801
  throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
5083
6802
  }
5084
- if (!fs6.existsSync(path2.dirname(output))) {
5085
- throw new Error(`Excel conversion: destination directory not found: ${path2.dirname(output)}`);
6803
+ if (!fs7.existsSync(path8.dirname(output))) {
6804
+ throw new Error(`Excel conversion: destination directory not found: ${path8.dirname(output)}`);
5086
6805
  }
5087
- if (fs6.existsSync(output)) {
5088
- if (fs6.statSync(output).isDirectory()) {
6806
+ if (fs7.existsSync(output)) {
6807
+ if (fs7.statSync(output).isDirectory()) {
5089
6808
  throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
5090
6809
  }
5091
6810
  if (!overwrite) {
@@ -5107,7 +6826,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5107
6826
  JUSTYBASE_EXCEL_OUTPUT: outputPath,
5108
6827
  JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
5109
6828
  };
5110
- return new Promise((resolve2, reject) => {
6829
+ return new Promise((resolve6, reject) => {
5111
6830
  var _a, _b, _c, _d;
5112
6831
  let stdout = "";
5113
6832
  let stderr = "";
@@ -5142,7 +6861,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5142
6861
  if (settled) return;
5143
6862
  settled = true;
5144
6863
  if (code === 0) {
5145
- resolve2();
6864
+ resolve6();
5146
6865
  return;
5147
6866
  }
5148
6867
  const details = (stderr || stdout).replace(/\s+/g, " ").trim();
@@ -5163,22 +6882,22 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
5163
6882
  if (process.platform !== "win32") {
5164
6883
  throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
5165
6884
  }
5166
- const temporaryOutput = path2.join(
5167
- path2.dirname(output),
5168
- `.${path2.basename(output, outputExtension)}.${(0, import_crypto2.randomUUID)()}.conversion${outputExtension}`
6885
+ const temporaryOutput = path8.join(
6886
+ path8.dirname(output),
6887
+ `.${path8.basename(output, outputExtension)}.${(0, import_crypto5.randomUUID)()}.conversion${outputExtension}`
5169
6888
  );
5170
6889
  try {
5171
6890
  await runPowerShellConversion(input, temporaryOutput, fileFormat);
5172
- if (!fs6.existsSync(temporaryOutput)) {
6891
+ if (!fs7.existsSync(temporaryOutput)) {
5173
6892
  throw new Error("Excel conversion completed without creating the destination file.");
5174
6893
  }
5175
6894
  installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
5176
6895
  } catch (error) {
5177
6896
  throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
5178
6897
  } finally {
5179
- if (fs6.existsSync(temporaryOutput)) {
6898
+ if (fs7.existsSync(temporaryOutput)) {
5180
6899
  try {
5181
- fs6.rmSync(temporaryOutput);
6900
+ fs7.rmSync(temporaryOutput);
5182
6901
  } catch {
5183
6902
  }
5184
6903
  }
@@ -5197,22 +6916,38 @@ function convertXlsxToXlsb(inputPath, outputPath, options) {
5197
6916
  ExcelReaderAbstract,
5198
6917
  F,
5199
6918
  INVALID_SHEET_NAME_CHARS,
6919
+ MAX_CELL_CHARS,
6920
+ MAX_COLS,
6921
+ MAX_ROWS,
5200
6922
  ReaderFactory,
5201
6923
  StreamingSheetState,
5202
6924
  XlsbReader,
5203
6925
  XlsbUpdater,
5204
6926
  XlsbWriter,
6927
+ XlsmUpdater,
5205
6928
  XlsxReader,
5206
6929
  XlsxUpdater,
5207
6930
  XlsxWriter,
5208
6931
  applyHeaderWidths,
6932
+ assertCellTextLength,
6933
+ assertColumnCount,
6934
+ assertRowCount,
6935
+ assertRowFitsColumns,
6936
+ columnIndexToLetter,
6937
+ columnLetterToIndex,
5209
6938
  convertXlsbToXlsx,
5210
6939
  convertXlsxToXlsb,
5211
6940
  defaultColWidth,
6941
+ escapeXmlText,
5212
6942
  getFormat,
5213
6943
  initColWidths,
5214
6944
  isFormattedCell,
6945
+ parseSharedStringsXml,
6946
+ parseTagAttributes,
6947
+ resolveRelTarget,
5215
6948
  sanitizeSheetName,
6949
+ trimTrailingEmptyRows,
6950
+ unescapeXml,
5216
6951
  unwrapCell,
5217
6952
  updateColWidthsFromRows
5218
6953
  });