@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/esm/index.js
CHANGED
|
@@ -402,8 +402,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
402
402
|
this.xfIdToNumFmtId = [];
|
|
403
403
|
this.customDateFormats = null;
|
|
404
404
|
}
|
|
405
|
-
async open(
|
|
406
|
-
this.zip = new AdmZip(
|
|
405
|
+
async open(path7, readSharedStrings = true) {
|
|
406
|
+
this.zip = new AdmZip(path7);
|
|
407
407
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
408
408
|
if (wbEntry) {
|
|
409
409
|
const buf = wbEntry.getData();
|
|
@@ -448,8 +448,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
448
448
|
this.resultsCount = this.sheets.length;
|
|
449
449
|
this._currentSheetIndex = -1;
|
|
450
450
|
}
|
|
451
|
-
_readSharedStrings(
|
|
452
|
-
const entry = this.zip.getEntry(
|
|
451
|
+
_readSharedStrings(path7) {
|
|
452
|
+
const entry = this.zip.getEntry(path7);
|
|
453
453
|
if (!entry) return;
|
|
454
454
|
const reader = new BiffReaderWriter(entry.getData());
|
|
455
455
|
while (reader.readSharedStrings()) {
|
|
@@ -458,8 +458,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
|
-
_readStyles(
|
|
462
|
-
const entry = this.zip.getEntry(
|
|
461
|
+
_readStyles(path7) {
|
|
462
|
+
const entry = this.zip.getEntry(path7);
|
|
463
463
|
if (!entry) return;
|
|
464
464
|
const reader = new BiffReaderWriter(entry.getData());
|
|
465
465
|
while (reader.readStyles()) {
|
|
@@ -582,7 +582,7 @@ function escapeXmlText(str) {
|
|
|
582
582
|
}
|
|
583
583
|
function unescapeXml(str) {
|
|
584
584
|
if (!str || str.indexOf("&") === -1) return str;
|
|
585
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
585
|
+
return str.replace(/&#x([0-9a-f]+);/gi, (_match, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_match, decimal) => String.fromCodePoint(parseInt(decimal, 10))).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
586
586
|
}
|
|
587
587
|
function columnIndexToLetter(colIndex) {
|
|
588
588
|
let n = colIndex + 1;
|
|
@@ -611,26 +611,17 @@ function trimTrailingEmptyRows(rows) {
|
|
|
611
611
|
}
|
|
612
612
|
function parseSharedStringsXml(xml) {
|
|
613
613
|
const result = [];
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if (tStart === -1) break;
|
|
626
|
-
const tagEnd = content.indexOf(">", tStart);
|
|
627
|
-
const tEnd = content.indexOf("</t>", tagEnd);
|
|
628
|
-
if (tEnd === -1) break;
|
|
629
|
-
val += content.substring(tagEnd + 1, tEnd);
|
|
630
|
-
tPos = tEnd + 4;
|
|
631
|
-
}
|
|
632
|
-
result.push(unescapeXml(val));
|
|
633
|
-
pos = siEnd + 5;
|
|
614
|
+
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>/gi;
|
|
615
|
+
let itemMatch;
|
|
616
|
+
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
617
|
+
const content = itemMatch[1];
|
|
618
|
+
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
619
|
+
let value = "";
|
|
620
|
+
let textMatch;
|
|
621
|
+
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
622
|
+
value += textMatch[1];
|
|
623
|
+
}
|
|
624
|
+
result.push(unescapeXml(value));
|
|
634
625
|
}
|
|
635
626
|
return result;
|
|
636
627
|
}
|
|
@@ -651,9 +642,9 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
651
642
|
this.xfIdToNumFmtId = [];
|
|
652
643
|
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
653
644
|
}
|
|
654
|
-
async open(
|
|
655
|
-
return new Promise((
|
|
656
|
-
yauzl.open(
|
|
645
|
+
async open(path7, readSharedStrings = true) {
|
|
646
|
+
return new Promise((resolve4, reject) => {
|
|
647
|
+
yauzl.open(path7, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
|
|
657
648
|
if (err) return reject(err);
|
|
658
649
|
this.zipfile = zipfile;
|
|
659
650
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -701,7 +692,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
701
692
|
}
|
|
702
693
|
this.resultsCount = this.sheets.length;
|
|
703
694
|
this._currentSheetIndex = -1;
|
|
704
|
-
|
|
695
|
+
resolve4();
|
|
705
696
|
} catch (e) {
|
|
706
697
|
reject(e);
|
|
707
698
|
}
|
|
@@ -715,16 +706,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
715
706
|
this.zipfile.close();
|
|
716
707
|
}
|
|
717
708
|
}
|
|
718
|
-
async _readZipEntryContent(
|
|
719
|
-
const entry = this.entries.get(
|
|
709
|
+
async _readZipEntryContent(path7) {
|
|
710
|
+
const entry = this.entries.get(path7);
|
|
720
711
|
if (!entry) return null;
|
|
721
|
-
return new Promise((
|
|
712
|
+
return new Promise((resolve4, reject) => {
|
|
722
713
|
this.zipfile.openReadStream(entry, (err, readStream) => {
|
|
723
714
|
if (err) return reject(err);
|
|
724
715
|
const chunks = [];
|
|
725
716
|
readStream.on("data", (chunk) => chunks.push(chunk));
|
|
726
717
|
readStream.on("end", () => {
|
|
727
|
-
|
|
718
|
+
resolve4(Buffer.concat(chunks).toString("utf8"));
|
|
728
719
|
});
|
|
729
720
|
readStream.on("error", reject);
|
|
730
721
|
});
|
|
@@ -3330,7 +3321,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3330
3321
|
* Must be awaited before the file is fully written.
|
|
3331
3322
|
*/
|
|
3332
3323
|
finalize() {
|
|
3333
|
-
return new Promise((
|
|
3324
|
+
return new Promise((resolve4, reject) => {
|
|
3334
3325
|
try {
|
|
3335
3326
|
this.saveSst();
|
|
3336
3327
|
this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
|
|
@@ -3428,7 +3419,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3428
3419
|
</Relationships>`;
|
|
3429
3420
|
this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
|
|
3430
3421
|
}
|
|
3431
|
-
this.output.on("close", () =>
|
|
3422
|
+
this.output.on("close", () => resolve4());
|
|
3432
3423
|
this.archive.on("error", (err) => reject(err));
|
|
3433
3424
|
this.archive.finalize();
|
|
3434
3425
|
} catch (err) {
|
|
@@ -3776,7 +3767,7 @@ var XlsxWriter = class {
|
|
|
3776
3767
|
}
|
|
3777
3768
|
/** Finalize the ZIP package and close the output stream. */
|
|
3778
3769
|
finalize() {
|
|
3779
|
-
return new Promise((
|
|
3770
|
+
return new Promise((resolve4, reject) => {
|
|
3780
3771
|
try {
|
|
3781
3772
|
this._writeSharedStrings();
|
|
3782
3773
|
this._writeStyles();
|
|
@@ -3784,7 +3775,7 @@ var XlsxWriter = class {
|
|
|
3784
3775
|
this._writeContentTypes();
|
|
3785
3776
|
this._writeRels();
|
|
3786
3777
|
this.output.on("close", () => {
|
|
3787
|
-
|
|
3778
|
+
resolve4();
|
|
3788
3779
|
});
|
|
3789
3780
|
this.archive.on("error", (err) => {
|
|
3790
3781
|
reject(err);
|
|
@@ -3921,13 +3912,237 @@ ${xfEntries.join("\n")}
|
|
|
3921
3912
|
};
|
|
3922
3913
|
|
|
3923
3914
|
// src/XlsxUpdater.ts
|
|
3924
|
-
import * as
|
|
3915
|
+
import * as fs5 from "fs";
|
|
3916
|
+
import * as os from "os";
|
|
3917
|
+
import * as path4 from "path";
|
|
3925
3918
|
import AdmZip2 from "adm-zip";
|
|
3926
|
-
|
|
3919
|
+
|
|
3920
|
+
// src/atomicFile.ts
|
|
3921
|
+
import * as fs3 from "fs";
|
|
3922
|
+
import * as path2 from "path";
|
|
3923
|
+
import { randomUUID } from "crypto";
|
|
3924
|
+
var realFileOperations = {
|
|
3925
|
+
exists: (filePath) => fs3.existsSync(filePath),
|
|
3926
|
+
rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
|
|
3927
|
+
remove: (filePath) => fs3.rmSync(filePath)
|
|
3928
|
+
};
|
|
3929
|
+
function errorMessage(error) {
|
|
3930
|
+
return error instanceof Error ? error.message : String(error);
|
|
3931
|
+
}
|
|
3932
|
+
function destinationExistsError(destinationPath) {
|
|
3933
|
+
return new Error(
|
|
3934
|
+
`Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
|
|
3935
|
+
);
|
|
3936
|
+
}
|
|
3937
|
+
function readDestinationMode(target) {
|
|
3938
|
+
try {
|
|
3939
|
+
if (!fs3.existsSync(target)) return void 0;
|
|
3940
|
+
return fs3.statSync(target).mode & 511;
|
|
3941
|
+
} catch {
|
|
3942
|
+
return void 0;
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
|
|
3946
|
+
const backupPath = `${destinationPath}.${randomUUID()}.replacement-backup`;
|
|
3947
|
+
let backupCreated = false;
|
|
3948
|
+
let installed = false;
|
|
3949
|
+
if (operations.exists(destinationPath)) {
|
|
3950
|
+
if (!overwrite) {
|
|
3951
|
+
throw destinationExistsError(destinationPath);
|
|
3952
|
+
}
|
|
3953
|
+
operations.rename(destinationPath, backupPath);
|
|
3954
|
+
backupCreated = true;
|
|
3955
|
+
}
|
|
3956
|
+
try {
|
|
3957
|
+
try {
|
|
3958
|
+
operations.rename(temporaryPath, destinationPath);
|
|
3959
|
+
installed = true;
|
|
3960
|
+
} catch (error) {
|
|
3961
|
+
if (overwrite && !backupCreated && operations.exists(destinationPath)) {
|
|
3962
|
+
operations.rename(destinationPath, backupPath);
|
|
3963
|
+
backupCreated = true;
|
|
3964
|
+
operations.rename(temporaryPath, destinationPath);
|
|
3965
|
+
installed = true;
|
|
3966
|
+
} else {
|
|
3967
|
+
throw error;
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
if (backupCreated) {
|
|
3971
|
+
operations.remove(backupPath);
|
|
3972
|
+
}
|
|
3973
|
+
} catch (error) {
|
|
3974
|
+
if (backupCreated && !installed) {
|
|
3975
|
+
if (!operations.exists(destinationPath)) {
|
|
3976
|
+
try {
|
|
3977
|
+
operations.rename(backupPath, destinationPath);
|
|
3978
|
+
backupCreated = false;
|
|
3979
|
+
} catch (rollbackError) {
|
|
3980
|
+
throw new Error(
|
|
3981
|
+
`Excel conversion: final destination install failed (${errorMessage(error)}), and restoring the original destination also failed (${errorMessage(rollbackError)}). Original file remains at ${backupPath}.`
|
|
3982
|
+
);
|
|
3983
|
+
}
|
|
3984
|
+
} else {
|
|
3985
|
+
throw new Error(
|
|
3986
|
+
`Excel conversion: final destination install failed (${errorMessage(error)}). A concurrent destination was preserved; the original file remains at ${backupPath}.`
|
|
3987
|
+
);
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
throw error;
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
function writeBufferAtomically(buffer, destinationPath) {
|
|
3994
|
+
const target = path2.resolve(destinationPath);
|
|
3995
|
+
fs3.mkdirSync(path2.dirname(target), { recursive: true });
|
|
3996
|
+
const temporaryPath = path2.join(
|
|
3997
|
+
path2.dirname(target),
|
|
3998
|
+
`.${path2.basename(target)}.${randomUUID()}.tmp`
|
|
3999
|
+
);
|
|
4000
|
+
const destinationMode = readDestinationMode(target);
|
|
4001
|
+
try {
|
|
4002
|
+
const fd = fs3.openSync(temporaryPath, "w", destinationMode ?? 438);
|
|
4003
|
+
try {
|
|
4004
|
+
if (destinationMode !== void 0) {
|
|
4005
|
+
try {
|
|
4006
|
+
fs3.fchmodSync(fd, destinationMode);
|
|
4007
|
+
} catch {
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
fs3.writeFileSync(fd, buffer);
|
|
4011
|
+
fs3.fsyncSync(fd);
|
|
4012
|
+
} finally {
|
|
4013
|
+
fs3.closeSync(fd);
|
|
4014
|
+
}
|
|
4015
|
+
if (destinationMode !== void 0) {
|
|
4016
|
+
try {
|
|
4017
|
+
fs3.chmodSync(temporaryPath, destinationMode);
|
|
4018
|
+
} catch {
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
fs3.renameSync(temporaryPath, target);
|
|
4022
|
+
} finally {
|
|
4023
|
+
if (fs3.existsSync(temporaryPath)) {
|
|
4024
|
+
try {
|
|
4025
|
+
fs3.rmSync(temporaryPath);
|
|
4026
|
+
} catch {
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
// src/streamingZip.ts
|
|
4033
|
+
import * as fs4 from "fs";
|
|
4034
|
+
import * as path3 from "path";
|
|
4035
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4036
|
+
import { finished } from "stream/promises";
|
|
4037
|
+
import { Readable as Readable3 } from "stream";
|
|
4038
|
+
import archiver3 from "archiver";
|
|
4039
|
+
function readDestinationMode2(target) {
|
|
4040
|
+
try {
|
|
4041
|
+
if (!fs4.existsSync(target)) return void 0;
|
|
4042
|
+
return fs4.statSync(target).mode & 511;
|
|
4043
|
+
} catch {
|
|
4044
|
+
return void 0;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
function appendAndWait(archive, source, name) {
|
|
4048
|
+
return new Promise((resolve4, reject) => {
|
|
4049
|
+
const cleanup = () => {
|
|
4050
|
+
archive.off("entry", onEntry);
|
|
4051
|
+
archive.off("error", onError);
|
|
4052
|
+
const maybeEmitter2 = source;
|
|
4053
|
+
if (typeof maybeEmitter2.off === "function") {
|
|
4054
|
+
maybeEmitter2.off("error", onError);
|
|
4055
|
+
}
|
|
4056
|
+
};
|
|
4057
|
+
const onEntry = () => {
|
|
4058
|
+
cleanup();
|
|
4059
|
+
resolve4();
|
|
4060
|
+
};
|
|
4061
|
+
const onError = (error) => {
|
|
4062
|
+
cleanup();
|
|
4063
|
+
reject(error);
|
|
4064
|
+
};
|
|
4065
|
+
archive.once("entry", onEntry);
|
|
4066
|
+
archive.once("error", onError);
|
|
4067
|
+
const maybeEmitter = source;
|
|
4068
|
+
if (typeof maybeEmitter.once === "function") {
|
|
4069
|
+
maybeEmitter.once("error", onError);
|
|
4070
|
+
}
|
|
4071
|
+
try {
|
|
4072
|
+
archive.append(source, { name });
|
|
4073
|
+
} catch (error) {
|
|
4074
|
+
cleanup();
|
|
4075
|
+
reject(error);
|
|
4076
|
+
}
|
|
4077
|
+
});
|
|
4078
|
+
}
|
|
4079
|
+
async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
4080
|
+
const target = path3.resolve(destinationPath);
|
|
4081
|
+
fs4.mkdirSync(path3.dirname(target), { recursive: true });
|
|
4082
|
+
const temporaryPath = path3.join(
|
|
4083
|
+
path3.dirname(target),
|
|
4084
|
+
`.${path3.basename(target)}.${randomUUID2()}.tmp`
|
|
4085
|
+
);
|
|
4086
|
+
const destinationMode = readDestinationMode2(target);
|
|
4087
|
+
let output = null;
|
|
4088
|
+
let archive = null;
|
|
4089
|
+
try {
|
|
4090
|
+
output = fs4.createWriteStream(
|
|
4091
|
+
temporaryPath,
|
|
4092
|
+
destinationMode !== void 0 ? { mode: destinationMode } : {}
|
|
4093
|
+
);
|
|
4094
|
+
archive = archiver3("zip");
|
|
4095
|
+
archive.pipe(output);
|
|
4096
|
+
for (const entry of zip.getEntries()) {
|
|
4097
|
+
const stagedPath = stagedParts.get(entry.entryName);
|
|
4098
|
+
if (stagedPath) {
|
|
4099
|
+
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName);
|
|
4100
|
+
} else {
|
|
4101
|
+
await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
await archive.finalize();
|
|
4105
|
+
await finished(output);
|
|
4106
|
+
if (destinationMode !== void 0) {
|
|
4107
|
+
try {
|
|
4108
|
+
fs4.chmodSync(temporaryPath, destinationMode);
|
|
4109
|
+
} catch {
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
const fd = fs4.openSync(temporaryPath, "r+");
|
|
4113
|
+
try {
|
|
4114
|
+
fs4.fsyncSync(fd);
|
|
4115
|
+
} finally {
|
|
4116
|
+
fs4.closeSync(fd);
|
|
4117
|
+
}
|
|
4118
|
+
fs4.renameSync(temporaryPath, target);
|
|
4119
|
+
} catch (error) {
|
|
4120
|
+
try {
|
|
4121
|
+
archive == null ? void 0 : archive.abort();
|
|
4122
|
+
} catch {
|
|
4123
|
+
}
|
|
4124
|
+
throw error;
|
|
4125
|
+
} finally {
|
|
4126
|
+
if (output && !output.closed) {
|
|
4127
|
+
output.destroy();
|
|
4128
|
+
}
|
|
4129
|
+
if (fs4.existsSync(temporaryPath)) {
|
|
4130
|
+
try {
|
|
4131
|
+
fs4.rmSync(temporaryPath);
|
|
4132
|
+
} catch {
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
// src/XlsxUpdater.ts
|
|
4139
|
+
var XlsxUpdater = class _XlsxUpdater {
|
|
3927
4140
|
/**
|
|
3928
4141
|
* @param path Path to an existing `.xlsx` file.
|
|
3929
4142
|
*/
|
|
3930
|
-
constructor(
|
|
4143
|
+
constructor(filePath) {
|
|
4144
|
+
this.temporaryDirectory = null;
|
|
4145
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
3931
4146
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
3932
4147
|
this.pivotCacheDefs = [];
|
|
3933
4148
|
this.pivotTableDefPaths = [];
|
|
@@ -3935,13 +4150,15 @@ var XlsxUpdater = class {
|
|
|
3935
4150
|
this.sharedStringsValues = [];
|
|
3936
4151
|
this.sharedStringsOriginalLength = 0;
|
|
3937
4152
|
this.sharedStringsCount = 0;
|
|
4153
|
+
this.sharedStringsPending = [];
|
|
4154
|
+
this.sharedStringsDirty = false;
|
|
3938
4155
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
3939
|
-
this.sourcePath =
|
|
4156
|
+
this.sourcePath = filePath;
|
|
3940
4157
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3941
|
-
if (!
|
|
3942
|
-
throw new Error(`XlsxUpdater: file not found: ${
|
|
4158
|
+
if (!fs5.existsSync(filePath)) {
|
|
4159
|
+
throw new Error(`XlsxUpdater: file not found: ${filePath}`);
|
|
3943
4160
|
}
|
|
3944
|
-
this.zip = new AdmZip2(
|
|
4161
|
+
this.zip = new AdmZip2(filePath);
|
|
3945
4162
|
if (this.zip.getEntry("xl/workbook.bin")) {
|
|
3946
4163
|
throw new Error(
|
|
3947
4164
|
"XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
|
|
@@ -3975,8 +4192,15 @@ var XlsxUpdater = class {
|
|
|
3975
4192
|
if (!entry) {
|
|
3976
4193
|
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
3977
4194
|
}
|
|
3978
|
-
const sheetXml =
|
|
4195
|
+
const sheetXml = this._entryData(sheetPath).toString("utf8");
|
|
3979
4196
|
this._ensureSharedStringsLoaded();
|
|
4197
|
+
if (this.sharedStringsXml !== null) {
|
|
4198
|
+
this.sharedStringsCount = Math.max(
|
|
4199
|
+
0,
|
|
4200
|
+
this.sharedStringsCount - this._sharedStringRefCount(sheetXml)
|
|
4201
|
+
);
|
|
4202
|
+
this.sharedStringsDirty = true;
|
|
4203
|
+
}
|
|
3980
4204
|
let colStyles = /* @__PURE__ */ new Map();
|
|
3981
4205
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
3982
4206
|
let dateStyle = null;
|
|
@@ -3991,6 +4215,7 @@ var XlsxUpdater = class {
|
|
|
3991
4215
|
const newSheetData = this._buildSheetData(rows, headers, colStyles, headerStyles, dateStyle);
|
|
3992
4216
|
const newXml = this._patchSheetXml(sheetXml, newSheetData, totalRows, lastCol);
|
|
3993
4217
|
this.zip.updateFile(sheetPath, Buffer.from(newXml, "utf8"));
|
|
4218
|
+
this._discardStagedPart(sheetPath);
|
|
3994
4219
|
if (this.sharedStringsXml !== null) {
|
|
3995
4220
|
this._commitSharedStrings();
|
|
3996
4221
|
}
|
|
@@ -3998,18 +4223,255 @@ var XlsxUpdater = class {
|
|
|
3998
4223
|
this._updatePivotCaches(sheetName, dimRef, rows.length);
|
|
3999
4224
|
this._addRefreshOnLoadToPivotTables();
|
|
4000
4225
|
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Replace worksheet rows from a one-pass source without materialising the
|
|
4228
|
+
* complete result set. Use {@link saveStreaming} to keep the final ZIP
|
|
4229
|
+
* write streaming as well.
|
|
4230
|
+
*/
|
|
4231
|
+
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
4232
|
+
const { headers, styleFallback = "inherit" } = options;
|
|
4233
|
+
if (styleFallback !== "inherit" && styleFallback !== "general") {
|
|
4234
|
+
throw new Error("XlsxUpdater: styleFallback must be 'inherit' or 'general'.");
|
|
4235
|
+
}
|
|
4236
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
4237
|
+
if (!sheetPath) {
|
|
4238
|
+
throw new Error(`XlsxUpdater: sheet "${sheetName}" not found in the workbook.`);
|
|
4239
|
+
}
|
|
4240
|
+
const entry = this.zip.getEntry(sheetPath);
|
|
4241
|
+
if (!entry) {
|
|
4242
|
+
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4243
|
+
}
|
|
4244
|
+
const oldStage = this.stagedParts.get(sheetPath);
|
|
4245
|
+
const beforeValuesLength = this.sharedStringsValues.length;
|
|
4246
|
+
const beforePendingLength = this.sharedStringsPending.length;
|
|
4247
|
+
const beforeSharedStringsCount = this.sharedStringsCount;
|
|
4248
|
+
const beforeSharedStringsDirty = this.sharedStringsDirty;
|
|
4249
|
+
const beforeSharedStringsXml = this.sharedStringsXml;
|
|
4250
|
+
const beforeSharedStringsOriginalLength = this.sharedStringsOriginalLength;
|
|
4251
|
+
const beforeStringIndexMap = new Map(this.stringIndexMap);
|
|
4252
|
+
const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4253
|
+
const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.xml") ? Buffer.from(this._entryData("xl/sharedStrings.xml")) : null;
|
|
4254
|
+
const createdParts = [];
|
|
4255
|
+
const oldSheetXml = this._entryData(sheetPath).toString("utf8");
|
|
4256
|
+
const rowsPath = this._temporaryPath(".rows");
|
|
4257
|
+
const outputPath = this._temporaryPath(".xml");
|
|
4258
|
+
createdParts.push(rowsPath, outputPath);
|
|
4259
|
+
let output = null;
|
|
4260
|
+
try {
|
|
4261
|
+
this._ensureSharedStringsLoaded();
|
|
4262
|
+
if (this.sharedStringsXml !== null) {
|
|
4263
|
+
this.sharedStringsCount = Math.max(
|
|
4264
|
+
0,
|
|
4265
|
+
this.sharedStringsCount - this._sharedStringRefCount(oldSheetXml)
|
|
4266
|
+
);
|
|
4267
|
+
this.sharedStringsDirty = true;
|
|
4268
|
+
}
|
|
4269
|
+
let colStyles = /* @__PURE__ */ new Map();
|
|
4270
|
+
let headerStyles = /* @__PURE__ */ new Map();
|
|
4271
|
+
let dateStyle = null;
|
|
4272
|
+
if (styleFallback !== "general") {
|
|
4273
|
+
const collected = this._collectExistingStyles(oldSheetXml);
|
|
4274
|
+
colStyles = collected.dataStyles;
|
|
4275
|
+
headerStyles = collected.headerStyles;
|
|
4276
|
+
dateStyle = this._findDateStyleIndex();
|
|
4277
|
+
}
|
|
4278
|
+
const prefixMatch = /<([A-Za-z_][\w.-]*:)?sheetData\b/.exec(oldSheetXml);
|
|
4279
|
+
const prefix = (prefixMatch == null ? void 0 : prefixMatch[1]) ?? "";
|
|
4280
|
+
const rowOutput = fs5.createWriteStream(rowsPath);
|
|
4281
|
+
output = rowOutput;
|
|
4282
|
+
let outputError = null;
|
|
4283
|
+
rowOutput.on("error", (error) => {
|
|
4284
|
+
outputError = error;
|
|
4285
|
+
});
|
|
4286
|
+
let nextRow = 1;
|
|
4287
|
+
let width = (headers == null ? void 0 : headers.length) ?? 0;
|
|
4288
|
+
let dataRowsSeen = 0;
|
|
4289
|
+
let keptDataRows = 0;
|
|
4290
|
+
let lastKeptEnd = 0;
|
|
4291
|
+
let pendingEmptyWidth = 0;
|
|
4292
|
+
let writtenBytes = 0;
|
|
4293
|
+
const write = async (data) => {
|
|
4294
|
+
if (outputError) throw outputError;
|
|
4295
|
+
writtenBytes += data.length;
|
|
4296
|
+
if (rowOutput.write(data)) return;
|
|
4297
|
+
await new Promise((resolve4, reject) => {
|
|
4298
|
+
const onDrain = () => {
|
|
4299
|
+
cleanup();
|
|
4300
|
+
resolve4();
|
|
4301
|
+
};
|
|
4302
|
+
const onError = (error) => {
|
|
4303
|
+
cleanup();
|
|
4304
|
+
reject(error);
|
|
4305
|
+
};
|
|
4306
|
+
const cleanup = () => {
|
|
4307
|
+
rowOutput.off("drain", onDrain);
|
|
4308
|
+
rowOutput.off("error", onError);
|
|
4309
|
+
};
|
|
4310
|
+
rowOutput.once("drain", onDrain);
|
|
4311
|
+
rowOutput.once("error", onError);
|
|
4312
|
+
});
|
|
4313
|
+
};
|
|
4314
|
+
if (headers !== void 0) {
|
|
4315
|
+
const cells = headers.map((value, column) => this._valueCell(value, column, nextRow, headerStyles, dateStyle));
|
|
4316
|
+
const header = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
|
|
4317
|
+
await write(Buffer.from(header, "utf8"));
|
|
4318
|
+
lastKeptEnd = writtenBytes;
|
|
4319
|
+
nextRow++;
|
|
4320
|
+
}
|
|
4321
|
+
for await (const sourceRow of rows) {
|
|
4322
|
+
if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
|
|
4323
|
+
throw new TypeError("Each row must be an iterable of cells, not text");
|
|
4324
|
+
}
|
|
4325
|
+
const row = Array.from(sourceRow);
|
|
4326
|
+
const cells = row.map((value, column) => value === null || value === void 0 ? "" : this._valueCell(value, column, nextRow, colStyles, dateStyle));
|
|
4327
|
+
const rowXml = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
|
|
4328
|
+
await write(Buffer.from(rowXml, "utf8"));
|
|
4329
|
+
dataRowsSeen++;
|
|
4330
|
+
nextRow++;
|
|
4331
|
+
if (row.every((value) => value === null || value === void 0)) {
|
|
4332
|
+
pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
|
|
4333
|
+
} else {
|
|
4334
|
+
width = Math.max(width, pendingEmptyWidth, row.length);
|
|
4335
|
+
pendingEmptyWidth = 0;
|
|
4336
|
+
keptDataRows = dataRowsSeen;
|
|
4337
|
+
lastKeptEnd = writtenBytes;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
await new Promise((resolve4, reject) => {
|
|
4341
|
+
rowOutput.once("close", resolve4);
|
|
4342
|
+
rowOutput.once("error", reject);
|
|
4343
|
+
rowOutput.end();
|
|
4344
|
+
});
|
|
4345
|
+
if (outputError) throw outputError;
|
|
4346
|
+
const rowsFd = fs5.openSync(rowsPath, "r+");
|
|
4347
|
+
try {
|
|
4348
|
+
fs5.ftruncateSync(rowsFd, lastKeptEnd);
|
|
4349
|
+
} finally {
|
|
4350
|
+
fs5.closeSync(rowsFd);
|
|
4351
|
+
}
|
|
4352
|
+
const totalRows = keptDataRows + (headers !== void 0 ? 1 : 0);
|
|
4353
|
+
const lastCol = width - 1;
|
|
4354
|
+
const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
|
|
4355
|
+
this._patchSheetFile(oldSheetXml, rowsPath, outputPath, dimRef);
|
|
4356
|
+
this._stagePart(sheetPath, outputPath);
|
|
4357
|
+
this._commitSharedStrings();
|
|
4358
|
+
this._updatePivotCaches(sheetName, dimRef, keptDataRows);
|
|
4359
|
+
this._addRefreshOnLoadToPivotTables();
|
|
4360
|
+
if (oldStage && oldStage !== outputPath) {
|
|
4361
|
+
this._removeTemporaryFile(oldStage);
|
|
4362
|
+
}
|
|
4363
|
+
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
4364
|
+
} catch (error) {
|
|
4365
|
+
this.sharedStringsValues.length = beforeValuesLength;
|
|
4366
|
+
this.sharedStringsPending.length = beforePendingLength;
|
|
4367
|
+
this.sharedStringsCount = beforeSharedStringsCount;
|
|
4368
|
+
this.sharedStringsDirty = beforeSharedStringsDirty;
|
|
4369
|
+
this.sharedStringsXml = beforeSharedStringsXml;
|
|
4370
|
+
this.sharedStringsOriginalLength = beforeSharedStringsOriginalLength;
|
|
4371
|
+
this.stringIndexMap.clear();
|
|
4372
|
+
for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
|
|
4373
|
+
if (beforeSharedStringsBytes) {
|
|
4374
|
+
this.zip.updateFile("xl/sharedStrings.xml", beforeSharedStringsBytes);
|
|
4375
|
+
}
|
|
4376
|
+
if (oldStage) this.stagedParts.set(sheetPath, oldStage);
|
|
4377
|
+
else this.stagedParts.delete(sheetPath);
|
|
4378
|
+
throw error;
|
|
4379
|
+
} finally {
|
|
4380
|
+
if (output && !output.closed) output.destroy();
|
|
4381
|
+
for (const temporaryPath of createdParts) {
|
|
4382
|
+
this._removeTemporaryFile(temporaryPath);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4001
4386
|
/**
|
|
4002
4387
|
* Write the updated workbook to disk.
|
|
4003
4388
|
* @param outputPath Destination path; defaults to the source file (overwrites in place).
|
|
4004
4389
|
*/
|
|
4005
4390
|
save(outputPath) {
|
|
4006
4391
|
const target = outputPath ?? this.sourcePath;
|
|
4007
|
-
|
|
4392
|
+
this._materializeStagedParts();
|
|
4393
|
+
writeBufferAtomically(this.zip.toBuffer(), target);
|
|
4394
|
+
}
|
|
4395
|
+
/** Save staged streaming replacements without materialising the ZIP. */
|
|
4396
|
+
async saveStreaming(outputPath) {
|
|
4397
|
+
const target = outputPath ?? this.sourcePath;
|
|
4398
|
+
await writeAdmZipStreaming(this.zip, this.stagedParts, target);
|
|
4399
|
+
this._materializeStagedParts();
|
|
4008
4400
|
}
|
|
4009
4401
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4010
4402
|
toBuffer() {
|
|
4403
|
+
this._materializeStagedParts();
|
|
4011
4404
|
return this.zip.toBuffer();
|
|
4012
4405
|
}
|
|
4406
|
+
/**
|
|
4407
|
+
* Release the staging directory and any unsaved staged parts.
|
|
4408
|
+
* The updater remains usable; a new staging directory is created lazily
|
|
4409
|
+
* on the next streaming replacement.
|
|
4410
|
+
*/
|
|
4411
|
+
dispose() {
|
|
4412
|
+
for (const stagedPath of this.stagedParts.values()) {
|
|
4413
|
+
try {
|
|
4414
|
+
fs5.rmSync(stagedPath, { force: true });
|
|
4415
|
+
} catch {
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
this.stagedParts.clear();
|
|
4419
|
+
if (this.temporaryDirectory !== null) {
|
|
4420
|
+
try {
|
|
4421
|
+
fs5.rmSync(this.temporaryDirectory, { recursive: true, force: true });
|
|
4422
|
+
} catch {
|
|
4423
|
+
}
|
|
4424
|
+
this.temporaryDirectory = null;
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
/** Support `using` declarations for automatic staging cleanup. */
|
|
4428
|
+
[Symbol.dispose]() {
|
|
4429
|
+
this.dispose();
|
|
4430
|
+
}
|
|
4431
|
+
_ensureTemporaryDirectory() {
|
|
4432
|
+
if (this.temporaryDirectory === null) {
|
|
4433
|
+
this.temporaryDirectory = fs5.mkdtempSync(path4.join(os.tmpdir(), "spreadsheet-updater-"));
|
|
4434
|
+
} else {
|
|
4435
|
+
try {
|
|
4436
|
+
fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
4437
|
+
} catch {
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
return this.temporaryDirectory;
|
|
4441
|
+
}
|
|
4442
|
+
_temporaryPath(suffix) {
|
|
4443
|
+
return path4.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
4444
|
+
}
|
|
4445
|
+
_removeTemporaryFile(filePath) {
|
|
4446
|
+
try {
|
|
4447
|
+
fs5.rmSync(filePath);
|
|
4448
|
+
} catch (error) {
|
|
4449
|
+
if (error.code !== "ENOENT") throw error;
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
_entryData(name) {
|
|
4453
|
+
const stagedPath = this.stagedParts.get(name);
|
|
4454
|
+
if (stagedPath) return fs5.readFileSync(stagedPath);
|
|
4455
|
+
const entry = this.zip.getEntry(name);
|
|
4456
|
+
if (!entry) throw new Error(`XlsxUpdater: ZIP member missing: ${name}`);
|
|
4457
|
+
return entry.getData();
|
|
4458
|
+
}
|
|
4459
|
+
_stagePart(name, filePath) {
|
|
4460
|
+
this.stagedParts.set(name, filePath);
|
|
4461
|
+
}
|
|
4462
|
+
_discardStagedPart(name) {
|
|
4463
|
+
const stagedPath = this.stagedParts.get(name);
|
|
4464
|
+
if (!stagedPath) return;
|
|
4465
|
+
this._removeTemporaryFile(stagedPath);
|
|
4466
|
+
this.stagedParts.delete(name);
|
|
4467
|
+
}
|
|
4468
|
+
_materializeStagedParts() {
|
|
4469
|
+
for (const [name, stagedPath] of this.stagedParts) {
|
|
4470
|
+
this.zip.updateFile(name, fs5.readFileSync(stagedPath));
|
|
4471
|
+
this._removeTemporaryFile(stagedPath);
|
|
4472
|
+
}
|
|
4473
|
+
this.stagedParts.clear();
|
|
4474
|
+
}
|
|
4013
4475
|
// ------------------------------------------------------------------
|
|
4014
4476
|
// Workbook structure
|
|
4015
4477
|
// ------------------------------------------------------------------
|
|
@@ -4062,47 +4524,59 @@ var XlsxUpdater = class {
|
|
|
4062
4524
|
return;
|
|
4063
4525
|
}
|
|
4064
4526
|
const entry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4065
|
-
if (!entry) {
|
|
4527
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.xml")) {
|
|
4066
4528
|
this.sharedStringsXml = null;
|
|
4067
4529
|
return;
|
|
4068
4530
|
}
|
|
4069
|
-
this.sharedStringsXml =
|
|
4531
|
+
this.sharedStringsXml = this._entryData("xl/sharedStrings.xml").toString("utf8");
|
|
4070
4532
|
this.sharedStringsValues = parseSharedStringsXml(this.sharedStringsXml);
|
|
4071
|
-
const countMatch = /<sst\b[^>]
|
|
4072
|
-
const countAttr = countMatch ? /count
|
|
4073
|
-
this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) :
|
|
4533
|
+
const countMatch = /<(?:[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(this.sharedStringsXml);
|
|
4534
|
+
const countAttr = countMatch ? /(?:^|\s)count\s*=\s*["'](\d+)["']/.exec(countMatch[0]) : null;
|
|
4535
|
+
this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) : this.sharedStringsValues.length;
|
|
4074
4536
|
this.sharedStringsValues.forEach((value, index) => {
|
|
4075
4537
|
if (!this.stringIndexMap.has(value)) {
|
|
4076
4538
|
this.stringIndexMap.set(value, index);
|
|
4077
4539
|
}
|
|
4078
4540
|
});
|
|
4079
4541
|
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
4542
|
+
this.sharedStringsPending = [];
|
|
4543
|
+
this.sharedStringsDirty = false;
|
|
4080
4544
|
}
|
|
4081
4545
|
_commitSharedStrings() {
|
|
4082
|
-
|
|
4546
|
+
var _a;
|
|
4547
|
+
if (this.sharedStringsXml === null || !this.sharedStringsDirty) {
|
|
4083
4548
|
return;
|
|
4084
4549
|
}
|
|
4085
4550
|
let xml = this.sharedStringsXml;
|
|
4086
|
-
const
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4551
|
+
const sstMatch = /<(?<prefix>[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(xml);
|
|
4552
|
+
if (!sstMatch || sstMatch.index === void 0) {
|
|
4553
|
+
throw new Error("XlsxUpdater: sharedStrings.xml has no sst element.");
|
|
4554
|
+
}
|
|
4555
|
+
const tag = sstMatch[0];
|
|
4556
|
+
const prefix = ((_a = sstMatch.groups) == null ? void 0 : _a.prefix) ?? "";
|
|
4557
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(
|
|
4558
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(tag, "count", String(this.sharedStringsCount)),
|
|
4559
|
+
"uniqueCount",
|
|
4560
|
+
String(this.sharedStringsValues.length)
|
|
4561
|
+
);
|
|
4562
|
+
xml = xml.substring(0, sstMatch.index) + newTag + xml.substring(sstMatch.index + tag.length);
|
|
4563
|
+
const closingRe = new RegExp(`</${prefix}sst\\s*>\\s*$`, "i");
|
|
4564
|
+
const closingMatch = closingRe.exec(xml);
|
|
4565
|
+
if (!closingMatch || closingMatch.index === void 0) {
|
|
4566
|
+
throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
|
|
4567
|
+
}
|
|
4568
|
+
const appended = this.sharedStringsPending.map((txt) => {
|
|
4100
4569
|
const clean = escapeXmlText(txt);
|
|
4101
4570
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4571
|
+
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
4572
|
+
return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
|
|
4573
|
+
}).join("");
|
|
4574
|
+
xml = xml.substring(0, closingMatch.index) + appended + xml.substring(closingMatch.index);
|
|
4105
4575
|
this.zip.updateFile("xl/sharedStrings.xml", Buffer.from(xml, "utf8"));
|
|
4576
|
+
this.sharedStringsXml = xml;
|
|
4577
|
+
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
4578
|
+
this.sharedStringsPending = [];
|
|
4579
|
+
this.sharedStringsDirty = false;
|
|
4106
4580
|
}
|
|
4107
4581
|
// ------------------------------------------------------------------
|
|
4108
4582
|
// Styles
|
|
@@ -4253,10 +4727,25 @@ var XlsxUpdater = class {
|
|
|
4253
4727
|
index = this.sharedStringsValues.length;
|
|
4254
4728
|
this.sharedStringsValues.push(text);
|
|
4255
4729
|
this.stringIndexMap.set(text, index);
|
|
4730
|
+
this.sharedStringsPending.push(text);
|
|
4256
4731
|
}
|
|
4257
4732
|
this.sharedStringsCount++;
|
|
4733
|
+
this.sharedStringsDirty = true;
|
|
4258
4734
|
return `<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`;
|
|
4259
4735
|
}
|
|
4736
|
+
static _replaceOrAddXmlAttribute(tag, name, value) {
|
|
4737
|
+
const escaped = escapeXmlText(value);
|
|
4738
|
+
const attribute = new RegExp(`(\\s${name}\\s*=\\s*["'])[^"']*(["'])`, "i");
|
|
4739
|
+
if (attribute.test(tag)) {
|
|
4740
|
+
return tag.replace(attribute, `$1${escaped}$2`);
|
|
4741
|
+
}
|
|
4742
|
+
const insertion = ` ${name}="${escaped}"`;
|
|
4743
|
+
return tag.endsWith("/>") ? `${tag.slice(0, -2)}${insertion}/>` : `${tag.slice(0, -1)}${insertion}>`;
|
|
4744
|
+
}
|
|
4745
|
+
_sharedStringRefCount(sheetXml) {
|
|
4746
|
+
const cellTags = sheetXml.match(/<(?:(?:[A-Za-z_][\w.-]*):)?c\b[^>]*>/gi) ?? [];
|
|
4747
|
+
return cellTags.filter((tag) => /(?:^|\s)t\s*=\s*["']s["']/i.test(tag)).length;
|
|
4748
|
+
}
|
|
4260
4749
|
// ------------------------------------------------------------------
|
|
4261
4750
|
// Worksheet XML patching
|
|
4262
4751
|
// ------------------------------------------------------------------
|
|
@@ -4298,6 +4787,109 @@ var XlsxUpdater = class {
|
|
|
4298
4787
|
}
|
|
4299
4788
|
return xml;
|
|
4300
4789
|
}
|
|
4790
|
+
_patchSheetFile(sheetXml, rowsPath, outputPath, dimensionRef) {
|
|
4791
|
+
var _a, _b, _c, _d;
|
|
4792
|
+
const source = Buffer.from(sheetXml, "utf8");
|
|
4793
|
+
const text = sheetXml;
|
|
4794
|
+
const replacements = [];
|
|
4795
|
+
const emptySheetData = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b(?<attrs>[^>]*)\/>/i.exec(text);
|
|
4796
|
+
if (emptySheetData && emptySheetData.index !== void 0) {
|
|
4797
|
+
const prefix = ((_a = emptySheetData.groups) == null ? void 0 : _a.prefix) ?? "";
|
|
4798
|
+
replacements.push({
|
|
4799
|
+
start: emptySheetData.index,
|
|
4800
|
+
end: emptySheetData.index + emptySheetData[0].length,
|
|
4801
|
+
data: Buffer.from(`<${prefix}sheetData${((_b = emptySheetData.groups) == null ? void 0 : _b.attrs) ?? ""}>`, "utf8"),
|
|
4802
|
+
rows: true,
|
|
4803
|
+
suffix: Buffer.from(`</${prefix}sheetData>`, "utf8")
|
|
4804
|
+
});
|
|
4805
|
+
} else {
|
|
4806
|
+
const opening = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(text);
|
|
4807
|
+
if (!opening || opening.index === void 0) {
|
|
4808
|
+
throw new Error("XlsxUpdater: <sheetData> element not found in worksheet XML.");
|
|
4809
|
+
}
|
|
4810
|
+
const prefix = ((_c = opening.groups) == null ? void 0 : _c.prefix) ?? "";
|
|
4811
|
+
const closeRe = new RegExp(`</${prefix}sheetData\\s*>`, "i");
|
|
4812
|
+
const closing = closeRe.exec(text.slice(opening.index + opening[0].length));
|
|
4813
|
+
if (!closing || closing.index === void 0) {
|
|
4814
|
+
throw new Error("XlsxUpdater: <sheetData> element not closed in worksheet XML.");
|
|
4815
|
+
}
|
|
4816
|
+
const closingStart = opening.index + opening[0].length + closing.index;
|
|
4817
|
+
replacements.push({
|
|
4818
|
+
start: opening.index + opening[0].length,
|
|
4819
|
+
end: closingStart,
|
|
4820
|
+
rows: true
|
|
4821
|
+
});
|
|
4822
|
+
}
|
|
4823
|
+
const dimension = /<(?<prefix>[A-Za-z_][\w.-]*:)?dimension\b[^>]*>/i.exec(text);
|
|
4824
|
+
if (dimension && dimension.index !== void 0) {
|
|
4825
|
+
replacements.push({
|
|
4826
|
+
start: dimension.index,
|
|
4827
|
+
end: dimension.index + dimension[0].length,
|
|
4828
|
+
data: Buffer.from(
|
|
4829
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(dimension[0], "ref", dimensionRef)
|
|
4830
|
+
)
|
|
4831
|
+
});
|
|
4832
|
+
} else {
|
|
4833
|
+
const root = /<(?<prefix>[A-Za-z_][\w.-]*:)?worksheet\b[^>]*>/i.exec(text);
|
|
4834
|
+
if (root && root.index !== void 0) {
|
|
4835
|
+
const prefix = ((_d = root.groups) == null ? void 0 : _d.prefix) ?? "";
|
|
4836
|
+
const insertAt = root.index + root[0].length;
|
|
4837
|
+
replacements.push({
|
|
4838
|
+
start: insertAt,
|
|
4839
|
+
end: insertAt,
|
|
4840
|
+
data: Buffer.from(`<${prefix}dimension ref="${dimensionRef}"/>`, "utf8")
|
|
4841
|
+
});
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
const autoFilter = /<(?<prefix>[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i.exec(text);
|
|
4845
|
+
if (autoFilter && autoFilter.index !== void 0) {
|
|
4846
|
+
replacements.push({
|
|
4847
|
+
start: autoFilter.index,
|
|
4848
|
+
end: autoFilter.index + autoFilter[0].length,
|
|
4849
|
+
data: Buffer.from(
|
|
4850
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(autoFilter[0], "ref", dimensionRef),
|
|
4851
|
+
"utf8"
|
|
4852
|
+
)
|
|
4853
|
+
});
|
|
4854
|
+
}
|
|
4855
|
+
replacements.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
4856
|
+
const fd = fs5.openSync(outputPath, "w");
|
|
4857
|
+
let cursor = 0;
|
|
4858
|
+
const writeBuffer = (data) => {
|
|
4859
|
+
fs5.writeSync(fd, data);
|
|
4860
|
+
};
|
|
4861
|
+
const copyRows = () => {
|
|
4862
|
+
const rowsFd = fs5.openSync(rowsPath, "r");
|
|
4863
|
+
try {
|
|
4864
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
4865
|
+
const length = fs5.statSync(rowsPath).size;
|
|
4866
|
+
let position = 0;
|
|
4867
|
+
while (position < length) {
|
|
4868
|
+
const count = fs5.readSync(rowsFd, chunk, 0, Math.min(chunk.length, length - position), position);
|
|
4869
|
+
if (count <= 0) throw new Error("XlsxUpdater: failed to read staged rows.");
|
|
4870
|
+
fs5.writeSync(fd, chunk, 0, count);
|
|
4871
|
+
position += count;
|
|
4872
|
+
}
|
|
4873
|
+
} finally {
|
|
4874
|
+
fs5.closeSync(rowsFd);
|
|
4875
|
+
}
|
|
4876
|
+
};
|
|
4877
|
+
try {
|
|
4878
|
+
for (const replacement of replacements) {
|
|
4879
|
+
const startByte = Buffer.byteLength(text.slice(0, replacement.start), "utf8");
|
|
4880
|
+
const endByte = Buffer.byteLength(text.slice(0, replacement.end), "utf8");
|
|
4881
|
+
if (startByte < cursor) throw new Error("XlsxUpdater: overlapping worksheet replacements.");
|
|
4882
|
+
writeBuffer(source.subarray(cursor, startByte));
|
|
4883
|
+
if (replacement.data) writeBuffer(replacement.data);
|
|
4884
|
+
if (replacement.rows) copyRows();
|
|
4885
|
+
if (replacement.suffix) writeBuffer(replacement.suffix);
|
|
4886
|
+
cursor = endByte;
|
|
4887
|
+
}
|
|
4888
|
+
writeBuffer(source.subarray(cursor));
|
|
4889
|
+
} finally {
|
|
4890
|
+
fs5.closeSync(fd);
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4301
4893
|
// ------------------------------------------------------------------
|
|
4302
4894
|
// Pivot table integration
|
|
4303
4895
|
// ------------------------------------------------------------------
|
|
@@ -4326,8 +4918,8 @@ var XlsxUpdater = class {
|
|
|
4326
4918
|
}
|
|
4327
4919
|
}
|
|
4328
4920
|
_addRefreshOnLoadToPivotTables() {
|
|
4329
|
-
for (const
|
|
4330
|
-
const entry = this.zip.getEntry(
|
|
4921
|
+
for (const path7 of this.pivotTableDefPaths) {
|
|
4922
|
+
const entry = this.zip.getEntry(path7);
|
|
4331
4923
|
if (!entry) continue;
|
|
4332
4924
|
let xml = entry.getData().toString("utf8");
|
|
4333
4925
|
const rootRe = /<pivotTableDefinition\b[^>]*>/;
|
|
@@ -4335,13 +4927,15 @@ var XlsxUpdater = class {
|
|
|
4335
4927
|
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
4336
4928
|
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
4337
4929
|
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
4338
|
-
this.zip.updateFile(
|
|
4930
|
+
this.zip.updateFile(path7, Buffer.from(xml, "utf8"));
|
|
4339
4931
|
}
|
|
4340
4932
|
}
|
|
4341
4933
|
};
|
|
4342
4934
|
|
|
4343
4935
|
// src/XlsbUpdater.ts
|
|
4344
|
-
import * as
|
|
4936
|
+
import * as fs6 from "fs";
|
|
4937
|
+
import * as os2 from "os";
|
|
4938
|
+
import * as path5 from "path";
|
|
4345
4939
|
import AdmZip3 from "adm-zip";
|
|
4346
4940
|
|
|
4347
4941
|
// src/biff12Utils.ts
|
|
@@ -4420,7 +5014,9 @@ var XlsbUpdater = class {
|
|
|
4420
5014
|
/**
|
|
4421
5015
|
* @param path Path to an existing `.xlsb` file.
|
|
4422
5016
|
*/
|
|
4423
|
-
constructor(
|
|
5017
|
+
constructor(filePath) {
|
|
5018
|
+
this.temporaryDirectory = null;
|
|
5019
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
4424
5020
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
4425
5021
|
this.pivotCacheDefPaths = [];
|
|
4426
5022
|
this.sharedStringsBuf = null;
|
|
@@ -4429,16 +5025,17 @@ var XlsbUpdater = class {
|
|
|
4429
5025
|
this.sharedStringsUnique = 0;
|
|
4430
5026
|
this.sharedStringsEndSst = 0;
|
|
4431
5027
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
5028
|
+
this.sharedStringsDirty = false;
|
|
4432
5029
|
// ------------------------------------------------------------------
|
|
4433
5030
|
// Shared strings
|
|
4434
5031
|
// ------------------------------------------------------------------
|
|
4435
5032
|
this._appendCount = 0;
|
|
4436
|
-
this.sourcePath =
|
|
5033
|
+
this.sourcePath = filePath;
|
|
4437
5034
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
4438
|
-
if (!
|
|
4439
|
-
throw new Error(`XlsbUpdater: file not found: ${
|
|
5035
|
+
if (!fs6.existsSync(filePath)) {
|
|
5036
|
+
throw new Error(`XlsbUpdater: file not found: ${filePath}`);
|
|
4440
5037
|
}
|
|
4441
|
-
this.zip = new AdmZip3(
|
|
5038
|
+
this.zip = new AdmZip3(filePath);
|
|
4442
5039
|
if (!this.zip.getEntry("xl/workbook.bin")) {
|
|
4443
5040
|
throw new Error(
|
|
4444
5041
|
"XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
|
|
@@ -4469,8 +5066,15 @@ var XlsbUpdater = class {
|
|
|
4469
5066
|
if (!entry) {
|
|
4470
5067
|
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4471
5068
|
}
|
|
4472
|
-
const sheetBuf =
|
|
5069
|
+
const sheetBuf = this._entryData(sheetPath);
|
|
4473
5070
|
this._ensureSharedStringsLoaded();
|
|
5071
|
+
if (this.sharedStringsBuf !== null) {
|
|
5072
|
+
this.sharedStringsTotal = Math.max(
|
|
5073
|
+
0,
|
|
5074
|
+
this.sharedStringsTotal - this._sharedStringRefCount(sheetBuf)
|
|
5075
|
+
);
|
|
5076
|
+
this.sharedStringsDirty = true;
|
|
5077
|
+
}
|
|
4474
5078
|
let dataStyles = /* @__PURE__ */ new Map();
|
|
4475
5079
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
4476
5080
|
let dateXf = null;
|
|
@@ -4499,25 +5103,310 @@ var XlsbUpdater = class {
|
|
|
4499
5103
|
}
|
|
4500
5104
|
this._patchDimension(newSheet, lastRowIdx, lastCol);
|
|
4501
5105
|
this.zip.updateFile(sheetPath, newSheet);
|
|
4502
|
-
|
|
5106
|
+
this._discardStagedPart(sheetPath);
|
|
5107
|
+
if (this.sharedStringsBuf !== null) {
|
|
4503
5108
|
this._commitSharedStrings();
|
|
4504
5109
|
}
|
|
4505
5110
|
const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
|
|
4506
5111
|
const colLast = lastCol;
|
|
4507
5112
|
this._patchPivotCaches(sheetName, rwLast, colLast);
|
|
4508
5113
|
}
|
|
5114
|
+
/** Replace worksheet rows from a one-pass source. */
|
|
5115
|
+
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
5116
|
+
const { headers, styleFallback = "inherit" } = options;
|
|
5117
|
+
if (styleFallback !== "inherit" && styleFallback !== "general") {
|
|
5118
|
+
throw new Error("XlsbUpdater: styleFallback must be 'inherit' or 'general'.");
|
|
5119
|
+
}
|
|
5120
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
5121
|
+
if (!sheetPath) {
|
|
5122
|
+
throw new Error(`XlsbUpdater: sheet "${sheetName}" not found in the workbook.`);
|
|
5123
|
+
}
|
|
5124
|
+
const entry = this.zip.getEntry(sheetPath);
|
|
5125
|
+
if (!entry) {
|
|
5126
|
+
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
5127
|
+
}
|
|
5128
|
+
const oldStage = this.stagedParts.get(sheetPath);
|
|
5129
|
+
const beforeValuesLength = this.sharedStringsValues.length;
|
|
5130
|
+
const beforeAppendCount = this._appendCount;
|
|
5131
|
+
const beforeSharedStringsTotal = this.sharedStringsTotal;
|
|
5132
|
+
const beforeSharedStringsUnique = this.sharedStringsUnique;
|
|
5133
|
+
const beforeSharedStringsEndSst = this.sharedStringsEndSst;
|
|
5134
|
+
const beforeSharedStringsDirty = this.sharedStringsDirty;
|
|
5135
|
+
const beforeStringIndexMap = new Map(this.stringIndexMap);
|
|
5136
|
+
const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
5137
|
+
const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.bin") ? Buffer.from(this._entryData("xl/sharedStrings.bin")) : null;
|
|
5138
|
+
const beforeSharedStringsBuf = this.sharedStringsBuf ? Buffer.from(this.sharedStringsBuf) : null;
|
|
5139
|
+
const oldSheet = this._entryData(sheetPath);
|
|
5140
|
+
const rowsPath = this._temporaryPath(".rows");
|
|
5141
|
+
const outputPath = this._temporaryPath(".bin");
|
|
5142
|
+
const createdParts = [rowsPath, outputPath];
|
|
5143
|
+
let output = null;
|
|
5144
|
+
try {
|
|
5145
|
+
this._ensureSharedStringsLoaded();
|
|
5146
|
+
if (this.sharedStringsBuf !== null) {
|
|
5147
|
+
this.sharedStringsTotal = Math.max(
|
|
5148
|
+
0,
|
|
5149
|
+
this.sharedStringsTotal - this._sharedStringRefCount(oldSheet)
|
|
5150
|
+
);
|
|
5151
|
+
this.sharedStringsDirty = true;
|
|
5152
|
+
}
|
|
5153
|
+
let dataStyles = /* @__PURE__ */ new Map();
|
|
5154
|
+
let headerStyles = /* @__PURE__ */ new Map();
|
|
5155
|
+
let dateXf = null;
|
|
5156
|
+
if (styleFallback !== "general") {
|
|
5157
|
+
const collected = this._collectStyles(oldSheet);
|
|
5158
|
+
dataStyles = collected.dataStyles;
|
|
5159
|
+
headerStyles = collected.headerStyles;
|
|
5160
|
+
dateXf = this._findDateXf();
|
|
5161
|
+
}
|
|
5162
|
+
const rowOutput = fs6.createWriteStream(rowsPath);
|
|
5163
|
+
output = rowOutput;
|
|
5164
|
+
let outputError = null;
|
|
5165
|
+
rowOutput.on("error", (error) => {
|
|
5166
|
+
outputError = error;
|
|
5167
|
+
});
|
|
5168
|
+
let writtenBytes = 0;
|
|
5169
|
+
let lastKeptEnd = 0;
|
|
5170
|
+
let width = (headers == null ? void 0 : headers.length) ?? 0;
|
|
5171
|
+
let dataRowsSeen = 0;
|
|
5172
|
+
let keptDataRows = 0;
|
|
5173
|
+
let pendingEmptyWidth = 0;
|
|
5174
|
+
let nextRow = 0;
|
|
5175
|
+
const rowHeaderOffsets = [];
|
|
5176
|
+
const write = async (data) => {
|
|
5177
|
+
if (outputError) throw outputError;
|
|
5178
|
+
writtenBytes += data.length;
|
|
5179
|
+
if (rowOutput.write(data)) return;
|
|
5180
|
+
await new Promise((resolve4, reject) => {
|
|
5181
|
+
const onDrain = () => {
|
|
5182
|
+
cleanup();
|
|
5183
|
+
resolve4();
|
|
5184
|
+
};
|
|
5185
|
+
const onError = (error) => {
|
|
5186
|
+
cleanup();
|
|
5187
|
+
reject(error);
|
|
5188
|
+
};
|
|
5189
|
+
const cleanup = () => {
|
|
5190
|
+
rowOutput.off("drain", onDrain);
|
|
5191
|
+
rowOutput.off("error", onError);
|
|
5192
|
+
};
|
|
5193
|
+
rowOutput.once("drain", onDrain);
|
|
5194
|
+
rowOutput.once("error", onError);
|
|
5195
|
+
});
|
|
5196
|
+
};
|
|
5197
|
+
const writeRow = async (rowNumber, cells) => {
|
|
5198
|
+
rowHeaderOffsets.push(writtenBytes);
|
|
5199
|
+
const header = Buffer.alloc(27);
|
|
5200
|
+
header[0] = 0;
|
|
5201
|
+
header[1] = 25;
|
|
5202
|
+
header.writeInt32LE(rowNumber, 2);
|
|
5203
|
+
header[10] = 44;
|
|
5204
|
+
header[11] = 1;
|
|
5205
|
+
header[15] = 1;
|
|
5206
|
+
header.writeInt32LE(0, 19);
|
|
5207
|
+
header.writeInt32LE(0, 23);
|
|
5208
|
+
await write(header);
|
|
5209
|
+
for (const cell of cells) await write(cell);
|
|
5210
|
+
};
|
|
5211
|
+
if (headers !== void 0) {
|
|
5212
|
+
await writeRow(0, headers.map((value, column) => this._stringCellBytes(column, styleFallback === "general" ? 0 : headerStyles.get(column) ?? 0, value)));
|
|
5213
|
+
lastKeptEnd = writtenBytes;
|
|
5214
|
+
nextRow = 1;
|
|
5215
|
+
}
|
|
5216
|
+
for await (const sourceRow of rows) {
|
|
5217
|
+
if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
|
|
5218
|
+
throw new TypeError("Each row must be an iterable of cells, not text");
|
|
5219
|
+
}
|
|
5220
|
+
const row = Array.from(sourceRow);
|
|
5221
|
+
const cells = row.map((value, column) => value === null || value === void 0 ? null : this._valueCellBytes(value, column, dataStyles, dateXf)).filter((cell) => cell !== null);
|
|
5222
|
+
await writeRow(nextRow, cells);
|
|
5223
|
+
dataRowsSeen++;
|
|
5224
|
+
nextRow++;
|
|
5225
|
+
if (row.every((value) => value === null || value === void 0)) {
|
|
5226
|
+
pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
|
|
5227
|
+
} else {
|
|
5228
|
+
width = Math.max(width, pendingEmptyWidth, row.length);
|
|
5229
|
+
pendingEmptyWidth = 0;
|
|
5230
|
+
keptDataRows = dataRowsSeen;
|
|
5231
|
+
lastKeptEnd = writtenBytes;
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
await new Promise((resolve4, reject) => {
|
|
5235
|
+
rowOutput.once("close", resolve4);
|
|
5236
|
+
rowOutput.once("error", reject);
|
|
5237
|
+
rowOutput.end();
|
|
5238
|
+
});
|
|
5239
|
+
if (outputError) throw outputError;
|
|
5240
|
+
const rowsFd = fs6.openSync(rowsPath, "r+");
|
|
5241
|
+
try {
|
|
5242
|
+
fs6.ftruncateSync(rowsFd, lastKeptEnd);
|
|
5243
|
+
} finally {
|
|
5244
|
+
fs6.closeSync(rowsFd);
|
|
5245
|
+
}
|
|
5246
|
+
const lastCol = Math.max(0, width - 1);
|
|
5247
|
+
const rowPatchFd = fs6.openSync(rowsPath, "r+");
|
|
5248
|
+
try {
|
|
5249
|
+
const lastColBuffer = Buffer.alloc(4);
|
|
5250
|
+
lastColBuffer.writeInt32LE(lastCol, 0);
|
|
5251
|
+
for (const offset of rowHeaderOffsets) {
|
|
5252
|
+
if (offset >= lastKeptEnd) continue;
|
|
5253
|
+
fs6.writeSync(rowPatchFd, lastColBuffer, 0, lastColBuffer.length, offset + 23);
|
|
5254
|
+
}
|
|
5255
|
+
} finally {
|
|
5256
|
+
fs6.closeSync(rowPatchFd);
|
|
5257
|
+
}
|
|
5258
|
+
const { rowsStart, rowsEnd, autoFilterRanges } = this._findRowsRegion(oldSheet);
|
|
5259
|
+
const oldLength = oldSheet.length;
|
|
5260
|
+
const newRowsLength = fs6.statSync(rowsPath).size;
|
|
5261
|
+
const delta = newRowsLength - (rowsEnd - rowsStart);
|
|
5262
|
+
const outputFd = fs6.openSync(outputPath, "w");
|
|
5263
|
+
try {
|
|
5264
|
+
fs6.writeSync(outputFd, oldSheet, 0, rowsStart, null);
|
|
5265
|
+
const rowsFdRead = fs6.openSync(rowsPath, "r");
|
|
5266
|
+
try {
|
|
5267
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
5268
|
+
let position = 0;
|
|
5269
|
+
while (position < newRowsLength) {
|
|
5270
|
+
const count = fs6.readSync(rowsFdRead, chunk, 0, Math.min(chunk.length, newRowsLength - position), position);
|
|
5271
|
+
if (count <= 0) throw new Error("XlsbUpdater: failed to read staged rows.");
|
|
5272
|
+
fs6.writeSync(outputFd, chunk, 0, count);
|
|
5273
|
+
position += count;
|
|
5274
|
+
}
|
|
5275
|
+
} finally {
|
|
5276
|
+
fs6.closeSync(rowsFdRead);
|
|
5277
|
+
}
|
|
5278
|
+
fs6.writeSync(outputFd, oldSheet, rowsEnd, oldLength - rowsEnd, rowsStart + newRowsLength);
|
|
5279
|
+
} finally {
|
|
5280
|
+
fs6.closeSync(outputFd);
|
|
5281
|
+
}
|
|
5282
|
+
this._patchSheetMetadataFile(
|
|
5283
|
+
outputPath,
|
|
5284
|
+
oldSheet,
|
|
5285
|
+
rowsEnd,
|
|
5286
|
+
delta,
|
|
5287
|
+
autoFilterRanges,
|
|
5288
|
+
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
5289
|
+
lastCol
|
|
5290
|
+
);
|
|
5291
|
+
this._stagePart(sheetPath, outputPath);
|
|
5292
|
+
this._commitSharedStrings();
|
|
5293
|
+
this._patchPivotCaches(
|
|
5294
|
+
sheetName,
|
|
5295
|
+
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
5296
|
+
lastCol
|
|
5297
|
+
);
|
|
5298
|
+
if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
|
|
5299
|
+
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
5300
|
+
} catch (error) {
|
|
5301
|
+
this.sharedStringsValues.length = beforeValuesLength;
|
|
5302
|
+
this._appendCount = beforeAppendCount;
|
|
5303
|
+
this.sharedStringsTotal = beforeSharedStringsTotal;
|
|
5304
|
+
this.sharedStringsUnique = beforeSharedStringsUnique;
|
|
5305
|
+
this.sharedStringsEndSst = beforeSharedStringsEndSst;
|
|
5306
|
+
this.sharedStringsDirty = beforeSharedStringsDirty;
|
|
5307
|
+
this.sharedStringsBuf = beforeSharedStringsBuf;
|
|
5308
|
+
this.stringIndexMap.clear();
|
|
5309
|
+
for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
|
|
5310
|
+
if (beforeSharedStringsBytes) {
|
|
5311
|
+
this.zip.updateFile("xl/sharedStrings.bin", beforeSharedStringsBytes);
|
|
5312
|
+
}
|
|
5313
|
+
if (oldStage) this.stagedParts.set(sheetPath, oldStage);
|
|
5314
|
+
else this.stagedParts.delete(sheetPath);
|
|
5315
|
+
throw error;
|
|
5316
|
+
} finally {
|
|
5317
|
+
if (output && !output.closed) output.destroy();
|
|
5318
|
+
for (const temporaryPath of createdParts) this._removeTemporaryFile(temporaryPath);
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
4509
5321
|
/**
|
|
4510
5322
|
* Write the updated workbook to disk.
|
|
4511
5323
|
* @param outputPath Destination path; defaults to the source file (overwrites in place).
|
|
4512
5324
|
*/
|
|
4513
5325
|
save(outputPath) {
|
|
4514
5326
|
const target = outputPath ?? this.sourcePath;
|
|
4515
|
-
|
|
5327
|
+
this._materializeStagedParts();
|
|
5328
|
+
writeBufferAtomically(this.zip.toBuffer(), target);
|
|
5329
|
+
}
|
|
5330
|
+
/** Save staged streaming replacements without materialising the ZIP. */
|
|
5331
|
+
async saveStreaming(outputPath) {
|
|
5332
|
+
const target = outputPath ?? this.sourcePath;
|
|
5333
|
+
await writeAdmZipStreaming(this.zip, this.stagedParts, target);
|
|
5334
|
+
this._materializeStagedParts();
|
|
4516
5335
|
}
|
|
4517
5336
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4518
5337
|
toBuffer() {
|
|
5338
|
+
this._materializeStagedParts();
|
|
4519
5339
|
return this.zip.toBuffer();
|
|
4520
5340
|
}
|
|
5341
|
+
/**
|
|
5342
|
+
* Release the staging directory and any unsaved staged parts.
|
|
5343
|
+
* The updater remains usable; a new staging directory is created lazily
|
|
5344
|
+
* on the next streaming replacement.
|
|
5345
|
+
*/
|
|
5346
|
+
dispose() {
|
|
5347
|
+
for (const stagedPath of this.stagedParts.values()) {
|
|
5348
|
+
try {
|
|
5349
|
+
fs6.rmSync(stagedPath, { force: true });
|
|
5350
|
+
} catch {
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
this.stagedParts.clear();
|
|
5354
|
+
if (this.temporaryDirectory !== null) {
|
|
5355
|
+
try {
|
|
5356
|
+
fs6.rmSync(this.temporaryDirectory, { recursive: true, force: true });
|
|
5357
|
+
} catch {
|
|
5358
|
+
}
|
|
5359
|
+
this.temporaryDirectory = null;
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
/** Support `using` declarations for automatic staging cleanup. */
|
|
5363
|
+
[Symbol.dispose]() {
|
|
5364
|
+
this.dispose();
|
|
5365
|
+
}
|
|
5366
|
+
_ensureTemporaryDirectory() {
|
|
5367
|
+
if (this.temporaryDirectory === null) {
|
|
5368
|
+
this.temporaryDirectory = fs6.mkdtempSync(path5.join(os2.tmpdir(), "spreadsheet-updater-"));
|
|
5369
|
+
} else {
|
|
5370
|
+
try {
|
|
5371
|
+
fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
5372
|
+
} catch {
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
return this.temporaryDirectory;
|
|
5376
|
+
}
|
|
5377
|
+
_temporaryPath(suffix) {
|
|
5378
|
+
return path5.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
5379
|
+
}
|
|
5380
|
+
_removeTemporaryFile(filePath) {
|
|
5381
|
+
try {
|
|
5382
|
+
fs6.rmSync(filePath);
|
|
5383
|
+
} catch (error) {
|
|
5384
|
+
if (error.code !== "ENOENT") throw error;
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
_entryData(name) {
|
|
5388
|
+
const stagedPath = this.stagedParts.get(name);
|
|
5389
|
+
if (stagedPath) return fs6.readFileSync(stagedPath);
|
|
5390
|
+
const entry = this.zip.getEntry(name);
|
|
5391
|
+
if (!entry) throw new Error(`XlsbUpdater: ZIP member missing: ${name}`);
|
|
5392
|
+
return entry.getData();
|
|
5393
|
+
}
|
|
5394
|
+
_stagePart(name, filePath) {
|
|
5395
|
+
this.stagedParts.set(name, filePath);
|
|
5396
|
+
}
|
|
5397
|
+
_discardStagedPart(name) {
|
|
5398
|
+
const stagedPath = this.stagedParts.get(name);
|
|
5399
|
+
if (!stagedPath) return;
|
|
5400
|
+
this._removeTemporaryFile(stagedPath);
|
|
5401
|
+
this.stagedParts.delete(name);
|
|
5402
|
+
}
|
|
5403
|
+
_materializeStagedParts() {
|
|
5404
|
+
for (const [name, stagedPath] of this.stagedParts) {
|
|
5405
|
+
this.zip.updateFile(name, fs6.readFileSync(stagedPath));
|
|
5406
|
+
this._removeTemporaryFile(stagedPath);
|
|
5407
|
+
}
|
|
5408
|
+
this.stagedParts.clear();
|
|
5409
|
+
}
|
|
4521
5410
|
// ------------------------------------------------------------------
|
|
4522
5411
|
// Workbook structure
|
|
4523
5412
|
// ------------------------------------------------------------------
|
|
@@ -4567,11 +5456,11 @@ var XlsbUpdater = class {
|
|
|
4567
5456
|
return;
|
|
4568
5457
|
}
|
|
4569
5458
|
const entry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
4570
|
-
if (!entry) {
|
|
5459
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.bin")) {
|
|
4571
5460
|
this.sharedStringsBuf = null;
|
|
4572
5461
|
return;
|
|
4573
5462
|
}
|
|
4574
|
-
this.sharedStringsBuf =
|
|
5463
|
+
this.sharedStringsBuf = this._entryData("xl/sharedStrings.bin");
|
|
4575
5464
|
const parsed = parseSharedStringsBin(this.sharedStringsBuf);
|
|
4576
5465
|
this.sharedStringsValues = parsed.values;
|
|
4577
5466
|
this.sharedStringsTotal = parsed.total;
|
|
@@ -4583,9 +5472,10 @@ var XlsbUpdater = class {
|
|
|
4583
5472
|
}
|
|
4584
5473
|
});
|
|
4585
5474
|
this._appendCount = 0;
|
|
5475
|
+
this.sharedStringsDirty = false;
|
|
4586
5476
|
}
|
|
4587
5477
|
_commitSharedStrings() {
|
|
4588
|
-
if (this.sharedStringsBuf === null || this.
|
|
5478
|
+
if (this.sharedStringsBuf === null || !this.sharedStringsDirty) return;
|
|
4589
5479
|
const originalValuesLength = this.sharedStringsValues.length - this._appendCount;
|
|
4590
5480
|
let newItems = Buffer.alloc(0);
|
|
4591
5481
|
for (let i = originalValuesLength; i < this.sharedStringsValues.length; i++) {
|
|
@@ -4611,6 +5501,9 @@ var XlsbUpdater = class {
|
|
|
4611
5501
|
pos = rec.dataEnd;
|
|
4612
5502
|
}
|
|
4613
5503
|
this.zip.updateFile("xl/sharedStrings.bin", out);
|
|
5504
|
+
this.sharedStringsBuf = out;
|
|
5505
|
+
this._appendCount = 0;
|
|
5506
|
+
this.sharedStringsDirty = false;
|
|
4614
5507
|
}
|
|
4615
5508
|
// ------------------------------------------------------------------
|
|
4616
5509
|
// Styles
|
|
@@ -4793,12 +5686,51 @@ var XlsbUpdater = class {
|
|
|
4793
5686
|
this._appendCount++;
|
|
4794
5687
|
}
|
|
4795
5688
|
this.sharedStringsTotal++;
|
|
5689
|
+
this.sharedStringsDirty = true;
|
|
4796
5690
|
const payload = Buffer.alloc(12);
|
|
4797
5691
|
payload.writeUInt32LE(col, 0);
|
|
4798
5692
|
payload.writeUInt32LE(style, 4);
|
|
4799
5693
|
payload.writeUInt32LE(index, 8);
|
|
4800
5694
|
return buildRecord(7, payload);
|
|
4801
5695
|
}
|
|
5696
|
+
_sharedStringRefCount(sheetBuf) {
|
|
5697
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5698
|
+
let pos = 0;
|
|
5699
|
+
let count = 0;
|
|
5700
|
+
while (readRecord(sheetBuf, pos, rec)) {
|
|
5701
|
+
if (rec.id === 7) count++;
|
|
5702
|
+
pos = rec.dataEnd;
|
|
5703
|
+
}
|
|
5704
|
+
return count;
|
|
5705
|
+
}
|
|
5706
|
+
_patchSheetMetadataFile(filePath, originalSheet, rowsEnd, delta, autoFilterRanges, lastRowIdx, lastCol) {
|
|
5707
|
+
const fd = fs6.openSync(filePath, "r+");
|
|
5708
|
+
try {
|
|
5709
|
+
const patchInt32 = (offset, value) => {
|
|
5710
|
+
const bytes = Buffer.alloc(4);
|
|
5711
|
+
bytes.writeInt32LE(value, 0);
|
|
5712
|
+
fs6.writeSync(fd, bytes, 0, bytes.length, offset);
|
|
5713
|
+
};
|
|
5714
|
+
for (const filterOffset of autoFilterRanges) {
|
|
5715
|
+
const newOffset = filterOffset >= rowsEnd ? filterOffset + delta : filterOffset;
|
|
5716
|
+
patchInt32(newOffset + 4, lastRowIdx);
|
|
5717
|
+
patchInt32(newOffset + 12, lastCol);
|
|
5718
|
+
}
|
|
5719
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5720
|
+
let pos = 0;
|
|
5721
|
+
while (readRecord(originalSheet, pos, rec)) {
|
|
5722
|
+
if (rec.id === 152 && rec.len >= 36) {
|
|
5723
|
+
const shift = rec.dataStart >= rowsEnd ? delta : 0;
|
|
5724
|
+
patchInt32(rec.dataStart + shift + 24, lastRowIdx);
|
|
5725
|
+
patchInt32(rec.dataStart + shift + 32, lastCol);
|
|
5726
|
+
break;
|
|
5727
|
+
}
|
|
5728
|
+
pos = rec.dataEnd;
|
|
5729
|
+
}
|
|
5730
|
+
} finally {
|
|
5731
|
+
fs6.closeSync(fd);
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
4802
5734
|
// ------------------------------------------------------------------
|
|
4803
5735
|
// Rows region detection
|
|
4804
5736
|
// ------------------------------------------------------------------
|
|
@@ -4850,8 +5782,8 @@ var XlsbUpdater = class {
|
|
|
4850
5782
|
// Pivot cache integration
|
|
4851
5783
|
// ------------------------------------------------------------------
|
|
4852
5784
|
_patchPivotCaches(sheetName, rwLast, colLast) {
|
|
4853
|
-
for (const
|
|
4854
|
-
const entry = this.zip.getEntry(
|
|
5785
|
+
for (const path7 of this.pivotCacheDefPaths) {
|
|
5786
|
+
const entry = this.zip.getEntry(path7);
|
|
4855
5787
|
if (!entry) continue;
|
|
4856
5788
|
const buf = Buffer.from(entry.getData());
|
|
4857
5789
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
@@ -4883,11 +5815,188 @@ var XlsbUpdater = class {
|
|
|
4883
5815
|
}
|
|
4884
5816
|
}
|
|
4885
5817
|
if (patched) {
|
|
4886
|
-
this.zip.updateFile(
|
|
5818
|
+
this.zip.updateFile(path7, buf);
|
|
4887
5819
|
}
|
|
4888
5820
|
}
|
|
4889
5821
|
}
|
|
4890
5822
|
};
|
|
5823
|
+
|
|
5824
|
+
// src/ExcelConverter.ts
|
|
5825
|
+
import * as fs7 from "fs";
|
|
5826
|
+
import * as path6 from "path";
|
|
5827
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5828
|
+
import { spawn } from "child_process";
|
|
5829
|
+
var POWERSHELL_CONVERSION_SCRIPT = `
|
|
5830
|
+
$ErrorActionPreference = 'Stop'
|
|
5831
|
+
$inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
|
|
5832
|
+
$outputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_OUTPUT')
|
|
5833
|
+
$fileFormat = [int][Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_FORMAT')
|
|
5834
|
+
$excel = $null
|
|
5835
|
+
$workbook = $null
|
|
5836
|
+
$exitCode = 0
|
|
5837
|
+
|
|
5838
|
+
try {
|
|
5839
|
+
$excel = New-Object -ComObject Excel.Application
|
|
5840
|
+
$excel.Visible = $false
|
|
5841
|
+
$excel.DisplayAlerts = $false
|
|
5842
|
+
$excel.ScreenUpdating = $false
|
|
5843
|
+
|
|
5844
|
+
# Open read-only so the source workbook is never changed by the conversion.
|
|
5845
|
+
$workbook = $excel.Workbooks.Open($inputPath, 0, $true)
|
|
5846
|
+
if ($null -eq $workbook) {
|
|
5847
|
+
throw 'Excel did not return a workbook when opening the source file.'
|
|
5848
|
+
}
|
|
5849
|
+
|
|
5850
|
+
$workbook.SaveAs($outputPath, $fileFormat)
|
|
5851
|
+
} catch {
|
|
5852
|
+
$exitCode = 1
|
|
5853
|
+
[Console]::Error.WriteLine($_.Exception.ToString())
|
|
5854
|
+
} finally {
|
|
5855
|
+
if ($null -ne $workbook) {
|
|
5856
|
+
try { $workbook.Close($false) } catch { }
|
|
5857
|
+
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook) | Out-Null } catch { }
|
|
5858
|
+
}
|
|
5859
|
+
if ($null -ne $excel) {
|
|
5860
|
+
try { $excel.Quit() } catch { }
|
|
5861
|
+
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null } catch { }
|
|
5862
|
+
}
|
|
5863
|
+
[GC]::Collect()
|
|
5864
|
+
[GC]::WaitForPendingFinalizers()
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
exit $exitCode
|
|
5868
|
+
`;
|
|
5869
|
+
function errorMessage2(error) {
|
|
5870
|
+
return error instanceof Error ? error.message : String(error);
|
|
5871
|
+
}
|
|
5872
|
+
function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
|
|
5873
|
+
const input = path6.resolve(inputPath);
|
|
5874
|
+
const output = path6.resolve(outputPath);
|
|
5875
|
+
if (path6.extname(input).toLowerCase() !== inputExtension) {
|
|
5876
|
+
throw new Error(
|
|
5877
|
+
`Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
|
|
5878
|
+
);
|
|
5879
|
+
}
|
|
5880
|
+
if (path6.extname(output).toLowerCase() !== outputExtension) {
|
|
5881
|
+
throw new Error(
|
|
5882
|
+
`Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
|
|
5883
|
+
);
|
|
5884
|
+
}
|
|
5885
|
+
if (!fs7.existsSync(input)) {
|
|
5886
|
+
throw new Error(`Excel conversion: source file not found: ${inputPath}`);
|
|
5887
|
+
}
|
|
5888
|
+
if (!fs7.statSync(input).isFile()) {
|
|
5889
|
+
throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
|
|
5890
|
+
}
|
|
5891
|
+
if (!fs7.existsSync(path6.dirname(output))) {
|
|
5892
|
+
throw new Error(`Excel conversion: destination directory not found: ${path6.dirname(output)}`);
|
|
5893
|
+
}
|
|
5894
|
+
if (fs7.existsSync(output)) {
|
|
5895
|
+
if (fs7.statSync(output).isDirectory()) {
|
|
5896
|
+
throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
|
|
5897
|
+
}
|
|
5898
|
+
if (!overwrite) {
|
|
5899
|
+
throw new Error(
|
|
5900
|
+
`Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${outputPath}`
|
|
5901
|
+
);
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
if (process.platform === "win32" && input.toLowerCase() === output.toLowerCase()) {
|
|
5905
|
+
throw new Error("Excel conversion: source and destination must be different files.");
|
|
5906
|
+
}
|
|
5907
|
+
return { input, output };
|
|
5908
|
+
}
|
|
5909
|
+
function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
5910
|
+
const encodedCommand = Buffer.from(POWERSHELL_CONVERSION_SCRIPT, "utf16le").toString("base64");
|
|
5911
|
+
const environment = {
|
|
5912
|
+
...process.env,
|
|
5913
|
+
JUSTYBASE_EXCEL_INPUT: inputPath,
|
|
5914
|
+
JUSTYBASE_EXCEL_OUTPUT: outputPath,
|
|
5915
|
+
JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
|
|
5916
|
+
};
|
|
5917
|
+
return new Promise((resolve4, reject) => {
|
|
5918
|
+
var _a, _b, _c, _d;
|
|
5919
|
+
let stdout = "";
|
|
5920
|
+
let stderr = "";
|
|
5921
|
+
let settled = false;
|
|
5922
|
+
const child = spawn(
|
|
5923
|
+
"powershell.exe",
|
|
5924
|
+
[
|
|
5925
|
+
"-NoLogo",
|
|
5926
|
+
"-NoProfile",
|
|
5927
|
+
"-NonInteractive",
|
|
5928
|
+
"-ExecutionPolicy",
|
|
5929
|
+
"Bypass",
|
|
5930
|
+
"-EncodedCommand",
|
|
5931
|
+
encodedCommand
|
|
5932
|
+
],
|
|
5933
|
+
{ env: environment, windowsHide: true }
|
|
5934
|
+
);
|
|
5935
|
+
(_a = child.stdout) == null ? void 0 : _a.setEncoding("utf8");
|
|
5936
|
+
(_b = child.stderr) == null ? void 0 : _b.setEncoding("utf8");
|
|
5937
|
+
(_c = child.stdout) == null ? void 0 : _c.on("data", (chunk) => {
|
|
5938
|
+
stdout += chunk;
|
|
5939
|
+
});
|
|
5940
|
+
(_d = child.stderr) == null ? void 0 : _d.on("data", (chunk) => {
|
|
5941
|
+
stderr += chunk;
|
|
5942
|
+
});
|
|
5943
|
+
child.once("error", (error) => {
|
|
5944
|
+
if (settled) return;
|
|
5945
|
+
settled = true;
|
|
5946
|
+
reject(new Error(`Excel conversion: unable to start PowerShell/COM: ${error.message}`));
|
|
5947
|
+
});
|
|
5948
|
+
child.once("close", (code) => {
|
|
5949
|
+
if (settled) return;
|
|
5950
|
+
settled = true;
|
|
5951
|
+
if (code === 0) {
|
|
5952
|
+
resolve4();
|
|
5953
|
+
return;
|
|
5954
|
+
}
|
|
5955
|
+
const details = (stderr || stdout).replace(/\s+/g, " ").trim();
|
|
5956
|
+
reject(new Error(
|
|
5957
|
+
`Excel conversion failed (PowerShell exit code ${code ?? "unknown"})${details ? `: ${details}` : "."}`
|
|
5958
|
+
));
|
|
5959
|
+
});
|
|
5960
|
+
});
|
|
5961
|
+
}
|
|
5962
|
+
async function convertExcelFile(inputPath, outputPath, inputExtension, outputExtension, fileFormat, options = {}) {
|
|
5963
|
+
const { input, output } = validateConversionPaths(
|
|
5964
|
+
inputPath,
|
|
5965
|
+
outputPath,
|
|
5966
|
+
inputExtension,
|
|
5967
|
+
outputExtension,
|
|
5968
|
+
options.overwrite === true
|
|
5969
|
+
);
|
|
5970
|
+
if (process.platform !== "win32") {
|
|
5971
|
+
throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
|
|
5972
|
+
}
|
|
5973
|
+
const temporaryOutput = path6.join(
|
|
5974
|
+
path6.dirname(output),
|
|
5975
|
+
`.${path6.basename(output, outputExtension)}.${randomUUID3()}.conversion${outputExtension}`
|
|
5976
|
+
);
|
|
5977
|
+
try {
|
|
5978
|
+
await runPowerShellConversion(input, temporaryOutput, fileFormat);
|
|
5979
|
+
if (!fs7.existsSync(temporaryOutput)) {
|
|
5980
|
+
throw new Error("Excel conversion completed without creating the destination file.");
|
|
5981
|
+
}
|
|
5982
|
+
installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
|
|
5983
|
+
} catch (error) {
|
|
5984
|
+
throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
|
|
5985
|
+
} finally {
|
|
5986
|
+
if (fs7.existsSync(temporaryOutput)) {
|
|
5987
|
+
try {
|
|
5988
|
+
fs7.rmSync(temporaryOutput);
|
|
5989
|
+
} catch {
|
|
5990
|
+
}
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
function convertXlsbToXlsx(inputPath, outputPath, options) {
|
|
5995
|
+
return convertExcelFile(inputPath, outputPath, ".xlsb", ".xlsx", 51, options);
|
|
5996
|
+
}
|
|
5997
|
+
function convertXlsxToXlsb(inputPath, outputPath, options) {
|
|
5998
|
+
return convertExcelFile(inputPath, outputPath, ".xlsx", ".xlsb", 50, options);
|
|
5999
|
+
}
|
|
4891
6000
|
export {
|
|
4892
6001
|
BiffReaderWriter,
|
|
4893
6002
|
BigBuffer,
|
|
@@ -4899,10 +6008,13 @@ export {
|
|
|
4899
6008
|
XlsbReader,
|
|
4900
6009
|
XlsbUpdater,
|
|
4901
6010
|
XlsbWriter,
|
|
6011
|
+
XlsxUpdater as XlsmUpdater,
|
|
4902
6012
|
XlsxReader,
|
|
4903
6013
|
XlsxUpdater,
|
|
4904
6014
|
XlsxWriter,
|
|
4905
6015
|
applyHeaderWidths,
|
|
6016
|
+
convertXlsbToXlsx,
|
|
6017
|
+
convertXlsxToXlsb,
|
|
4906
6018
|
defaultColWidth,
|
|
4907
6019
|
getFormat,
|
|
4908
6020
|
initColWidths,
|