@justybase/spreadsheet-tasks 2.0.0 → 2.1.1

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
@@ -38,10 +38,14 @@ __export(index_exports, {
38
38
  ReaderFactory: () => ReaderFactory,
39
39
  StreamingSheetState: () => StreamingSheetState,
40
40
  XlsbReader: () => XlsbReader,
41
+ XlsbUpdater: () => XlsbUpdater,
41
42
  XlsbWriter: () => XlsbWriter,
42
43
  XlsxReader: () => XlsxReader,
44
+ XlsxUpdater: () => XlsxUpdater,
43
45
  XlsxWriter: () => XlsxWriter,
44
46
  applyHeaderWidths: () => applyHeaderWidths,
47
+ convertXlsbToXlsx: () => convertXlsbToXlsx,
48
+ convertXlsxToXlsb: () => convertXlsxToXlsb,
45
49
  defaultColWidth: () => defaultColWidth,
46
50
  getFormat: () => getFormat,
47
51
  initColWidths: () => initColWidths,
@@ -456,8 +460,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
456
460
  this.xfIdToNumFmtId = [];
457
461
  this.customDateFormats = null;
458
462
  }
459
- async open(path2, readSharedStrings = true) {
460
- this.zip = new import_adm_zip.default(path2);
463
+ async open(path3, readSharedStrings = true) {
464
+ this.zip = new import_adm_zip.default(path3);
461
465
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
462
466
  if (wbEntry) {
463
467
  const buf = wbEntry.getData();
@@ -502,8 +506,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
502
506
  this.resultsCount = this.sheets.length;
503
507
  this._currentSheetIndex = -1;
504
508
  }
505
- _readSharedStrings(path2) {
506
- const entry = this.zip.getEntry(path2);
509
+ _readSharedStrings(path3) {
510
+ const entry = this.zip.getEntry(path3);
507
511
  if (!entry) return;
508
512
  const reader = new BiffReaderWriter(entry.getData());
509
513
  while (reader.readSharedStrings()) {
@@ -512,8 +516,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
512
516
  }
513
517
  }
514
518
  }
515
- _readStyles(path2) {
516
- const entry = this.zip.getEntry(path2);
519
+ _readStyles(path3) {
520
+ const entry = this.zip.getEntry(path3);
517
521
  if (!entry) return;
518
522
  const reader = new BiffReaderWriter(entry.getData());
519
523
  while (reader.readStyles()) {
@@ -603,6 +607,93 @@ var XlsbReader = class extends ExcelReaderAbstract {
603
607
 
604
608
  // src/XlsxReader.ts
605
609
  var import_yauzl = __toESM(require("yauzl"));
610
+
611
+ // src/xmlUtils.ts
612
+ function hasInvalidControlChars(str) {
613
+ for (let i = 0; i < str.length; i++) {
614
+ const c = str.charCodeAt(i);
615
+ if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
616
+ return true;
617
+ }
618
+ }
619
+ return false;
620
+ }
621
+ function stripInvalidControlChars(str) {
622
+ let out = "";
623
+ for (let i = 0; i < str.length; i++) {
624
+ const c = str.charCodeAt(i);
625
+ if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
626
+ out += str[i];
627
+ }
628
+ }
629
+ return out;
630
+ }
631
+ function escapeXmlText(str) {
632
+ if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
633
+ return str;
634
+ }
635
+ let out = str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
636
+ if (hasInvalidControlChars(out)) {
637
+ out = stripInvalidControlChars(out);
638
+ }
639
+ return out;
640
+ }
641
+ function unescapeXml(str) {
642
+ 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
+ }
645
+ function columnIndexToLetter(colIndex) {
646
+ let n = colIndex + 1;
647
+ let result = "";
648
+ while (n > 0) {
649
+ const rem = (n - 1) % 26;
650
+ result = String.fromCharCode(65 + rem) + result;
651
+ n = Math.floor((n - 1) / 26);
652
+ }
653
+ return result;
654
+ }
655
+ function columnLetterToIndex(letter) {
656
+ let column = 0;
657
+ const length = letter.length;
658
+ for (let i = 0; i < length; i++) {
659
+ column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
660
+ }
661
+ return column - 1;
662
+ }
663
+ function trimTrailingEmptyRows(rows) {
664
+ let end = rows.length;
665
+ while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
666
+ end--;
667
+ }
668
+ return end === rows.length ? rows : rows.slice(0, end);
669
+ }
670
+ function parseSharedStringsXml(xml) {
671
+ const result = [];
672
+ 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;
689
+ }
690
+ result.push(unescapeXml(val));
691
+ pos = siEnd + 5;
692
+ }
693
+ return result;
694
+ }
695
+
696
+ // src/XlsxReader.ts
606
697
  var XlsxReader = class extends ExcelReaderAbstract {
607
698
  constructor() {
608
699
  super();
@@ -618,9 +709,9 @@ var XlsxReader = class extends ExcelReaderAbstract {
618
709
  this.xfIdToNumFmtId = [];
619
710
  this.customDateFormats = /* @__PURE__ */ new Set();
620
711
  }
621
- async open(path2, readSharedStrings = true) {
622
- return new Promise((resolve, reject) => {
623
- import_yauzl.default.open(path2, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
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) => {
624
715
  if (err) return reject(err);
625
716
  this.zipfile = zipfile;
626
717
  this.entries = /* @__PURE__ */ new Map();
@@ -644,7 +735,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
644
735
  const sheetRegex = /<sheet[^>]*name="([^"]*)"[^>]*sheetId="([^"]*)"[^>]*r:id="([^"]*)"/g;
645
736
  let match;
646
737
  while ((match = sheetRegex.exec(wbContent)) !== null) {
647
- const name = this._unescapeXml(match[1]);
738
+ const name = unescapeXml(match[1]);
648
739
  const sheetId = match[2];
649
740
  const rId = match[3];
650
741
  const target = rIdToTarget[rId];
@@ -668,7 +759,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
668
759
  }
669
760
  this.resultsCount = this.sheets.length;
670
761
  this._currentSheetIndex = -1;
671
- resolve();
762
+ resolve2();
672
763
  } catch (e) {
673
764
  reject(e);
674
765
  }
@@ -682,48 +773,23 @@ var XlsxReader = class extends ExcelReaderAbstract {
682
773
  this.zipfile.close();
683
774
  }
684
775
  }
685
- async _readZipEntryContent(path2) {
686
- const entry = this.entries.get(path2);
776
+ async _readZipEntryContent(path3) {
777
+ const entry = this.entries.get(path3);
687
778
  if (!entry) return null;
688
- return new Promise((resolve, reject) => {
779
+ return new Promise((resolve2, reject) => {
689
780
  this.zipfile.openReadStream(entry, (err, readStream) => {
690
781
  if (err) return reject(err);
691
782
  const chunks = [];
692
783
  readStream.on("data", (chunk) => chunks.push(chunk));
693
784
  readStream.on("end", () => {
694
- resolve(Buffer.concat(chunks).toString("utf8"));
785
+ resolve2(Buffer.concat(chunks).toString("utf8"));
695
786
  });
696
787
  readStream.on("error", reject);
697
788
  });
698
789
  });
699
790
  }
700
- _unescapeXml(str) {
701
- if (!str) return "";
702
- if (str.indexOf("&") === -1) return str;
703
- return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
704
- }
705
791
  _parseSharedStrings(xml) {
706
- let pos = 0;
707
- while (true) {
708
- const siStart = xml.indexOf("<si>", pos);
709
- if (siStart === -1) break;
710
- const siEnd = xml.indexOf("</si>", siStart);
711
- if (siEnd === -1) break;
712
- const content = xml.substring(siStart, siEnd);
713
- let val = "";
714
- let tPos = 0;
715
- while (true) {
716
- const tStart = content.indexOf("<t", tPos);
717
- if (tStart === -1) break;
718
- const tagEnd = content.indexOf(">", tStart);
719
- const tEnd = content.indexOf("</t>", tagEnd);
720
- if (tEnd === -1) break;
721
- val += content.substring(tagEnd + 1, tEnd);
722
- tPos = tEnd + 4;
723
- }
724
- this.sharedStrings.push(this._unescapeXml(val));
725
- pos = siEnd + 5;
726
- }
792
+ this.sharedStrings = parseSharedStringsXml(xml);
727
793
  }
728
794
  _parseStyles(xml) {
729
795
  const numFmtRegex = /<numFmt\s+[^>]*numFmtId="(\d+)"[^>]*formatCode="([^"]*)"/g;
@@ -814,7 +880,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
814
880
  if (key === "r") {
815
881
  let letterLen = 0;
816
882
  while (letterLen < val2.length && val2.charCodeAt(letterLen) >= 65) letterLen++;
817
- colIndex = this._columnLetterToIndex(val2.substring(0, letterLen));
883
+ colIndex = columnLetterToIndex(val2.substring(0, letterLen));
818
884
  } else if (key === "t") {
819
885
  type = val2;
820
886
  } else if (key === "s") {
@@ -857,7 +923,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
857
923
  } else if (type === "b") {
858
924
  finalVal = val === "1" || val === "true";
859
925
  } else if (type === "inlineStr") {
860
- finalVal = this._unescapeXml(val);
926
+ finalVal = unescapeXml(val);
861
927
  } else if (type === "n" || type === "") {
862
928
  finalVal = parseFloat(val);
863
929
  if (styleIndex > 0 && this.xfIdToNumFmtId) {
@@ -881,14 +947,6 @@ var XlsxReader = class extends ExcelReaderAbstract {
881
947
  }
882
948
  }
883
949
  }
884
- _columnLetterToIndex(letter) {
885
- let column = 0;
886
- const length = letter.length;
887
- for (let i = 0; i < length; i++) {
888
- column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
889
- }
890
- return column - 1;
891
- }
892
950
  getValue(i) {
893
951
  if (i < 0 || i >= this._currentRow.length) return null;
894
952
  return this._currentRow[i];
@@ -3330,7 +3388,7 @@ var XlsbWriter = class _XlsbWriter {
3330
3388
  * Must be awaited before the file is fully written.
3331
3389
  */
3332
3390
  finalize() {
3333
- return new Promise((resolve, reject) => {
3391
+ return new Promise((resolve2, reject) => {
3334
3392
  try {
3335
3393
  this.saveSst();
3336
3394
  this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
@@ -3428,7 +3486,7 @@ var XlsbWriter = class _XlsbWriter {
3428
3486
  </Relationships>`;
3429
3487
  this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
3430
3488
  }
3431
- this.output.on("close", () => resolve());
3489
+ this.output.on("close", () => resolve2());
3432
3490
  this.archive.on("error", (err) => reject(err));
3433
3491
  this.archive.finalize();
3434
3492
  } catch (err) {
@@ -3776,7 +3834,7 @@ var XlsxWriter = class {
3776
3834
  }
3777
3835
  /** Finalize the ZIP package and close the output stream. */
3778
3836
  finalize() {
3779
- return new Promise((resolve, reject) => {
3837
+ return new Promise((resolve2, reject) => {
3780
3838
  try {
3781
3839
  this._writeSharedStrings();
3782
3840
  this._writeStyles();
@@ -3784,7 +3842,7 @@ var XlsxWriter = class {
3784
3842
  this._writeContentTypes();
3785
3843
  this._writeRels();
3786
3844
  this.output.on("close", () => {
3787
- resolve();
3845
+ resolve2();
3788
3846
  });
3789
3847
  this.archive.on("error", (err) => {
3790
3848
  reject(err);
@@ -3919,6 +3977,1219 @@ ${xfEntries.join("\n")}
3919
3977
  this.archive.append(wbRels, { name: "xl/_rels/workbook.xml.rels" });
3920
3978
  }
3921
3979
  };
3980
+
3981
+ // src/XlsxUpdater.ts
3982
+ var fs3 = __toESM(require("fs"));
3983
+ var import_adm_zip2 = __toESM(require("adm-zip"));
3984
+ var XlsxUpdater = class {
3985
+ /**
3986
+ * @param path Path to an existing `.xlsx` file.
3987
+ */
3988
+ constructor(path3) {
3989
+ this.sheetNameToPath = /* @__PURE__ */ new Map();
3990
+ this.pivotCacheDefs = [];
3991
+ this.pivotTableDefPaths = [];
3992
+ this.sharedStringsXml = null;
3993
+ this.sharedStringsValues = [];
3994
+ this.sharedStringsOriginalLength = 0;
3995
+ this.sharedStringsCount = 0;
3996
+ this.stringIndexMap = /* @__PURE__ */ new Map();
3997
+ this.sourcePath = path3;
3998
+ this._oaEpoch = Date.UTC(1899, 11, 30);
3999
+ if (!fs3.existsSync(path3)) {
4000
+ throw new Error(`XlsxUpdater: file not found: ${path3}`);
4001
+ }
4002
+ this.zip = new import_adm_zip2.default(path3);
4003
+ if (this.zip.getEntry("xl/workbook.bin")) {
4004
+ throw new Error(
4005
+ "XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
4006
+ );
4007
+ }
4008
+ if (!this.zip.getEntry("xl/workbook.xml")) {
4009
+ throw new Error("XlsxUpdater: not a valid XLSX workbook (xl/workbook.xml missing).");
4010
+ }
4011
+ this._loadWorkbookStructure();
4012
+ }
4013
+ /** Worksheet names in workbook order. */
4014
+ getSheetNames() {
4015
+ return [...this.sheetNameToPath.keys()];
4016
+ }
4017
+ /**
4018
+ * Clear the entire cell data of the target sheet and write new rows in its place.
4019
+ * All other parts of the workbook (pivot tables, other sheets, styles, …) are preserved.
4020
+ *
4021
+ * @param sheetName Name of the worksheet to replace.
4022
+ * @param rows New data rows (`CellValue` cells; null/undefined = blank).
4023
+ * @param options See {@link ReplaceSheetDataOptions}.
4024
+ */
4025
+ replaceSheetData(sheetName, rows, options = {}) {
4026
+ const { headers, styleFallback = "inherit" } = options;
4027
+ rows = trimTrailingEmptyRows(rows);
4028
+ const sheetPath = this.sheetNameToPath.get(sheetName);
4029
+ if (!sheetPath) {
4030
+ throw new Error(`XlsxUpdater: sheet "${sheetName}" not found in the workbook.`);
4031
+ }
4032
+ const entry = this.zip.getEntry(sheetPath);
4033
+ if (!entry) {
4034
+ throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
4035
+ }
4036
+ const sheetXml = entry.getData().toString("utf8");
4037
+ this._ensureSharedStringsLoaded();
4038
+ let colStyles = /* @__PURE__ */ new Map();
4039
+ let headerStyles = /* @__PURE__ */ new Map();
4040
+ let dateStyle = null;
4041
+ if (styleFallback !== "general") {
4042
+ const collected = this._collectExistingStyles(sheetXml);
4043
+ colStyles = collected.dataStyles;
4044
+ headerStyles = collected.headerStyles;
4045
+ dateStyle = this._findDateStyleIndex();
4046
+ }
4047
+ const lastCol = Math.max(rows.reduce((m, r) => Math.max(m, r.length), 0), (headers == null ? void 0 : headers.length) ?? 0) - 1;
4048
+ const totalRows = rows.length + (headers ? 1 : 0);
4049
+ const newSheetData = this._buildSheetData(rows, headers, colStyles, headerStyles, dateStyle);
4050
+ const newXml = this._patchSheetXml(sheetXml, newSheetData, totalRows, lastCol);
4051
+ this.zip.updateFile(sheetPath, Buffer.from(newXml, "utf8"));
4052
+ if (this.sharedStringsXml !== null) {
4053
+ this._commitSharedStrings();
4054
+ }
4055
+ const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
4056
+ this._updatePivotCaches(sheetName, dimRef, rows.length);
4057
+ this._addRefreshOnLoadToPivotTables();
4058
+ }
4059
+ /**
4060
+ * Write the updated workbook to disk.
4061
+ * @param outputPath Destination path; defaults to the source file (overwrites in place).
4062
+ */
4063
+ save(outputPath) {
4064
+ const target = outputPath ?? this.sourcePath;
4065
+ fs3.writeFileSync(target, this.zip.toBuffer());
4066
+ }
4067
+ /** The updated workbook as an in-memory ZIP buffer. */
4068
+ toBuffer() {
4069
+ return this.zip.toBuffer();
4070
+ }
4071
+ // ------------------------------------------------------------------
4072
+ // Workbook structure
4073
+ // ------------------------------------------------------------------
4074
+ _loadWorkbookStructure() {
4075
+ const relsEntry = this.zip.getEntry("xl/_rels/workbook.xml.rels");
4076
+ const rIdToTarget = {};
4077
+ if (relsEntry) {
4078
+ 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];
4083
+ }
4084
+ }
4085
+ const wbEntry = this.zip.getEntry("xl/workbook.xml");
4086
+ const wbXml = wbEntry ? wbEntry.getData().toString("utf8") : "";
4087
+ const sheetRe = /<sheet\b[^>]*>/g;
4088
+ let match;
4089
+ while ((match = sheetRe.exec(wbXml)) !== null) {
4090
+ 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
+ }
4102
+ if (fullPath) {
4103
+ this.sheetNameToPath.set(name, fullPath);
4104
+ }
4105
+ }
4106
+ for (const entry of this.zip.getEntries()) {
4107
+ const name = entry.entryName;
4108
+ if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
4109
+ this.pivotCacheDefs.push({ path: name, xml: entry.getData().toString("utf8") });
4110
+ } else if (/^xl\/pivotTables\/pivotTable\d*\.xml$/.test(name)) {
4111
+ this.pivotTableDefPaths.push(name);
4112
+ }
4113
+ }
4114
+ }
4115
+ // ------------------------------------------------------------------
4116
+ // Shared strings
4117
+ // ------------------------------------------------------------------
4118
+ _ensureSharedStringsLoaded() {
4119
+ if (this.stringIndexMap.size > 0 || this.sharedStringsXml !== null) {
4120
+ return;
4121
+ }
4122
+ const entry = this.zip.getEntry("xl/sharedStrings.xml");
4123
+ if (!entry) {
4124
+ this.sharedStringsXml = null;
4125
+ return;
4126
+ }
4127
+ this.sharedStringsXml = entry.getData().toString("utf8");
4128
+ 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;
4132
+ this.sharedStringsValues.forEach((value, index) => {
4133
+ if (!this.stringIndexMap.has(value)) {
4134
+ this.stringIndexMap.set(value, index);
4135
+ }
4136
+ });
4137
+ this.sharedStringsOriginalLength = this.sharedStringsValues.length;
4138
+ }
4139
+ _commitSharedStrings() {
4140
+ if (this.sharedStringsXml === null || this.sharedStringsValues.length === this.sharedStringsOriginalLength) {
4141
+ return;
4142
+ }
4143
+ 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;
4154
+ }
4155
+ let appended = "";
4156
+ for (let i = this.sharedStringsOriginalLength; i < this.sharedStringsValues.length; i++) {
4157
+ const txt = this.sharedStringsValues[i];
4158
+ const clean = escapeXmlText(txt);
4159
+ 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);
4163
+ this.zip.updateFile("xl/sharedStrings.xml", Buffer.from(xml, "utf8"));
4164
+ }
4165
+ // ------------------------------------------------------------------
4166
+ // Styles
4167
+ // ------------------------------------------------------------------
4168
+ _collectExistingStyles(sheetXml) {
4169
+ const dataCounts = /* @__PURE__ */ new Map();
4170
+ const headerStyles = /* @__PURE__ */ new Map();
4171
+ const sdStart = sheetXml.indexOf("<sheetData>");
4172
+ if (sdStart === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
4173
+ const sdEnd = sheetXml.indexOf("</sheetData>", sdStart);
4174
+ if (sdEnd === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
4175
+ const region = sheetXml.substring(sdStart, sdEnd);
4176
+ const cellRe = /<c\b[^>]*>/g;
4177
+ let m;
4178
+ while ((m = cellRe.exec(region)) !== null) {
4179
+ const tag = m[0];
4180
+ const rMatch = /r="([A-Z]+)(\d+)"/.exec(tag);
4181
+ if (!rMatch) continue;
4182
+ const col = columnLetterToIndex(rMatch[1]);
4183
+ const row = parseInt(rMatch[2], 10);
4184
+ const sMatch = /s="(\d+)"/.exec(tag);
4185
+ if (!sMatch) continue;
4186
+ const s = parseInt(sMatch[1], 10);
4187
+ if (s <= 0) continue;
4188
+ if (row === 1) {
4189
+ if (!headerStyles.has(col)) headerStyles.set(col, s);
4190
+ continue;
4191
+ }
4192
+ let colCounts = dataCounts.get(col);
4193
+ if (!colCounts) {
4194
+ colCounts = /* @__PURE__ */ new Map();
4195
+ dataCounts.set(col, colCounts);
4196
+ }
4197
+ colCounts.set(s, (colCounts.get(s) ?? 0) + 1);
4198
+ }
4199
+ const dataStyles = /* @__PURE__ */ new Map();
4200
+ for (const [col, colCounts] of dataCounts) {
4201
+ let best = 0;
4202
+ let bestCount = -1;
4203
+ for (const [s, count] of colCounts) {
4204
+ if (count > bestCount) {
4205
+ best = s;
4206
+ bestCount = count;
4207
+ }
4208
+ }
4209
+ if (bestCount > 0) dataStyles.set(col, best);
4210
+ }
4211
+ return { dataStyles, headerStyles };
4212
+ }
4213
+ _findDateStyleIndex() {
4214
+ const entry = this.zip.getEntry("xl/styles.xml");
4215
+ if (!entry) return null;
4216
+ const xml = entry.getData().toString("utf8");
4217
+ const customDateFormats = /* @__PURE__ */ new Set();
4218
+ const numFmtRe = /<numFmt\s+[^>]*numFmtId="(\d+)"[^>]*formatCode="([^"]*)"/g;
4219
+ let m;
4220
+ while ((m = numFmtRe.exec(xml)) !== null) {
4221
+ const id = parseInt(m[1], 10);
4222
+ const code = m[2].toLowerCase();
4223
+ if (code.includes("yy") || code.includes("mm") || code.includes("dd") || code.includes("h:mm")) {
4224
+ customDateFormats.add(id);
4225
+ }
4226
+ }
4227
+ const cellXfsStart = xml.indexOf("<cellXfs");
4228
+ if (cellXfsStart === -1) return null;
4229
+ const cellXfsEnd = xml.indexOf("</cellXfs>", cellXfsStart);
4230
+ if (cellXfsEnd === -1) return null;
4231
+ const cellXfsContent = xml.substring(cellXfsStart, cellXfsEnd);
4232
+ const xfRe = /<xf\b[^>]*>/g;
4233
+ let index = 0;
4234
+ while ((m = xfRe.exec(cellXfsContent)) !== null) {
4235
+ const nfMatch = /numFmtId="(\d+)"/.exec(m[0]);
4236
+ const nfId = nfMatch ? parseInt(nfMatch[1], 10) : 0;
4237
+ const isDate = nfId >= 14 && nfId <= 22 || nfId >= 45 && nfId <= 47 || customDateFormats.has(nfId);
4238
+ if (isDate) return index;
4239
+ index++;
4240
+ }
4241
+ return null;
4242
+ }
4243
+ // ------------------------------------------------------------------
4244
+ // Cell / row serialization
4245
+ // ------------------------------------------------------------------
4246
+ _buildSheetData(rows, headers, colStyles, headerStyles, dateStyle) {
4247
+ let out = "";
4248
+ let rowNum = 0;
4249
+ if (headers) {
4250
+ rowNum = 1;
4251
+ out += '<row r="1">';
4252
+ for (let c = 0; c < headers.length; c++) {
4253
+ const style = headerStyles.get(c) ?? 0;
4254
+ out += this._stringCell(headers[c], c, rowNum, style);
4255
+ }
4256
+ out += "</row>";
4257
+ }
4258
+ for (const row of rows) {
4259
+ rowNum++;
4260
+ out += `<row r="${rowNum}">`;
4261
+ for (let c = 0; c < row.length; c++) {
4262
+ const raw = row[c];
4263
+ if (raw === null || raw === void 0) continue;
4264
+ out += this._valueCell(raw, c, rowNum, colStyles, dateStyle);
4265
+ }
4266
+ out += "</row>";
4267
+ }
4268
+ return out;
4269
+ }
4270
+ _valueCell(raw, col, rowNum, colStyles, dateStyle) {
4271
+ const colRef = columnIndexToLetter(col);
4272
+ const fmtString = getFormat(raw);
4273
+ const val = fmtString !== null ? unwrapCell(raw) : raw;
4274
+ const colStyle = colStyles.get(col) ?? 0;
4275
+ if (typeof val === "number") {
4276
+ if (Number.isFinite(val)) {
4277
+ return `<c r="${colRef}${rowNum}"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val}</v></c>`;
4278
+ }
4279
+ return this._stringCell(val.toString(), col, rowNum, colStyle);
4280
+ }
4281
+ if (typeof val === "bigint") {
4282
+ return this._stringCell(val.toString(), col, rowNum, colStyle);
4283
+ }
4284
+ if (typeof val === "boolean") {
4285
+ return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
4286
+ }
4287
+ if (val instanceof Date) {
4288
+ const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
4289
+ if (Number.isFinite(oaDate)) {
4290
+ const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
4291
+ return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
4292
+ }
4293
+ return this._stringCell(val.toString(), col, rowNum, colStyle);
4294
+ }
4295
+ if (val !== null && val !== void 0) {
4296
+ return this._stringCell(String(val), col, rowNum, colStyle);
4297
+ }
4298
+ return "";
4299
+ }
4300
+ _stringCell(text, col, rowNum, style) {
4301
+ const colRef = columnIndexToLetter(col);
4302
+ const styleAttr = style > 0 ? ` s="${style}"` : "";
4303
+ if (this.sharedStringsXml === null) {
4304
+ const clean = escapeXmlText(text);
4305
+ const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
4306
+ const tAttr = preserve ? ' xml:space="preserve"' : "";
4307
+ return `<c r="${colRef}${rowNum}" t="inlineStr"${styleAttr}><is><t${tAttr}>${clean}</t></is></c>`;
4308
+ }
4309
+ let index = this.stringIndexMap.get(text);
4310
+ if (index === void 0) {
4311
+ index = this.sharedStringsValues.length;
4312
+ this.sharedStringsValues.push(text);
4313
+ this.stringIndexMap.set(text, index);
4314
+ }
4315
+ this.sharedStringsCount++;
4316
+ return `<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`;
4317
+ }
4318
+ // ------------------------------------------------------------------
4319
+ // Worksheet XML patching
4320
+ // ------------------------------------------------------------------
4321
+ _patchSheetXml(sheetXml, newSheetData, totalRows, lastCol) {
4322
+ let xml = sheetXml;
4323
+ const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
4324
+ const dimRe = /<dimension\b[^>]*>/;
4325
+ const dimMatch = dimRe.exec(xml);
4326
+ if (dimMatch) {
4327
+ xml = xml.substring(0, dimMatch.index) + dimMatch[0].replace(/ref="[^"]*"/, `ref="${dimRef}"`) + xml.substring(dimMatch.index + dimMatch[0].length);
4328
+ } else {
4329
+ const rootRe = /<worksheet\b[^>]*>/;
4330
+ const rootMatch = rootRe.exec(xml);
4331
+ if (rootMatch) {
4332
+ const insertAt = rootMatch.index + rootMatch[0].length;
4333
+ xml = xml.substring(0, insertAt) + `<dimension ref="${dimRef}"/>` + xml.substring(insertAt);
4334
+ }
4335
+ }
4336
+ const sdOpen = "<sheetData>";
4337
+ const sdStart = xml.indexOf(sdOpen);
4338
+ if (sdStart === -1) {
4339
+ const selfClosing = /<sheetData\s*\/\s*>/;
4340
+ const scMatch = selfClosing.exec(xml);
4341
+ if (scMatch) {
4342
+ xml = xml.substring(0, scMatch.index) + "<sheetData>" + newSheetData + "</sheetData>" + xml.substring(scMatch.index + scMatch[0].length);
4343
+ return xml;
4344
+ }
4345
+ throw new Error("XlsxUpdater: <sheetData> element not found in worksheet XML.");
4346
+ }
4347
+ const sdEnd = xml.indexOf("</sheetData>", sdStart);
4348
+ if (sdEnd === -1) {
4349
+ throw new Error("XlsxUpdater: <sheetData> element not closed in worksheet XML.");
4350
+ }
4351
+ xml = xml.substring(0, sdStart + sdOpen.length) + newSheetData + xml.substring(sdEnd);
4352
+ const afRe = /<autoFilter\b[^>]*>/;
4353
+ const afMatch = afRe.exec(xml);
4354
+ if (afMatch && totalRows > 0 && lastCol >= 0 && afMatch[0].includes("ref=")) {
4355
+ xml = xml.substring(0, afMatch.index) + afMatch[0].replace(/ref="[^"]*"/, `ref="${dimRef}"`) + xml.substring(afMatch.index + afMatch[0].length);
4356
+ }
4357
+ return xml;
4358
+ }
4359
+ // ------------------------------------------------------------------
4360
+ // Pivot table integration
4361
+ // ------------------------------------------------------------------
4362
+ _updatePivotCaches(sheetName, dimRef, recordCount) {
4363
+ for (const cache of this.pivotCacheDefs) {
4364
+ const wsRe = /<worksheetSource\b[^>]*>/;
4365
+ const wsMatch = wsRe.exec(cache.xml);
4366
+ if (!wsMatch) continue;
4367
+ const wsTag = wsMatch[0];
4368
+ const sheetAttr = /sheet="([^"]*)"/.exec(wsTag);
4369
+ if (!sheetAttr || sheetAttr[1] !== sheetName) continue;
4370
+ let xml = cache.xml;
4371
+ xml = xml.substring(0, wsMatch.index) + wsTag.replace(/ref="[^"]*"/, `ref="${dimRef}"`) + xml.substring(wsMatch.index + wsTag.length);
4372
+ const rootRe = /<pivotCacheDefinition\b[^>]*>/;
4373
+ const rootMatch = rootRe.exec(xml);
4374
+ if (rootMatch) {
4375
+ let rootTag = rootMatch[0];
4376
+ if (/recordCount="\d+"/.test(rootTag)) {
4377
+ rootTag = rootTag.replace(/recordCount="\d+"/, `recordCount="${recordCount}"`);
4378
+ } else if (!/\/>$/.test(rootTag)) {
4379
+ rootTag = rootTag.replace(/>$/, ` recordCount="${recordCount}">`);
4380
+ }
4381
+ xml = xml.substring(0, rootMatch.index) + rootTag + xml.substring(rootMatch.index + rootMatch[0].length);
4382
+ }
4383
+ this.zip.updateFile(cache.path, Buffer.from(xml, "utf8"));
4384
+ }
4385
+ }
4386
+ _addRefreshOnLoadToPivotTables() {
4387
+ for (const path3 of this.pivotTableDefPaths) {
4388
+ const entry = this.zip.getEntry(path3);
4389
+ if (!entry) continue;
4390
+ let xml = entry.getData().toString("utf8");
4391
+ const rootRe = /<pivotTableDefinition\b[^>]*>/;
4392
+ 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;
4468
+ }
4469
+ }
4470
+ return { values, total, unique, endSstOffset };
4471
+ }
4472
+
4473
+ // src/XlsbUpdater.ts
4474
+ var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
4475
+ var RK_INT_LOWER = -1 << 29;
4476
+ var RK_INT_UPPER = (1 << 29) - 1;
4477
+ var XlsbUpdater = class {
4478
+ /**
4479
+ * @param path Path to an existing `.xlsb` file.
4480
+ */
4481
+ constructor(path3) {
4482
+ this.sheetNameToPath = /* @__PURE__ */ new Map();
4483
+ this.pivotCacheDefPaths = [];
4484
+ this.sharedStringsBuf = null;
4485
+ this.sharedStringsValues = [];
4486
+ this.sharedStringsTotal = 0;
4487
+ this.sharedStringsUnique = 0;
4488
+ this.sharedStringsEndSst = 0;
4489
+ this.stringIndexMap = /* @__PURE__ */ new Map();
4490
+ // ------------------------------------------------------------------
4491
+ // Shared strings
4492
+ // ------------------------------------------------------------------
4493
+ this._appendCount = 0;
4494
+ this.sourcePath = path3;
4495
+ this._oaEpoch = Date.UTC(1899, 11, 30);
4496
+ if (!fs4.existsSync(path3)) {
4497
+ throw new Error(`XlsbUpdater: file not found: ${path3}`);
4498
+ }
4499
+ this.zip = new import_adm_zip3.default(path3);
4500
+ if (!this.zip.getEntry("xl/workbook.bin")) {
4501
+ throw new Error(
4502
+ "XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
4503
+ );
4504
+ }
4505
+ this._loadWorkbookStructure();
4506
+ }
4507
+ /** Worksheet names in workbook order. */
4508
+ getSheetNames() {
4509
+ return [...this.sheetNameToPath.keys()];
4510
+ }
4511
+ /**
4512
+ * Clear the entire cell data of the target sheet and write new rows in its place.
4513
+ * All other parts of the workbook (pivot tables, other sheets, styles, …) are preserved.
4514
+ *
4515
+ * @param sheetName Name of the worksheet to replace.
4516
+ * @param rows New data rows (`CellValue` cells; null/undefined = blank).
4517
+ * @param options See {@link ReplaceSheetDataOptions}.
4518
+ */
4519
+ replaceSheetData(sheetName, rows, options = {}) {
4520
+ const { headers, styleFallback = "inherit" } = options;
4521
+ rows = trimTrailingEmptyRows(rows);
4522
+ const sheetPath = this.sheetNameToPath.get(sheetName);
4523
+ if (!sheetPath) {
4524
+ throw new Error(`XlsbUpdater: sheet "${sheetName}" not found in the workbook.`);
4525
+ }
4526
+ const entry = this.zip.getEntry(sheetPath);
4527
+ if (!entry) {
4528
+ throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
4529
+ }
4530
+ const sheetBuf = entry.getData();
4531
+ this._ensureSharedStringsLoaded();
4532
+ let dataStyles = /* @__PURE__ */ new Map();
4533
+ let headerStyles = /* @__PURE__ */ new Map();
4534
+ let dateXf = null;
4535
+ if (styleFallback !== "general") {
4536
+ const collected = this._collectStyles(sheetBuf);
4537
+ dataStyles = collected.dataStyles;
4538
+ headerStyles = collected.headerStyles;
4539
+ dateXf = this._findDateXf();
4540
+ }
4541
+ const lastCol = Math.max(0, Math.max(
4542
+ rows.reduce((m, r) => Math.max(m, r.length), 0),
4543
+ (headers == null ? void 0 : headers.length) ?? 0
4544
+ ) - 1);
4545
+ const newRows = this._buildRows(rows, headers, dataStyles, headerStyles, dateXf);
4546
+ const { rowsStart, rowsEnd, autoFilterRanges } = this._findRowsRegion(sheetBuf);
4547
+ const newSheet = Buffer.alloc(sheetBuf.length - (rowsEnd - rowsStart) + newRows.length);
4548
+ sheetBuf.copy(newSheet, 0, 0, rowsStart);
4549
+ newRows.copy(newSheet, rowsStart);
4550
+ sheetBuf.copy(newSheet, rowsStart + newRows.length, rowsEnd);
4551
+ const lastRowIdx = rows.length + (headers ? 1 : 0) - 1;
4552
+ const delta = newRows.length - (rowsEnd - rowsStart);
4553
+ for (const af of autoFilterRanges) {
4554
+ const newAf = af + delta;
4555
+ newSheet.writeInt32LE(lastCol, newAf + 12);
4556
+ newSheet.writeInt32LE(lastRowIdx, newAf + 4);
4557
+ }
4558
+ this._patchDimension(newSheet, lastRowIdx, lastCol);
4559
+ this.zip.updateFile(sheetPath, newSheet);
4560
+ if (this.sharedStringsBuf !== null && this._appendCount > 0) {
4561
+ this._commitSharedStrings();
4562
+ }
4563
+ const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
4564
+ const colLast = lastCol;
4565
+ this._patchPivotCaches(sheetName, rwLast, colLast);
4566
+ }
4567
+ /**
4568
+ * Write the updated workbook to disk.
4569
+ * @param outputPath Destination path; defaults to the source file (overwrites in place).
4570
+ */
4571
+ save(outputPath) {
4572
+ const target = outputPath ?? this.sourcePath;
4573
+ fs4.writeFileSync(target, this.zip.toBuffer());
4574
+ }
4575
+ /** The updated workbook as an in-memory ZIP buffer. */
4576
+ toBuffer() {
4577
+ return this.zip.toBuffer();
4578
+ }
4579
+ // ------------------------------------------------------------------
4580
+ // Workbook structure
4581
+ // ------------------------------------------------------------------
4582
+ _loadWorkbookStructure() {
4583
+ const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
4584
+ const rIdToTarget = {};
4585
+ if (relsEntry) {
4586
+ 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];
4591
+ }
4592
+ }
4593
+ const wbEntry = this.zip.getEntry("xl/workbook.bin");
4594
+ if (wbEntry) {
4595
+ const wbBuf = wbEntry.getData();
4596
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4597
+ let pos = 0;
4598
+ while (readRecord(wbBuf, pos, rec)) {
4599
+ pos = rec.dataEnd;
4600
+ if (rec.id !== 156 || rec.len < 8) continue;
4601
+ let o = rec.dataStart + 8;
4602
+ const rIdLen = wbBuf.readUInt32LE(o);
4603
+ o += 4;
4604
+ const rId = readUtf16(wbBuf, o, rIdLen);
4605
+ o += rIdLen * 2;
4606
+ const nameLen = wbBuf.readUInt32LE(o);
4607
+ o += 4;
4608
+ const name = readUtf16(wbBuf, o, nameLen);
4609
+ let target = rIdToTarget[rId] ?? "";
4610
+ if (target.startsWith("/")) target = target.substring(1);
4611
+ else if (!target.startsWith("xl/")) target = "xl/" + target;
4612
+ if (target) {
4613
+ this.sheetNameToPath.set(name, target);
4614
+ }
4615
+ }
4616
+ }
4617
+ for (const e of this.zip.getEntries()) {
4618
+ if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
4619
+ this.pivotCacheDefPaths.push(e.entryName);
4620
+ }
4621
+ }
4622
+ }
4623
+ _ensureSharedStringsLoaded() {
4624
+ if (this.stringIndexMap.size > 0 || this.sharedStringsBuf !== null) {
4625
+ return;
4626
+ }
4627
+ const entry = this.zip.getEntry("xl/sharedStrings.bin");
4628
+ if (!entry) {
4629
+ this.sharedStringsBuf = null;
4630
+ return;
4631
+ }
4632
+ this.sharedStringsBuf = entry.getData();
4633
+ const parsed = parseSharedStringsBin(this.sharedStringsBuf);
4634
+ this.sharedStringsValues = parsed.values;
4635
+ this.sharedStringsTotal = parsed.total;
4636
+ this.sharedStringsUnique = parsed.unique;
4637
+ this.sharedStringsEndSst = parsed.endSstOffset;
4638
+ this.sharedStringsValues.forEach((value, index) => {
4639
+ if (!this.stringIndexMap.has(value)) {
4640
+ this.stringIndexMap.set(value, index);
4641
+ }
4642
+ });
4643
+ this._appendCount = 0;
4644
+ }
4645
+ _commitSharedStrings() {
4646
+ if (this.sharedStringsBuf === null || this._appendCount === 0) return;
4647
+ const originalValuesLength = this.sharedStringsValues.length - this._appendCount;
4648
+ let newItems = Buffer.alloc(0);
4649
+ for (let i = originalValuesLength; i < this.sharedStringsValues.length; i++) {
4650
+ const text = this.sharedStringsValues[i];
4651
+ const payload = Buffer.alloc(1 + 4 + text.length * 2);
4652
+ payload[0] = 0;
4653
+ payload.writeUInt32LE(text.length, 1);
4654
+ payload.write(text, 5, text.length * 2, "utf16le");
4655
+ newItems = Buffer.concat([newItems, buildRecord(19, payload)]);
4656
+ }
4657
+ const out = Buffer.alloc(this.sharedStringsBuf.length + newItems.length);
4658
+ this.sharedStringsBuf.copy(out, 0, 0, this.sharedStringsEndSst);
4659
+ newItems.copy(out, this.sharedStringsEndSst);
4660
+ this.sharedStringsBuf.copy(out, this.sharedStringsEndSst + newItems.length, this.sharedStringsEndSst);
4661
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4662
+ let pos = 0;
4663
+ while (readRecord(out, pos, rec)) {
4664
+ if (rec.id === 159) {
4665
+ out.writeUInt32LE(this.sharedStringsTotal, rec.dataStart);
4666
+ out.writeUInt32LE(this.sharedStringsUnique, rec.dataStart + 4);
4667
+ break;
4668
+ }
4669
+ pos = rec.dataEnd;
4670
+ }
4671
+ this.zip.updateFile("xl/sharedStrings.bin", out);
4672
+ }
4673
+ // ------------------------------------------------------------------
4674
+ // Styles
4675
+ // ------------------------------------------------------------------
4676
+ _collectStyles(sheetBuf) {
4677
+ const dataCounts = /* @__PURE__ */ new Map();
4678
+ const headerStyles = /* @__PURE__ */ new Map();
4679
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4680
+ let pos = 0;
4681
+ let row = -1;
4682
+ while (readRecord(sheetBuf, pos, rec)) {
4683
+ pos = rec.dataEnd;
4684
+ if (rec.id === 0) {
4685
+ row = sheetBuf.readInt32LE(rec.dataStart);
4686
+ continue;
4687
+ }
4688
+ if (!CELL_RECORDS.has(rec.id) || rec.len < 8) continue;
4689
+ const col = sheetBuf.readUInt32LE(rec.dataStart);
4690
+ const xf = sheetBuf.readUInt32LE(rec.dataStart + 4) & 16777215;
4691
+ if (xf <= 0) continue;
4692
+ if (row === 0) {
4693
+ if (!headerStyles.has(col)) headerStyles.set(col, xf);
4694
+ continue;
4695
+ }
4696
+ let colCounts = dataCounts.get(col);
4697
+ if (!colCounts) {
4698
+ colCounts = /* @__PURE__ */ new Map();
4699
+ dataCounts.set(col, colCounts);
4700
+ }
4701
+ colCounts.set(xf, (colCounts.get(xf) ?? 0) + 1);
4702
+ }
4703
+ const dataStyles = /* @__PURE__ */ new Map();
4704
+ for (const [col, colCounts] of dataCounts) {
4705
+ let best = 0;
4706
+ let bestCount = -1;
4707
+ for (const [xf, count] of colCounts) {
4708
+ if (count > bestCount) {
4709
+ best = xf;
4710
+ bestCount = count;
4711
+ }
4712
+ }
4713
+ if (bestCount > 0) dataStyles.set(col, best);
4714
+ }
4715
+ return { dataStyles, headerStyles };
4716
+ }
4717
+ _findDateXf() {
4718
+ const entry = this.zip.getEntry("xl/styles.bin");
4719
+ 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++;
4741
+ }
4742
+ }
4743
+ return null;
4744
+ }
4745
+ // ------------------------------------------------------------------
4746
+ // Row / cell serialization
4747
+ // ------------------------------------------------------------------
4748
+ _buildRows(rows, headers, dataStyles, headerStyles, dateXf) {
4749
+ const chunks = [];
4750
+ const maxCol = Math.max(rows.reduce((m, r) => Math.max(m, r.length), 0), (headers == null ? void 0 : headers.length) ?? 0, 0);
4751
+ const colLast = Math.max(0, maxCol - 1);
4752
+ const pushRow = (rowNum2, cellBytes) => {
4753
+ const header = Buffer.alloc(27);
4754
+ header[0] = 0;
4755
+ header[1] = 25;
4756
+ header.writeInt32LE(rowNum2, 2);
4757
+ header[10] = 44;
4758
+ header[11] = 1;
4759
+ header[15] = 1;
4760
+ header.writeInt32LE(0, 19);
4761
+ header.writeInt32LE(colLast, 23);
4762
+ chunks.push(header);
4763
+ for (const cell of cellBytes) {
4764
+ chunks.push(cell);
4765
+ }
4766
+ };
4767
+ if (headers) {
4768
+ const cells = [];
4769
+ for (let c = 0; c < headers.length; c++) {
4770
+ const style = headerStyles.get(c) ?? 0;
4771
+ cells.push(this._stringCellBytes(c, style, headers[c]));
4772
+ }
4773
+ pushRow(0, cells);
4774
+ }
4775
+ let rowNum = headers ? 1 : 0;
4776
+ for (const row of rows) {
4777
+ const cells = [];
4778
+ for (let c = 0; c < row.length; c++) {
4779
+ const raw = row[c];
4780
+ if (raw === null || raw === void 0) continue;
4781
+ cells.push(this._valueCellBytes(raw, c, dataStyles, dateXf));
4782
+ }
4783
+ pushRow(rowNum, cells);
4784
+ rowNum++;
4785
+ }
4786
+ return Buffer.concat(chunks);
4787
+ }
4788
+ _valueCellBytes(raw, col, dataStyles, dateXf) {
4789
+ const fmtString = getFormat(raw);
4790
+ const val = fmtString !== null ? unwrapCell(raw) : raw;
4791
+ const colStyle = dataStyles.get(col) ?? 0;
4792
+ if (typeof val === "number") {
4793
+ if (Number.isFinite(val) && Number.isInteger(val) && val >= RK_INT_LOWER && val <= RK_INT_UPPER) {
4794
+ const payload = Buffer.alloc(12);
4795
+ payload.writeUInt32LE(col, 0);
4796
+ payload.writeUInt32LE(colStyle, 4);
4797
+ payload.writeInt32LE(val << 2 | 2, 8);
4798
+ return buildRecord(2, payload);
4799
+ }
4800
+ if (Number.isFinite(val)) {
4801
+ const payload = Buffer.alloc(16);
4802
+ payload.writeUInt32LE(col, 0);
4803
+ payload.writeUInt32LE(colStyle, 4);
4804
+ payload.writeDoubleLE(val, 8);
4805
+ return buildRecord(5, payload);
4806
+ }
4807
+ return this._stringCellBytes(col, colStyle, val.toString());
4808
+ }
4809
+ if (typeof val === "bigint") {
4810
+ return this._stringCellBytes(col, colStyle, val.toString());
4811
+ }
4812
+ if (typeof val === "boolean") {
4813
+ const payload = Buffer.alloc(9);
4814
+ payload.writeUInt32LE(col, 0);
4815
+ payload.writeUInt32LE(colStyle, 4);
4816
+ payload[8] = val ? 1 : 0;
4817
+ return buildRecord(4, payload);
4818
+ }
4819
+ if (val instanceof Date) {
4820
+ const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
4821
+ if (Number.isFinite(oaDate)) {
4822
+ const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
4823
+ const payload = Buffer.alloc(16);
4824
+ payload.writeUInt32LE(col, 0);
4825
+ payload.writeUInt32LE(style, 4);
4826
+ payload.writeDoubleLE(oaDate, 8);
4827
+ return buildRecord(5, payload);
4828
+ }
4829
+ return this._stringCellBytes(col, colStyle, val.toString());
4830
+ }
4831
+ if (val !== null && val !== void 0) {
4832
+ return this._stringCellBytes(col, colStyle, String(val));
4833
+ }
4834
+ return this._stringCellBytes(col, colStyle, "");
4835
+ }
4836
+ _stringCellBytes(col, style, text) {
4837
+ if (this.sharedStringsBuf === null) {
4838
+ const payload2 = Buffer.alloc(12 + text.length * 2);
4839
+ payload2.writeUInt32LE(col, 0);
4840
+ payload2.writeUInt32LE(style, 4);
4841
+ payload2.writeUInt32LE(text.length, 8);
4842
+ payload2.write(text, 12, text.length * 2, "utf16le");
4843
+ return buildRecord(6, payload2);
4844
+ }
4845
+ let index = this.stringIndexMap.get(text);
4846
+ if (index === void 0) {
4847
+ index = this.sharedStringsValues.length;
4848
+ this.sharedStringsValues.push(text);
4849
+ this.stringIndexMap.set(text, index);
4850
+ this.sharedStringsUnique++;
4851
+ this._appendCount++;
4852
+ }
4853
+ this.sharedStringsTotal++;
4854
+ const payload = Buffer.alloc(12);
4855
+ payload.writeUInt32LE(col, 0);
4856
+ payload.writeUInt32LE(style, 4);
4857
+ payload.writeUInt32LE(index, 8);
4858
+ return buildRecord(7, payload);
4859
+ }
4860
+ // ------------------------------------------------------------------
4861
+ // Rows region detection
4862
+ // ------------------------------------------------------------------
4863
+ _findRowsRegion(sheetBuf) {
4864
+ const autoFilterRanges = [];
4865
+ let rowsStart = -1;
4866
+ let lastCellEnd = -1;
4867
+ let lastW25 = -1;
4868
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4869
+ let pos = 0;
4870
+ while (readRecord(sheetBuf, pos, rec)) {
4871
+ if (rec.id === 0) {
4872
+ if (rowsStart === -1) rowsStart = rec.headerStart;
4873
+ } else if (rec.id >= 1 && rec.id <= 11) {
4874
+ lastCellEnd = rec.dataEnd;
4875
+ } else if (rec.id === 37 && rec.len === 6) {
4876
+ lastW25 = rec.headerStart;
4877
+ } else if (rec.id === 161 && rec.len >= 16) {
4878
+ autoFilterRanges.push(rec.dataStart);
4879
+ }
4880
+ pos = rec.dataEnd;
4881
+ }
4882
+ if (rowsStart === -1) {
4883
+ const insertAt = lastW25 >= 0 ? lastW25 : sheetBuf.length;
4884
+ return { rowsStart: insertAt, rowsEnd: insertAt, autoFilterRanges };
4885
+ }
4886
+ const rowsEnd = lastCellEnd >= 0 ? lastCellEnd : lastW25 >= 0 ? lastW25 : sheetBuf.length;
4887
+ return { rowsStart, rowsEnd, autoFilterRanges };
4888
+ }
4889
+ /**
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)
4892
+ * fields to read the full row range; stale values make the last written row
4893
+ * invisible to Excel (and drop it from pivot refreshes).
4894
+ */
4895
+ _patchDimension(sheetBuf, lastRowIdx, lastCol) {
4896
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4897
+ let pos = 0;
4898
+ 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);
4902
+ return;
4903
+ }
4904
+ pos = rec.dataEnd;
4905
+ }
4906
+ }
4907
+ // ------------------------------------------------------------------
4908
+ // Pivot cache integration
4909
+ // ------------------------------------------------------------------
4910
+ _patchPivotCaches(sheetName, rwLast, colLast) {
4911
+ for (const path3 of this.pivotCacheDefPaths) {
4912
+ const entry = this.zip.getEntry(path3);
4913
+ if (!entry) continue;
4914
+ const buf = Buffer.from(entry.getData());
4915
+ const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
4916
+ let pos = 0;
4917
+ let patched = false;
4918
+ let refreshXfPatched = false;
4919
+ while (readRecord(buf, pos, rec)) {
4920
+ pos = rec.dataEnd;
4921
+ if (rec.id === 179 && rec.len >= 4 && !refreshXfPatched) {
4922
+ if (rec.dataStart + 4 <= buf.length) {
4923
+ const v = buf[rec.dataStart + 3];
4924
+ if ((v & 4) === 0) {
4925
+ buf[rec.dataStart + 3] = v | 4;
4926
+ patched = true;
4927
+ }
4928
+ }
4929
+ refreshXfPatched = true;
4930
+ }
4931
+ if (rec.id === 187 && rec.len >= 23) {
4932
+ const cch = buf.readUInt32LE(rec.dataStart + 3);
4933
+ const nameStart = rec.dataStart + 7;
4934
+ const refStart = nameStart + cch * 2;
4935
+ const name = readUtf16(buf, nameStart, cch);
4936
+ if (name === sheetName && refStart + 16 <= buf.length) {
4937
+ buf.writeInt32LE(rwLast, refStart + 4);
4938
+ buf.writeInt32LE(colLast, refStart + 12);
4939
+ patched = true;
4940
+ }
4941
+ }
4942
+ }
4943
+ if (patched) {
4944
+ this.zip.updateFile(path3, buf);
4945
+ }
4946
+ }
4947
+ }
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 {
4984
+ 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;
4995
+ }
4996
+ }
4997
+ if (backupCreated) {
4998
+ operations.remove(backupPath);
4999
+ }
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
+ );
5010
+ }
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
+ );
5015
+ }
5016
+ }
5017
+ throw error;
5018
+ }
5019
+ }
5020
+
5021
+ // src/ExcelConverter.ts
5022
+ var POWERSHELL_CONVERSION_SCRIPT = `
5023
+ $ErrorActionPreference = 'Stop'
5024
+ $inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
5025
+ $outputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_OUTPUT')
5026
+ $fileFormat = [int][Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_FORMAT')
5027
+ $excel = $null
5028
+ $workbook = $null
5029
+ $exitCode = 0
5030
+
5031
+ try {
5032
+ $excel = New-Object -ComObject Excel.Application
5033
+ $excel.Visible = $false
5034
+ $excel.DisplayAlerts = $false
5035
+ $excel.ScreenUpdating = $false
5036
+
5037
+ # Open read-only so the source workbook is never changed by the conversion.
5038
+ $workbook = $excel.Workbooks.Open($inputPath, 0, $true)
5039
+ if ($null -eq $workbook) {
5040
+ throw 'Excel did not return a workbook when opening the source file.'
5041
+ }
5042
+
5043
+ $workbook.SaveAs($outputPath, $fileFormat)
5044
+ } catch {
5045
+ $exitCode = 1
5046
+ [Console]::Error.WriteLine($_.Exception.ToString())
5047
+ } finally {
5048
+ if ($null -ne $workbook) {
5049
+ try { $workbook.Close($false) } catch { }
5050
+ try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook) | Out-Null } catch { }
5051
+ }
5052
+ if ($null -ne $excel) {
5053
+ try { $excel.Quit() } catch { }
5054
+ try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null } catch { }
5055
+ }
5056
+ [GC]::Collect()
5057
+ [GC]::WaitForPendingFinalizers()
5058
+ }
5059
+
5060
+ exit $exitCode
5061
+ `;
5062
+ function errorMessage2(error) {
5063
+ return error instanceof Error ? error.message : String(error);
5064
+ }
5065
+ 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) {
5069
+ throw new Error(
5070
+ `Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
5071
+ );
5072
+ }
5073
+ if (path2.extname(output).toLowerCase() !== outputExtension) {
5074
+ throw new Error(
5075
+ `Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
5076
+ );
5077
+ }
5078
+ if (!fs6.existsSync(input)) {
5079
+ throw new Error(`Excel conversion: source file not found: ${inputPath}`);
5080
+ }
5081
+ if (!fs6.statSync(input).isFile()) {
5082
+ throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
5083
+ }
5084
+ if (!fs6.existsSync(path2.dirname(output))) {
5085
+ throw new Error(`Excel conversion: destination directory not found: ${path2.dirname(output)}`);
5086
+ }
5087
+ if (fs6.existsSync(output)) {
5088
+ if (fs6.statSync(output).isDirectory()) {
5089
+ throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
5090
+ }
5091
+ if (!overwrite) {
5092
+ throw new Error(
5093
+ `Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${outputPath}`
5094
+ );
5095
+ }
5096
+ }
5097
+ if (process.platform === "win32" && input.toLowerCase() === output.toLowerCase()) {
5098
+ throw new Error("Excel conversion: source and destination must be different files.");
5099
+ }
5100
+ return { input, output };
5101
+ }
5102
+ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5103
+ const encodedCommand = Buffer.from(POWERSHELL_CONVERSION_SCRIPT, "utf16le").toString("base64");
5104
+ const environment = {
5105
+ ...process.env,
5106
+ JUSTYBASE_EXCEL_INPUT: inputPath,
5107
+ JUSTYBASE_EXCEL_OUTPUT: outputPath,
5108
+ JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
5109
+ };
5110
+ return new Promise((resolve2, reject) => {
5111
+ var _a, _b, _c, _d;
5112
+ let stdout = "";
5113
+ let stderr = "";
5114
+ let settled = false;
5115
+ const child = (0, import_child_process.spawn)(
5116
+ "powershell.exe",
5117
+ [
5118
+ "-NoLogo",
5119
+ "-NoProfile",
5120
+ "-NonInteractive",
5121
+ "-ExecutionPolicy",
5122
+ "Bypass",
5123
+ "-EncodedCommand",
5124
+ encodedCommand
5125
+ ],
5126
+ { env: environment, windowsHide: true }
5127
+ );
5128
+ (_a = child.stdout) == null ? void 0 : _a.setEncoding("utf8");
5129
+ (_b = child.stderr) == null ? void 0 : _b.setEncoding("utf8");
5130
+ (_c = child.stdout) == null ? void 0 : _c.on("data", (chunk) => {
5131
+ stdout += chunk;
5132
+ });
5133
+ (_d = child.stderr) == null ? void 0 : _d.on("data", (chunk) => {
5134
+ stderr += chunk;
5135
+ });
5136
+ child.once("error", (error) => {
5137
+ if (settled) return;
5138
+ settled = true;
5139
+ reject(new Error(`Excel conversion: unable to start PowerShell/COM: ${error.message}`));
5140
+ });
5141
+ child.once("close", (code) => {
5142
+ if (settled) return;
5143
+ settled = true;
5144
+ if (code === 0) {
5145
+ resolve2();
5146
+ return;
5147
+ }
5148
+ const details = (stderr || stdout).replace(/\s+/g, " ").trim();
5149
+ reject(new Error(
5150
+ `Excel conversion failed (PowerShell exit code ${code ?? "unknown"})${details ? `: ${details}` : "."}`
5151
+ ));
5152
+ });
5153
+ });
5154
+ }
5155
+ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExtension, fileFormat, options = {}) {
5156
+ const { input, output } = validateConversionPaths(
5157
+ inputPath,
5158
+ outputPath,
5159
+ inputExtension,
5160
+ outputExtension,
5161
+ options.overwrite === true
5162
+ );
5163
+ if (process.platform !== "win32") {
5164
+ throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
5165
+ }
5166
+ const temporaryOutput = path2.join(
5167
+ path2.dirname(output),
5168
+ `.${path2.basename(output, outputExtension)}.${(0, import_crypto2.randomUUID)()}.conversion${outputExtension}`
5169
+ );
5170
+ try {
5171
+ await runPowerShellConversion(input, temporaryOutput, fileFormat);
5172
+ if (!fs6.existsSync(temporaryOutput)) {
5173
+ throw new Error("Excel conversion completed without creating the destination file.");
5174
+ }
5175
+ installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
5176
+ } catch (error) {
5177
+ throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
5178
+ } finally {
5179
+ if (fs6.existsSync(temporaryOutput)) {
5180
+ try {
5181
+ fs6.rmSync(temporaryOutput);
5182
+ } catch {
5183
+ }
5184
+ }
5185
+ }
5186
+ }
5187
+ function convertXlsbToXlsx(inputPath, outputPath, options) {
5188
+ return convertExcelFile(inputPath, outputPath, ".xlsb", ".xlsx", 51, options);
5189
+ }
5190
+ function convertXlsxToXlsb(inputPath, outputPath, options) {
5191
+ return convertExcelFile(inputPath, outputPath, ".xlsx", ".xlsb", 50, options);
5192
+ }
3922
5193
  // Annotate the CommonJS export names for ESM import in node:
3923
5194
  0 && (module.exports = {
3924
5195
  BiffReaderWriter,
@@ -3929,10 +5200,14 @@ ${xfEntries.join("\n")}
3929
5200
  ReaderFactory,
3930
5201
  StreamingSheetState,
3931
5202
  XlsbReader,
5203
+ XlsbUpdater,
3932
5204
  XlsbWriter,
3933
5205
  XlsxReader,
5206
+ XlsxUpdater,
3934
5207
  XlsxWriter,
3935
5208
  applyHeaderWidths,
5209
+ convertXlsbToXlsx,
5210
+ convertXlsxToXlsb,
3936
5211
  defaultColWidth,
3937
5212
  getFormat,
3938
5213
  initColWidths,