@justybase/spreadsheet-tasks 2.1.1 → 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 +10 -0
- package/README.md +20 -2
- package/dist/cjs/index.d.ts +61 -3
- package/dist/cjs/index.js +1043 -176
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1042 -176
- package/dist/esm/index.js.map +1 -1
- package/docs/API.md +32 -2
- package/package.json +2 -2
package/dist/cjs/index.js
CHANGED
|
@@ -40,6 +40,7 @@ __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,
|
|
@@ -460,8 +461,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
460
461
|
this.xfIdToNumFmtId = [];
|
|
461
462
|
this.customDateFormats = null;
|
|
462
463
|
}
|
|
463
|
-
async open(
|
|
464
|
-
this.zip = new import_adm_zip.default(
|
|
464
|
+
async open(path7, readSharedStrings = true) {
|
|
465
|
+
this.zip = new import_adm_zip.default(path7);
|
|
465
466
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
466
467
|
if (wbEntry) {
|
|
467
468
|
const buf = wbEntry.getData();
|
|
@@ -506,8 +507,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
506
507
|
this.resultsCount = this.sheets.length;
|
|
507
508
|
this._currentSheetIndex = -1;
|
|
508
509
|
}
|
|
509
|
-
_readSharedStrings(
|
|
510
|
-
const entry = this.zip.getEntry(
|
|
510
|
+
_readSharedStrings(path7) {
|
|
511
|
+
const entry = this.zip.getEntry(path7);
|
|
511
512
|
if (!entry) return;
|
|
512
513
|
const reader = new BiffReaderWriter(entry.getData());
|
|
513
514
|
while (reader.readSharedStrings()) {
|
|
@@ -516,8 +517,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
516
517
|
}
|
|
517
518
|
}
|
|
518
519
|
}
|
|
519
|
-
_readStyles(
|
|
520
|
-
const entry = this.zip.getEntry(
|
|
520
|
+
_readStyles(path7) {
|
|
521
|
+
const entry = this.zip.getEntry(path7);
|
|
521
522
|
if (!entry) return;
|
|
522
523
|
const reader = new BiffReaderWriter(entry.getData());
|
|
523
524
|
while (reader.readStyles()) {
|
|
@@ -640,7 +641,7 @@ function escapeXmlText(str) {
|
|
|
640
641
|
}
|
|
641
642
|
function unescapeXml(str) {
|
|
642
643
|
if (!str || str.indexOf("&") === -1) return str;
|
|
643
|
-
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, "'");
|
|
644
645
|
}
|
|
645
646
|
function columnIndexToLetter(colIndex) {
|
|
646
647
|
let n = colIndex + 1;
|
|
@@ -669,26 +670,17 @@ function trimTrailingEmptyRows(rows) {
|
|
|
669
670
|
}
|
|
670
671
|
function parseSharedStringsXml(xml) {
|
|
671
672
|
const result = [];
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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;
|
|
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));
|
|
692
684
|
}
|
|
693
685
|
return result;
|
|
694
686
|
}
|
|
@@ -709,9 +701,9 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
709
701
|
this.xfIdToNumFmtId = [];
|
|
710
702
|
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
711
703
|
}
|
|
712
|
-
async open(
|
|
713
|
-
return new Promise((
|
|
714
|
-
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) => {
|
|
715
707
|
if (err) return reject(err);
|
|
716
708
|
this.zipfile = zipfile;
|
|
717
709
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -759,7 +751,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
759
751
|
}
|
|
760
752
|
this.resultsCount = this.sheets.length;
|
|
761
753
|
this._currentSheetIndex = -1;
|
|
762
|
-
|
|
754
|
+
resolve4();
|
|
763
755
|
} catch (e) {
|
|
764
756
|
reject(e);
|
|
765
757
|
}
|
|
@@ -773,16 +765,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
773
765
|
this.zipfile.close();
|
|
774
766
|
}
|
|
775
767
|
}
|
|
776
|
-
async _readZipEntryContent(
|
|
777
|
-
const entry = this.entries.get(
|
|
768
|
+
async _readZipEntryContent(path7) {
|
|
769
|
+
const entry = this.entries.get(path7);
|
|
778
770
|
if (!entry) return null;
|
|
779
|
-
return new Promise((
|
|
771
|
+
return new Promise((resolve4, reject) => {
|
|
780
772
|
this.zipfile.openReadStream(entry, (err, readStream) => {
|
|
781
773
|
if (err) return reject(err);
|
|
782
774
|
const chunks = [];
|
|
783
775
|
readStream.on("data", (chunk) => chunks.push(chunk));
|
|
784
776
|
readStream.on("end", () => {
|
|
785
|
-
|
|
777
|
+
resolve4(Buffer.concat(chunks).toString("utf8"));
|
|
786
778
|
});
|
|
787
779
|
readStream.on("error", reject);
|
|
788
780
|
});
|
|
@@ -3388,7 +3380,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3388
3380
|
* Must be awaited before the file is fully written.
|
|
3389
3381
|
*/
|
|
3390
3382
|
finalize() {
|
|
3391
|
-
return new Promise((
|
|
3383
|
+
return new Promise((resolve4, reject) => {
|
|
3392
3384
|
try {
|
|
3393
3385
|
this.saveSst();
|
|
3394
3386
|
this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
|
|
@@ -3486,7 +3478,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3486
3478
|
</Relationships>`;
|
|
3487
3479
|
this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
|
|
3488
3480
|
}
|
|
3489
|
-
this.output.on("close", () =>
|
|
3481
|
+
this.output.on("close", () => resolve4());
|
|
3490
3482
|
this.archive.on("error", (err) => reject(err));
|
|
3491
3483
|
this.archive.finalize();
|
|
3492
3484
|
} catch (err) {
|
|
@@ -3834,7 +3826,7 @@ var XlsxWriter = class {
|
|
|
3834
3826
|
}
|
|
3835
3827
|
/** Finalize the ZIP package and close the output stream. */
|
|
3836
3828
|
finalize() {
|
|
3837
|
-
return new Promise((
|
|
3829
|
+
return new Promise((resolve4, reject) => {
|
|
3838
3830
|
try {
|
|
3839
3831
|
this._writeSharedStrings();
|
|
3840
3832
|
this._writeStyles();
|
|
@@ -3842,7 +3834,7 @@ var XlsxWriter = class {
|
|
|
3842
3834
|
this._writeContentTypes();
|
|
3843
3835
|
this._writeRels();
|
|
3844
3836
|
this.output.on("close", () => {
|
|
3845
|
-
|
|
3837
|
+
resolve4();
|
|
3846
3838
|
});
|
|
3847
3839
|
this.archive.on("error", (err) => {
|
|
3848
3840
|
reject(err);
|
|
@@ -3979,13 +3971,237 @@ ${xfEntries.join("\n")}
|
|
|
3979
3971
|
};
|
|
3980
3972
|
|
|
3981
3973
|
// src/XlsxUpdater.ts
|
|
3982
|
-
var
|
|
3974
|
+
var fs5 = __toESM(require("fs"));
|
|
3975
|
+
var os = __toESM(require("os"));
|
|
3976
|
+
var path4 = __toESM(require("path"));
|
|
3983
3977
|
var import_adm_zip2 = __toESM(require("adm-zip"));
|
|
3984
|
-
|
|
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 {
|
|
3985
4199
|
/**
|
|
3986
4200
|
* @param path Path to an existing `.xlsx` file.
|
|
3987
4201
|
*/
|
|
3988
|
-
constructor(
|
|
4202
|
+
constructor(filePath) {
|
|
4203
|
+
this.temporaryDirectory = null;
|
|
4204
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
3989
4205
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
3990
4206
|
this.pivotCacheDefs = [];
|
|
3991
4207
|
this.pivotTableDefPaths = [];
|
|
@@ -3993,13 +4209,15 @@ var XlsxUpdater = class {
|
|
|
3993
4209
|
this.sharedStringsValues = [];
|
|
3994
4210
|
this.sharedStringsOriginalLength = 0;
|
|
3995
4211
|
this.sharedStringsCount = 0;
|
|
4212
|
+
this.sharedStringsPending = [];
|
|
4213
|
+
this.sharedStringsDirty = false;
|
|
3996
4214
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
3997
|
-
this.sourcePath =
|
|
4215
|
+
this.sourcePath = filePath;
|
|
3998
4216
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3999
|
-
if (!
|
|
4000
|
-
throw new Error(`XlsxUpdater: file not found: ${
|
|
4217
|
+
if (!fs5.existsSync(filePath)) {
|
|
4218
|
+
throw new Error(`XlsxUpdater: file not found: ${filePath}`);
|
|
4001
4219
|
}
|
|
4002
|
-
this.zip = new import_adm_zip2.default(
|
|
4220
|
+
this.zip = new import_adm_zip2.default(filePath);
|
|
4003
4221
|
if (this.zip.getEntry("xl/workbook.bin")) {
|
|
4004
4222
|
throw new Error(
|
|
4005
4223
|
"XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
|
|
@@ -4033,8 +4251,15 @@ var XlsxUpdater = class {
|
|
|
4033
4251
|
if (!entry) {
|
|
4034
4252
|
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4035
4253
|
}
|
|
4036
|
-
const sheetXml =
|
|
4254
|
+
const sheetXml = this._entryData(sheetPath).toString("utf8");
|
|
4037
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
|
+
}
|
|
4038
4263
|
let colStyles = /* @__PURE__ */ new Map();
|
|
4039
4264
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
4040
4265
|
let dateStyle = null;
|
|
@@ -4049,6 +4274,7 @@ var XlsxUpdater = class {
|
|
|
4049
4274
|
const newSheetData = this._buildSheetData(rows, headers, colStyles, headerStyles, dateStyle);
|
|
4050
4275
|
const newXml = this._patchSheetXml(sheetXml, newSheetData, totalRows, lastCol);
|
|
4051
4276
|
this.zip.updateFile(sheetPath, Buffer.from(newXml, "utf8"));
|
|
4277
|
+
this._discardStagedPart(sheetPath);
|
|
4052
4278
|
if (this.sharedStringsXml !== null) {
|
|
4053
4279
|
this._commitSharedStrings();
|
|
4054
4280
|
}
|
|
@@ -4056,18 +4282,255 @@ var XlsxUpdater = class {
|
|
|
4056
4282
|
this._updatePivotCaches(sheetName, dimRef, rows.length);
|
|
4057
4283
|
this._addRefreshOnLoadToPivotTables();
|
|
4058
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
|
+
}
|
|
4059
4445
|
/**
|
|
4060
4446
|
* Write the updated workbook to disk.
|
|
4061
4447
|
* @param outputPath Destination path; defaults to the source file (overwrites in place).
|
|
4062
4448
|
*/
|
|
4063
4449
|
save(outputPath) {
|
|
4064
4450
|
const target = outputPath ?? this.sourcePath;
|
|
4065
|
-
|
|
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();
|
|
4066
4459
|
}
|
|
4067
4460
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4068
4461
|
toBuffer() {
|
|
4462
|
+
this._materializeStagedParts();
|
|
4069
4463
|
return this.zip.toBuffer();
|
|
4070
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
|
+
}
|
|
4071
4534
|
// ------------------------------------------------------------------
|
|
4072
4535
|
// Workbook structure
|
|
4073
4536
|
// ------------------------------------------------------------------
|
|
@@ -4120,47 +4583,59 @@ var XlsxUpdater = class {
|
|
|
4120
4583
|
return;
|
|
4121
4584
|
}
|
|
4122
4585
|
const entry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4123
|
-
if (!entry) {
|
|
4586
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.xml")) {
|
|
4124
4587
|
this.sharedStringsXml = null;
|
|
4125
4588
|
return;
|
|
4126
4589
|
}
|
|
4127
|
-
this.sharedStringsXml =
|
|
4590
|
+
this.sharedStringsXml = this._entryData("xl/sharedStrings.xml").toString("utf8");
|
|
4128
4591
|
this.sharedStringsValues = parseSharedStringsXml(this.sharedStringsXml);
|
|
4129
|
-
const countMatch = /<sst\b[^>]
|
|
4130
|
-
const countAttr = countMatch ? /count
|
|
4131
|
-
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;
|
|
4132
4595
|
this.sharedStringsValues.forEach((value, index) => {
|
|
4133
4596
|
if (!this.stringIndexMap.has(value)) {
|
|
4134
4597
|
this.stringIndexMap.set(value, index);
|
|
4135
4598
|
}
|
|
4136
4599
|
});
|
|
4137
4600
|
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
4601
|
+
this.sharedStringsPending = [];
|
|
4602
|
+
this.sharedStringsDirty = false;
|
|
4138
4603
|
}
|
|
4139
4604
|
_commitSharedStrings() {
|
|
4140
|
-
|
|
4605
|
+
var _a;
|
|
4606
|
+
if (this.sharedStringsXml === null || !this.sharedStringsDirty) {
|
|
4141
4607
|
return;
|
|
4142
4608
|
}
|
|
4143
4609
|
let xml = this.sharedStringsXml;
|
|
4144
|
-
const
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
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.");
|
|
4154
4626
|
}
|
|
4155
|
-
|
|
4156
|
-
for (let i = this.sharedStringsOriginalLength; i < this.sharedStringsValues.length; i++) {
|
|
4157
|
-
const txt = this.sharedStringsValues[i];
|
|
4627
|
+
const appended = this.sharedStringsPending.map((txt) => {
|
|
4158
4628
|
const clean = escapeXmlText(txt);
|
|
4159
4629
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
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);
|
|
4163
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;
|
|
4164
4639
|
}
|
|
4165
4640
|
// ------------------------------------------------------------------
|
|
4166
4641
|
// Styles
|
|
@@ -4311,10 +4786,25 @@ var XlsxUpdater = class {
|
|
|
4311
4786
|
index = this.sharedStringsValues.length;
|
|
4312
4787
|
this.sharedStringsValues.push(text);
|
|
4313
4788
|
this.stringIndexMap.set(text, index);
|
|
4789
|
+
this.sharedStringsPending.push(text);
|
|
4314
4790
|
}
|
|
4315
4791
|
this.sharedStringsCount++;
|
|
4792
|
+
this.sharedStringsDirty = true;
|
|
4316
4793
|
return `<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`;
|
|
4317
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
|
+
}
|
|
4318
4808
|
// ------------------------------------------------------------------
|
|
4319
4809
|
// Worksheet XML patching
|
|
4320
4810
|
// ------------------------------------------------------------------
|
|
@@ -4356,6 +4846,109 @@ var XlsxUpdater = class {
|
|
|
4356
4846
|
}
|
|
4357
4847
|
return xml;
|
|
4358
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
|
+
}
|
|
4359
4952
|
// ------------------------------------------------------------------
|
|
4360
4953
|
// Pivot table integration
|
|
4361
4954
|
// ------------------------------------------------------------------
|
|
@@ -4384,8 +4977,8 @@ var XlsxUpdater = class {
|
|
|
4384
4977
|
}
|
|
4385
4978
|
}
|
|
4386
4979
|
_addRefreshOnLoadToPivotTables() {
|
|
4387
|
-
for (const
|
|
4388
|
-
const entry = this.zip.getEntry(
|
|
4980
|
+
for (const path7 of this.pivotTableDefPaths) {
|
|
4981
|
+
const entry = this.zip.getEntry(path7);
|
|
4389
4982
|
if (!entry) continue;
|
|
4390
4983
|
let xml = entry.getData().toString("utf8");
|
|
4391
4984
|
const rootRe = /<pivotTableDefinition\b[^>]*>/;
|
|
@@ -4393,13 +4986,15 @@ var XlsxUpdater = class {
|
|
|
4393
4986
|
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
4394
4987
|
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
4395
4988
|
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
4396
|
-
this.zip.updateFile(
|
|
4989
|
+
this.zip.updateFile(path7, Buffer.from(xml, "utf8"));
|
|
4397
4990
|
}
|
|
4398
4991
|
}
|
|
4399
4992
|
};
|
|
4400
4993
|
|
|
4401
4994
|
// src/XlsbUpdater.ts
|
|
4402
|
-
var
|
|
4995
|
+
var fs6 = __toESM(require("fs"));
|
|
4996
|
+
var os2 = __toESM(require("os"));
|
|
4997
|
+
var path5 = __toESM(require("path"));
|
|
4403
4998
|
var import_adm_zip3 = __toESM(require("adm-zip"));
|
|
4404
4999
|
|
|
4405
5000
|
// src/biff12Utils.ts
|
|
@@ -4478,7 +5073,9 @@ var XlsbUpdater = class {
|
|
|
4478
5073
|
/**
|
|
4479
5074
|
* @param path Path to an existing `.xlsb` file.
|
|
4480
5075
|
*/
|
|
4481
|
-
constructor(
|
|
5076
|
+
constructor(filePath) {
|
|
5077
|
+
this.temporaryDirectory = null;
|
|
5078
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
4482
5079
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
4483
5080
|
this.pivotCacheDefPaths = [];
|
|
4484
5081
|
this.sharedStringsBuf = null;
|
|
@@ -4487,16 +5084,17 @@ var XlsbUpdater = class {
|
|
|
4487
5084
|
this.sharedStringsUnique = 0;
|
|
4488
5085
|
this.sharedStringsEndSst = 0;
|
|
4489
5086
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
5087
|
+
this.sharedStringsDirty = false;
|
|
4490
5088
|
// ------------------------------------------------------------------
|
|
4491
5089
|
// Shared strings
|
|
4492
5090
|
// ------------------------------------------------------------------
|
|
4493
5091
|
this._appendCount = 0;
|
|
4494
|
-
this.sourcePath =
|
|
5092
|
+
this.sourcePath = filePath;
|
|
4495
5093
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
4496
|
-
if (!
|
|
4497
|
-
throw new Error(`XlsbUpdater: file not found: ${
|
|
5094
|
+
if (!fs6.existsSync(filePath)) {
|
|
5095
|
+
throw new Error(`XlsbUpdater: file not found: ${filePath}`);
|
|
4498
5096
|
}
|
|
4499
|
-
this.zip = new import_adm_zip3.default(
|
|
5097
|
+
this.zip = new import_adm_zip3.default(filePath);
|
|
4500
5098
|
if (!this.zip.getEntry("xl/workbook.bin")) {
|
|
4501
5099
|
throw new Error(
|
|
4502
5100
|
"XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
|
|
@@ -4527,8 +5125,15 @@ var XlsbUpdater = class {
|
|
|
4527
5125
|
if (!entry) {
|
|
4528
5126
|
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4529
5127
|
}
|
|
4530
|
-
const sheetBuf =
|
|
5128
|
+
const sheetBuf = this._entryData(sheetPath);
|
|
4531
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
|
+
}
|
|
4532
5137
|
let dataStyles = /* @__PURE__ */ new Map();
|
|
4533
5138
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
4534
5139
|
let dateXf = null;
|
|
@@ -4557,25 +5162,310 @@ var XlsbUpdater = class {
|
|
|
4557
5162
|
}
|
|
4558
5163
|
this._patchDimension(newSheet, lastRowIdx, lastCol);
|
|
4559
5164
|
this.zip.updateFile(sheetPath, newSheet);
|
|
4560
|
-
|
|
5165
|
+
this._discardStagedPart(sheetPath);
|
|
5166
|
+
if (this.sharedStringsBuf !== null) {
|
|
4561
5167
|
this._commitSharedStrings();
|
|
4562
5168
|
}
|
|
4563
5169
|
const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
|
|
4564
5170
|
const colLast = lastCol;
|
|
4565
5171
|
this._patchPivotCaches(sheetName, rwLast, colLast);
|
|
4566
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
|
+
}
|
|
4567
5380
|
/**
|
|
4568
5381
|
* Write the updated workbook to disk.
|
|
4569
5382
|
* @param outputPath Destination path; defaults to the source file (overwrites in place).
|
|
4570
5383
|
*/
|
|
4571
5384
|
save(outputPath) {
|
|
4572
5385
|
const target = outputPath ?? this.sourcePath;
|
|
4573
|
-
|
|
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();
|
|
4574
5394
|
}
|
|
4575
5395
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4576
5396
|
toBuffer() {
|
|
5397
|
+
this._materializeStagedParts();
|
|
4577
5398
|
return this.zip.toBuffer();
|
|
4578
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
|
+
}
|
|
4579
5469
|
// ------------------------------------------------------------------
|
|
4580
5470
|
// Workbook structure
|
|
4581
5471
|
// ------------------------------------------------------------------
|
|
@@ -4625,11 +5515,11 @@ var XlsbUpdater = class {
|
|
|
4625
5515
|
return;
|
|
4626
5516
|
}
|
|
4627
5517
|
const entry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
4628
|
-
if (!entry) {
|
|
5518
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.bin")) {
|
|
4629
5519
|
this.sharedStringsBuf = null;
|
|
4630
5520
|
return;
|
|
4631
5521
|
}
|
|
4632
|
-
this.sharedStringsBuf =
|
|
5522
|
+
this.sharedStringsBuf = this._entryData("xl/sharedStrings.bin");
|
|
4633
5523
|
const parsed = parseSharedStringsBin(this.sharedStringsBuf);
|
|
4634
5524
|
this.sharedStringsValues = parsed.values;
|
|
4635
5525
|
this.sharedStringsTotal = parsed.total;
|
|
@@ -4641,9 +5531,10 @@ var XlsbUpdater = class {
|
|
|
4641
5531
|
}
|
|
4642
5532
|
});
|
|
4643
5533
|
this._appendCount = 0;
|
|
5534
|
+
this.sharedStringsDirty = false;
|
|
4644
5535
|
}
|
|
4645
5536
|
_commitSharedStrings() {
|
|
4646
|
-
if (this.sharedStringsBuf === null || this.
|
|
5537
|
+
if (this.sharedStringsBuf === null || !this.sharedStringsDirty) return;
|
|
4647
5538
|
const originalValuesLength = this.sharedStringsValues.length - this._appendCount;
|
|
4648
5539
|
let newItems = Buffer.alloc(0);
|
|
4649
5540
|
for (let i = originalValuesLength; i < this.sharedStringsValues.length; i++) {
|
|
@@ -4669,6 +5560,9 @@ var XlsbUpdater = class {
|
|
|
4669
5560
|
pos = rec.dataEnd;
|
|
4670
5561
|
}
|
|
4671
5562
|
this.zip.updateFile("xl/sharedStrings.bin", out);
|
|
5563
|
+
this.sharedStringsBuf = out;
|
|
5564
|
+
this._appendCount = 0;
|
|
5565
|
+
this.sharedStringsDirty = false;
|
|
4672
5566
|
}
|
|
4673
5567
|
// ------------------------------------------------------------------
|
|
4674
5568
|
// Styles
|
|
@@ -4851,12 +5745,51 @@ var XlsbUpdater = class {
|
|
|
4851
5745
|
this._appendCount++;
|
|
4852
5746
|
}
|
|
4853
5747
|
this.sharedStringsTotal++;
|
|
5748
|
+
this.sharedStringsDirty = true;
|
|
4854
5749
|
const payload = Buffer.alloc(12);
|
|
4855
5750
|
payload.writeUInt32LE(col, 0);
|
|
4856
5751
|
payload.writeUInt32LE(style, 4);
|
|
4857
5752
|
payload.writeUInt32LE(index, 8);
|
|
4858
5753
|
return buildRecord(7, payload);
|
|
4859
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
|
+
}
|
|
4860
5793
|
// ------------------------------------------------------------------
|
|
4861
5794
|
// Rows region detection
|
|
4862
5795
|
// ------------------------------------------------------------------
|
|
@@ -4908,8 +5841,8 @@ var XlsbUpdater = class {
|
|
|
4908
5841
|
// Pivot cache integration
|
|
4909
5842
|
// ------------------------------------------------------------------
|
|
4910
5843
|
_patchPivotCaches(sheetName, rwLast, colLast) {
|
|
4911
|
-
for (const
|
|
4912
|
-
const entry = this.zip.getEntry(
|
|
5844
|
+
for (const path7 of this.pivotCacheDefPaths) {
|
|
5845
|
+
const entry = this.zip.getEntry(path7);
|
|
4913
5846
|
if (!entry) continue;
|
|
4914
5847
|
const buf = Buffer.from(entry.getData());
|
|
4915
5848
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
@@ -4941,84 +5874,17 @@ var XlsbUpdater = class {
|
|
|
4941
5874
|
}
|
|
4942
5875
|
}
|
|
4943
5876
|
if (patched) {
|
|
4944
|
-
this.zip.updateFile(
|
|
5877
|
+
this.zip.updateFile(path7, buf);
|
|
4945
5878
|
}
|
|
4946
5879
|
}
|
|
4947
5880
|
}
|
|
4948
5881
|
};
|
|
4949
5882
|
|
|
4950
5883
|
// src/ExcelConverter.ts
|
|
4951
|
-
var
|
|
4952
|
-
var
|
|
4953
|
-
var
|
|
5884
|
+
var fs7 = __toESM(require("fs"));
|
|
5885
|
+
var path6 = __toESM(require("path"));
|
|
5886
|
+
var import_crypto3 = require("crypto");
|
|
4954
5887
|
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
5888
|
var POWERSHELL_CONVERSION_SCRIPT = `
|
|
5023
5889
|
$ErrorActionPreference = 'Stop'
|
|
5024
5890
|
$inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
|
|
@@ -5063,29 +5929,29 @@ function errorMessage2(error) {
|
|
|
5063
5929
|
return error instanceof Error ? error.message : String(error);
|
|
5064
5930
|
}
|
|
5065
5931
|
function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
|
|
5066
|
-
const input =
|
|
5067
|
-
const output =
|
|
5068
|
-
if (
|
|
5932
|
+
const input = path6.resolve(inputPath);
|
|
5933
|
+
const output = path6.resolve(outputPath);
|
|
5934
|
+
if (path6.extname(input).toLowerCase() !== inputExtension) {
|
|
5069
5935
|
throw new Error(
|
|
5070
5936
|
`Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
|
|
5071
5937
|
);
|
|
5072
5938
|
}
|
|
5073
|
-
if (
|
|
5939
|
+
if (path6.extname(output).toLowerCase() !== outputExtension) {
|
|
5074
5940
|
throw new Error(
|
|
5075
5941
|
`Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
|
|
5076
5942
|
);
|
|
5077
5943
|
}
|
|
5078
|
-
if (!
|
|
5944
|
+
if (!fs7.existsSync(input)) {
|
|
5079
5945
|
throw new Error(`Excel conversion: source file not found: ${inputPath}`);
|
|
5080
5946
|
}
|
|
5081
|
-
if (!
|
|
5947
|
+
if (!fs7.statSync(input).isFile()) {
|
|
5082
5948
|
throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
|
|
5083
5949
|
}
|
|
5084
|
-
if (!
|
|
5085
|
-
throw new Error(`Excel conversion: destination directory not found: ${
|
|
5950
|
+
if (!fs7.existsSync(path6.dirname(output))) {
|
|
5951
|
+
throw new Error(`Excel conversion: destination directory not found: ${path6.dirname(output)}`);
|
|
5086
5952
|
}
|
|
5087
|
-
if (
|
|
5088
|
-
if (
|
|
5953
|
+
if (fs7.existsSync(output)) {
|
|
5954
|
+
if (fs7.statSync(output).isDirectory()) {
|
|
5089
5955
|
throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
|
|
5090
5956
|
}
|
|
5091
5957
|
if (!overwrite) {
|
|
@@ -5107,7 +5973,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5107
5973
|
JUSTYBASE_EXCEL_OUTPUT: outputPath,
|
|
5108
5974
|
JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
|
|
5109
5975
|
};
|
|
5110
|
-
return new Promise((
|
|
5976
|
+
return new Promise((resolve4, reject) => {
|
|
5111
5977
|
var _a, _b, _c, _d;
|
|
5112
5978
|
let stdout = "";
|
|
5113
5979
|
let stderr = "";
|
|
@@ -5142,7 +6008,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5142
6008
|
if (settled) return;
|
|
5143
6009
|
settled = true;
|
|
5144
6010
|
if (code === 0) {
|
|
5145
|
-
|
|
6011
|
+
resolve4();
|
|
5146
6012
|
return;
|
|
5147
6013
|
}
|
|
5148
6014
|
const details = (stderr || stdout).replace(/\s+/g, " ").trim();
|
|
@@ -5163,22 +6029,22 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
|
|
|
5163
6029
|
if (process.platform !== "win32") {
|
|
5164
6030
|
throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
|
|
5165
6031
|
}
|
|
5166
|
-
const temporaryOutput =
|
|
5167
|
-
|
|
5168
|
-
`.${
|
|
6032
|
+
const temporaryOutput = path6.join(
|
|
6033
|
+
path6.dirname(output),
|
|
6034
|
+
`.${path6.basename(output, outputExtension)}.${(0, import_crypto3.randomUUID)()}.conversion${outputExtension}`
|
|
5169
6035
|
);
|
|
5170
6036
|
try {
|
|
5171
6037
|
await runPowerShellConversion(input, temporaryOutput, fileFormat);
|
|
5172
|
-
if (!
|
|
6038
|
+
if (!fs7.existsSync(temporaryOutput)) {
|
|
5173
6039
|
throw new Error("Excel conversion completed without creating the destination file.");
|
|
5174
6040
|
}
|
|
5175
6041
|
installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
|
|
5176
6042
|
} catch (error) {
|
|
5177
6043
|
throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
|
|
5178
6044
|
} finally {
|
|
5179
|
-
if (
|
|
6045
|
+
if (fs7.existsSync(temporaryOutput)) {
|
|
5180
6046
|
try {
|
|
5181
|
-
|
|
6047
|
+
fs7.rmSync(temporaryOutput);
|
|
5182
6048
|
} catch {
|
|
5183
6049
|
}
|
|
5184
6050
|
}
|
|
@@ -5202,6 +6068,7 @@ function convertXlsxToXlsb(inputPath, outputPath, options) {
|
|
|
5202
6068
|
XlsbReader,
|
|
5203
6069
|
XlsbUpdater,
|
|
5204
6070
|
XlsbWriter,
|
|
6071
|
+
XlsmUpdater,
|
|
5205
6072
|
XlsxReader,
|
|
5206
6073
|
XlsxUpdater,
|
|
5207
6074
|
XlsxWriter,
|