@justybase/spreadsheet-tasks 2.1.0 → 2.2.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/CHANGELOG.md +22 -0
- package/README.md +46 -2
- package/dist/cjs/index.d.ts +71 -3
- package/dist/cjs/index.js +1204 -89
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1201 -89
- package/dist/esm/index.js.map +1 -1
- package/docs/API.md +83 -2
- package/package.json +3 -2
package/dist/cjs/index.js
CHANGED
|
@@ -40,10 +40,13 @@ __export(index_exports, {
|
|
|
40
40
|
XlsbReader: () => XlsbReader,
|
|
41
41
|
XlsbUpdater: () => XlsbUpdater,
|
|
42
42
|
XlsbWriter: () => XlsbWriter,
|
|
43
|
+
XlsmUpdater: () => XlsxUpdater,
|
|
43
44
|
XlsxReader: () => XlsxReader,
|
|
44
45
|
XlsxUpdater: () => XlsxUpdater,
|
|
45
46
|
XlsxWriter: () => XlsxWriter,
|
|
46
47
|
applyHeaderWidths: () => applyHeaderWidths,
|
|
48
|
+
convertXlsbToXlsx: () => convertXlsbToXlsx,
|
|
49
|
+
convertXlsxToXlsb: () => convertXlsxToXlsb,
|
|
47
50
|
defaultColWidth: () => defaultColWidth,
|
|
48
51
|
getFormat: () => getFormat,
|
|
49
52
|
initColWidths: () => initColWidths,
|
|
@@ -458,8 +461,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
458
461
|
this.xfIdToNumFmtId = [];
|
|
459
462
|
this.customDateFormats = null;
|
|
460
463
|
}
|
|
461
|
-
async open(
|
|
462
|
-
this.zip = new import_adm_zip.default(
|
|
464
|
+
async open(path7, readSharedStrings = true) {
|
|
465
|
+
this.zip = new import_adm_zip.default(path7);
|
|
463
466
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
464
467
|
if (wbEntry) {
|
|
465
468
|
const buf = wbEntry.getData();
|
|
@@ -504,8 +507,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
504
507
|
this.resultsCount = this.sheets.length;
|
|
505
508
|
this._currentSheetIndex = -1;
|
|
506
509
|
}
|
|
507
|
-
_readSharedStrings(
|
|
508
|
-
const entry = this.zip.getEntry(
|
|
510
|
+
_readSharedStrings(path7) {
|
|
511
|
+
const entry = this.zip.getEntry(path7);
|
|
509
512
|
if (!entry) return;
|
|
510
513
|
const reader = new BiffReaderWriter(entry.getData());
|
|
511
514
|
while (reader.readSharedStrings()) {
|
|
@@ -514,8 +517,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
514
517
|
}
|
|
515
518
|
}
|
|
516
519
|
}
|
|
517
|
-
_readStyles(
|
|
518
|
-
const entry = this.zip.getEntry(
|
|
520
|
+
_readStyles(path7) {
|
|
521
|
+
const entry = this.zip.getEntry(path7);
|
|
519
522
|
if (!entry) return;
|
|
520
523
|
const reader = new BiffReaderWriter(entry.getData());
|
|
521
524
|
while (reader.readStyles()) {
|
|
@@ -638,7 +641,7 @@ function escapeXmlText(str) {
|
|
|
638
641
|
}
|
|
639
642
|
function unescapeXml(str) {
|
|
640
643
|
if (!str || str.indexOf("&") === -1) return str;
|
|
641
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
644
|
+
return str.replace(/&#x([0-9a-f]+);/gi, (_match, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_match, decimal) => String.fromCodePoint(parseInt(decimal, 10))).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
642
645
|
}
|
|
643
646
|
function columnIndexToLetter(colIndex) {
|
|
644
647
|
let n = colIndex + 1;
|
|
@@ -667,26 +670,17 @@ function trimTrailingEmptyRows(rows) {
|
|
|
667
670
|
}
|
|
668
671
|
function parseSharedStringsXml(xml) {
|
|
669
672
|
const result = [];
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (tStart === -1) break;
|
|
682
|
-
const tagEnd = content.indexOf(">", tStart);
|
|
683
|
-
const tEnd = content.indexOf("</t>", tagEnd);
|
|
684
|
-
if (tEnd === -1) break;
|
|
685
|
-
val += content.substring(tagEnd + 1, tEnd);
|
|
686
|
-
tPos = tEnd + 4;
|
|
687
|
-
}
|
|
688
|
-
result.push(unescapeXml(val));
|
|
689
|
-
pos = siEnd + 5;
|
|
673
|
+
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>/gi;
|
|
674
|
+
let itemMatch;
|
|
675
|
+
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
676
|
+
const content = itemMatch[1];
|
|
677
|
+
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
678
|
+
let value = "";
|
|
679
|
+
let textMatch;
|
|
680
|
+
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
681
|
+
value += textMatch[1];
|
|
682
|
+
}
|
|
683
|
+
result.push(unescapeXml(value));
|
|
690
684
|
}
|
|
691
685
|
return result;
|
|
692
686
|
}
|
|
@@ -707,9 +701,9 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
707
701
|
this.xfIdToNumFmtId = [];
|
|
708
702
|
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
709
703
|
}
|
|
710
|
-
async open(
|
|
711
|
-
return new Promise((
|
|
712
|
-
import_yauzl.default.open(
|
|
704
|
+
async open(path7, readSharedStrings = true) {
|
|
705
|
+
return new Promise((resolve4, reject) => {
|
|
706
|
+
import_yauzl.default.open(path7, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
|
|
713
707
|
if (err) return reject(err);
|
|
714
708
|
this.zipfile = zipfile;
|
|
715
709
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -757,7 +751,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
757
751
|
}
|
|
758
752
|
this.resultsCount = this.sheets.length;
|
|
759
753
|
this._currentSheetIndex = -1;
|
|
760
|
-
|
|
754
|
+
resolve4();
|
|
761
755
|
} catch (e) {
|
|
762
756
|
reject(e);
|
|
763
757
|
}
|
|
@@ -771,16 +765,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
771
765
|
this.zipfile.close();
|
|
772
766
|
}
|
|
773
767
|
}
|
|
774
|
-
async _readZipEntryContent(
|
|
775
|
-
const entry = this.entries.get(
|
|
768
|
+
async _readZipEntryContent(path7) {
|
|
769
|
+
const entry = this.entries.get(path7);
|
|
776
770
|
if (!entry) return null;
|
|
777
|
-
return new Promise((
|
|
771
|
+
return new Promise((resolve4, reject) => {
|
|
778
772
|
this.zipfile.openReadStream(entry, (err, readStream) => {
|
|
779
773
|
if (err) return reject(err);
|
|
780
774
|
const chunks = [];
|
|
781
775
|
readStream.on("data", (chunk) => chunks.push(chunk));
|
|
782
776
|
readStream.on("end", () => {
|
|
783
|
-
|
|
777
|
+
resolve4(Buffer.concat(chunks).toString("utf8"));
|
|
784
778
|
});
|
|
785
779
|
readStream.on("error", reject);
|
|
786
780
|
});
|
|
@@ -3386,7 +3380,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3386
3380
|
* Must be awaited before the file is fully written.
|
|
3387
3381
|
*/
|
|
3388
3382
|
finalize() {
|
|
3389
|
-
return new Promise((
|
|
3383
|
+
return new Promise((resolve4, reject) => {
|
|
3390
3384
|
try {
|
|
3391
3385
|
this.saveSst();
|
|
3392
3386
|
this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
|
|
@@ -3484,7 +3478,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3484
3478
|
</Relationships>`;
|
|
3485
3479
|
this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
|
|
3486
3480
|
}
|
|
3487
|
-
this.output.on("close", () =>
|
|
3481
|
+
this.output.on("close", () => resolve4());
|
|
3488
3482
|
this.archive.on("error", (err) => reject(err));
|
|
3489
3483
|
this.archive.finalize();
|
|
3490
3484
|
} catch (err) {
|
|
@@ -3832,7 +3826,7 @@ var XlsxWriter = class {
|
|
|
3832
3826
|
}
|
|
3833
3827
|
/** Finalize the ZIP package and close the output stream. */
|
|
3834
3828
|
finalize() {
|
|
3835
|
-
return new Promise((
|
|
3829
|
+
return new Promise((resolve4, reject) => {
|
|
3836
3830
|
try {
|
|
3837
3831
|
this._writeSharedStrings();
|
|
3838
3832
|
this._writeStyles();
|
|
@@ -3840,7 +3834,7 @@ var XlsxWriter = class {
|
|
|
3840
3834
|
this._writeContentTypes();
|
|
3841
3835
|
this._writeRels();
|
|
3842
3836
|
this.output.on("close", () => {
|
|
3843
|
-
|
|
3837
|
+
resolve4();
|
|
3844
3838
|
});
|
|
3845
3839
|
this.archive.on("error", (err) => {
|
|
3846
3840
|
reject(err);
|
|
@@ -3977,13 +3971,237 @@ ${xfEntries.join("\n")}
|
|
|
3977
3971
|
};
|
|
3978
3972
|
|
|
3979
3973
|
// src/XlsxUpdater.ts
|
|
3980
|
-
var
|
|
3974
|
+
var fs5 = __toESM(require("fs"));
|
|
3975
|
+
var os = __toESM(require("os"));
|
|
3976
|
+
var path4 = __toESM(require("path"));
|
|
3981
3977
|
var import_adm_zip2 = __toESM(require("adm-zip"));
|
|
3982
|
-
|
|
3978
|
+
|
|
3979
|
+
// src/atomicFile.ts
|
|
3980
|
+
var fs3 = __toESM(require("fs"));
|
|
3981
|
+
var path2 = __toESM(require("path"));
|
|
3982
|
+
var import_crypto = require("crypto");
|
|
3983
|
+
var realFileOperations = {
|
|
3984
|
+
exists: (filePath) => fs3.existsSync(filePath),
|
|
3985
|
+
rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
|
|
3986
|
+
remove: (filePath) => fs3.rmSync(filePath)
|
|
3987
|
+
};
|
|
3988
|
+
function errorMessage(error) {
|
|
3989
|
+
return error instanceof Error ? error.message : String(error);
|
|
3990
|
+
}
|
|
3991
|
+
function destinationExistsError(destinationPath) {
|
|
3992
|
+
return new Error(
|
|
3993
|
+
`Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
|
|
3994
|
+
);
|
|
3995
|
+
}
|
|
3996
|
+
function readDestinationMode(target) {
|
|
3997
|
+
try {
|
|
3998
|
+
if (!fs3.existsSync(target)) return void 0;
|
|
3999
|
+
return fs3.statSync(target).mode & 511;
|
|
4000
|
+
} catch {
|
|
4001
|
+
return void 0;
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
|
|
4005
|
+
const backupPath = `${destinationPath}.${(0, import_crypto.randomUUID)()}.replacement-backup`;
|
|
4006
|
+
let backupCreated = false;
|
|
4007
|
+
let installed = false;
|
|
4008
|
+
if (operations.exists(destinationPath)) {
|
|
4009
|
+
if (!overwrite) {
|
|
4010
|
+
throw destinationExistsError(destinationPath);
|
|
4011
|
+
}
|
|
4012
|
+
operations.rename(destinationPath, backupPath);
|
|
4013
|
+
backupCreated = true;
|
|
4014
|
+
}
|
|
4015
|
+
try {
|
|
4016
|
+
try {
|
|
4017
|
+
operations.rename(temporaryPath, destinationPath);
|
|
4018
|
+
installed = true;
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
if (overwrite && !backupCreated && operations.exists(destinationPath)) {
|
|
4021
|
+
operations.rename(destinationPath, backupPath);
|
|
4022
|
+
backupCreated = true;
|
|
4023
|
+
operations.rename(temporaryPath, destinationPath);
|
|
4024
|
+
installed = true;
|
|
4025
|
+
} else {
|
|
4026
|
+
throw error;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
if (backupCreated) {
|
|
4030
|
+
operations.remove(backupPath);
|
|
4031
|
+
}
|
|
4032
|
+
} catch (error) {
|
|
4033
|
+
if (backupCreated && !installed) {
|
|
4034
|
+
if (!operations.exists(destinationPath)) {
|
|
4035
|
+
try {
|
|
4036
|
+
operations.rename(backupPath, destinationPath);
|
|
4037
|
+
backupCreated = false;
|
|
4038
|
+
} catch (rollbackError) {
|
|
4039
|
+
throw new Error(
|
|
4040
|
+
`Excel conversion: final destination install failed (${errorMessage(error)}), and restoring the original destination also failed (${errorMessage(rollbackError)}). Original file remains at ${backupPath}.`
|
|
4041
|
+
);
|
|
4042
|
+
}
|
|
4043
|
+
} else {
|
|
4044
|
+
throw new Error(
|
|
4045
|
+
`Excel conversion: final destination install failed (${errorMessage(error)}). A concurrent destination was preserved; the original file remains at ${backupPath}.`
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
throw error;
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
function writeBufferAtomically(buffer, destinationPath) {
|
|
4053
|
+
const target = path2.resolve(destinationPath);
|
|
4054
|
+
fs3.mkdirSync(path2.dirname(target), { recursive: true });
|
|
4055
|
+
const temporaryPath = path2.join(
|
|
4056
|
+
path2.dirname(target),
|
|
4057
|
+
`.${path2.basename(target)}.${(0, import_crypto.randomUUID)()}.tmp`
|
|
4058
|
+
);
|
|
4059
|
+
const destinationMode = readDestinationMode(target);
|
|
4060
|
+
try {
|
|
4061
|
+
const fd = fs3.openSync(temporaryPath, "w", destinationMode ?? 438);
|
|
4062
|
+
try {
|
|
4063
|
+
if (destinationMode !== void 0) {
|
|
4064
|
+
try {
|
|
4065
|
+
fs3.fchmodSync(fd, destinationMode);
|
|
4066
|
+
} catch {
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
fs3.writeFileSync(fd, buffer);
|
|
4070
|
+
fs3.fsyncSync(fd);
|
|
4071
|
+
} finally {
|
|
4072
|
+
fs3.closeSync(fd);
|
|
4073
|
+
}
|
|
4074
|
+
if (destinationMode !== void 0) {
|
|
4075
|
+
try {
|
|
4076
|
+
fs3.chmodSync(temporaryPath, destinationMode);
|
|
4077
|
+
} catch {
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
fs3.renameSync(temporaryPath, target);
|
|
4081
|
+
} finally {
|
|
4082
|
+
if (fs3.existsSync(temporaryPath)) {
|
|
4083
|
+
try {
|
|
4084
|
+
fs3.rmSync(temporaryPath);
|
|
4085
|
+
} catch {
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
// src/streamingZip.ts
|
|
4092
|
+
var fs4 = __toESM(require("fs"));
|
|
4093
|
+
var path3 = __toESM(require("path"));
|
|
4094
|
+
var import_crypto2 = require("crypto");
|
|
4095
|
+
var import_promises = require("stream/promises");
|
|
4096
|
+
var import_stream3 = require("stream");
|
|
4097
|
+
var import_archiver3 = __toESM(require("archiver"));
|
|
4098
|
+
function readDestinationMode2(target) {
|
|
4099
|
+
try {
|
|
4100
|
+
if (!fs4.existsSync(target)) return void 0;
|
|
4101
|
+
return fs4.statSync(target).mode & 511;
|
|
4102
|
+
} catch {
|
|
4103
|
+
return void 0;
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
function appendAndWait(archive, source, name) {
|
|
4107
|
+
return new Promise((resolve4, reject) => {
|
|
4108
|
+
const cleanup = () => {
|
|
4109
|
+
archive.off("entry", onEntry);
|
|
4110
|
+
archive.off("error", onError);
|
|
4111
|
+
const maybeEmitter2 = source;
|
|
4112
|
+
if (typeof maybeEmitter2.off === "function") {
|
|
4113
|
+
maybeEmitter2.off("error", onError);
|
|
4114
|
+
}
|
|
4115
|
+
};
|
|
4116
|
+
const onEntry = () => {
|
|
4117
|
+
cleanup();
|
|
4118
|
+
resolve4();
|
|
4119
|
+
};
|
|
4120
|
+
const onError = (error) => {
|
|
4121
|
+
cleanup();
|
|
4122
|
+
reject(error);
|
|
4123
|
+
};
|
|
4124
|
+
archive.once("entry", onEntry);
|
|
4125
|
+
archive.once("error", onError);
|
|
4126
|
+
const maybeEmitter = source;
|
|
4127
|
+
if (typeof maybeEmitter.once === "function") {
|
|
4128
|
+
maybeEmitter.once("error", onError);
|
|
4129
|
+
}
|
|
4130
|
+
try {
|
|
4131
|
+
archive.append(source, { name });
|
|
4132
|
+
} catch (error) {
|
|
4133
|
+
cleanup();
|
|
4134
|
+
reject(error);
|
|
4135
|
+
}
|
|
4136
|
+
});
|
|
4137
|
+
}
|
|
4138
|
+
async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
4139
|
+
const target = path3.resolve(destinationPath);
|
|
4140
|
+
fs4.mkdirSync(path3.dirname(target), { recursive: true });
|
|
4141
|
+
const temporaryPath = path3.join(
|
|
4142
|
+
path3.dirname(target),
|
|
4143
|
+
`.${path3.basename(target)}.${(0, import_crypto2.randomUUID)()}.tmp`
|
|
4144
|
+
);
|
|
4145
|
+
const destinationMode = readDestinationMode2(target);
|
|
4146
|
+
let output = null;
|
|
4147
|
+
let archive = null;
|
|
4148
|
+
try {
|
|
4149
|
+
output = fs4.createWriteStream(
|
|
4150
|
+
temporaryPath,
|
|
4151
|
+
destinationMode !== void 0 ? { mode: destinationMode } : {}
|
|
4152
|
+
);
|
|
4153
|
+
archive = (0, import_archiver3.default)("zip");
|
|
4154
|
+
archive.pipe(output);
|
|
4155
|
+
for (const entry of zip.getEntries()) {
|
|
4156
|
+
const stagedPath = stagedParts.get(entry.entryName);
|
|
4157
|
+
if (stagedPath) {
|
|
4158
|
+
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName);
|
|
4159
|
+
} else {
|
|
4160
|
+
await appendAndWait(archive, import_stream3.Readable.from([entry.getData()]), entry.entryName);
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
await archive.finalize();
|
|
4164
|
+
await (0, import_promises.finished)(output);
|
|
4165
|
+
if (destinationMode !== void 0) {
|
|
4166
|
+
try {
|
|
4167
|
+
fs4.chmodSync(temporaryPath, destinationMode);
|
|
4168
|
+
} catch {
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
const fd = fs4.openSync(temporaryPath, "r+");
|
|
4172
|
+
try {
|
|
4173
|
+
fs4.fsyncSync(fd);
|
|
4174
|
+
} finally {
|
|
4175
|
+
fs4.closeSync(fd);
|
|
4176
|
+
}
|
|
4177
|
+
fs4.renameSync(temporaryPath, target);
|
|
4178
|
+
} catch (error) {
|
|
4179
|
+
try {
|
|
4180
|
+
archive == null ? void 0 : archive.abort();
|
|
4181
|
+
} catch {
|
|
4182
|
+
}
|
|
4183
|
+
throw error;
|
|
4184
|
+
} finally {
|
|
4185
|
+
if (output && !output.closed) {
|
|
4186
|
+
output.destroy();
|
|
4187
|
+
}
|
|
4188
|
+
if (fs4.existsSync(temporaryPath)) {
|
|
4189
|
+
try {
|
|
4190
|
+
fs4.rmSync(temporaryPath);
|
|
4191
|
+
} catch {
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
// src/XlsxUpdater.ts
|
|
4198
|
+
var XlsxUpdater = class _XlsxUpdater {
|
|
3983
4199
|
/**
|
|
3984
4200
|
* @param path Path to an existing `.xlsx` file.
|
|
3985
4201
|
*/
|
|
3986
|
-
constructor(
|
|
4202
|
+
constructor(filePath) {
|
|
4203
|
+
this.temporaryDirectory = null;
|
|
4204
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
3987
4205
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
3988
4206
|
this.pivotCacheDefs = [];
|
|
3989
4207
|
this.pivotTableDefPaths = [];
|
|
@@ -3991,13 +4209,15 @@ var XlsxUpdater = class {
|
|
|
3991
4209
|
this.sharedStringsValues = [];
|
|
3992
4210
|
this.sharedStringsOriginalLength = 0;
|
|
3993
4211
|
this.sharedStringsCount = 0;
|
|
4212
|
+
this.sharedStringsPending = [];
|
|
4213
|
+
this.sharedStringsDirty = false;
|
|
3994
4214
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
3995
|
-
this.sourcePath =
|
|
4215
|
+
this.sourcePath = filePath;
|
|
3996
4216
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3997
|
-
if (!
|
|
3998
|
-
throw new Error(`XlsxUpdater: file not found: ${
|
|
4217
|
+
if (!fs5.existsSync(filePath)) {
|
|
4218
|
+
throw new Error(`XlsxUpdater: file not found: ${filePath}`);
|
|
3999
4219
|
}
|
|
4000
|
-
this.zip = new import_adm_zip2.default(
|
|
4220
|
+
this.zip = new import_adm_zip2.default(filePath);
|
|
4001
4221
|
if (this.zip.getEntry("xl/workbook.bin")) {
|
|
4002
4222
|
throw new Error(
|
|
4003
4223
|
"XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
|
|
@@ -4031,8 +4251,15 @@ var XlsxUpdater = class {
|
|
|
4031
4251
|
if (!entry) {
|
|
4032
4252
|
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4033
4253
|
}
|
|
4034
|
-
const sheetXml =
|
|
4254
|
+
const sheetXml = this._entryData(sheetPath).toString("utf8");
|
|
4035
4255
|
this._ensureSharedStringsLoaded();
|
|
4256
|
+
if (this.sharedStringsXml !== null) {
|
|
4257
|
+
this.sharedStringsCount = Math.max(
|
|
4258
|
+
0,
|
|
4259
|
+
this.sharedStringsCount - this._sharedStringRefCount(sheetXml)
|
|
4260
|
+
);
|
|
4261
|
+
this.sharedStringsDirty = true;
|
|
4262
|
+
}
|
|
4036
4263
|
let colStyles = /* @__PURE__ */ new Map();
|
|
4037
4264
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
4038
4265
|
let dateStyle = null;
|
|
@@ -4047,6 +4274,7 @@ var XlsxUpdater = class {
|
|
|
4047
4274
|
const newSheetData = this._buildSheetData(rows, headers, colStyles, headerStyles, dateStyle);
|
|
4048
4275
|
const newXml = this._patchSheetXml(sheetXml, newSheetData, totalRows, lastCol);
|
|
4049
4276
|
this.zip.updateFile(sheetPath, Buffer.from(newXml, "utf8"));
|
|
4277
|
+
this._discardStagedPart(sheetPath);
|
|
4050
4278
|
if (this.sharedStringsXml !== null) {
|
|
4051
4279
|
this._commitSharedStrings();
|
|
4052
4280
|
}
|
|
@@ -4054,18 +4282,255 @@ var XlsxUpdater = class {
|
|
|
4054
4282
|
this._updatePivotCaches(sheetName, dimRef, rows.length);
|
|
4055
4283
|
this._addRefreshOnLoadToPivotTables();
|
|
4056
4284
|
}
|
|
4285
|
+
/**
|
|
4286
|
+
* Replace worksheet rows from a one-pass source without materialising the
|
|
4287
|
+
* complete result set. Use {@link saveStreaming} to keep the final ZIP
|
|
4288
|
+
* write streaming as well.
|
|
4289
|
+
*/
|
|
4290
|
+
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
4291
|
+
const { headers, styleFallback = "inherit" } = options;
|
|
4292
|
+
if (styleFallback !== "inherit" && styleFallback !== "general") {
|
|
4293
|
+
throw new Error("XlsxUpdater: styleFallback must be 'inherit' or 'general'.");
|
|
4294
|
+
}
|
|
4295
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
4296
|
+
if (!sheetPath) {
|
|
4297
|
+
throw new Error(`XlsxUpdater: sheet "${sheetName}" not found in the workbook.`);
|
|
4298
|
+
}
|
|
4299
|
+
const entry = this.zip.getEntry(sheetPath);
|
|
4300
|
+
if (!entry) {
|
|
4301
|
+
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4302
|
+
}
|
|
4303
|
+
const oldStage = this.stagedParts.get(sheetPath);
|
|
4304
|
+
const beforeValuesLength = this.sharedStringsValues.length;
|
|
4305
|
+
const beforePendingLength = this.sharedStringsPending.length;
|
|
4306
|
+
const beforeSharedStringsCount = this.sharedStringsCount;
|
|
4307
|
+
const beforeSharedStringsDirty = this.sharedStringsDirty;
|
|
4308
|
+
const beforeSharedStringsXml = this.sharedStringsXml;
|
|
4309
|
+
const beforeSharedStringsOriginalLength = this.sharedStringsOriginalLength;
|
|
4310
|
+
const beforeStringIndexMap = new Map(this.stringIndexMap);
|
|
4311
|
+
const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4312
|
+
const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.xml") ? Buffer.from(this._entryData("xl/sharedStrings.xml")) : null;
|
|
4313
|
+
const createdParts = [];
|
|
4314
|
+
const oldSheetXml = this._entryData(sheetPath).toString("utf8");
|
|
4315
|
+
const rowsPath = this._temporaryPath(".rows");
|
|
4316
|
+
const outputPath = this._temporaryPath(".xml");
|
|
4317
|
+
createdParts.push(rowsPath, outputPath);
|
|
4318
|
+
let output = null;
|
|
4319
|
+
try {
|
|
4320
|
+
this._ensureSharedStringsLoaded();
|
|
4321
|
+
if (this.sharedStringsXml !== null) {
|
|
4322
|
+
this.sharedStringsCount = Math.max(
|
|
4323
|
+
0,
|
|
4324
|
+
this.sharedStringsCount - this._sharedStringRefCount(oldSheetXml)
|
|
4325
|
+
);
|
|
4326
|
+
this.sharedStringsDirty = true;
|
|
4327
|
+
}
|
|
4328
|
+
let colStyles = /* @__PURE__ */ new Map();
|
|
4329
|
+
let headerStyles = /* @__PURE__ */ new Map();
|
|
4330
|
+
let dateStyle = null;
|
|
4331
|
+
if (styleFallback !== "general") {
|
|
4332
|
+
const collected = this._collectExistingStyles(oldSheetXml);
|
|
4333
|
+
colStyles = collected.dataStyles;
|
|
4334
|
+
headerStyles = collected.headerStyles;
|
|
4335
|
+
dateStyle = this._findDateStyleIndex();
|
|
4336
|
+
}
|
|
4337
|
+
const prefixMatch = /<([A-Za-z_][\w.-]*:)?sheetData\b/.exec(oldSheetXml);
|
|
4338
|
+
const prefix = (prefixMatch == null ? void 0 : prefixMatch[1]) ?? "";
|
|
4339
|
+
const rowOutput = fs5.createWriteStream(rowsPath);
|
|
4340
|
+
output = rowOutput;
|
|
4341
|
+
let outputError = null;
|
|
4342
|
+
rowOutput.on("error", (error) => {
|
|
4343
|
+
outputError = error;
|
|
4344
|
+
});
|
|
4345
|
+
let nextRow = 1;
|
|
4346
|
+
let width = (headers == null ? void 0 : headers.length) ?? 0;
|
|
4347
|
+
let dataRowsSeen = 0;
|
|
4348
|
+
let keptDataRows = 0;
|
|
4349
|
+
let lastKeptEnd = 0;
|
|
4350
|
+
let pendingEmptyWidth = 0;
|
|
4351
|
+
let writtenBytes = 0;
|
|
4352
|
+
const write = async (data) => {
|
|
4353
|
+
if (outputError) throw outputError;
|
|
4354
|
+
writtenBytes += data.length;
|
|
4355
|
+
if (rowOutput.write(data)) return;
|
|
4356
|
+
await new Promise((resolve4, reject) => {
|
|
4357
|
+
const onDrain = () => {
|
|
4358
|
+
cleanup();
|
|
4359
|
+
resolve4();
|
|
4360
|
+
};
|
|
4361
|
+
const onError = (error) => {
|
|
4362
|
+
cleanup();
|
|
4363
|
+
reject(error);
|
|
4364
|
+
};
|
|
4365
|
+
const cleanup = () => {
|
|
4366
|
+
rowOutput.off("drain", onDrain);
|
|
4367
|
+
rowOutput.off("error", onError);
|
|
4368
|
+
};
|
|
4369
|
+
rowOutput.once("drain", onDrain);
|
|
4370
|
+
rowOutput.once("error", onError);
|
|
4371
|
+
});
|
|
4372
|
+
};
|
|
4373
|
+
if (headers !== void 0) {
|
|
4374
|
+
const cells = headers.map((value, column) => this._valueCell(value, column, nextRow, headerStyles, dateStyle));
|
|
4375
|
+
const header = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
|
|
4376
|
+
await write(Buffer.from(header, "utf8"));
|
|
4377
|
+
lastKeptEnd = writtenBytes;
|
|
4378
|
+
nextRow++;
|
|
4379
|
+
}
|
|
4380
|
+
for await (const sourceRow of rows) {
|
|
4381
|
+
if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
|
|
4382
|
+
throw new TypeError("Each row must be an iterable of cells, not text");
|
|
4383
|
+
}
|
|
4384
|
+
const row = Array.from(sourceRow);
|
|
4385
|
+
const cells = row.map((value, column) => value === null || value === void 0 ? "" : this._valueCell(value, column, nextRow, colStyles, dateStyle));
|
|
4386
|
+
const rowXml = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
|
|
4387
|
+
await write(Buffer.from(rowXml, "utf8"));
|
|
4388
|
+
dataRowsSeen++;
|
|
4389
|
+
nextRow++;
|
|
4390
|
+
if (row.every((value) => value === null || value === void 0)) {
|
|
4391
|
+
pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
|
|
4392
|
+
} else {
|
|
4393
|
+
width = Math.max(width, pendingEmptyWidth, row.length);
|
|
4394
|
+
pendingEmptyWidth = 0;
|
|
4395
|
+
keptDataRows = dataRowsSeen;
|
|
4396
|
+
lastKeptEnd = writtenBytes;
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
await new Promise((resolve4, reject) => {
|
|
4400
|
+
rowOutput.once("close", resolve4);
|
|
4401
|
+
rowOutput.once("error", reject);
|
|
4402
|
+
rowOutput.end();
|
|
4403
|
+
});
|
|
4404
|
+
if (outputError) throw outputError;
|
|
4405
|
+
const rowsFd = fs5.openSync(rowsPath, "r+");
|
|
4406
|
+
try {
|
|
4407
|
+
fs5.ftruncateSync(rowsFd, lastKeptEnd);
|
|
4408
|
+
} finally {
|
|
4409
|
+
fs5.closeSync(rowsFd);
|
|
4410
|
+
}
|
|
4411
|
+
const totalRows = keptDataRows + (headers !== void 0 ? 1 : 0);
|
|
4412
|
+
const lastCol = width - 1;
|
|
4413
|
+
const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
|
|
4414
|
+
this._patchSheetFile(oldSheetXml, rowsPath, outputPath, dimRef);
|
|
4415
|
+
this._stagePart(sheetPath, outputPath);
|
|
4416
|
+
this._commitSharedStrings();
|
|
4417
|
+
this._updatePivotCaches(sheetName, dimRef, keptDataRows);
|
|
4418
|
+
this._addRefreshOnLoadToPivotTables();
|
|
4419
|
+
if (oldStage && oldStage !== outputPath) {
|
|
4420
|
+
this._removeTemporaryFile(oldStage);
|
|
4421
|
+
}
|
|
4422
|
+
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
4423
|
+
} catch (error) {
|
|
4424
|
+
this.sharedStringsValues.length = beforeValuesLength;
|
|
4425
|
+
this.sharedStringsPending.length = beforePendingLength;
|
|
4426
|
+
this.sharedStringsCount = beforeSharedStringsCount;
|
|
4427
|
+
this.sharedStringsDirty = beforeSharedStringsDirty;
|
|
4428
|
+
this.sharedStringsXml = beforeSharedStringsXml;
|
|
4429
|
+
this.sharedStringsOriginalLength = beforeSharedStringsOriginalLength;
|
|
4430
|
+
this.stringIndexMap.clear();
|
|
4431
|
+
for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
|
|
4432
|
+
if (beforeSharedStringsBytes) {
|
|
4433
|
+
this.zip.updateFile("xl/sharedStrings.xml", beforeSharedStringsBytes);
|
|
4434
|
+
}
|
|
4435
|
+
if (oldStage) this.stagedParts.set(sheetPath, oldStage);
|
|
4436
|
+
else this.stagedParts.delete(sheetPath);
|
|
4437
|
+
throw error;
|
|
4438
|
+
} finally {
|
|
4439
|
+
if (output && !output.closed) output.destroy();
|
|
4440
|
+
for (const temporaryPath of createdParts) {
|
|
4441
|
+
this._removeTemporaryFile(temporaryPath);
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4057
4445
|
/**
|
|
4058
4446
|
* Write the updated workbook to disk.
|
|
4059
4447
|
* @param outputPath Destination path; defaults to the source file (overwrites in place).
|
|
4060
4448
|
*/
|
|
4061
4449
|
save(outputPath) {
|
|
4062
4450
|
const target = outputPath ?? this.sourcePath;
|
|
4063
|
-
|
|
4451
|
+
this._materializeStagedParts();
|
|
4452
|
+
writeBufferAtomically(this.zip.toBuffer(), target);
|
|
4453
|
+
}
|
|
4454
|
+
/** Save staged streaming replacements without materialising the ZIP. */
|
|
4455
|
+
async saveStreaming(outputPath) {
|
|
4456
|
+
const target = outputPath ?? this.sourcePath;
|
|
4457
|
+
await writeAdmZipStreaming(this.zip, this.stagedParts, target);
|
|
4458
|
+
this._materializeStagedParts();
|
|
4064
4459
|
}
|
|
4065
4460
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4066
4461
|
toBuffer() {
|
|
4462
|
+
this._materializeStagedParts();
|
|
4067
4463
|
return this.zip.toBuffer();
|
|
4068
4464
|
}
|
|
4465
|
+
/**
|
|
4466
|
+
* Release the staging directory and any unsaved staged parts.
|
|
4467
|
+
* The updater remains usable; a new staging directory is created lazily
|
|
4468
|
+
* on the next streaming replacement.
|
|
4469
|
+
*/
|
|
4470
|
+
dispose() {
|
|
4471
|
+
for (const stagedPath of this.stagedParts.values()) {
|
|
4472
|
+
try {
|
|
4473
|
+
fs5.rmSync(stagedPath, { force: true });
|
|
4474
|
+
} catch {
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
this.stagedParts.clear();
|
|
4478
|
+
if (this.temporaryDirectory !== null) {
|
|
4479
|
+
try {
|
|
4480
|
+
fs5.rmSync(this.temporaryDirectory, { recursive: true, force: true });
|
|
4481
|
+
} catch {
|
|
4482
|
+
}
|
|
4483
|
+
this.temporaryDirectory = null;
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
/** Support `using` declarations for automatic staging cleanup. */
|
|
4487
|
+
[Symbol.dispose]() {
|
|
4488
|
+
this.dispose();
|
|
4489
|
+
}
|
|
4490
|
+
_ensureTemporaryDirectory() {
|
|
4491
|
+
if (this.temporaryDirectory === null) {
|
|
4492
|
+
this.temporaryDirectory = fs5.mkdtempSync(path4.join(os.tmpdir(), "spreadsheet-updater-"));
|
|
4493
|
+
} else {
|
|
4494
|
+
try {
|
|
4495
|
+
fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
4496
|
+
} catch {
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
return this.temporaryDirectory;
|
|
4500
|
+
}
|
|
4501
|
+
_temporaryPath(suffix) {
|
|
4502
|
+
return path4.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
4503
|
+
}
|
|
4504
|
+
_removeTemporaryFile(filePath) {
|
|
4505
|
+
try {
|
|
4506
|
+
fs5.rmSync(filePath);
|
|
4507
|
+
} catch (error) {
|
|
4508
|
+
if (error.code !== "ENOENT") throw error;
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
_entryData(name) {
|
|
4512
|
+
const stagedPath = this.stagedParts.get(name);
|
|
4513
|
+
if (stagedPath) return fs5.readFileSync(stagedPath);
|
|
4514
|
+
const entry = this.zip.getEntry(name);
|
|
4515
|
+
if (!entry) throw new Error(`XlsxUpdater: ZIP member missing: ${name}`);
|
|
4516
|
+
return entry.getData();
|
|
4517
|
+
}
|
|
4518
|
+
_stagePart(name, filePath) {
|
|
4519
|
+
this.stagedParts.set(name, filePath);
|
|
4520
|
+
}
|
|
4521
|
+
_discardStagedPart(name) {
|
|
4522
|
+
const stagedPath = this.stagedParts.get(name);
|
|
4523
|
+
if (!stagedPath) return;
|
|
4524
|
+
this._removeTemporaryFile(stagedPath);
|
|
4525
|
+
this.stagedParts.delete(name);
|
|
4526
|
+
}
|
|
4527
|
+
_materializeStagedParts() {
|
|
4528
|
+
for (const [name, stagedPath] of this.stagedParts) {
|
|
4529
|
+
this.zip.updateFile(name, fs5.readFileSync(stagedPath));
|
|
4530
|
+
this._removeTemporaryFile(stagedPath);
|
|
4531
|
+
}
|
|
4532
|
+
this.stagedParts.clear();
|
|
4533
|
+
}
|
|
4069
4534
|
// ------------------------------------------------------------------
|
|
4070
4535
|
// Workbook structure
|
|
4071
4536
|
// ------------------------------------------------------------------
|
|
@@ -4118,47 +4583,59 @@ var XlsxUpdater = class {
|
|
|
4118
4583
|
return;
|
|
4119
4584
|
}
|
|
4120
4585
|
const entry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4121
|
-
if (!entry) {
|
|
4586
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.xml")) {
|
|
4122
4587
|
this.sharedStringsXml = null;
|
|
4123
4588
|
return;
|
|
4124
4589
|
}
|
|
4125
|
-
this.sharedStringsXml =
|
|
4590
|
+
this.sharedStringsXml = this._entryData("xl/sharedStrings.xml").toString("utf8");
|
|
4126
4591
|
this.sharedStringsValues = parseSharedStringsXml(this.sharedStringsXml);
|
|
4127
|
-
const countMatch = /<sst\b[^>]
|
|
4128
|
-
const countAttr = countMatch ? /count
|
|
4129
|
-
this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) :
|
|
4592
|
+
const countMatch = /<(?:[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(this.sharedStringsXml);
|
|
4593
|
+
const countAttr = countMatch ? /(?:^|\s)count\s*=\s*["'](\d+)["']/.exec(countMatch[0]) : null;
|
|
4594
|
+
this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) : this.sharedStringsValues.length;
|
|
4130
4595
|
this.sharedStringsValues.forEach((value, index) => {
|
|
4131
4596
|
if (!this.stringIndexMap.has(value)) {
|
|
4132
4597
|
this.stringIndexMap.set(value, index);
|
|
4133
4598
|
}
|
|
4134
4599
|
});
|
|
4135
4600
|
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
4601
|
+
this.sharedStringsPending = [];
|
|
4602
|
+
this.sharedStringsDirty = false;
|
|
4136
4603
|
}
|
|
4137
4604
|
_commitSharedStrings() {
|
|
4138
|
-
|
|
4605
|
+
var _a;
|
|
4606
|
+
if (this.sharedStringsXml === null || !this.sharedStringsDirty) {
|
|
4139
4607
|
return;
|
|
4140
4608
|
}
|
|
4141
4609
|
let xml = this.sharedStringsXml;
|
|
4142
|
-
const
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4610
|
+
const sstMatch = /<(?<prefix>[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(xml);
|
|
4611
|
+
if (!sstMatch || sstMatch.index === void 0) {
|
|
4612
|
+
throw new Error("XlsxUpdater: sharedStrings.xml has no sst element.");
|
|
4613
|
+
}
|
|
4614
|
+
const tag = sstMatch[0];
|
|
4615
|
+
const prefix = ((_a = sstMatch.groups) == null ? void 0 : _a.prefix) ?? "";
|
|
4616
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(
|
|
4617
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(tag, "count", String(this.sharedStringsCount)),
|
|
4618
|
+
"uniqueCount",
|
|
4619
|
+
String(this.sharedStringsValues.length)
|
|
4620
|
+
);
|
|
4621
|
+
xml = xml.substring(0, sstMatch.index) + newTag + xml.substring(sstMatch.index + tag.length);
|
|
4622
|
+
const closingRe = new RegExp(`</${prefix}sst\\s*>\\s*$`, "i");
|
|
4623
|
+
const closingMatch = closingRe.exec(xml);
|
|
4624
|
+
if (!closingMatch || closingMatch.index === void 0) {
|
|
4625
|
+
throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
|
|
4626
|
+
}
|
|
4627
|
+
const appended = this.sharedStringsPending.map((txt) => {
|
|
4156
4628
|
const clean = escapeXmlText(txt);
|
|
4157
4629
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4630
|
+
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
4631
|
+
return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
|
|
4632
|
+
}).join("");
|
|
4633
|
+
xml = xml.substring(0, closingMatch.index) + appended + xml.substring(closingMatch.index);
|
|
4161
4634
|
this.zip.updateFile("xl/sharedStrings.xml", Buffer.from(xml, "utf8"));
|
|
4635
|
+
this.sharedStringsXml = xml;
|
|
4636
|
+
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
4637
|
+
this.sharedStringsPending = [];
|
|
4638
|
+
this.sharedStringsDirty = false;
|
|
4162
4639
|
}
|
|
4163
4640
|
// ------------------------------------------------------------------
|
|
4164
4641
|
// Styles
|
|
@@ -4309,10 +4786,25 @@ var XlsxUpdater = class {
|
|
|
4309
4786
|
index = this.sharedStringsValues.length;
|
|
4310
4787
|
this.sharedStringsValues.push(text);
|
|
4311
4788
|
this.stringIndexMap.set(text, index);
|
|
4789
|
+
this.sharedStringsPending.push(text);
|
|
4312
4790
|
}
|
|
4313
4791
|
this.sharedStringsCount++;
|
|
4792
|
+
this.sharedStringsDirty = true;
|
|
4314
4793
|
return `<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`;
|
|
4315
4794
|
}
|
|
4795
|
+
static _replaceOrAddXmlAttribute(tag, name, value) {
|
|
4796
|
+
const escaped = escapeXmlText(value);
|
|
4797
|
+
const attribute = new RegExp(`(\\s${name}\\s*=\\s*["'])[^"']*(["'])`, "i");
|
|
4798
|
+
if (attribute.test(tag)) {
|
|
4799
|
+
return tag.replace(attribute, `$1${escaped}$2`);
|
|
4800
|
+
}
|
|
4801
|
+
const insertion = ` ${name}="${escaped}"`;
|
|
4802
|
+
return tag.endsWith("/>") ? `${tag.slice(0, -2)}${insertion}/>` : `${tag.slice(0, -1)}${insertion}>`;
|
|
4803
|
+
}
|
|
4804
|
+
_sharedStringRefCount(sheetXml) {
|
|
4805
|
+
const cellTags = sheetXml.match(/<(?:(?:[A-Za-z_][\w.-]*):)?c\b[^>]*>/gi) ?? [];
|
|
4806
|
+
return cellTags.filter((tag) => /(?:^|\s)t\s*=\s*["']s["']/i.test(tag)).length;
|
|
4807
|
+
}
|
|
4316
4808
|
// ------------------------------------------------------------------
|
|
4317
4809
|
// Worksheet XML patching
|
|
4318
4810
|
// ------------------------------------------------------------------
|
|
@@ -4354,6 +4846,109 @@ var XlsxUpdater = class {
|
|
|
4354
4846
|
}
|
|
4355
4847
|
return xml;
|
|
4356
4848
|
}
|
|
4849
|
+
_patchSheetFile(sheetXml, rowsPath, outputPath, dimensionRef) {
|
|
4850
|
+
var _a, _b, _c, _d;
|
|
4851
|
+
const source = Buffer.from(sheetXml, "utf8");
|
|
4852
|
+
const text = sheetXml;
|
|
4853
|
+
const replacements = [];
|
|
4854
|
+
const emptySheetData = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b(?<attrs>[^>]*)\/>/i.exec(text);
|
|
4855
|
+
if (emptySheetData && emptySheetData.index !== void 0) {
|
|
4856
|
+
const prefix = ((_a = emptySheetData.groups) == null ? void 0 : _a.prefix) ?? "";
|
|
4857
|
+
replacements.push({
|
|
4858
|
+
start: emptySheetData.index,
|
|
4859
|
+
end: emptySheetData.index + emptySheetData[0].length,
|
|
4860
|
+
data: Buffer.from(`<${prefix}sheetData${((_b = emptySheetData.groups) == null ? void 0 : _b.attrs) ?? ""}>`, "utf8"),
|
|
4861
|
+
rows: true,
|
|
4862
|
+
suffix: Buffer.from(`</${prefix}sheetData>`, "utf8")
|
|
4863
|
+
});
|
|
4864
|
+
} else {
|
|
4865
|
+
const opening = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(text);
|
|
4866
|
+
if (!opening || opening.index === void 0) {
|
|
4867
|
+
throw new Error("XlsxUpdater: <sheetData> element not found in worksheet XML.");
|
|
4868
|
+
}
|
|
4869
|
+
const prefix = ((_c = opening.groups) == null ? void 0 : _c.prefix) ?? "";
|
|
4870
|
+
const closeRe = new RegExp(`</${prefix}sheetData\\s*>`, "i");
|
|
4871
|
+
const closing = closeRe.exec(text.slice(opening.index + opening[0].length));
|
|
4872
|
+
if (!closing || closing.index === void 0) {
|
|
4873
|
+
throw new Error("XlsxUpdater: <sheetData> element not closed in worksheet XML.");
|
|
4874
|
+
}
|
|
4875
|
+
const closingStart = opening.index + opening[0].length + closing.index;
|
|
4876
|
+
replacements.push({
|
|
4877
|
+
start: opening.index + opening[0].length,
|
|
4878
|
+
end: closingStart,
|
|
4879
|
+
rows: true
|
|
4880
|
+
});
|
|
4881
|
+
}
|
|
4882
|
+
const dimension = /<(?<prefix>[A-Za-z_][\w.-]*:)?dimension\b[^>]*>/i.exec(text);
|
|
4883
|
+
if (dimension && dimension.index !== void 0) {
|
|
4884
|
+
replacements.push({
|
|
4885
|
+
start: dimension.index,
|
|
4886
|
+
end: dimension.index + dimension[0].length,
|
|
4887
|
+
data: Buffer.from(
|
|
4888
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(dimension[0], "ref", dimensionRef)
|
|
4889
|
+
)
|
|
4890
|
+
});
|
|
4891
|
+
} else {
|
|
4892
|
+
const root = /<(?<prefix>[A-Za-z_][\w.-]*:)?worksheet\b[^>]*>/i.exec(text);
|
|
4893
|
+
if (root && root.index !== void 0) {
|
|
4894
|
+
const prefix = ((_d = root.groups) == null ? void 0 : _d.prefix) ?? "";
|
|
4895
|
+
const insertAt = root.index + root[0].length;
|
|
4896
|
+
replacements.push({
|
|
4897
|
+
start: insertAt,
|
|
4898
|
+
end: insertAt,
|
|
4899
|
+
data: Buffer.from(`<${prefix}dimension ref="${dimensionRef}"/>`, "utf8")
|
|
4900
|
+
});
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
const autoFilter = /<(?<prefix>[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i.exec(text);
|
|
4904
|
+
if (autoFilter && autoFilter.index !== void 0) {
|
|
4905
|
+
replacements.push({
|
|
4906
|
+
start: autoFilter.index,
|
|
4907
|
+
end: autoFilter.index + autoFilter[0].length,
|
|
4908
|
+
data: Buffer.from(
|
|
4909
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(autoFilter[0], "ref", dimensionRef),
|
|
4910
|
+
"utf8"
|
|
4911
|
+
)
|
|
4912
|
+
});
|
|
4913
|
+
}
|
|
4914
|
+
replacements.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
4915
|
+
const fd = fs5.openSync(outputPath, "w");
|
|
4916
|
+
let cursor = 0;
|
|
4917
|
+
const writeBuffer = (data) => {
|
|
4918
|
+
fs5.writeSync(fd, data);
|
|
4919
|
+
};
|
|
4920
|
+
const copyRows = () => {
|
|
4921
|
+
const rowsFd = fs5.openSync(rowsPath, "r");
|
|
4922
|
+
try {
|
|
4923
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
4924
|
+
const length = fs5.statSync(rowsPath).size;
|
|
4925
|
+
let position = 0;
|
|
4926
|
+
while (position < length) {
|
|
4927
|
+
const count = fs5.readSync(rowsFd, chunk, 0, Math.min(chunk.length, length - position), position);
|
|
4928
|
+
if (count <= 0) throw new Error("XlsxUpdater: failed to read staged rows.");
|
|
4929
|
+
fs5.writeSync(fd, chunk, 0, count);
|
|
4930
|
+
position += count;
|
|
4931
|
+
}
|
|
4932
|
+
} finally {
|
|
4933
|
+
fs5.closeSync(rowsFd);
|
|
4934
|
+
}
|
|
4935
|
+
};
|
|
4936
|
+
try {
|
|
4937
|
+
for (const replacement of replacements) {
|
|
4938
|
+
const startByte = Buffer.byteLength(text.slice(0, replacement.start), "utf8");
|
|
4939
|
+
const endByte = Buffer.byteLength(text.slice(0, replacement.end), "utf8");
|
|
4940
|
+
if (startByte < cursor) throw new Error("XlsxUpdater: overlapping worksheet replacements.");
|
|
4941
|
+
writeBuffer(source.subarray(cursor, startByte));
|
|
4942
|
+
if (replacement.data) writeBuffer(replacement.data);
|
|
4943
|
+
if (replacement.rows) copyRows();
|
|
4944
|
+
if (replacement.suffix) writeBuffer(replacement.suffix);
|
|
4945
|
+
cursor = endByte;
|
|
4946
|
+
}
|
|
4947
|
+
writeBuffer(source.subarray(cursor));
|
|
4948
|
+
} finally {
|
|
4949
|
+
fs5.closeSync(fd);
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4357
4952
|
// ------------------------------------------------------------------
|
|
4358
4953
|
// Pivot table integration
|
|
4359
4954
|
// ------------------------------------------------------------------
|
|
@@ -4382,8 +4977,8 @@ var XlsxUpdater = class {
|
|
|
4382
4977
|
}
|
|
4383
4978
|
}
|
|
4384
4979
|
_addRefreshOnLoadToPivotTables() {
|
|
4385
|
-
for (const
|
|
4386
|
-
const entry = this.zip.getEntry(
|
|
4980
|
+
for (const path7 of this.pivotTableDefPaths) {
|
|
4981
|
+
const entry = this.zip.getEntry(path7);
|
|
4387
4982
|
if (!entry) continue;
|
|
4388
4983
|
let xml = entry.getData().toString("utf8");
|
|
4389
4984
|
const rootRe = /<pivotTableDefinition\b[^>]*>/;
|
|
@@ -4391,13 +4986,15 @@ var XlsxUpdater = class {
|
|
|
4391
4986
|
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
4392
4987
|
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
4393
4988
|
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
4394
|
-
this.zip.updateFile(
|
|
4989
|
+
this.zip.updateFile(path7, Buffer.from(xml, "utf8"));
|
|
4395
4990
|
}
|
|
4396
4991
|
}
|
|
4397
4992
|
};
|
|
4398
4993
|
|
|
4399
4994
|
// src/XlsbUpdater.ts
|
|
4400
|
-
var
|
|
4995
|
+
var fs6 = __toESM(require("fs"));
|
|
4996
|
+
var os2 = __toESM(require("os"));
|
|
4997
|
+
var path5 = __toESM(require("path"));
|
|
4401
4998
|
var import_adm_zip3 = __toESM(require("adm-zip"));
|
|
4402
4999
|
|
|
4403
5000
|
// src/biff12Utils.ts
|
|
@@ -4476,7 +5073,9 @@ var XlsbUpdater = class {
|
|
|
4476
5073
|
/**
|
|
4477
5074
|
* @param path Path to an existing `.xlsb` file.
|
|
4478
5075
|
*/
|
|
4479
|
-
constructor(
|
|
5076
|
+
constructor(filePath) {
|
|
5077
|
+
this.temporaryDirectory = null;
|
|
5078
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
4480
5079
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
4481
5080
|
this.pivotCacheDefPaths = [];
|
|
4482
5081
|
this.sharedStringsBuf = null;
|
|
@@ -4485,16 +5084,17 @@ var XlsbUpdater = class {
|
|
|
4485
5084
|
this.sharedStringsUnique = 0;
|
|
4486
5085
|
this.sharedStringsEndSst = 0;
|
|
4487
5086
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
5087
|
+
this.sharedStringsDirty = false;
|
|
4488
5088
|
// ------------------------------------------------------------------
|
|
4489
5089
|
// Shared strings
|
|
4490
5090
|
// ------------------------------------------------------------------
|
|
4491
5091
|
this._appendCount = 0;
|
|
4492
|
-
this.sourcePath =
|
|
5092
|
+
this.sourcePath = filePath;
|
|
4493
5093
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
4494
|
-
if (!
|
|
4495
|
-
throw new Error(`XlsbUpdater: file not found: ${
|
|
5094
|
+
if (!fs6.existsSync(filePath)) {
|
|
5095
|
+
throw new Error(`XlsbUpdater: file not found: ${filePath}`);
|
|
4496
5096
|
}
|
|
4497
|
-
this.zip = new import_adm_zip3.default(
|
|
5097
|
+
this.zip = new import_adm_zip3.default(filePath);
|
|
4498
5098
|
if (!this.zip.getEntry("xl/workbook.bin")) {
|
|
4499
5099
|
throw new Error(
|
|
4500
5100
|
"XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
|
|
@@ -4525,8 +5125,15 @@ var XlsbUpdater = class {
|
|
|
4525
5125
|
if (!entry) {
|
|
4526
5126
|
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4527
5127
|
}
|
|
4528
|
-
const sheetBuf =
|
|
5128
|
+
const sheetBuf = this._entryData(sheetPath);
|
|
4529
5129
|
this._ensureSharedStringsLoaded();
|
|
5130
|
+
if (this.sharedStringsBuf !== null) {
|
|
5131
|
+
this.sharedStringsTotal = Math.max(
|
|
5132
|
+
0,
|
|
5133
|
+
this.sharedStringsTotal - this._sharedStringRefCount(sheetBuf)
|
|
5134
|
+
);
|
|
5135
|
+
this.sharedStringsDirty = true;
|
|
5136
|
+
}
|
|
4530
5137
|
let dataStyles = /* @__PURE__ */ new Map();
|
|
4531
5138
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
4532
5139
|
let dateXf = null;
|
|
@@ -4555,25 +5162,310 @@ var XlsbUpdater = class {
|
|
|
4555
5162
|
}
|
|
4556
5163
|
this._patchDimension(newSheet, lastRowIdx, lastCol);
|
|
4557
5164
|
this.zip.updateFile(sheetPath, newSheet);
|
|
4558
|
-
|
|
5165
|
+
this._discardStagedPart(sheetPath);
|
|
5166
|
+
if (this.sharedStringsBuf !== null) {
|
|
4559
5167
|
this._commitSharedStrings();
|
|
4560
5168
|
}
|
|
4561
5169
|
const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
|
|
4562
5170
|
const colLast = lastCol;
|
|
4563
5171
|
this._patchPivotCaches(sheetName, rwLast, colLast);
|
|
4564
5172
|
}
|
|
5173
|
+
/** Replace worksheet rows from a one-pass source. */
|
|
5174
|
+
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
5175
|
+
const { headers, styleFallback = "inherit" } = options;
|
|
5176
|
+
if (styleFallback !== "inherit" && styleFallback !== "general") {
|
|
5177
|
+
throw new Error("XlsbUpdater: styleFallback must be 'inherit' or 'general'.");
|
|
5178
|
+
}
|
|
5179
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
5180
|
+
if (!sheetPath) {
|
|
5181
|
+
throw new Error(`XlsbUpdater: sheet "${sheetName}" not found in the workbook.`);
|
|
5182
|
+
}
|
|
5183
|
+
const entry = this.zip.getEntry(sheetPath);
|
|
5184
|
+
if (!entry) {
|
|
5185
|
+
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
5186
|
+
}
|
|
5187
|
+
const oldStage = this.stagedParts.get(sheetPath);
|
|
5188
|
+
const beforeValuesLength = this.sharedStringsValues.length;
|
|
5189
|
+
const beforeAppendCount = this._appendCount;
|
|
5190
|
+
const beforeSharedStringsTotal = this.sharedStringsTotal;
|
|
5191
|
+
const beforeSharedStringsUnique = this.sharedStringsUnique;
|
|
5192
|
+
const beforeSharedStringsEndSst = this.sharedStringsEndSst;
|
|
5193
|
+
const beforeSharedStringsDirty = this.sharedStringsDirty;
|
|
5194
|
+
const beforeStringIndexMap = new Map(this.stringIndexMap);
|
|
5195
|
+
const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
5196
|
+
const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.bin") ? Buffer.from(this._entryData("xl/sharedStrings.bin")) : null;
|
|
5197
|
+
const beforeSharedStringsBuf = this.sharedStringsBuf ? Buffer.from(this.sharedStringsBuf) : null;
|
|
5198
|
+
const oldSheet = this._entryData(sheetPath);
|
|
5199
|
+
const rowsPath = this._temporaryPath(".rows");
|
|
5200
|
+
const outputPath = this._temporaryPath(".bin");
|
|
5201
|
+
const createdParts = [rowsPath, outputPath];
|
|
5202
|
+
let output = null;
|
|
5203
|
+
try {
|
|
5204
|
+
this._ensureSharedStringsLoaded();
|
|
5205
|
+
if (this.sharedStringsBuf !== null) {
|
|
5206
|
+
this.sharedStringsTotal = Math.max(
|
|
5207
|
+
0,
|
|
5208
|
+
this.sharedStringsTotal - this._sharedStringRefCount(oldSheet)
|
|
5209
|
+
);
|
|
5210
|
+
this.sharedStringsDirty = true;
|
|
5211
|
+
}
|
|
5212
|
+
let dataStyles = /* @__PURE__ */ new Map();
|
|
5213
|
+
let headerStyles = /* @__PURE__ */ new Map();
|
|
5214
|
+
let dateXf = null;
|
|
5215
|
+
if (styleFallback !== "general") {
|
|
5216
|
+
const collected = this._collectStyles(oldSheet);
|
|
5217
|
+
dataStyles = collected.dataStyles;
|
|
5218
|
+
headerStyles = collected.headerStyles;
|
|
5219
|
+
dateXf = this._findDateXf();
|
|
5220
|
+
}
|
|
5221
|
+
const rowOutput = fs6.createWriteStream(rowsPath);
|
|
5222
|
+
output = rowOutput;
|
|
5223
|
+
let outputError = null;
|
|
5224
|
+
rowOutput.on("error", (error) => {
|
|
5225
|
+
outputError = error;
|
|
5226
|
+
});
|
|
5227
|
+
let writtenBytes = 0;
|
|
5228
|
+
let lastKeptEnd = 0;
|
|
5229
|
+
let width = (headers == null ? void 0 : headers.length) ?? 0;
|
|
5230
|
+
let dataRowsSeen = 0;
|
|
5231
|
+
let keptDataRows = 0;
|
|
5232
|
+
let pendingEmptyWidth = 0;
|
|
5233
|
+
let nextRow = 0;
|
|
5234
|
+
const rowHeaderOffsets = [];
|
|
5235
|
+
const write = async (data) => {
|
|
5236
|
+
if (outputError) throw outputError;
|
|
5237
|
+
writtenBytes += data.length;
|
|
5238
|
+
if (rowOutput.write(data)) return;
|
|
5239
|
+
await new Promise((resolve4, reject) => {
|
|
5240
|
+
const onDrain = () => {
|
|
5241
|
+
cleanup();
|
|
5242
|
+
resolve4();
|
|
5243
|
+
};
|
|
5244
|
+
const onError = (error) => {
|
|
5245
|
+
cleanup();
|
|
5246
|
+
reject(error);
|
|
5247
|
+
};
|
|
5248
|
+
const cleanup = () => {
|
|
5249
|
+
rowOutput.off("drain", onDrain);
|
|
5250
|
+
rowOutput.off("error", onError);
|
|
5251
|
+
};
|
|
5252
|
+
rowOutput.once("drain", onDrain);
|
|
5253
|
+
rowOutput.once("error", onError);
|
|
5254
|
+
});
|
|
5255
|
+
};
|
|
5256
|
+
const writeRow = async (rowNumber, cells) => {
|
|
5257
|
+
rowHeaderOffsets.push(writtenBytes);
|
|
5258
|
+
const header = Buffer.alloc(27);
|
|
5259
|
+
header[0] = 0;
|
|
5260
|
+
header[1] = 25;
|
|
5261
|
+
header.writeInt32LE(rowNumber, 2);
|
|
5262
|
+
header[10] = 44;
|
|
5263
|
+
header[11] = 1;
|
|
5264
|
+
header[15] = 1;
|
|
5265
|
+
header.writeInt32LE(0, 19);
|
|
5266
|
+
header.writeInt32LE(0, 23);
|
|
5267
|
+
await write(header);
|
|
5268
|
+
for (const cell of cells) await write(cell);
|
|
5269
|
+
};
|
|
5270
|
+
if (headers !== void 0) {
|
|
5271
|
+
await writeRow(0, headers.map((value, column) => this._stringCellBytes(column, styleFallback === "general" ? 0 : headerStyles.get(column) ?? 0, value)));
|
|
5272
|
+
lastKeptEnd = writtenBytes;
|
|
5273
|
+
nextRow = 1;
|
|
5274
|
+
}
|
|
5275
|
+
for await (const sourceRow of rows) {
|
|
5276
|
+
if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
|
|
5277
|
+
throw new TypeError("Each row must be an iterable of cells, not text");
|
|
5278
|
+
}
|
|
5279
|
+
const row = Array.from(sourceRow);
|
|
5280
|
+
const cells = row.map((value, column) => value === null || value === void 0 ? null : this._valueCellBytes(value, column, dataStyles, dateXf)).filter((cell) => cell !== null);
|
|
5281
|
+
await writeRow(nextRow, cells);
|
|
5282
|
+
dataRowsSeen++;
|
|
5283
|
+
nextRow++;
|
|
5284
|
+
if (row.every((value) => value === null || value === void 0)) {
|
|
5285
|
+
pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
|
|
5286
|
+
} else {
|
|
5287
|
+
width = Math.max(width, pendingEmptyWidth, row.length);
|
|
5288
|
+
pendingEmptyWidth = 0;
|
|
5289
|
+
keptDataRows = dataRowsSeen;
|
|
5290
|
+
lastKeptEnd = writtenBytes;
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
await new Promise((resolve4, reject) => {
|
|
5294
|
+
rowOutput.once("close", resolve4);
|
|
5295
|
+
rowOutput.once("error", reject);
|
|
5296
|
+
rowOutput.end();
|
|
5297
|
+
});
|
|
5298
|
+
if (outputError) throw outputError;
|
|
5299
|
+
const rowsFd = fs6.openSync(rowsPath, "r+");
|
|
5300
|
+
try {
|
|
5301
|
+
fs6.ftruncateSync(rowsFd, lastKeptEnd);
|
|
5302
|
+
} finally {
|
|
5303
|
+
fs6.closeSync(rowsFd);
|
|
5304
|
+
}
|
|
5305
|
+
const lastCol = Math.max(0, width - 1);
|
|
5306
|
+
const rowPatchFd = fs6.openSync(rowsPath, "r+");
|
|
5307
|
+
try {
|
|
5308
|
+
const lastColBuffer = Buffer.alloc(4);
|
|
5309
|
+
lastColBuffer.writeInt32LE(lastCol, 0);
|
|
5310
|
+
for (const offset of rowHeaderOffsets) {
|
|
5311
|
+
if (offset >= lastKeptEnd) continue;
|
|
5312
|
+
fs6.writeSync(rowPatchFd, lastColBuffer, 0, lastColBuffer.length, offset + 23);
|
|
5313
|
+
}
|
|
5314
|
+
} finally {
|
|
5315
|
+
fs6.closeSync(rowPatchFd);
|
|
5316
|
+
}
|
|
5317
|
+
const { rowsStart, rowsEnd, autoFilterRanges } = this._findRowsRegion(oldSheet);
|
|
5318
|
+
const oldLength = oldSheet.length;
|
|
5319
|
+
const newRowsLength = fs6.statSync(rowsPath).size;
|
|
5320
|
+
const delta = newRowsLength - (rowsEnd - rowsStart);
|
|
5321
|
+
const outputFd = fs6.openSync(outputPath, "w");
|
|
5322
|
+
try {
|
|
5323
|
+
fs6.writeSync(outputFd, oldSheet, 0, rowsStart, null);
|
|
5324
|
+
const rowsFdRead = fs6.openSync(rowsPath, "r");
|
|
5325
|
+
try {
|
|
5326
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
5327
|
+
let position = 0;
|
|
5328
|
+
while (position < newRowsLength) {
|
|
5329
|
+
const count = fs6.readSync(rowsFdRead, chunk, 0, Math.min(chunk.length, newRowsLength - position), position);
|
|
5330
|
+
if (count <= 0) throw new Error("XlsbUpdater: failed to read staged rows.");
|
|
5331
|
+
fs6.writeSync(outputFd, chunk, 0, count);
|
|
5332
|
+
position += count;
|
|
5333
|
+
}
|
|
5334
|
+
} finally {
|
|
5335
|
+
fs6.closeSync(rowsFdRead);
|
|
5336
|
+
}
|
|
5337
|
+
fs6.writeSync(outputFd, oldSheet, rowsEnd, oldLength - rowsEnd, rowsStart + newRowsLength);
|
|
5338
|
+
} finally {
|
|
5339
|
+
fs6.closeSync(outputFd);
|
|
5340
|
+
}
|
|
5341
|
+
this._patchSheetMetadataFile(
|
|
5342
|
+
outputPath,
|
|
5343
|
+
oldSheet,
|
|
5344
|
+
rowsEnd,
|
|
5345
|
+
delta,
|
|
5346
|
+
autoFilterRanges,
|
|
5347
|
+
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
5348
|
+
lastCol
|
|
5349
|
+
);
|
|
5350
|
+
this._stagePart(sheetPath, outputPath);
|
|
5351
|
+
this._commitSharedStrings();
|
|
5352
|
+
this._patchPivotCaches(
|
|
5353
|
+
sheetName,
|
|
5354
|
+
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
5355
|
+
lastCol
|
|
5356
|
+
);
|
|
5357
|
+
if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
|
|
5358
|
+
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
5359
|
+
} catch (error) {
|
|
5360
|
+
this.sharedStringsValues.length = beforeValuesLength;
|
|
5361
|
+
this._appendCount = beforeAppendCount;
|
|
5362
|
+
this.sharedStringsTotal = beforeSharedStringsTotal;
|
|
5363
|
+
this.sharedStringsUnique = beforeSharedStringsUnique;
|
|
5364
|
+
this.sharedStringsEndSst = beforeSharedStringsEndSst;
|
|
5365
|
+
this.sharedStringsDirty = beforeSharedStringsDirty;
|
|
5366
|
+
this.sharedStringsBuf = beforeSharedStringsBuf;
|
|
5367
|
+
this.stringIndexMap.clear();
|
|
5368
|
+
for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
|
|
5369
|
+
if (beforeSharedStringsBytes) {
|
|
5370
|
+
this.zip.updateFile("xl/sharedStrings.bin", beforeSharedStringsBytes);
|
|
5371
|
+
}
|
|
5372
|
+
if (oldStage) this.stagedParts.set(sheetPath, oldStage);
|
|
5373
|
+
else this.stagedParts.delete(sheetPath);
|
|
5374
|
+
throw error;
|
|
5375
|
+
} finally {
|
|
5376
|
+
if (output && !output.closed) output.destroy();
|
|
5377
|
+
for (const temporaryPath of createdParts) this._removeTemporaryFile(temporaryPath);
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
4565
5380
|
/**
|
|
4566
5381
|
* Write the updated workbook to disk.
|
|
4567
5382
|
* @param outputPath Destination path; defaults to the source file (overwrites in place).
|
|
4568
5383
|
*/
|
|
4569
5384
|
save(outputPath) {
|
|
4570
5385
|
const target = outputPath ?? this.sourcePath;
|
|
4571
|
-
|
|
5386
|
+
this._materializeStagedParts();
|
|
5387
|
+
writeBufferAtomically(this.zip.toBuffer(), target);
|
|
5388
|
+
}
|
|
5389
|
+
/** Save staged streaming replacements without materialising the ZIP. */
|
|
5390
|
+
async saveStreaming(outputPath) {
|
|
5391
|
+
const target = outputPath ?? this.sourcePath;
|
|
5392
|
+
await writeAdmZipStreaming(this.zip, this.stagedParts, target);
|
|
5393
|
+
this._materializeStagedParts();
|
|
4572
5394
|
}
|
|
4573
5395
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4574
5396
|
toBuffer() {
|
|
5397
|
+
this._materializeStagedParts();
|
|
4575
5398
|
return this.zip.toBuffer();
|
|
4576
5399
|
}
|
|
5400
|
+
/**
|
|
5401
|
+
* Release the staging directory and any unsaved staged parts.
|
|
5402
|
+
* The updater remains usable; a new staging directory is created lazily
|
|
5403
|
+
* on the next streaming replacement.
|
|
5404
|
+
*/
|
|
5405
|
+
dispose() {
|
|
5406
|
+
for (const stagedPath of this.stagedParts.values()) {
|
|
5407
|
+
try {
|
|
5408
|
+
fs6.rmSync(stagedPath, { force: true });
|
|
5409
|
+
} catch {
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
this.stagedParts.clear();
|
|
5413
|
+
if (this.temporaryDirectory !== null) {
|
|
5414
|
+
try {
|
|
5415
|
+
fs6.rmSync(this.temporaryDirectory, { recursive: true, force: true });
|
|
5416
|
+
} catch {
|
|
5417
|
+
}
|
|
5418
|
+
this.temporaryDirectory = null;
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
/** Support `using` declarations for automatic staging cleanup. */
|
|
5422
|
+
[Symbol.dispose]() {
|
|
5423
|
+
this.dispose();
|
|
5424
|
+
}
|
|
5425
|
+
_ensureTemporaryDirectory() {
|
|
5426
|
+
if (this.temporaryDirectory === null) {
|
|
5427
|
+
this.temporaryDirectory = fs6.mkdtempSync(path5.join(os2.tmpdir(), "spreadsheet-updater-"));
|
|
5428
|
+
} else {
|
|
5429
|
+
try {
|
|
5430
|
+
fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
5431
|
+
} catch {
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
return this.temporaryDirectory;
|
|
5435
|
+
}
|
|
5436
|
+
_temporaryPath(suffix) {
|
|
5437
|
+
return path5.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
5438
|
+
}
|
|
5439
|
+
_removeTemporaryFile(filePath) {
|
|
5440
|
+
try {
|
|
5441
|
+
fs6.rmSync(filePath);
|
|
5442
|
+
} catch (error) {
|
|
5443
|
+
if (error.code !== "ENOENT") throw error;
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
_entryData(name) {
|
|
5447
|
+
const stagedPath = this.stagedParts.get(name);
|
|
5448
|
+
if (stagedPath) return fs6.readFileSync(stagedPath);
|
|
5449
|
+
const entry = this.zip.getEntry(name);
|
|
5450
|
+
if (!entry) throw new Error(`XlsbUpdater: ZIP member missing: ${name}`);
|
|
5451
|
+
return entry.getData();
|
|
5452
|
+
}
|
|
5453
|
+
_stagePart(name, filePath) {
|
|
5454
|
+
this.stagedParts.set(name, filePath);
|
|
5455
|
+
}
|
|
5456
|
+
_discardStagedPart(name) {
|
|
5457
|
+
const stagedPath = this.stagedParts.get(name);
|
|
5458
|
+
if (!stagedPath) return;
|
|
5459
|
+
this._removeTemporaryFile(stagedPath);
|
|
5460
|
+
this.stagedParts.delete(name);
|
|
5461
|
+
}
|
|
5462
|
+
_materializeStagedParts() {
|
|
5463
|
+
for (const [name, stagedPath] of this.stagedParts) {
|
|
5464
|
+
this.zip.updateFile(name, fs6.readFileSync(stagedPath));
|
|
5465
|
+
this._removeTemporaryFile(stagedPath);
|
|
5466
|
+
}
|
|
5467
|
+
this.stagedParts.clear();
|
|
5468
|
+
}
|
|
4577
5469
|
// ------------------------------------------------------------------
|
|
4578
5470
|
// Workbook structure
|
|
4579
5471
|
// ------------------------------------------------------------------
|
|
@@ -4623,11 +5515,11 @@ var XlsbUpdater = class {
|
|
|
4623
5515
|
return;
|
|
4624
5516
|
}
|
|
4625
5517
|
const entry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
4626
|
-
if (!entry) {
|
|
5518
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.bin")) {
|
|
4627
5519
|
this.sharedStringsBuf = null;
|
|
4628
5520
|
return;
|
|
4629
5521
|
}
|
|
4630
|
-
this.sharedStringsBuf =
|
|
5522
|
+
this.sharedStringsBuf = this._entryData("xl/sharedStrings.bin");
|
|
4631
5523
|
const parsed = parseSharedStringsBin(this.sharedStringsBuf);
|
|
4632
5524
|
this.sharedStringsValues = parsed.values;
|
|
4633
5525
|
this.sharedStringsTotal = parsed.total;
|
|
@@ -4639,9 +5531,10 @@ var XlsbUpdater = class {
|
|
|
4639
5531
|
}
|
|
4640
5532
|
});
|
|
4641
5533
|
this._appendCount = 0;
|
|
5534
|
+
this.sharedStringsDirty = false;
|
|
4642
5535
|
}
|
|
4643
5536
|
_commitSharedStrings() {
|
|
4644
|
-
if (this.sharedStringsBuf === null || this.
|
|
5537
|
+
if (this.sharedStringsBuf === null || !this.sharedStringsDirty) return;
|
|
4645
5538
|
const originalValuesLength = this.sharedStringsValues.length - this._appendCount;
|
|
4646
5539
|
let newItems = Buffer.alloc(0);
|
|
4647
5540
|
for (let i = originalValuesLength; i < this.sharedStringsValues.length; i++) {
|
|
@@ -4667,6 +5560,9 @@ var XlsbUpdater = class {
|
|
|
4667
5560
|
pos = rec.dataEnd;
|
|
4668
5561
|
}
|
|
4669
5562
|
this.zip.updateFile("xl/sharedStrings.bin", out);
|
|
5563
|
+
this.sharedStringsBuf = out;
|
|
5564
|
+
this._appendCount = 0;
|
|
5565
|
+
this.sharedStringsDirty = false;
|
|
4670
5566
|
}
|
|
4671
5567
|
// ------------------------------------------------------------------
|
|
4672
5568
|
// Styles
|
|
@@ -4849,12 +5745,51 @@ var XlsbUpdater = class {
|
|
|
4849
5745
|
this._appendCount++;
|
|
4850
5746
|
}
|
|
4851
5747
|
this.sharedStringsTotal++;
|
|
5748
|
+
this.sharedStringsDirty = true;
|
|
4852
5749
|
const payload = Buffer.alloc(12);
|
|
4853
5750
|
payload.writeUInt32LE(col, 0);
|
|
4854
5751
|
payload.writeUInt32LE(style, 4);
|
|
4855
5752
|
payload.writeUInt32LE(index, 8);
|
|
4856
5753
|
return buildRecord(7, payload);
|
|
4857
5754
|
}
|
|
5755
|
+
_sharedStringRefCount(sheetBuf) {
|
|
5756
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5757
|
+
let pos = 0;
|
|
5758
|
+
let count = 0;
|
|
5759
|
+
while (readRecord(sheetBuf, pos, rec)) {
|
|
5760
|
+
if (rec.id === 7) count++;
|
|
5761
|
+
pos = rec.dataEnd;
|
|
5762
|
+
}
|
|
5763
|
+
return count;
|
|
5764
|
+
}
|
|
5765
|
+
_patchSheetMetadataFile(filePath, originalSheet, rowsEnd, delta, autoFilterRanges, lastRowIdx, lastCol) {
|
|
5766
|
+
const fd = fs6.openSync(filePath, "r+");
|
|
5767
|
+
try {
|
|
5768
|
+
const patchInt32 = (offset, value) => {
|
|
5769
|
+
const bytes = Buffer.alloc(4);
|
|
5770
|
+
bytes.writeInt32LE(value, 0);
|
|
5771
|
+
fs6.writeSync(fd, bytes, 0, bytes.length, offset);
|
|
5772
|
+
};
|
|
5773
|
+
for (const filterOffset of autoFilterRanges) {
|
|
5774
|
+
const newOffset = filterOffset >= rowsEnd ? filterOffset + delta : filterOffset;
|
|
5775
|
+
patchInt32(newOffset + 4, lastRowIdx);
|
|
5776
|
+
patchInt32(newOffset + 12, lastCol);
|
|
5777
|
+
}
|
|
5778
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5779
|
+
let pos = 0;
|
|
5780
|
+
while (readRecord(originalSheet, pos, rec)) {
|
|
5781
|
+
if (rec.id === 152 && rec.len >= 36) {
|
|
5782
|
+
const shift = rec.dataStart >= rowsEnd ? delta : 0;
|
|
5783
|
+
patchInt32(rec.dataStart + shift + 24, lastRowIdx);
|
|
5784
|
+
patchInt32(rec.dataStart + shift + 32, lastCol);
|
|
5785
|
+
break;
|
|
5786
|
+
}
|
|
5787
|
+
pos = rec.dataEnd;
|
|
5788
|
+
}
|
|
5789
|
+
} finally {
|
|
5790
|
+
fs6.closeSync(fd);
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
4858
5793
|
// ------------------------------------------------------------------
|
|
4859
5794
|
// Rows region detection
|
|
4860
5795
|
// ------------------------------------------------------------------
|
|
@@ -4906,8 +5841,8 @@ var XlsbUpdater = class {
|
|
|
4906
5841
|
// Pivot cache integration
|
|
4907
5842
|
// ------------------------------------------------------------------
|
|
4908
5843
|
_patchPivotCaches(sheetName, rwLast, colLast) {
|
|
4909
|
-
for (const
|
|
4910
|
-
const entry = this.zip.getEntry(
|
|
5844
|
+
for (const path7 of this.pivotCacheDefPaths) {
|
|
5845
|
+
const entry = this.zip.getEntry(path7);
|
|
4911
5846
|
if (!entry) continue;
|
|
4912
5847
|
const buf = Buffer.from(entry.getData());
|
|
4913
5848
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
@@ -4939,11 +5874,188 @@ var XlsbUpdater = class {
|
|
|
4939
5874
|
}
|
|
4940
5875
|
}
|
|
4941
5876
|
if (patched) {
|
|
4942
|
-
this.zip.updateFile(
|
|
5877
|
+
this.zip.updateFile(path7, buf);
|
|
4943
5878
|
}
|
|
4944
5879
|
}
|
|
4945
5880
|
}
|
|
4946
5881
|
};
|
|
5882
|
+
|
|
5883
|
+
// src/ExcelConverter.ts
|
|
5884
|
+
var fs7 = __toESM(require("fs"));
|
|
5885
|
+
var path6 = __toESM(require("path"));
|
|
5886
|
+
var import_crypto3 = require("crypto");
|
|
5887
|
+
var import_child_process = require("child_process");
|
|
5888
|
+
var POWERSHELL_CONVERSION_SCRIPT = `
|
|
5889
|
+
$ErrorActionPreference = 'Stop'
|
|
5890
|
+
$inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
|
|
5891
|
+
$outputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_OUTPUT')
|
|
5892
|
+
$fileFormat = [int][Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_FORMAT')
|
|
5893
|
+
$excel = $null
|
|
5894
|
+
$workbook = $null
|
|
5895
|
+
$exitCode = 0
|
|
5896
|
+
|
|
5897
|
+
try {
|
|
5898
|
+
$excel = New-Object -ComObject Excel.Application
|
|
5899
|
+
$excel.Visible = $false
|
|
5900
|
+
$excel.DisplayAlerts = $false
|
|
5901
|
+
$excel.ScreenUpdating = $false
|
|
5902
|
+
|
|
5903
|
+
# Open read-only so the source workbook is never changed by the conversion.
|
|
5904
|
+
$workbook = $excel.Workbooks.Open($inputPath, 0, $true)
|
|
5905
|
+
if ($null -eq $workbook) {
|
|
5906
|
+
throw 'Excel did not return a workbook when opening the source file.'
|
|
5907
|
+
}
|
|
5908
|
+
|
|
5909
|
+
$workbook.SaveAs($outputPath, $fileFormat)
|
|
5910
|
+
} catch {
|
|
5911
|
+
$exitCode = 1
|
|
5912
|
+
[Console]::Error.WriteLine($_.Exception.ToString())
|
|
5913
|
+
} finally {
|
|
5914
|
+
if ($null -ne $workbook) {
|
|
5915
|
+
try { $workbook.Close($false) } catch { }
|
|
5916
|
+
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook) | Out-Null } catch { }
|
|
5917
|
+
}
|
|
5918
|
+
if ($null -ne $excel) {
|
|
5919
|
+
try { $excel.Quit() } catch { }
|
|
5920
|
+
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null } catch { }
|
|
5921
|
+
}
|
|
5922
|
+
[GC]::Collect()
|
|
5923
|
+
[GC]::WaitForPendingFinalizers()
|
|
5924
|
+
}
|
|
5925
|
+
|
|
5926
|
+
exit $exitCode
|
|
5927
|
+
`;
|
|
5928
|
+
function errorMessage2(error) {
|
|
5929
|
+
return error instanceof Error ? error.message : String(error);
|
|
5930
|
+
}
|
|
5931
|
+
function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
|
|
5932
|
+
const input = path6.resolve(inputPath);
|
|
5933
|
+
const output = path6.resolve(outputPath);
|
|
5934
|
+
if (path6.extname(input).toLowerCase() !== inputExtension) {
|
|
5935
|
+
throw new Error(
|
|
5936
|
+
`Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
|
|
5937
|
+
);
|
|
5938
|
+
}
|
|
5939
|
+
if (path6.extname(output).toLowerCase() !== outputExtension) {
|
|
5940
|
+
throw new Error(
|
|
5941
|
+
`Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
|
|
5942
|
+
);
|
|
5943
|
+
}
|
|
5944
|
+
if (!fs7.existsSync(input)) {
|
|
5945
|
+
throw new Error(`Excel conversion: source file not found: ${inputPath}`);
|
|
5946
|
+
}
|
|
5947
|
+
if (!fs7.statSync(input).isFile()) {
|
|
5948
|
+
throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
|
|
5949
|
+
}
|
|
5950
|
+
if (!fs7.existsSync(path6.dirname(output))) {
|
|
5951
|
+
throw new Error(`Excel conversion: destination directory not found: ${path6.dirname(output)}`);
|
|
5952
|
+
}
|
|
5953
|
+
if (fs7.existsSync(output)) {
|
|
5954
|
+
if (fs7.statSync(output).isDirectory()) {
|
|
5955
|
+
throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
|
|
5956
|
+
}
|
|
5957
|
+
if (!overwrite) {
|
|
5958
|
+
throw new Error(
|
|
5959
|
+
`Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${outputPath}`
|
|
5960
|
+
);
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
if (process.platform === "win32" && input.toLowerCase() === output.toLowerCase()) {
|
|
5964
|
+
throw new Error("Excel conversion: source and destination must be different files.");
|
|
5965
|
+
}
|
|
5966
|
+
return { input, output };
|
|
5967
|
+
}
|
|
5968
|
+
function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
5969
|
+
const encodedCommand = Buffer.from(POWERSHELL_CONVERSION_SCRIPT, "utf16le").toString("base64");
|
|
5970
|
+
const environment = {
|
|
5971
|
+
...process.env,
|
|
5972
|
+
JUSTYBASE_EXCEL_INPUT: inputPath,
|
|
5973
|
+
JUSTYBASE_EXCEL_OUTPUT: outputPath,
|
|
5974
|
+
JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
|
|
5975
|
+
};
|
|
5976
|
+
return new Promise((resolve4, reject) => {
|
|
5977
|
+
var _a, _b, _c, _d;
|
|
5978
|
+
let stdout = "";
|
|
5979
|
+
let stderr = "";
|
|
5980
|
+
let settled = false;
|
|
5981
|
+
const child = (0, import_child_process.spawn)(
|
|
5982
|
+
"powershell.exe",
|
|
5983
|
+
[
|
|
5984
|
+
"-NoLogo",
|
|
5985
|
+
"-NoProfile",
|
|
5986
|
+
"-NonInteractive",
|
|
5987
|
+
"-ExecutionPolicy",
|
|
5988
|
+
"Bypass",
|
|
5989
|
+
"-EncodedCommand",
|
|
5990
|
+
encodedCommand
|
|
5991
|
+
],
|
|
5992
|
+
{ env: environment, windowsHide: true }
|
|
5993
|
+
);
|
|
5994
|
+
(_a = child.stdout) == null ? void 0 : _a.setEncoding("utf8");
|
|
5995
|
+
(_b = child.stderr) == null ? void 0 : _b.setEncoding("utf8");
|
|
5996
|
+
(_c = child.stdout) == null ? void 0 : _c.on("data", (chunk) => {
|
|
5997
|
+
stdout += chunk;
|
|
5998
|
+
});
|
|
5999
|
+
(_d = child.stderr) == null ? void 0 : _d.on("data", (chunk) => {
|
|
6000
|
+
stderr += chunk;
|
|
6001
|
+
});
|
|
6002
|
+
child.once("error", (error) => {
|
|
6003
|
+
if (settled) return;
|
|
6004
|
+
settled = true;
|
|
6005
|
+
reject(new Error(`Excel conversion: unable to start PowerShell/COM: ${error.message}`));
|
|
6006
|
+
});
|
|
6007
|
+
child.once("close", (code) => {
|
|
6008
|
+
if (settled) return;
|
|
6009
|
+
settled = true;
|
|
6010
|
+
if (code === 0) {
|
|
6011
|
+
resolve4();
|
|
6012
|
+
return;
|
|
6013
|
+
}
|
|
6014
|
+
const details = (stderr || stdout).replace(/\s+/g, " ").trim();
|
|
6015
|
+
reject(new Error(
|
|
6016
|
+
`Excel conversion failed (PowerShell exit code ${code ?? "unknown"})${details ? `: ${details}` : "."}`
|
|
6017
|
+
));
|
|
6018
|
+
});
|
|
6019
|
+
});
|
|
6020
|
+
}
|
|
6021
|
+
async function convertExcelFile(inputPath, outputPath, inputExtension, outputExtension, fileFormat, options = {}) {
|
|
6022
|
+
const { input, output } = validateConversionPaths(
|
|
6023
|
+
inputPath,
|
|
6024
|
+
outputPath,
|
|
6025
|
+
inputExtension,
|
|
6026
|
+
outputExtension,
|
|
6027
|
+
options.overwrite === true
|
|
6028
|
+
);
|
|
6029
|
+
if (process.platform !== "win32") {
|
|
6030
|
+
throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
|
|
6031
|
+
}
|
|
6032
|
+
const temporaryOutput = path6.join(
|
|
6033
|
+
path6.dirname(output),
|
|
6034
|
+
`.${path6.basename(output, outputExtension)}.${(0, import_crypto3.randomUUID)()}.conversion${outputExtension}`
|
|
6035
|
+
);
|
|
6036
|
+
try {
|
|
6037
|
+
await runPowerShellConversion(input, temporaryOutput, fileFormat);
|
|
6038
|
+
if (!fs7.existsSync(temporaryOutput)) {
|
|
6039
|
+
throw new Error("Excel conversion completed without creating the destination file.");
|
|
6040
|
+
}
|
|
6041
|
+
installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
|
|
6042
|
+
} catch (error) {
|
|
6043
|
+
throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
|
|
6044
|
+
} finally {
|
|
6045
|
+
if (fs7.existsSync(temporaryOutput)) {
|
|
6046
|
+
try {
|
|
6047
|
+
fs7.rmSync(temporaryOutput);
|
|
6048
|
+
} catch {
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
function convertXlsbToXlsx(inputPath, outputPath, options) {
|
|
6054
|
+
return convertExcelFile(inputPath, outputPath, ".xlsb", ".xlsx", 51, options);
|
|
6055
|
+
}
|
|
6056
|
+
function convertXlsxToXlsb(inputPath, outputPath, options) {
|
|
6057
|
+
return convertExcelFile(inputPath, outputPath, ".xlsx", ".xlsb", 50, options);
|
|
6058
|
+
}
|
|
4947
6059
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4948
6060
|
0 && (module.exports = {
|
|
4949
6061
|
BiffReaderWriter,
|
|
@@ -4956,10 +6068,13 @@ var XlsbUpdater = class {
|
|
|
4956
6068
|
XlsbReader,
|
|
4957
6069
|
XlsbUpdater,
|
|
4958
6070
|
XlsbWriter,
|
|
6071
|
+
XlsmUpdater,
|
|
4959
6072
|
XlsxReader,
|
|
4960
6073
|
XlsxUpdater,
|
|
4961
6074
|
XlsxWriter,
|
|
4962
6075
|
applyHeaderWidths,
|
|
6076
|
+
convertXlsbToXlsx,
|
|
6077
|
+
convertXlsxToXlsb,
|
|
4963
6078
|
defaultColWidth,
|
|
4964
6079
|
getFormat,
|
|
4965
6080
|
initColWidths,
|