@justybase/spreadsheet-tasks 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +20 -2
- package/browser/browser-spreadsheet.js +131 -43
- package/browser/justybase-spreadsheet.min.js +13 -13
- package/dist/cjs/index.d.ts +167 -7
- package/dist/cjs/index.js +2420 -685
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +2404 -685
- package/dist/esm/index.js.map +1 -1
- package/docs/API.md +98 -3
- package/package.json +4 -3
package/dist/esm/index.js
CHANGED
|
@@ -112,6 +112,143 @@ var BigBuffer = class {
|
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
// src/Formats.ts
|
|
116
|
+
var F = {
|
|
117
|
+
THOUSANDS_SEP: "#,##0",
|
|
118
|
+
CURRENCY_PLN: '#,##0.00 "z\u0142"',
|
|
119
|
+
CURRENCY_EUR: "#,##0.00 \u20AC",
|
|
120
|
+
PERCENTAGE: "0%",
|
|
121
|
+
SCIENTIFIC: "0.00E+00",
|
|
122
|
+
TWO_DECIMALS: "#,##0.00",
|
|
123
|
+
TEXT: "@",
|
|
124
|
+
LEADING_ZEROS: "000000000",
|
|
125
|
+
DATE_SHORT: "dd.mm.yyyy",
|
|
126
|
+
DATE_LONG: "d mmmm yyyy",
|
|
127
|
+
DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
|
|
128
|
+
DATE_ISO: "yyyy-mm-dd",
|
|
129
|
+
DATE_MONTH_YEAR: "mmmm yyyy",
|
|
130
|
+
DATE_WEEKDAY: "dddd, d mmmm yyyy",
|
|
131
|
+
DATE_DAY_MONTH: "d mmmm",
|
|
132
|
+
DATE_YEAR_ONLY: "yyyy",
|
|
133
|
+
DATETIME_SHORT: "dd.mm.yyyy hh:mm",
|
|
134
|
+
DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
|
|
135
|
+
TIME_HH_MM: "hh:mm",
|
|
136
|
+
TIME_HH_MM_SS: "hh:mm:ss",
|
|
137
|
+
TIME_12H: "h:mm AM/PM",
|
|
138
|
+
DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
|
|
139
|
+
DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
|
|
140
|
+
TIME_MS: "hh:mm:ss.000"
|
|
141
|
+
};
|
|
142
|
+
function isFormattedCell(val) {
|
|
143
|
+
return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
|
|
144
|
+
}
|
|
145
|
+
function unwrapCell(val) {
|
|
146
|
+
if (isFormattedCell(val)) {
|
|
147
|
+
return val.value;
|
|
148
|
+
}
|
|
149
|
+
return val;
|
|
150
|
+
}
|
|
151
|
+
function getFormat(val) {
|
|
152
|
+
if (isFormattedCell(val)) {
|
|
153
|
+
return val.format;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/writerHelpers.ts
|
|
159
|
+
var OA_EPOCH_1900 = Date.UTC(1899, 11, 30);
|
|
160
|
+
var MARCH_1_1900 = Date.UTC(1900, 2, 1);
|
|
161
|
+
function oaDateToTime(oaDate, epochMs) {
|
|
162
|
+
let serial = oaDate;
|
|
163
|
+
if (epochMs === OA_EPOCH_1900 && serial >= 1 && serial < 60) {
|
|
164
|
+
serial += 1;
|
|
165
|
+
}
|
|
166
|
+
return Math.round(serial * 864e5) + epochMs;
|
|
167
|
+
}
|
|
168
|
+
function timeToOaDate(timeMs, epochMs) {
|
|
169
|
+
let serial = (timeMs - epochMs) / 864e5;
|
|
170
|
+
if (epochMs === OA_EPOCH_1900 && timeMs < MARCH_1_1900) {
|
|
171
|
+
serial -= 1;
|
|
172
|
+
}
|
|
173
|
+
return serial;
|
|
174
|
+
}
|
|
175
|
+
var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
|
|
176
|
+
function sanitizeSheetName(name, sheetCount) {
|
|
177
|
+
if (!name || typeof name !== "string") {
|
|
178
|
+
return `Sheet${sheetCount + 1}`;
|
|
179
|
+
}
|
|
180
|
+
let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
|
|
181
|
+
if (sanitized.length > 31) {
|
|
182
|
+
sanitized = sanitized.substring(0, 31);
|
|
183
|
+
}
|
|
184
|
+
if (sanitized.trim().length === 0) {
|
|
185
|
+
sanitized = `Sheet${sheetCount + 1}`;
|
|
186
|
+
}
|
|
187
|
+
return sanitized;
|
|
188
|
+
}
|
|
189
|
+
var MAX_ROWS = 1048576;
|
|
190
|
+
var MAX_COLS = 16384;
|
|
191
|
+
var MAX_CELL_CHARS = 32767;
|
|
192
|
+
function assertCellTextLength(text) {
|
|
193
|
+
if (text.length > MAX_CELL_CHARS) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Excel cell text exceeds ${MAX_CELL_CHARS} UTF-16 code units (got ${text.length}).`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function assertColumnCount(columnCount) {
|
|
200
|
+
if (!Number.isInteger(columnCount) || columnCount < 1 || columnCount > MAX_COLS) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Excel column count must be 1..${MAX_COLS} (got ${columnCount}).`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function assertRowFitsColumns(kind, length, columnCount) {
|
|
207
|
+
if (length > columnCount) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Excel ${kind} length ${length} exceeds columnCount ${columnCount}.`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function assertRowCount(totalRows) {
|
|
214
|
+
if (totalRows > MAX_ROWS) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Excel row count exceeds ${MAX_ROWS} (got ${totalRows}).`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function initColWidths(columnCount) {
|
|
221
|
+
return new Array(columnCount).fill(-1);
|
|
222
|
+
}
|
|
223
|
+
function applyHeaderWidths(colWidths, headers, columnCount) {
|
|
224
|
+
for (let i = 0; i < columnCount; i++) {
|
|
225
|
+
const len = headers[i] ? headers[i].length + 1 : 0;
|
|
226
|
+
let width = 1.25 * len + 2;
|
|
227
|
+
if (width > 80) width = 80;
|
|
228
|
+
if (colWidths[i] < width) colWidths[i] = width;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
|
|
232
|
+
for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
|
|
233
|
+
const row = rows[r];
|
|
234
|
+
for (let c = 0; c < row.length; c++) {
|
|
235
|
+
const raw = row[c];
|
|
236
|
+
if (raw === null || raw === void 0) continue;
|
|
237
|
+
const val = isFormattedCell(raw) ? raw.value : raw;
|
|
238
|
+
const len = val instanceof Date ? 20 : String(val).length + 1;
|
|
239
|
+
let width = 1.25 * len + 2;
|
|
240
|
+
if (width > 80) width = 80;
|
|
241
|
+
if (colWidths[c] < width) colWidths[c] = width;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function defaultColWidth(width, floor = false) {
|
|
246
|
+
if (width > 0) {
|
|
247
|
+
return floor ? Math.floor(width) : width;
|
|
248
|
+
}
|
|
249
|
+
return 10;
|
|
250
|
+
}
|
|
251
|
+
|
|
115
252
|
// src/ExcelReaderAbstract.ts
|
|
116
253
|
var ExcelReaderAbstract = class {
|
|
117
254
|
constructor() {
|
|
@@ -126,8 +263,7 @@ var ExcelReaderAbstract = class {
|
|
|
126
263
|
dispose() {
|
|
127
264
|
}
|
|
128
265
|
getDateTimeFromOaDate(oaDate) {
|
|
129
|
-
|
|
130
|
-
return new Date(ms);
|
|
266
|
+
return new Date(oaDateToTime(oaDate, this._oaEpoch));
|
|
131
267
|
}
|
|
132
268
|
};
|
|
133
269
|
|
|
@@ -387,168 +523,6 @@ var BiffReaderWriter = class {
|
|
|
387
523
|
|
|
388
524
|
// src/XlsbReader.ts
|
|
389
525
|
import AdmZip from "adm-zip";
|
|
390
|
-
var XlsbReader = class extends ExcelReaderAbstract {
|
|
391
|
-
constructor() {
|
|
392
|
-
super();
|
|
393
|
-
this.zip = null;
|
|
394
|
-
this.sharedStrings = [];
|
|
395
|
-
this.sheetNames = [];
|
|
396
|
-
this.sheets = [];
|
|
397
|
-
this._currentSheetIndex = -1;
|
|
398
|
-
this._reader = null;
|
|
399
|
-
this._currentRow = [];
|
|
400
|
-
this._pendingRowIndex = -1;
|
|
401
|
-
this._eof = false;
|
|
402
|
-
this.xfIdToNumFmtId = [];
|
|
403
|
-
this.customDateFormats = null;
|
|
404
|
-
}
|
|
405
|
-
async open(path3, readSharedStrings = true) {
|
|
406
|
-
this.zip = new AdmZip(path3);
|
|
407
|
-
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
408
|
-
if (wbEntry) {
|
|
409
|
-
const buf = wbEntry.getData();
|
|
410
|
-
const reader = new BiffReaderWriter(buf);
|
|
411
|
-
while (reader.readWorkbook()) {
|
|
412
|
-
if (reader._isSheet) {
|
|
413
|
-
const name = reader._workbookName;
|
|
414
|
-
const rId = reader._recId;
|
|
415
|
-
this.sheetNames.push(name);
|
|
416
|
-
this.sheets.push({ name, rId, path: null });
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
|
|
421
|
-
if (relsEntry) {
|
|
422
|
-
const xml = relsEntry.getData().toString("utf8");
|
|
423
|
-
const relRegex = /<Relationship[^>]*Id="([^"]*)"[^>]*Target="([^"]*)"/g;
|
|
424
|
-
let match;
|
|
425
|
-
const rIdToTarget = {};
|
|
426
|
-
while ((match = relRegex.exec(xml)) !== null) {
|
|
427
|
-
rIdToTarget[match[1]] = match[2];
|
|
428
|
-
}
|
|
429
|
-
for (const sheet of this.sheets) {
|
|
430
|
-
let target = rIdToTarget[sheet.rId];
|
|
431
|
-
if (target) {
|
|
432
|
-
if (target.startsWith("/")) target = target.substring(1);
|
|
433
|
-
if (!target.startsWith("xl/")) target = "xl/" + target;
|
|
434
|
-
sheet.path = target;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
if (readSharedStrings) {
|
|
439
|
-
const ssPath = "xl/sharedStrings.bin";
|
|
440
|
-
if (this.zip.getEntry(ssPath)) {
|
|
441
|
-
this._readSharedStrings(ssPath);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
const stylesPath = "xl/styles.bin";
|
|
445
|
-
if (this.zip.getEntry(stylesPath)) {
|
|
446
|
-
this._readStyles(stylesPath);
|
|
447
|
-
}
|
|
448
|
-
this.resultsCount = this.sheets.length;
|
|
449
|
-
this._currentSheetIndex = -1;
|
|
450
|
-
}
|
|
451
|
-
_readSharedStrings(path3) {
|
|
452
|
-
const entry = this.zip.getEntry(path3);
|
|
453
|
-
if (!entry) return;
|
|
454
|
-
const reader = new BiffReaderWriter(entry.getData());
|
|
455
|
-
while (reader.readSharedStrings()) {
|
|
456
|
-
if (reader._sharedStringValue !== null) {
|
|
457
|
-
this.sharedStrings.push(reader._sharedStringValue);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
_readStyles(path3) {
|
|
462
|
-
const entry = this.zip.getEntry(path3);
|
|
463
|
-
if (!entry) return;
|
|
464
|
-
const reader = new BiffReaderWriter(entry.getData());
|
|
465
|
-
while (reader.readStyles()) {
|
|
466
|
-
}
|
|
467
|
-
this.xfIdToNumFmtId = reader._xfIndexToNumFmtId;
|
|
468
|
-
this.customDateFormats = reader._customNumFmts;
|
|
469
|
-
}
|
|
470
|
-
getSheetNames() {
|
|
471
|
-
return this.sheetNames;
|
|
472
|
-
}
|
|
473
|
-
async read() {
|
|
474
|
-
if (this._currentSheetIndex === -1) {
|
|
475
|
-
this._currentSheetIndex = 0;
|
|
476
|
-
if (!this._initSheet(0)) return false;
|
|
477
|
-
}
|
|
478
|
-
if (this._eof) return false;
|
|
479
|
-
this._currentRow = [];
|
|
480
|
-
while (true) {
|
|
481
|
-
const hasRecord = this._reader.readWorksheet();
|
|
482
|
-
if (!hasRecord) {
|
|
483
|
-
this._eof = true;
|
|
484
|
-
return true;
|
|
485
|
-
}
|
|
486
|
-
if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
|
|
487
|
-
this._pendingRowIndex = this._reader._rowIndex;
|
|
488
|
-
return true;
|
|
489
|
-
}
|
|
490
|
-
if (this._reader._readCell) {
|
|
491
|
-
const col = this._reader._columnNum;
|
|
492
|
-
let val = null;
|
|
493
|
-
switch (this._reader._cellType) {
|
|
494
|
-
case 2:
|
|
495
|
-
val = this.sharedStrings[this._reader._intValue];
|
|
496
|
-
break;
|
|
497
|
-
case 3: {
|
|
498
|
-
val = this._reader._doubleVal;
|
|
499
|
-
const xfIndex = this._reader._xfIndex;
|
|
500
|
-
let numFmtId = 0;
|
|
501
|
-
if (this.xfIdToNumFmtId && xfIndex < this.xfIdToNumFmtId.length) {
|
|
502
|
-
numFmtId = this.xfIdToNumFmtId[xfIndex];
|
|
503
|
-
}
|
|
504
|
-
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
|
|
505
|
-
if (isDate) {
|
|
506
|
-
try {
|
|
507
|
-
val = this.getDateTimeFromOaDate(val);
|
|
508
|
-
} catch {
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
case 4:
|
|
514
|
-
val = this._reader._boolValue;
|
|
515
|
-
break;
|
|
516
|
-
case 5:
|
|
517
|
-
val = this._reader._stringValue;
|
|
518
|
-
break;
|
|
519
|
-
default:
|
|
520
|
-
val = null;
|
|
521
|
-
}
|
|
522
|
-
this._currentRow[col] = val;
|
|
523
|
-
if (col >= this.fieldCount) this.fieldCount = col + 1;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
_initSheet(index) {
|
|
528
|
-
if (index >= this.sheets.length) return false;
|
|
529
|
-
const sheet = this.sheets[index];
|
|
530
|
-
const entry = this.zip.getEntry(sheet.path);
|
|
531
|
-
if (!entry) return false;
|
|
532
|
-
this._reader = new BiffReaderWriter(entry.getData());
|
|
533
|
-
this._eof = false;
|
|
534
|
-
this._pendingRowIndex = -1;
|
|
535
|
-
while (this._reader.readWorksheet()) {
|
|
536
|
-
if (this._reader._rowIndex !== -1) {
|
|
537
|
-
this._pendingRowIndex = this._reader._rowIndex;
|
|
538
|
-
return true;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
this._eof = true;
|
|
542
|
-
return false;
|
|
543
|
-
}
|
|
544
|
-
getValue(i) {
|
|
545
|
-
if (i < 0 || i >= this._currentRow.length) return null;
|
|
546
|
-
return this._currentRow[i];
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
// src/XlsxReader.ts
|
|
551
|
-
import yauzl from "yauzl";
|
|
552
526
|
|
|
553
527
|
// src/xmlUtils.ts
|
|
554
528
|
function hasInvalidControlChars(str) {
|
|
@@ -582,8 +556,57 @@ function escapeXmlText(str) {
|
|
|
582
556
|
}
|
|
583
557
|
function unescapeXml(str) {
|
|
584
558
|
if (!str || str.indexOf("&") === -1) return str;
|
|
585
|
-
return str.replace(/&
|
|
586
|
-
|
|
559
|
+
return str.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);/g, (entity) => {
|
|
560
|
+
switch (entity) {
|
|
561
|
+
case "&":
|
|
562
|
+
return "&";
|
|
563
|
+
case "<":
|
|
564
|
+
return "<";
|
|
565
|
+
case ">":
|
|
566
|
+
return ">";
|
|
567
|
+
case """:
|
|
568
|
+
return '"';
|
|
569
|
+
case "'":
|
|
570
|
+
return "'";
|
|
571
|
+
default: {
|
|
572
|
+
if (entity[1] === "#") {
|
|
573
|
+
const body = entity.slice(2, -1);
|
|
574
|
+
const code = body[0] === "x" || body[0] === "X" ? parseInt(body.slice(1), 16) : parseInt(body, 10);
|
|
575
|
+
if (Number.isFinite(code)) {
|
|
576
|
+
try {
|
|
577
|
+
return String.fromCodePoint(code);
|
|
578
|
+
} catch {
|
|
579
|
+
return entity;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return entity;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function parseTagAttributes(tag) {
|
|
589
|
+
const attrs = {};
|
|
590
|
+
const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
591
|
+
let m;
|
|
592
|
+
while ((m = re.exec(tag)) !== null) {
|
|
593
|
+
const name = m[1];
|
|
594
|
+
const value = m[3] !== void 0 ? m[3] : m[4];
|
|
595
|
+
attrs[name] = value;
|
|
596
|
+
const colon = name.indexOf(":");
|
|
597
|
+
if (colon !== -1) {
|
|
598
|
+
const local = name.slice(colon + 1);
|
|
599
|
+
if (!(local in attrs)) attrs[local] = value;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return attrs;
|
|
603
|
+
}
|
|
604
|
+
function resolveRelTarget(target) {
|
|
605
|
+
if (!target) return target;
|
|
606
|
+
if (target.startsWith("/")) return target.slice(1);
|
|
607
|
+
if (target.startsWith("xl/")) return target;
|
|
608
|
+
return "xl/" + target;
|
|
609
|
+
}
|
|
587
610
|
function columnIndexToLetter(colIndex) {
|
|
588
611
|
let n = colIndex + 1;
|
|
589
612
|
let result = "";
|
|
@@ -611,31 +634,341 @@ function trimTrailingEmptyRows(rows) {
|
|
|
611
634
|
}
|
|
612
635
|
function parseSharedStringsXml(xml) {
|
|
613
636
|
const result = [];
|
|
637
|
+
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>|<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*\/>/gi;
|
|
638
|
+
let itemMatch;
|
|
639
|
+
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
640
|
+
const full = itemMatch[0];
|
|
641
|
+
if (full.endsWith("/>") && itemMatch[1] === void 0) {
|
|
642
|
+
result.push("");
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
let content = itemMatch[1] ?? "";
|
|
646
|
+
content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*>[\s\S]*?<\/(?:(?:[A-Za-z_][\w.-]*):)?rPh\s*>/gi, "");
|
|
647
|
+
content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*\/>/gi, "");
|
|
648
|
+
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
649
|
+
let value = "";
|
|
650
|
+
let textMatch;
|
|
651
|
+
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
652
|
+
value += textMatch[1];
|
|
653
|
+
}
|
|
654
|
+
result.push(unescapeXml(value));
|
|
655
|
+
}
|
|
656
|
+
return result;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/biff12Utils.ts
|
|
660
|
+
function readVlq(buf, pos) {
|
|
661
|
+
let value = 0;
|
|
662
|
+
let shift = 0;
|
|
663
|
+
let byte;
|
|
664
|
+
do {
|
|
665
|
+
byte = buf[pos++];
|
|
666
|
+
value |= (byte & 127) << shift;
|
|
667
|
+
shift += 7;
|
|
668
|
+
} while ((byte & 128) !== 0);
|
|
669
|
+
return { value, nextPos: pos };
|
|
670
|
+
}
|
|
671
|
+
function vlqBytes(value) {
|
|
672
|
+
const out = [];
|
|
673
|
+
while (value >= 128) {
|
|
674
|
+
out.push(value & 127 | 128);
|
|
675
|
+
value >>>= 7;
|
|
676
|
+
}
|
|
677
|
+
out.push(value & 127);
|
|
678
|
+
return out;
|
|
679
|
+
}
|
|
680
|
+
function readRecord(buf, pos, rec) {
|
|
681
|
+
const idRes = readVlq(buf, pos);
|
|
682
|
+
if (idRes.nextPos > buf.length) return false;
|
|
683
|
+
const lenRes = readVlq(buf, idRes.nextPos);
|
|
684
|
+
if (lenRes.nextPos + lenRes.value > buf.length) return false;
|
|
685
|
+
rec.headerStart = pos;
|
|
686
|
+
rec.dataStart = lenRes.nextPos;
|
|
687
|
+
rec.dataEnd = lenRes.nextPos + lenRes.value;
|
|
688
|
+
rec.id = idRes.value;
|
|
689
|
+
rec.len = lenRes.value;
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
function buildRecord(id, payload) {
|
|
693
|
+
const idBytes = vlqBytes(id);
|
|
694
|
+
const lenBytes = vlqBytes(payload.length);
|
|
695
|
+
const header = Buffer.alloc(idBytes.length + lenBytes.length);
|
|
696
|
+
idBytes.forEach((b, i) => header[i] = b);
|
|
697
|
+
lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
|
|
698
|
+
return Buffer.concat([header, payload]);
|
|
699
|
+
}
|
|
700
|
+
function readUtf16(buf, start, charCount) {
|
|
701
|
+
const end = start + charCount * 2;
|
|
702
|
+
if (end > buf.length) return "";
|
|
703
|
+
return buf.toString("utf16le", start, end);
|
|
704
|
+
}
|
|
705
|
+
function parseSharedStringsBin(buf) {
|
|
706
|
+
const values = [];
|
|
707
|
+
let total = 0;
|
|
708
|
+
let unique = 0;
|
|
709
|
+
let endSstOffset = buf.length;
|
|
710
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
614
711
|
let pos = 0;
|
|
615
|
-
while (
|
|
616
|
-
|
|
617
|
-
if (
|
|
618
|
-
|
|
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;
|
|
712
|
+
while (readRecord(buf, pos, rec)) {
|
|
713
|
+
pos = rec.dataEnd;
|
|
714
|
+
if (rec.id === 159) {
|
|
715
|
+
total = buf.readUInt32LE(rec.dataStart);
|
|
716
|
+
unique = buf.readUInt32LE(rec.dataStart + 4);
|
|
717
|
+
} else if (rec.id === 19) {
|
|
718
|
+
const cch = buf.readUInt32LE(rec.dataStart + 1);
|
|
719
|
+
values.push(readUtf16(buf, rec.dataStart + 5, cch));
|
|
720
|
+
} else if (rec.id === 160) {
|
|
721
|
+
endSstOffset = rec.headerStart;
|
|
631
722
|
}
|
|
632
|
-
result.push(unescapeXml(val));
|
|
633
|
-
pos = siEnd + 5;
|
|
634
723
|
}
|
|
635
|
-
return
|
|
724
|
+
return { values, total, unique, endSstOffset };
|
|
636
725
|
}
|
|
637
726
|
|
|
727
|
+
// src/XlsbReader.ts
|
|
728
|
+
var XlsbReader = class extends ExcelReaderAbstract {
|
|
729
|
+
constructor() {
|
|
730
|
+
super();
|
|
731
|
+
this.zip = null;
|
|
732
|
+
this.sharedStrings = [];
|
|
733
|
+
this.sheetNames = [];
|
|
734
|
+
this.sheets = [];
|
|
735
|
+
this._currentSheetIndex = -1;
|
|
736
|
+
this._reader = null;
|
|
737
|
+
this._currentRow = [];
|
|
738
|
+
this._pendingRowIndex = -1;
|
|
739
|
+
this._nextExpectedRow = 0;
|
|
740
|
+
this._eof = false;
|
|
741
|
+
this.xfIdToNumFmtId = [];
|
|
742
|
+
this.customDateFormats = null;
|
|
743
|
+
}
|
|
744
|
+
async open(path9, readSharedStrings = true) {
|
|
745
|
+
await this.close();
|
|
746
|
+
this.sheetNames = [];
|
|
747
|
+
this.sheets = [];
|
|
748
|
+
this.sharedStrings = [];
|
|
749
|
+
this.xfIdToNumFmtId = [];
|
|
750
|
+
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
751
|
+
this.fieldCount = 0;
|
|
752
|
+
this.resultsCount = 0;
|
|
753
|
+
this.actualSheetName = "";
|
|
754
|
+
this._currentSheetIndex = -1;
|
|
755
|
+
this._currentRow = [];
|
|
756
|
+
this._pendingRowIndex = -1;
|
|
757
|
+
this._nextExpectedRow = 0;
|
|
758
|
+
this._eof = false;
|
|
759
|
+
this._reader = null;
|
|
760
|
+
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
761
|
+
this.zip = new AdmZip(path9);
|
|
762
|
+
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
763
|
+
if (wbEntry) {
|
|
764
|
+
const buf = wbEntry.getData();
|
|
765
|
+
const reader = new BiffReaderWriter(buf);
|
|
766
|
+
while (reader.readWorkbook()) {
|
|
767
|
+
if (reader._isSheet) {
|
|
768
|
+
const name = reader._workbookName;
|
|
769
|
+
const rId = reader._recId;
|
|
770
|
+
this.sheetNames.push(name);
|
|
771
|
+
this.sheets.push({ name, rId, path: null });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
|
|
776
|
+
if (relsEntry) {
|
|
777
|
+
const xml = relsEntry.getData().toString("utf8");
|
|
778
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
779
|
+
let tagMatch;
|
|
780
|
+
const rIdToTarget = {};
|
|
781
|
+
while ((tagMatch = relTagRe.exec(xml)) !== null) {
|
|
782
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
783
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
784
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
785
|
+
if (id !== void 0 && target !== void 0) {
|
|
786
|
+
rIdToTarget[id] = target;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
for (const sheet of this.sheets) {
|
|
790
|
+
const target = rIdToTarget[sheet.rId];
|
|
791
|
+
if (target) {
|
|
792
|
+
sheet.path = resolveRelTarget(target);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
try {
|
|
797
|
+
const raw = wbEntry ? wbEntry.getData() : null;
|
|
798
|
+
if (raw) {
|
|
799
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
800
|
+
let pos = 0;
|
|
801
|
+
while (readRecord(raw, pos, rec)) {
|
|
802
|
+
if (rec.id === 153 && rec.len >= 4) {
|
|
803
|
+
const is1904 = (raw[rec.dataStart] & 1) !== 0;
|
|
804
|
+
this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
pos = rec.dataEnd;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
812
|
+
if (readSharedStrings) {
|
|
813
|
+
const ssPath = "xl/sharedStrings.bin";
|
|
814
|
+
if (this.zip.getEntry(ssPath)) {
|
|
815
|
+
this._readSharedStrings(ssPath);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const stylesPath = "xl/styles.bin";
|
|
819
|
+
if (this.zip.getEntry(stylesPath)) {
|
|
820
|
+
this._readStyles(stylesPath);
|
|
821
|
+
}
|
|
822
|
+
this.resultsCount = this.sheets.length;
|
|
823
|
+
this._currentSheetIndex = -1;
|
|
824
|
+
}
|
|
825
|
+
_readSharedStrings(path9) {
|
|
826
|
+
const entry = this.zip.getEntry(path9);
|
|
827
|
+
if (!entry) return;
|
|
828
|
+
const reader = new BiffReaderWriter(entry.getData());
|
|
829
|
+
while (reader.readSharedStrings()) {
|
|
830
|
+
if (reader._sharedStringValue !== null) {
|
|
831
|
+
this.sharedStrings.push(reader._sharedStringValue);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
_readStyles(path9) {
|
|
836
|
+
const entry = this.zip.getEntry(path9);
|
|
837
|
+
if (!entry) return;
|
|
838
|
+
const reader = new BiffReaderWriter(entry.getData());
|
|
839
|
+
while (reader.readStyles()) {
|
|
840
|
+
}
|
|
841
|
+
this.xfIdToNumFmtId = reader._xfIndexToNumFmtId;
|
|
842
|
+
this.customDateFormats = reader._customNumFmts;
|
|
843
|
+
}
|
|
844
|
+
getSheetNames() {
|
|
845
|
+
return this.sheetNames;
|
|
846
|
+
}
|
|
847
|
+
async selectSheet(nameOrIndex) {
|
|
848
|
+
let index = -1;
|
|
849
|
+
if (typeof nameOrIndex === "number") {
|
|
850
|
+
index = nameOrIndex;
|
|
851
|
+
} else {
|
|
852
|
+
index = this.sheetNames.indexOf(nameOrIndex);
|
|
853
|
+
if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
|
|
854
|
+
}
|
|
855
|
+
if (index < 0 || index >= this.sheets.length) {
|
|
856
|
+
throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
|
|
857
|
+
}
|
|
858
|
+
this._currentSheetIndex = index;
|
|
859
|
+
if (!this._initSheet(index)) {
|
|
860
|
+
throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[index]}'.`);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async nextResult() {
|
|
864
|
+
const next = this._currentSheetIndex + 1;
|
|
865
|
+
if (next >= this.sheets.length) return false;
|
|
866
|
+
this._currentSheetIndex = next;
|
|
867
|
+
if (!this._initSheet(next)) {
|
|
868
|
+
throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[next]}'.`);
|
|
869
|
+
}
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
async read() {
|
|
873
|
+
if (this._currentSheetIndex === -1) {
|
|
874
|
+
this._currentSheetIndex = 0;
|
|
875
|
+
if (!this._initSheet(0)) return false;
|
|
876
|
+
}
|
|
877
|
+
if (this._eof) return false;
|
|
878
|
+
if (this._pendingRowIndex > this._nextExpectedRow && this._pendingRowIndex < MAX_ROWS) {
|
|
879
|
+
this._currentRow = [];
|
|
880
|
+
this._nextExpectedRow++;
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
this._currentRow = [];
|
|
884
|
+
while (true) {
|
|
885
|
+
const hasRecord = this._reader.readWorksheet();
|
|
886
|
+
if (!hasRecord) {
|
|
887
|
+
this._eof = true;
|
|
888
|
+
this._nextExpectedRow = this._pendingRowIndex + 1;
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
|
|
892
|
+
const completed = this._pendingRowIndex;
|
|
893
|
+
this._pendingRowIndex = this._reader._rowIndex;
|
|
894
|
+
this._nextExpectedRow = completed + 1;
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
if (this._reader._readCell) {
|
|
898
|
+
const col = this._reader._columnNum;
|
|
899
|
+
let val = null;
|
|
900
|
+
switch (this._reader._cellType) {
|
|
901
|
+
case 2:
|
|
902
|
+
val = this.sharedStrings[this._reader._intValue];
|
|
903
|
+
break;
|
|
904
|
+
case 3: {
|
|
905
|
+
val = this._reader._doubleVal;
|
|
906
|
+
const xfIndex = this._reader._xfIndex;
|
|
907
|
+
let numFmtId = 0;
|
|
908
|
+
if (this.xfIdToNumFmtId && xfIndex < this.xfIdToNumFmtId.length) {
|
|
909
|
+
numFmtId = this.xfIdToNumFmtId[xfIndex];
|
|
910
|
+
}
|
|
911
|
+
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
|
|
912
|
+
if (isDate) {
|
|
913
|
+
try {
|
|
914
|
+
val = this.getDateTimeFromOaDate(val);
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
case 4:
|
|
921
|
+
val = this._reader._boolValue;
|
|
922
|
+
break;
|
|
923
|
+
case 5:
|
|
924
|
+
val = this._reader._stringValue;
|
|
925
|
+
break;
|
|
926
|
+
default:
|
|
927
|
+
val = null;
|
|
928
|
+
}
|
|
929
|
+
this._currentRow[col] = val;
|
|
930
|
+
if (col >= this.fieldCount) this.fieldCount = col + 1;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
_initSheet(index) {
|
|
935
|
+
if (index < 0 || index >= this.sheets.length) return false;
|
|
936
|
+
const sheet = this.sheets[index];
|
|
937
|
+
const entry = sheet.path ? this.zip.getEntry(sheet.path) : void 0;
|
|
938
|
+
if (!entry) return false;
|
|
939
|
+
this._reader = new BiffReaderWriter(entry.getData());
|
|
940
|
+
this._eof = false;
|
|
941
|
+
this._pendingRowIndex = -1;
|
|
942
|
+
this._nextExpectedRow = 0;
|
|
943
|
+
this._currentRow = [];
|
|
944
|
+
this.fieldCount = 0;
|
|
945
|
+
this.actualSheetName = sheet.name;
|
|
946
|
+
while (this._reader.readWorksheet()) {
|
|
947
|
+
if (this._reader._rowIndex !== -1) {
|
|
948
|
+
this._pendingRowIndex = this._reader._rowIndex;
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
this._eof = true;
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
getValue(i) {
|
|
956
|
+
if (i < 0 || i >= this._currentRow.length) return null;
|
|
957
|
+
return this._currentRow[i];
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
638
961
|
// src/XlsxReader.ts
|
|
962
|
+
import yauzl from "yauzl";
|
|
963
|
+
function parseIsoDateUtc(value) {
|
|
964
|
+
let text = value.trim().replace(" ", "T");
|
|
965
|
+
if (!text) return null;
|
|
966
|
+
if (!/z$|[+-]\d{2}:?\d{2}$/i.test(text) && text.includes("T")) {
|
|
967
|
+
text += "Z";
|
|
968
|
+
}
|
|
969
|
+
const date = new Date(text);
|
|
970
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
971
|
+
}
|
|
639
972
|
var XlsxReader = class extends ExcelReaderAbstract {
|
|
640
973
|
constructor() {
|
|
641
974
|
super();
|
|
@@ -647,13 +980,34 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
647
980
|
this._currentSheetIndex = -1;
|
|
648
981
|
this._sheetXml = null;
|
|
649
982
|
this._xmlPos = 0;
|
|
983
|
+
/** End of the sheetData element; row tags must not be searched past it. */
|
|
984
|
+
this._xmlEnd = 0;
|
|
650
985
|
this._currentRow = [];
|
|
986
|
+
this._expectedRowNum = 1;
|
|
987
|
+
this._pendingEmptyCount = 0;
|
|
651
988
|
this.xfIdToNumFmtId = [];
|
|
652
989
|
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
653
990
|
}
|
|
654
|
-
async open(
|
|
655
|
-
|
|
656
|
-
|
|
991
|
+
async open(path9, readSharedStrings = true) {
|
|
992
|
+
await this.close();
|
|
993
|
+
this.sheetNames = [];
|
|
994
|
+
this.sheets = [];
|
|
995
|
+
this.sharedStrings = [];
|
|
996
|
+
this.xfIdToNumFmtId = [];
|
|
997
|
+
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
998
|
+
this.fieldCount = 0;
|
|
999
|
+
this.resultsCount = 0;
|
|
1000
|
+
this.actualSheetName = "";
|
|
1001
|
+
this._currentSheetIndex = -1;
|
|
1002
|
+
this._sheetXml = null;
|
|
1003
|
+
this._xmlPos = 0;
|
|
1004
|
+
this._xmlEnd = 0;
|
|
1005
|
+
this._currentRow = [];
|
|
1006
|
+
this._expectedRowNum = 1;
|
|
1007
|
+
this._pendingEmptyCount = 0;
|
|
1008
|
+
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
1009
|
+
return new Promise((resolve6, reject) => {
|
|
1010
|
+
yauzl.open(path9, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
|
|
657
1011
|
if (err) return reject(err);
|
|
658
1012
|
this.zipfile = zipfile;
|
|
659
1013
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -666,28 +1020,40 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
666
1020
|
const wbRelsContent = await this._readZipEntryContent("xl/_rels/workbook.xml.rels");
|
|
667
1021
|
const rIdToTarget = {};
|
|
668
1022
|
if (wbRelsContent) {
|
|
669
|
-
const
|
|
670
|
-
let
|
|
671
|
-
while ((
|
|
672
|
-
|
|
1023
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
1024
|
+
let tagMatch;
|
|
1025
|
+
while ((tagMatch = relTagRe.exec(wbRelsContent)) !== null) {
|
|
1026
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
1027
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
1028
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
1029
|
+
if (id !== void 0 && target !== void 0) {
|
|
1030
|
+
rIdToTarget[id] = target;
|
|
1031
|
+
}
|
|
673
1032
|
}
|
|
674
1033
|
}
|
|
675
1034
|
const wbContent = await this._readZipEntryContent("xl/workbook.xml");
|
|
676
1035
|
if (wbContent) {
|
|
677
|
-
const
|
|
678
|
-
let
|
|
679
|
-
while ((
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
const
|
|
1036
|
+
const sheetTagRe = /<sheet\b[^>]*>/g;
|
|
1037
|
+
let tagMatch;
|
|
1038
|
+
while ((tagMatch = sheetTagRe.exec(wbContent)) !== null) {
|
|
1039
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
1040
|
+
const rawName = attrs["name"];
|
|
1041
|
+
const sheetId = attrs["sheetId"] ?? "";
|
|
1042
|
+
const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
|
|
1043
|
+
if (rawName === void 0 || rId === void 0) continue;
|
|
1044
|
+
const name = unescapeXml(rawName);
|
|
683
1045
|
const target = rIdToTarget[rId];
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
fullPath = "xl/" + fullPath;
|
|
687
|
-
}
|
|
1046
|
+
if (!target) continue;
|
|
1047
|
+
const fullPath = resolveRelTarget(target);
|
|
688
1048
|
this.sheetNames.push(name);
|
|
689
1049
|
this.sheets.push({ name, sheetId, rId, path: fullPath });
|
|
690
1050
|
}
|
|
1051
|
+
const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbContent);
|
|
1052
|
+
if (wbPrMatch) {
|
|
1053
|
+
const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
|
|
1054
|
+
const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
|
|
1055
|
+
this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
1056
|
+
}
|
|
691
1057
|
}
|
|
692
1058
|
if (readSharedStrings) {
|
|
693
1059
|
const ssContent = await this._readZipEntryContent("xl/sharedStrings.xml");
|
|
@@ -701,7 +1067,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
701
1067
|
}
|
|
702
1068
|
this.resultsCount = this.sheets.length;
|
|
703
1069
|
this._currentSheetIndex = -1;
|
|
704
|
-
|
|
1070
|
+
resolve6();
|
|
705
1071
|
} catch (e) {
|
|
706
1072
|
reject(e);
|
|
707
1073
|
}
|
|
@@ -715,16 +1081,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
715
1081
|
this.zipfile.close();
|
|
716
1082
|
}
|
|
717
1083
|
}
|
|
718
|
-
async _readZipEntryContent(
|
|
719
|
-
const entry = this.entries.get(
|
|
1084
|
+
async _readZipEntryContent(path9) {
|
|
1085
|
+
const entry = this.entries.get(path9);
|
|
720
1086
|
if (!entry) return null;
|
|
721
|
-
return new Promise((
|
|
1087
|
+
return new Promise((resolve6, reject) => {
|
|
722
1088
|
this.zipfile.openReadStream(entry, (err, readStream) => {
|
|
723
1089
|
if (err) return reject(err);
|
|
724
1090
|
const chunks = [];
|
|
725
1091
|
readStream.on("data", (chunk) => chunks.push(chunk));
|
|
726
1092
|
readStream.on("end", () => {
|
|
727
|
-
|
|
1093
|
+
resolve6(Buffer.concat(chunks).toString("utf8"));
|
|
728
1094
|
});
|
|
729
1095
|
readStream.on("error", reject);
|
|
730
1096
|
});
|
|
@@ -772,6 +1138,27 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
772
1138
|
return false;
|
|
773
1139
|
}
|
|
774
1140
|
}
|
|
1141
|
+
async selectSheet(nameOrIndex) {
|
|
1142
|
+
let index = -1;
|
|
1143
|
+
if (typeof nameOrIndex === "number") {
|
|
1144
|
+
index = nameOrIndex;
|
|
1145
|
+
} else {
|
|
1146
|
+
index = this.sheetNames.indexOf(nameOrIndex);
|
|
1147
|
+
if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
|
|
1148
|
+
}
|
|
1149
|
+
if (index < 0 || index >= this.sheets.length) {
|
|
1150
|
+
throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
|
|
1151
|
+
}
|
|
1152
|
+
this._currentSheetIndex = index;
|
|
1153
|
+
await this._initSheet(index);
|
|
1154
|
+
}
|
|
1155
|
+
async nextResult() {
|
|
1156
|
+
const next = this._currentSheetIndex + 1;
|
|
1157
|
+
if (next >= this.sheets.length) return false;
|
|
1158
|
+
this._currentSheetIndex = next;
|
|
1159
|
+
await this._initSheet(next);
|
|
1160
|
+
return true;
|
|
1161
|
+
}
|
|
775
1162
|
async _initSheet(index) {
|
|
776
1163
|
if (index >= this.sheets.length) return;
|
|
777
1164
|
const sheet = this.sheets[index];
|
|
@@ -781,23 +1168,106 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
781
1168
|
this._sheetXml = "";
|
|
782
1169
|
}
|
|
783
1170
|
this._xmlPos = 0;
|
|
1171
|
+
this._xmlEnd = this._sheetXml.length;
|
|
1172
|
+
this._expectedRowNum = 1;
|
|
1173
|
+
this._pendingEmptyCount = 0;
|
|
1174
|
+
this._currentRow = [];
|
|
1175
|
+
this.fieldCount = 0;
|
|
784
1176
|
const sd = this._sheetXml.indexOf("<sheetData>");
|
|
785
|
-
if (sd !== -1)
|
|
1177
|
+
if (sd !== -1) {
|
|
1178
|
+
this._xmlPos = sd + 11;
|
|
1179
|
+
const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
|
|
1180
|
+
if (se !== -1) this._xmlEnd = se;
|
|
1181
|
+
} else {
|
|
1182
|
+
const m = /<(?:[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(this._sheetXml);
|
|
1183
|
+
if (m && m.index !== void 0) {
|
|
1184
|
+
this._xmlPos = m.index + m[0].length;
|
|
1185
|
+
if (/\/>\s*$/.test(m[0])) {
|
|
1186
|
+
this._xmlEnd = this._xmlPos;
|
|
1187
|
+
} else {
|
|
1188
|
+
const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
|
|
1189
|
+
if (se !== -1) this._xmlEnd = se;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
786
1193
|
}
|
|
787
1194
|
_readNextRow() {
|
|
788
1195
|
if (!this._sheetXml) return false;
|
|
789
|
-
|
|
1196
|
+
if (this._pendingEmptyCount > 0) {
|
|
1197
|
+
this._pendingEmptyCount--;
|
|
1198
|
+
this._currentRow = [];
|
|
1199
|
+
this._expectedRowNum++;
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
1202
|
+
const rowStart = this._findRowTagStart(this._xmlPos);
|
|
790
1203
|
if (rowStart === -1) return false;
|
|
1204
|
+
const rowTagEnd = this._sheetXml.indexOf(">", rowStart);
|
|
1205
|
+
if (rowTagEnd === -1) return false;
|
|
1206
|
+
const rowTag = this._sheetXml.substring(rowStart, rowTagEnd + 1);
|
|
1207
|
+
const isSelfClosing = this._sheetXml.charCodeAt(rowTagEnd - 1) === 47;
|
|
1208
|
+
const rowAttrs = parseTagAttributes(rowTag);
|
|
1209
|
+
const rRaw = rowAttrs["r"];
|
|
1210
|
+
let rowNum = this._expectedRowNum;
|
|
1211
|
+
if (rRaw !== void 0) {
|
|
1212
|
+
const parsed = parseInt(rRaw, 10);
|
|
1213
|
+
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= MAX_ROWS) rowNum = parsed;
|
|
1214
|
+
}
|
|
1215
|
+
if (rowNum > this._expectedRowNum) {
|
|
1216
|
+
const gap = rowNum - this._expectedRowNum;
|
|
1217
|
+
this._pendingEmptyCount = gap - 1;
|
|
1218
|
+
this._currentRow = [];
|
|
1219
|
+
this._expectedRowNum++;
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
if (rowNum < this._expectedRowNum) {
|
|
1223
|
+
if (isSelfClosing) {
|
|
1224
|
+
this._xmlPos = rowTagEnd + 1;
|
|
1225
|
+
this._currentRow = [];
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
const rowEnd2 = this._sheetXml.indexOf("</row>", rowStart);
|
|
1229
|
+
if (rowEnd2 === -1) return false;
|
|
1230
|
+
this._xmlPos = rowEnd2 + 6;
|
|
1231
|
+
this._parseRowByIndex(rowStart, rowEnd2);
|
|
1232
|
+
this._expectedRowNum = Math.max(this._expectedRowNum + 1, rowNum + 1);
|
|
1233
|
+
return true;
|
|
1234
|
+
}
|
|
1235
|
+
if (isSelfClosing) {
|
|
1236
|
+
this._xmlPos = rowTagEnd + 1;
|
|
1237
|
+
this._currentRow = [];
|
|
1238
|
+
this._expectedRowNum = rowNum + 1;
|
|
1239
|
+
return true;
|
|
1240
|
+
}
|
|
791
1241
|
const rowEnd = this._sheetXml.indexOf("</row>", rowStart);
|
|
792
1242
|
if (rowEnd === -1) return false;
|
|
793
1243
|
this._xmlPos = rowEnd + 6;
|
|
794
1244
|
this._parseRowByIndex(rowStart, rowEnd);
|
|
1245
|
+
this._expectedRowNum = rowNum + 1;
|
|
795
1246
|
return true;
|
|
796
1247
|
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Find the next `<row …>` start, ignoring similarly named elements such as
|
|
1250
|
+
* `<rowBreaks>` that follow `</sheetData>`.
|
|
1251
|
+
*/
|
|
1252
|
+
_findRowTagStart(from) {
|
|
1253
|
+
const xml = this._sheetXml;
|
|
1254
|
+
let pos = from;
|
|
1255
|
+
while (pos < this._xmlEnd) {
|
|
1256
|
+
const idx = xml.indexOf("<row", pos);
|
|
1257
|
+
if (idx === -1 || idx >= this._xmlEnd) return -1;
|
|
1258
|
+
const next = xml.charCodeAt(idx + 4);
|
|
1259
|
+
if (next === 62 || next === 47 || next === 32 || next === 9 || next === 10 || next === 13) {
|
|
1260
|
+
return idx;
|
|
1261
|
+
}
|
|
1262
|
+
pos = idx + 4;
|
|
1263
|
+
}
|
|
1264
|
+
return -1;
|
|
1265
|
+
}
|
|
797
1266
|
_parseRowByIndex(rowStart, rowEnd) {
|
|
798
1267
|
this._currentRow = [];
|
|
799
1268
|
const xml = this._sheetXml;
|
|
800
1269
|
let pos = xml.indexOf(">", rowStart) + 1;
|
|
1270
|
+
let nextCol = 0;
|
|
801
1271
|
while (pos < rowEnd) {
|
|
802
1272
|
const cStart = xml.indexOf("<c", pos);
|
|
803
1273
|
if (cStart === -1 || cStart >= rowEnd) break;
|
|
@@ -831,6 +1301,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
831
1301
|
}
|
|
832
1302
|
}
|
|
833
1303
|
let val = null;
|
|
1304
|
+
let inlineVals = null;
|
|
834
1305
|
if (xml.charCodeAt(nextTagClose - 1) === 47) {
|
|
835
1306
|
pos = nextTagClose + 1;
|
|
836
1307
|
} else {
|
|
@@ -845,20 +1316,28 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
845
1316
|
}
|
|
846
1317
|
} else {
|
|
847
1318
|
if (type === "inlineStr") {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1319
|
+
inlineVals = [];
|
|
1320
|
+
let tSearch = nextTagClose;
|
|
1321
|
+
while (true) {
|
|
1322
|
+
const tStart = xml.indexOf("<t", tSearch);
|
|
1323
|
+
if (tStart === -1 || tStart >= cellContentEnd) break;
|
|
1324
|
+
const tClose = xml.indexOf(">", tStart);
|
|
1325
|
+
if (tClose === -1 || tClose >= cellContentEnd) break;
|
|
1326
|
+
const tContentStart = tClose + 1;
|
|
851
1327
|
const tEnd = xml.indexOf("</t>", tContentStart);
|
|
852
|
-
if (tEnd
|
|
853
|
-
|
|
854
|
-
|
|
1328
|
+
if (tEnd === -1 || tEnd > cellContentEnd) break;
|
|
1329
|
+
inlineVals.push(xml.substring(tContentStart, tEnd));
|
|
1330
|
+
tSearch = tEnd + 4;
|
|
855
1331
|
}
|
|
1332
|
+
if (inlineVals.length === 0) inlineVals = null;
|
|
856
1333
|
}
|
|
857
1334
|
}
|
|
858
1335
|
pos = cellContentEnd + 4;
|
|
859
1336
|
}
|
|
860
1337
|
let finalVal = null;
|
|
861
|
-
if (
|
|
1338
|
+
if (inlineVals !== null) {
|
|
1339
|
+
finalVal = unescapeXml(inlineVals.join(""));
|
|
1340
|
+
} else if (val !== null) {
|
|
862
1341
|
if (type === "s") {
|
|
863
1342
|
const idx = parseInt(val, 10);
|
|
864
1343
|
finalVal = this.sharedStrings[idx];
|
|
@@ -866,148 +1345,66 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
866
1345
|
finalVal = val === "1" || val === "true";
|
|
867
1346
|
} else if (type === "inlineStr") {
|
|
868
1347
|
finalVal = unescapeXml(val);
|
|
1348
|
+
} else if (type === "str") {
|
|
1349
|
+
finalVal = unescapeXml(val);
|
|
1350
|
+
} else if (type === "e") {
|
|
1351
|
+
finalVal = unescapeXml(val);
|
|
1352
|
+
} else if (type === "d") {
|
|
1353
|
+
finalVal = parseIsoDateUtc(val) ?? unescapeXml(val);
|
|
869
1354
|
} else if (type === "n" || type === "") {
|
|
870
1355
|
finalVal = parseFloat(val);
|
|
871
1356
|
if (styleIndex > 0 && this.xfIdToNumFmtId) {
|
|
872
1357
|
let numFmtId = 0;
|
|
873
|
-
if (styleIndex < this.xfIdToNumFmtId.length) {
|
|
874
|
-
numFmtId = this.xfIdToNumFmtId[styleIndex];
|
|
875
|
-
}
|
|
876
|
-
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
|
|
877
|
-
if (isDate) {
|
|
878
|
-
try {
|
|
879
|
-
finalVal = this.getDateTimeFromOaDate(finalVal);
|
|
880
|
-
} catch {
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
if (colIndex
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
getValue(i) {
|
|
893
|
-
if (i < 0 || i >= this._currentRow.length) return null;
|
|
894
|
-
return this._currentRow[i];
|
|
895
|
-
}
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
// src/ReaderFactory.ts
|
|
899
|
-
import * as path from "path";
|
|
900
|
-
var ReaderFactory = class {
|
|
901
|
-
/**
|
|
902
|
-
* @param filePath Path ending in `.xlsb` or `.xlsx`
|
|
903
|
-
* @throws if the extension is unsupported
|
|
904
|
-
*/
|
|
905
|
-
static create(filePath) {
|
|
906
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
907
|
-
if (ext === ".xlsx") {
|
|
908
|
-
return new XlsxReader();
|
|
909
|
-
} else if (ext === ".xlsb") {
|
|
910
|
-
return new XlsbReader();
|
|
911
|
-
} else {
|
|
912
|
-
throw new Error(`Unsupported extension: ${ext}`);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
// src/XlsbWriter.ts
|
|
918
|
-
import * as fs from "fs";
|
|
919
|
-
import archiver from "archiver";
|
|
920
|
-
import { Readable } from "stream";
|
|
921
|
-
|
|
922
|
-
// src/Formats.ts
|
|
923
|
-
var F = {
|
|
924
|
-
THOUSANDS_SEP: "#,##0",
|
|
925
|
-
CURRENCY_PLN: '#,##0.00 "z\u0142"',
|
|
926
|
-
CURRENCY_EUR: "#,##0.00 \u20AC",
|
|
927
|
-
PERCENTAGE: "0%",
|
|
928
|
-
SCIENTIFIC: "0.00E+00",
|
|
929
|
-
TWO_DECIMALS: "#,##0.00",
|
|
930
|
-
TEXT: "@",
|
|
931
|
-
LEADING_ZEROS: "000000000",
|
|
932
|
-
DATE_SHORT: "dd.mm.yyyy",
|
|
933
|
-
DATE_LONG: "d mmmm yyyy",
|
|
934
|
-
DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
|
|
935
|
-
DATE_ISO: "yyyy-mm-dd",
|
|
936
|
-
DATE_MONTH_YEAR: "mmmm yyyy",
|
|
937
|
-
DATE_WEEKDAY: "dddd, d mmmm yyyy",
|
|
938
|
-
DATE_DAY_MONTH: "d mmmm",
|
|
939
|
-
DATE_YEAR_ONLY: "yyyy",
|
|
940
|
-
DATETIME_SHORT: "dd.mm.yyyy hh:mm",
|
|
941
|
-
DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
|
|
942
|
-
TIME_HH_MM: "hh:mm",
|
|
943
|
-
TIME_HH_MM_SS: "hh:mm:ss",
|
|
944
|
-
TIME_12H: "h:mm AM/PM",
|
|
945
|
-
DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
|
|
946
|
-
DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
|
|
947
|
-
TIME_MS: "hh:mm:ss.000"
|
|
948
|
-
};
|
|
949
|
-
function isFormattedCell(val) {
|
|
950
|
-
return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
|
|
951
|
-
}
|
|
952
|
-
function unwrapCell(val) {
|
|
953
|
-
if (isFormattedCell(val)) {
|
|
954
|
-
return val.value;
|
|
955
|
-
}
|
|
956
|
-
return val;
|
|
957
|
-
}
|
|
958
|
-
function getFormat(val) {
|
|
959
|
-
if (isFormattedCell(val)) {
|
|
960
|
-
return val.format;
|
|
961
|
-
}
|
|
962
|
-
return null;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// src/writerHelpers.ts
|
|
966
|
-
var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
|
|
967
|
-
function sanitizeSheetName(name, sheetCount) {
|
|
968
|
-
if (!name || typeof name !== "string") {
|
|
969
|
-
return `Sheet${sheetCount + 1}`;
|
|
970
|
-
}
|
|
971
|
-
let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
|
|
972
|
-
if (sanitized.length > 31) {
|
|
973
|
-
sanitized = sanitized.substring(0, 31);
|
|
974
|
-
}
|
|
975
|
-
if (sanitized.trim().length === 0) {
|
|
976
|
-
sanitized = `Sheet${sheetCount + 1}`;
|
|
977
|
-
}
|
|
978
|
-
return sanitized;
|
|
979
|
-
}
|
|
980
|
-
function initColWidths(columnCount) {
|
|
981
|
-
return new Array(columnCount).fill(-1);
|
|
982
|
-
}
|
|
983
|
-
function applyHeaderWidths(colWidths, headers, columnCount) {
|
|
984
|
-
for (let i = 0; i < columnCount; i++) {
|
|
985
|
-
const len = headers[i] ? headers[i].length + 1 : 0;
|
|
986
|
-
let width = 1.25 * len + 2;
|
|
987
|
-
if (width > 80) width = 80;
|
|
988
|
-
if (colWidths[i] < width) colWidths[i] = width;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
|
|
992
|
-
for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
|
|
993
|
-
const row = rows[r];
|
|
994
|
-
for (let c = 0; c < row.length; c++) {
|
|
995
|
-
const raw = row[c];
|
|
996
|
-
if (raw === null || raw === void 0) continue;
|
|
997
|
-
const val = isFormattedCell(raw) ? raw.value : raw;
|
|
998
|
-
const len = val instanceof Date ? 20 : String(val).length + 1;
|
|
999
|
-
let width = 1.25 * len + 2;
|
|
1000
|
-
if (width > 80) width = 80;
|
|
1001
|
-
if (colWidths[c] < width) colWidths[c] = width;
|
|
1358
|
+
if (styleIndex < this.xfIdToNumFmtId.length) {
|
|
1359
|
+
numFmtId = this.xfIdToNumFmtId[styleIndex];
|
|
1360
|
+
}
|
|
1361
|
+
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || this.customDateFormats && this.customDateFormats.has(numFmtId);
|
|
1362
|
+
if (isDate) {
|
|
1363
|
+
try {
|
|
1364
|
+
finalVal = this.getDateTimeFromOaDate(finalVal);
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
if (colIndex === -1) colIndex = nextCol;
|
|
1372
|
+
nextCol = colIndex + 1;
|
|
1373
|
+
this._currentRow[colIndex] = finalVal;
|
|
1374
|
+
if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
|
|
1002
1375
|
}
|
|
1003
1376
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
return floor ? Math.floor(width) : width;
|
|
1377
|
+
getValue(i) {
|
|
1378
|
+
if (i < 0 || i >= this._currentRow.length) return null;
|
|
1379
|
+
return this._currentRow[i];
|
|
1008
1380
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// src/ReaderFactory.ts
|
|
1384
|
+
import * as path from "path";
|
|
1385
|
+
var ReaderFactory = class {
|
|
1386
|
+
/**
|
|
1387
|
+
* @param filePath Path ending in `.xlsb` or `.xlsx`
|
|
1388
|
+
* @throws if the extension is unsupported
|
|
1389
|
+
*/
|
|
1390
|
+
static create(filePath) {
|
|
1391
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
1392
|
+
if (ext === ".xlsx") {
|
|
1393
|
+
return new XlsxReader();
|
|
1394
|
+
} else if (ext === ".xlsb") {
|
|
1395
|
+
return new XlsbReader();
|
|
1396
|
+
} else {
|
|
1397
|
+
throw new Error(`Unsupported extension: ${ext}`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
|
|
1402
|
+
// src/XlsbWriter.ts
|
|
1403
|
+
import * as fs from "fs";
|
|
1404
|
+
import * as path2 from "path";
|
|
1405
|
+
import { randomUUID } from "crypto";
|
|
1406
|
+
import archiver from "archiver";
|
|
1407
|
+
import { Readable } from "stream";
|
|
1011
1408
|
|
|
1012
1409
|
// src/StreamingSheetState.ts
|
|
1013
1410
|
var StreamingSheetState = class {
|
|
@@ -1061,6 +1458,8 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
1061
1458
|
* @param filePath Destination `.xlsb` path on disk.
|
|
1062
1459
|
*/
|
|
1063
1460
|
constructor(filePath) {
|
|
1461
|
+
this._outputError = null;
|
|
1462
|
+
this._finalized = false;
|
|
1064
1463
|
this.sheetCount = 0;
|
|
1065
1464
|
this.sheetList = [];
|
|
1066
1465
|
this.sstDic = /* @__PURE__ */ new Map();
|
|
@@ -1071,8 +1470,20 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
1071
1470
|
this._nextXfIdx = 4;
|
|
1072
1471
|
this._stFmtRecords = /* @__PURE__ */ new Map();
|
|
1073
1472
|
this.stream = new StreamingSheetState();
|
|
1074
|
-
this.
|
|
1473
|
+
this._targetPath = path2.resolve(filePath);
|
|
1474
|
+
fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
|
|
1475
|
+
this._tempPath = path2.join(
|
|
1476
|
+
path2.dirname(this._targetPath),
|
|
1477
|
+
`.${path2.basename(this._targetPath)}.${randomUUID()}.tmp`
|
|
1478
|
+
);
|
|
1479
|
+
this.output = fs.createWriteStream(this._tempPath);
|
|
1480
|
+
this.output.on("error", (err) => {
|
|
1481
|
+
this._outputError = err;
|
|
1482
|
+
});
|
|
1075
1483
|
this.archive = archiver("zip");
|
|
1484
|
+
this.archive.on("error", (err) => {
|
|
1485
|
+
this._outputError = err;
|
|
1486
|
+
});
|
|
1076
1487
|
this.archive.pipe(this.output);
|
|
1077
1488
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
1078
1489
|
this._sheet1Bytes = Buffer.from([
|
|
@@ -2942,6 +3353,11 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
2942
3353
|
* @param options Optional settings (hidden, doAutofilter)
|
|
2943
3354
|
*/
|
|
2944
3355
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
3356
|
+
assertColumnCount(columnCount);
|
|
3357
|
+
if (headers !== void 0) {
|
|
3358
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
3359
|
+
for (const h of headers) assertCellTextLength(h);
|
|
3360
|
+
}
|
|
2945
3361
|
const { hidden = false, doAutofilter = true, sampleRows } = options;
|
|
2946
3362
|
this.addSheet(sheetName, hidden);
|
|
2947
3363
|
this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
|
|
@@ -3007,6 +3423,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3007
3423
|
`Row length mismatch. Expected ${this.stream.endCol - this.stream.startCol} columns, got ${row.length}`
|
|
3008
3424
|
);
|
|
3009
3425
|
}
|
|
3426
|
+
assertRowCount(this.stream.rowNum + 1);
|
|
3010
3427
|
this.createRowHeader(bigBuf, this.stream.rowNum, this.stream.startCol, this.stream.endCol);
|
|
3011
3428
|
for (let c = 0; c < row.length; c++) {
|
|
3012
3429
|
const raw = row[c];
|
|
@@ -3029,7 +3446,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3029
3446
|
} else if (typeof val === "boolean") {
|
|
3030
3447
|
this.writeBool(bigBuf, val, c);
|
|
3031
3448
|
} else if (val instanceof Date) {
|
|
3032
|
-
const oaDate = (val.getTime()
|
|
3449
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
3033
3450
|
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
3034
3451
|
} else if (val !== null && val !== void 0) {
|
|
3035
3452
|
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
|
|
@@ -3086,6 +3503,16 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3086
3503
|
} else if (headers) {
|
|
3087
3504
|
columnCount = headers.length;
|
|
3088
3505
|
}
|
|
3506
|
+
if (columnCount > 0) assertColumnCount(columnCount);
|
|
3507
|
+
const totalRowsCheck = rows.length + (headers ? 1 : 0);
|
|
3508
|
+
assertRowCount(totalRowsCheck);
|
|
3509
|
+
if (headers) {
|
|
3510
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
3511
|
+
for (const h of headers) assertCellTextLength(h);
|
|
3512
|
+
}
|
|
3513
|
+
for (let r = 0; r < rows.length; r++) {
|
|
3514
|
+
assertRowFitsColumns("row", rows[r].length, columnCount);
|
|
3515
|
+
}
|
|
3089
3516
|
const colWidths = initColWidths(columnCount);
|
|
3090
3517
|
if (headers) {
|
|
3091
3518
|
applyHeaderWidths(colWidths, headers, columnCount);
|
|
@@ -3161,7 +3588,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3161
3588
|
} else if (typeof val === "boolean") {
|
|
3162
3589
|
this.writeBool(bigBuf, val, c);
|
|
3163
3590
|
} else if (val instanceof Date) {
|
|
3164
|
-
const oaDate = (val.getTime()
|
|
3591
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
3165
3592
|
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
3166
3593
|
} else if (val !== null && val !== void 0) {
|
|
3167
3594
|
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
|
|
@@ -3244,13 +3671,14 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3244
3671
|
bigBuf.writeUnsafeByte(val ? 1 : 0);
|
|
3245
3672
|
}
|
|
3246
3673
|
writeDateTime(bigBuf, date, colNum) {
|
|
3247
|
-
const oaDate = (date.getTime()
|
|
3674
|
+
const oaDate = timeToOaDate(date.getTime(), this._oaEpoch);
|
|
3248
3675
|
this.writeDouble(bigBuf, oaDate, colNum, 1);
|
|
3249
3676
|
}
|
|
3250
3677
|
_getDateWidth(_date) {
|
|
3251
3678
|
return 19;
|
|
3252
3679
|
}
|
|
3253
3680
|
writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
|
|
3681
|
+
assertCellTextLength(val);
|
|
3254
3682
|
let index;
|
|
3255
3683
|
if (this.sstDic.has(val)) {
|
|
3256
3684
|
index = this.sstDic.get(val);
|
|
@@ -3276,8 +3704,8 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3276
3704
|
bigBuf.writeByte(159);
|
|
3277
3705
|
bigBuf.writeByte(1);
|
|
3278
3706
|
bigBuf.writeByte(8);
|
|
3279
|
-
bigBuf.writeInt32LE(this.sstCntUnique);
|
|
3280
3707
|
bigBuf.writeInt32LE(this.sstCntAll);
|
|
3708
|
+
bigBuf.writeInt32LE(this.sstCntUnique);
|
|
3281
3709
|
for (const [txt] of this.sstDic) {
|
|
3282
3710
|
const txtLen = txt.length;
|
|
3283
3711
|
bigBuf.writeByte(19);
|
|
@@ -3330,8 +3758,61 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3330
3758
|
* Must be awaited before the file is fully written.
|
|
3331
3759
|
*/
|
|
3332
3760
|
finalize() {
|
|
3333
|
-
return new Promise((
|
|
3761
|
+
return new Promise((resolve6, reject) => {
|
|
3762
|
+
if (this._finalized) {
|
|
3763
|
+
reject(new Error("XlsbWriter: already finalized"));
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
this._finalized = true;
|
|
3767
|
+
let settled = false;
|
|
3768
|
+
const cleanupTemp = () => {
|
|
3769
|
+
try {
|
|
3770
|
+
if (fs.existsSync(this._tempPath)) fs.rmSync(this._tempPath);
|
|
3771
|
+
} catch {
|
|
3772
|
+
}
|
|
3773
|
+
};
|
|
3774
|
+
const fail = (err) => {
|
|
3775
|
+
if (settled) return;
|
|
3776
|
+
settled = true;
|
|
3777
|
+
try {
|
|
3778
|
+
this.archive.abort();
|
|
3779
|
+
} catch {
|
|
3780
|
+
}
|
|
3781
|
+
try {
|
|
3782
|
+
this.output.destroy();
|
|
3783
|
+
} catch {
|
|
3784
|
+
}
|
|
3785
|
+
cleanupTemp();
|
|
3786
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3787
|
+
};
|
|
3788
|
+
const succeed = () => {
|
|
3789
|
+
if (settled) return;
|
|
3790
|
+
if (this._outputError) {
|
|
3791
|
+
fail(this._outputError);
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
settled = true;
|
|
3795
|
+
try {
|
|
3796
|
+
const fd = fs.openSync(this._tempPath, "r+");
|
|
3797
|
+
try {
|
|
3798
|
+
fs.fsyncSync(fd);
|
|
3799
|
+
} finally {
|
|
3800
|
+
fs.closeSync(fd);
|
|
3801
|
+
}
|
|
3802
|
+
try {
|
|
3803
|
+
const existing = fs.statSync(this._targetPath);
|
|
3804
|
+
fs.chmodSync(this._tempPath, existing.mode & 511);
|
|
3805
|
+
} catch {
|
|
3806
|
+
}
|
|
3807
|
+
fs.renameSync(this._tempPath, this._targetPath);
|
|
3808
|
+
resolve6();
|
|
3809
|
+
} catch (err) {
|
|
3810
|
+
cleanupTemp();
|
|
3811
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3334
3814
|
try {
|
|
3815
|
+
if (this._outputError) throw this._outputError;
|
|
3335
3816
|
this.saveSst();
|
|
3336
3817
|
this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
|
|
3337
3818
|
const wbBuffers = [];
|
|
@@ -3428,11 +3909,12 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3428
3909
|
</Relationships>`;
|
|
3429
3910
|
this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
|
|
3430
3911
|
}
|
|
3431
|
-
this.output.
|
|
3432
|
-
this.
|
|
3912
|
+
this.output.once("close", succeed);
|
|
3913
|
+
this.output.once("error", fail);
|
|
3914
|
+
this.archive.once("error", fail);
|
|
3433
3915
|
this.archive.finalize();
|
|
3434
3916
|
} catch (err) {
|
|
3435
|
-
|
|
3917
|
+
fail(err);
|
|
3436
3918
|
}
|
|
3437
3919
|
});
|
|
3438
3920
|
}
|
|
@@ -3440,27 +3922,17 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3440
3922
|
|
|
3441
3923
|
// src/XlsxWriter.ts
|
|
3442
3924
|
import * as fs2 from "fs";
|
|
3925
|
+
import * as path3 from "path";
|
|
3926
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3443
3927
|
import archiver2 from "archiver";
|
|
3444
3928
|
import { Readable as Readable2 } from "stream";
|
|
3445
|
-
var COLUMN_LETTERS = (() => {
|
|
3446
|
-
const letters = [];
|
|
3447
|
-
for (let i = 65; i < 91; i++) {
|
|
3448
|
-
letters.push(String.fromCharCode(i));
|
|
3449
|
-
}
|
|
3450
|
-
const temp = [];
|
|
3451
|
-
for (const p of letters) {
|
|
3452
|
-
for (let i = 65; i < 91; i++) {
|
|
3453
|
-
temp.push(p + String.fromCharCode(i));
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
letters.push(...temp);
|
|
3457
|
-
return letters;
|
|
3458
|
-
})();
|
|
3459
3929
|
var XlsxWriter = class {
|
|
3460
3930
|
/**
|
|
3461
3931
|
* @param filePath Destination `.xlsx` path on disk.
|
|
3462
3932
|
*/
|
|
3463
3933
|
constructor(filePath) {
|
|
3934
|
+
this._outputError = null;
|
|
3935
|
+
this._finalized = false;
|
|
3464
3936
|
this.sheetCount = 0;
|
|
3465
3937
|
this.sheetList = [];
|
|
3466
3938
|
this.sstArray = [];
|
|
@@ -3474,13 +3946,25 @@ var XlsxWriter = class {
|
|
|
3474
3946
|
this._nextXfIndex = 4;
|
|
3475
3947
|
this.stream = new StreamingSheetState();
|
|
3476
3948
|
this.currentColLetters = [];
|
|
3477
|
-
this.
|
|
3949
|
+
this._targetPath = path3.resolve(filePath);
|
|
3950
|
+
fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
|
|
3951
|
+
this._tempPath = path3.join(
|
|
3952
|
+
path3.dirname(this._targetPath),
|
|
3953
|
+
`.${path3.basename(this._targetPath)}.${randomUUID2()}.tmp`
|
|
3954
|
+
);
|
|
3955
|
+
this.output = fs2.createWriteStream(this._tempPath);
|
|
3956
|
+
this.output.on("error", (err) => {
|
|
3957
|
+
this._outputError = err;
|
|
3958
|
+
});
|
|
3478
3959
|
this.archive = archiver2("zip");
|
|
3960
|
+
this.archive.on("error", (err) => {
|
|
3961
|
+
this._outputError = err;
|
|
3962
|
+
});
|
|
3479
3963
|
this.archive.pipe(this.output);
|
|
3480
3964
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3481
3965
|
}
|
|
3482
3966
|
_getColumnLetter(colIndex) {
|
|
3483
|
-
return colIndex
|
|
3967
|
+
return columnIndexToLetter(colIndex);
|
|
3484
3968
|
}
|
|
3485
3969
|
/** @deprecated Internal; kept for unit-test access. Prefer sanitizeSheetName(). */
|
|
3486
3970
|
_sanitizeSheetName(name) {
|
|
@@ -3532,6 +4016,11 @@ var XlsxWriter = class {
|
|
|
3532
4016
|
* Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
|
|
3533
4017
|
*/
|
|
3534
4018
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
4019
|
+
assertColumnCount(columnCount);
|
|
4020
|
+
if (headers !== void 0) {
|
|
4021
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
4022
|
+
for (const h of headers) assertCellTextLength(h);
|
|
4023
|
+
}
|
|
3535
4024
|
const { hidden = false, doAutofilter = true, sampleRows } = options;
|
|
3536
4025
|
this.addSheet(sheetName, hidden);
|
|
3537
4026
|
this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
|
|
@@ -3588,6 +4077,7 @@ var XlsxWriter = class {
|
|
|
3588
4077
|
);
|
|
3589
4078
|
}
|
|
3590
4079
|
this.stream.rowNum++;
|
|
4080
|
+
assertRowCount(this.stream.rowNum);
|
|
3591
4081
|
bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
|
|
3592
4082
|
for (let c = 0; c < row.length; c++) {
|
|
3593
4083
|
const raw = row[c];
|
|
@@ -3651,6 +4141,16 @@ var XlsxWriter = class {
|
|
|
3651
4141
|
} else if (headers) {
|
|
3652
4142
|
columnCount = headers.length;
|
|
3653
4143
|
}
|
|
4144
|
+
if (columnCount > 0) assertColumnCount(columnCount);
|
|
4145
|
+
const totalRows = rows.length + (headers ? 1 : 0);
|
|
4146
|
+
assertRowCount(totalRows);
|
|
4147
|
+
if (headers) {
|
|
4148
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
4149
|
+
for (const h of headers) assertCellTextLength(h);
|
|
4150
|
+
}
|
|
4151
|
+
for (let r = 0; r < rows.length; r++) {
|
|
4152
|
+
assertRowFitsColumns("row", rows[r].length, columnCount);
|
|
4153
|
+
}
|
|
3654
4154
|
const colWidths = initColWidths(columnCount);
|
|
3655
4155
|
const colLetters = new Array(columnCount);
|
|
3656
4156
|
for (let i = 0; i < columnCount; i++) {
|
|
@@ -3662,7 +4162,6 @@ var XlsxWriter = class {
|
|
|
3662
4162
|
updateColWidthsFromRows(colWidths, rows);
|
|
3663
4163
|
bigBuf.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
|
|
3664
4164
|
bigBuf.writeString('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
|
|
3665
|
-
const totalRows = rows.length + (headers ? 1 : 0);
|
|
3666
4165
|
if (totalRows > 0 && columnCount > 0) {
|
|
3667
4166
|
bigBuf.writeString(`<dimension ref="A1:${colLetters[columnCount - 1]}${totalRows}"/>`);
|
|
3668
4167
|
} else {
|
|
@@ -3748,6 +4247,7 @@ var XlsxWriter = class {
|
|
|
3748
4247
|
});
|
|
3749
4248
|
}
|
|
3750
4249
|
_writeStringCell(bigBuf, val, colRef, rowNum, styleOverride) {
|
|
4250
|
+
assertCellTextLength(val);
|
|
3751
4251
|
let index = this.sstMap.get(val);
|
|
3752
4252
|
if (index === void 0) {
|
|
3753
4253
|
index = this.sstArray.length;
|
|
@@ -3769,29 +4269,79 @@ var XlsxWriter = class {
|
|
|
3769
4269
|
return xfIndex;
|
|
3770
4270
|
}
|
|
3771
4271
|
_toOADate(date) {
|
|
3772
|
-
return (date.getTime()
|
|
4272
|
+
return timeToOaDate(date.getTime(), this._oaEpoch);
|
|
3773
4273
|
}
|
|
3774
4274
|
_getDateWidth(_date) {
|
|
3775
4275
|
return 19;
|
|
3776
4276
|
}
|
|
3777
4277
|
/** Finalize the ZIP package and close the output stream. */
|
|
3778
4278
|
finalize() {
|
|
3779
|
-
return new Promise((
|
|
4279
|
+
return new Promise((resolve6, reject) => {
|
|
4280
|
+
if (this._finalized) {
|
|
4281
|
+
reject(new Error("XlsxWriter: already finalized"));
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
this._finalized = true;
|
|
4285
|
+
let settled = false;
|
|
4286
|
+
const cleanupTemp = () => {
|
|
4287
|
+
try {
|
|
4288
|
+
if (fs2.existsSync(this._tempPath)) fs2.rmSync(this._tempPath);
|
|
4289
|
+
} catch {
|
|
4290
|
+
}
|
|
4291
|
+
};
|
|
4292
|
+
const fail = (err) => {
|
|
4293
|
+
if (settled) return;
|
|
4294
|
+
settled = true;
|
|
4295
|
+
try {
|
|
4296
|
+
this.archive.abort();
|
|
4297
|
+
} catch {
|
|
4298
|
+
}
|
|
4299
|
+
try {
|
|
4300
|
+
this.output.destroy();
|
|
4301
|
+
} catch {
|
|
4302
|
+
}
|
|
4303
|
+
cleanupTemp();
|
|
4304
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4305
|
+
};
|
|
4306
|
+
const succeed = () => {
|
|
4307
|
+
if (settled) return;
|
|
4308
|
+
if (this._outputError) {
|
|
4309
|
+
fail(this._outputError);
|
|
4310
|
+
return;
|
|
4311
|
+
}
|
|
4312
|
+
settled = true;
|
|
4313
|
+
try {
|
|
4314
|
+
const fd = fs2.openSync(this._tempPath, "r+");
|
|
4315
|
+
try {
|
|
4316
|
+
fs2.fsyncSync(fd);
|
|
4317
|
+
} finally {
|
|
4318
|
+
fs2.closeSync(fd);
|
|
4319
|
+
}
|
|
4320
|
+
try {
|
|
4321
|
+
const existing = fs2.statSync(this._targetPath);
|
|
4322
|
+
fs2.chmodSync(this._tempPath, existing.mode & 511);
|
|
4323
|
+
} catch {
|
|
4324
|
+
}
|
|
4325
|
+
fs2.renameSync(this._tempPath, this._targetPath);
|
|
4326
|
+
resolve6();
|
|
4327
|
+
} catch (err) {
|
|
4328
|
+
cleanupTemp();
|
|
4329
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4330
|
+
}
|
|
4331
|
+
};
|
|
3780
4332
|
try {
|
|
4333
|
+
if (this._outputError) throw this._outputError;
|
|
3781
4334
|
this._writeSharedStrings();
|
|
3782
4335
|
this._writeStyles();
|
|
3783
4336
|
this._writeWorkbook();
|
|
3784
4337
|
this._writeContentTypes();
|
|
3785
4338
|
this._writeRels();
|
|
3786
|
-
this.output.
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
this.archive.on("error", (err) => {
|
|
3790
|
-
reject(err);
|
|
3791
|
-
});
|
|
4339
|
+
this.output.once("close", succeed);
|
|
4340
|
+
this.output.once("error", fail);
|
|
4341
|
+
this.archive.once("error", fail);
|
|
3792
4342
|
this.archive.finalize();
|
|
3793
4343
|
} catch (err) {
|
|
3794
|
-
|
|
4344
|
+
fail(err);
|
|
3795
4345
|
}
|
|
3796
4346
|
});
|
|
3797
4347
|
}
|
|
@@ -3871,63 +4421,324 @@ ${xfEntries.join("\n")}
|
|
|
3871
4421
|
const escapedName = this._escapeSheetNameForXml(sheet.name);
|
|
3872
4422
|
xml += `<sheet name="${escapedName}" sheetId="${sheet.sheetId}"${state} r:id="${sheet.rId}"/>`;
|
|
3873
4423
|
}
|
|
3874
|
-
xml += `</sheets>`;
|
|
3875
|
-
if (this._autofilterIsOn) {
|
|
3876
|
-
xml += "<definedNames>";
|
|
3877
|
-
for (const sheet of this.sheetList) {
|
|
3878
|
-
if (sheet.filterHeaderRange) {
|
|
3879
|
-
const localSheetId = sheet.sheetId - 1;
|
|
3880
|
-
const escapedRange = this._escape(sheet.filterHeaderRange);
|
|
3881
|
-
xml += `<definedName name="_xlnm._FilterDatabase" localSheetId="${localSheetId}" hidden="1">${escapedRange}</definedName>`;
|
|
3882
|
-
}
|
|
4424
|
+
xml += `</sheets>`;
|
|
4425
|
+
if (this._autofilterIsOn) {
|
|
4426
|
+
xml += "<definedNames>";
|
|
4427
|
+
for (const sheet of this.sheetList) {
|
|
4428
|
+
if (sheet.filterHeaderRange) {
|
|
4429
|
+
const localSheetId = sheet.sheetId - 1;
|
|
4430
|
+
const escapedRange = this._escape(sheet.filterHeaderRange);
|
|
4431
|
+
xml += `<definedName name="_xlnm._FilterDatabase" localSheetId="${localSheetId}" hidden="1">${escapedRange}</definedName>`;
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
xml += "</definedNames>";
|
|
4435
|
+
}
|
|
4436
|
+
xml += '<calcPr calcId="124519" fullCalcOnLoad="1"/>';
|
|
4437
|
+
xml += `</workbook>`;
|
|
4438
|
+
this.archive.append(xml, { name: "xl/workbook.xml" });
|
|
4439
|
+
}
|
|
4440
|
+
_writeContentTypes() {
|
|
4441
|
+
let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
4442
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
4443
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
4444
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
4445
|
+
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
4446
|
+
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
|
4447
|
+
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>`;
|
|
4448
|
+
for (const sheet of this.sheetList) {
|
|
4449
|
+
xml += `<Override PartName="/${sheet.pathInArchive}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
|
4450
|
+
}
|
|
4451
|
+
xml += `</Types>`;
|
|
4452
|
+
this.archive.append(xml, { name: "[Content_Types].xml" });
|
|
4453
|
+
}
|
|
4454
|
+
_writeRels() {
|
|
4455
|
+
const globalRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
4456
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
4457
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
|
4458
|
+
</Relationships>`;
|
|
4459
|
+
this.archive.append(globalRels, { name: "_rels/.rels" });
|
|
4460
|
+
let wbRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
4461
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`;
|
|
4462
|
+
for (const sheet of this.sheetList) {
|
|
4463
|
+
wbRels += `<Relationship Id="${sheet.rId}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/${sheet.nameInArchive}"/>`;
|
|
4464
|
+
}
|
|
4465
|
+
let nextId = this.sheetList.length + 1;
|
|
4466
|
+
wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
4467
|
+
wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`;
|
|
4468
|
+
wbRels += `</Relationships>`;
|
|
4469
|
+
this.archive.append(wbRels, { name: "xl/_rels/workbook.xml.rels" });
|
|
4470
|
+
}
|
|
4471
|
+
};
|
|
4472
|
+
|
|
4473
|
+
// src/XlsxUpdater.ts
|
|
4474
|
+
import * as fs5 from "fs";
|
|
4475
|
+
import * as os from "os";
|
|
4476
|
+
import * as path6 from "path";
|
|
4477
|
+
import AdmZip2 from "adm-zip";
|
|
4478
|
+
|
|
4479
|
+
// src/atomicFile.ts
|
|
4480
|
+
import * as fs3 from "fs";
|
|
4481
|
+
import * as path4 from "path";
|
|
4482
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4483
|
+
var realFileOperations = {
|
|
4484
|
+
exists: (filePath) => fs3.existsSync(filePath),
|
|
4485
|
+
rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
|
|
4486
|
+
remove: (filePath) => fs3.rmSync(filePath)
|
|
4487
|
+
};
|
|
4488
|
+
function errorMessage(error) {
|
|
4489
|
+
return error instanceof Error ? error.message : String(error);
|
|
4490
|
+
}
|
|
4491
|
+
function destinationExistsError(destinationPath) {
|
|
4492
|
+
return new Error(
|
|
4493
|
+
`Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
function readDestinationMode(target) {
|
|
4497
|
+
try {
|
|
4498
|
+
if (!fs3.existsSync(target)) return void 0;
|
|
4499
|
+
return fs3.statSync(target).mode & 511;
|
|
4500
|
+
} catch {
|
|
4501
|
+
return void 0;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
|
|
4505
|
+
const backupPath = `${destinationPath}.${randomUUID3()}.replacement-backup`;
|
|
4506
|
+
let backupCreated = false;
|
|
4507
|
+
let installed = false;
|
|
4508
|
+
if (operations.exists(destinationPath)) {
|
|
4509
|
+
if (!overwrite) {
|
|
4510
|
+
throw destinationExistsError(destinationPath);
|
|
4511
|
+
}
|
|
4512
|
+
operations.rename(destinationPath, backupPath);
|
|
4513
|
+
backupCreated = true;
|
|
4514
|
+
}
|
|
4515
|
+
try {
|
|
4516
|
+
try {
|
|
4517
|
+
operations.rename(temporaryPath, destinationPath);
|
|
4518
|
+
installed = true;
|
|
4519
|
+
} catch (error) {
|
|
4520
|
+
if (overwrite && !backupCreated && operations.exists(destinationPath)) {
|
|
4521
|
+
operations.rename(destinationPath, backupPath);
|
|
4522
|
+
backupCreated = true;
|
|
4523
|
+
operations.rename(temporaryPath, destinationPath);
|
|
4524
|
+
installed = true;
|
|
4525
|
+
} else {
|
|
4526
|
+
throw error;
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
if (backupCreated) {
|
|
4530
|
+
operations.remove(backupPath);
|
|
4531
|
+
}
|
|
4532
|
+
} catch (error) {
|
|
4533
|
+
if (backupCreated && !installed) {
|
|
4534
|
+
if (!operations.exists(destinationPath)) {
|
|
4535
|
+
try {
|
|
4536
|
+
operations.rename(backupPath, destinationPath);
|
|
4537
|
+
backupCreated = false;
|
|
4538
|
+
} catch (rollbackError) {
|
|
4539
|
+
throw new Error(
|
|
4540
|
+
`Excel conversion: final destination install failed (${errorMessage(error)}), and restoring the original destination also failed (${errorMessage(rollbackError)}). Original file remains at ${backupPath}.`
|
|
4541
|
+
);
|
|
4542
|
+
}
|
|
4543
|
+
} else {
|
|
4544
|
+
throw new Error(
|
|
4545
|
+
`Excel conversion: final destination install failed (${errorMessage(error)}). A concurrent destination was preserved; the original file remains at ${backupPath}.`
|
|
4546
|
+
);
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
throw error;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
function writeBufferAtomically(buffer, destinationPath) {
|
|
4553
|
+
const target = path4.resolve(destinationPath);
|
|
4554
|
+
fs3.mkdirSync(path4.dirname(target), { recursive: true });
|
|
4555
|
+
const temporaryPath = path4.join(
|
|
4556
|
+
path4.dirname(target),
|
|
4557
|
+
`.${path4.basename(target)}.${randomUUID3()}.tmp`
|
|
4558
|
+
);
|
|
4559
|
+
const destinationMode = readDestinationMode(target);
|
|
4560
|
+
try {
|
|
4561
|
+
const fd = fs3.openSync(temporaryPath, "w", destinationMode ?? 438);
|
|
4562
|
+
try {
|
|
4563
|
+
if (destinationMode !== void 0) {
|
|
4564
|
+
try {
|
|
4565
|
+
fs3.fchmodSync(fd, destinationMode);
|
|
4566
|
+
} catch {
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
fs3.writeFileSync(fd, buffer);
|
|
4570
|
+
fs3.fsyncSync(fd);
|
|
4571
|
+
} finally {
|
|
4572
|
+
fs3.closeSync(fd);
|
|
4573
|
+
}
|
|
4574
|
+
if (destinationMode !== void 0) {
|
|
4575
|
+
try {
|
|
4576
|
+
fs3.chmodSync(temporaryPath, destinationMode);
|
|
4577
|
+
} catch {
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4580
|
+
fs3.renameSync(temporaryPath, target);
|
|
4581
|
+
} finally {
|
|
4582
|
+
if (fs3.existsSync(temporaryPath)) {
|
|
4583
|
+
try {
|
|
4584
|
+
fs3.rmSync(temporaryPath);
|
|
4585
|
+
} catch {
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
|
|
4591
|
+
// src/streamingZip.ts
|
|
4592
|
+
import * as fs4 from "fs";
|
|
4593
|
+
import * as path5 from "path";
|
|
4594
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4595
|
+
import { finished } from "stream/promises";
|
|
4596
|
+
import { Readable as Readable3 } from "stream";
|
|
4597
|
+
import archiver3 from "archiver";
|
|
4598
|
+
function readDestinationMode2(target) {
|
|
4599
|
+
try {
|
|
4600
|
+
if (!fs4.existsSync(target)) return void 0;
|
|
4601
|
+
return fs4.statSync(target).mode & 511;
|
|
4602
|
+
} catch {
|
|
4603
|
+
return void 0;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
function appendAndWait(archive, source, name, output) {
|
|
4607
|
+
return new Promise((resolve6, reject) => {
|
|
4608
|
+
const cleanup = () => {
|
|
4609
|
+
archive.off("entry", onEntry);
|
|
4610
|
+
archive.off("error", onError);
|
|
4611
|
+
const maybeEmitter2 = source;
|
|
4612
|
+
if (typeof maybeEmitter2.off === "function") {
|
|
4613
|
+
maybeEmitter2.off("error", onError);
|
|
4614
|
+
}
|
|
4615
|
+
if (output) {
|
|
4616
|
+
output.off("error", onError);
|
|
4617
|
+
}
|
|
4618
|
+
};
|
|
4619
|
+
const onEntry = () => {
|
|
4620
|
+
cleanup();
|
|
4621
|
+
resolve6();
|
|
4622
|
+
};
|
|
4623
|
+
const onError = (error) => {
|
|
4624
|
+
cleanup();
|
|
4625
|
+
reject(error);
|
|
4626
|
+
};
|
|
4627
|
+
archive.once("entry", onEntry);
|
|
4628
|
+
archive.once("error", onError);
|
|
4629
|
+
if (output) {
|
|
4630
|
+
output.once("error", onError);
|
|
4631
|
+
}
|
|
4632
|
+
const maybeEmitter = source;
|
|
4633
|
+
if (typeof maybeEmitter.once === "function") {
|
|
4634
|
+
maybeEmitter.once("error", onError);
|
|
4635
|
+
}
|
|
4636
|
+
try {
|
|
4637
|
+
archive.append(source, { name });
|
|
4638
|
+
} catch (error) {
|
|
4639
|
+
cleanup();
|
|
4640
|
+
reject(error);
|
|
4641
|
+
}
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
4645
|
+
const target = path5.resolve(destinationPath);
|
|
4646
|
+
fs4.mkdirSync(path5.dirname(target), { recursive: true });
|
|
4647
|
+
const temporaryPath = path5.join(
|
|
4648
|
+
path5.dirname(target),
|
|
4649
|
+
`.${path5.basename(target)}.${randomUUID4()}.tmp`
|
|
4650
|
+
);
|
|
4651
|
+
const destinationMode = readDestinationMode2(target);
|
|
4652
|
+
let output = null;
|
|
4653
|
+
let archive = null;
|
|
4654
|
+
try {
|
|
4655
|
+
output = fs4.createWriteStream(
|
|
4656
|
+
temporaryPath,
|
|
4657
|
+
destinationMode !== void 0 ? { mode: destinationMode } : {}
|
|
4658
|
+
);
|
|
4659
|
+
let outputError = null;
|
|
4660
|
+
output.on("error", (err) => {
|
|
4661
|
+
outputError = err;
|
|
4662
|
+
try {
|
|
4663
|
+
archive == null ? void 0 : archive.abort();
|
|
4664
|
+
} catch {
|
|
4665
|
+
}
|
|
4666
|
+
});
|
|
4667
|
+
archive = archiver3("zip");
|
|
4668
|
+
archive.on("error", (err) => {
|
|
4669
|
+
outputError = err;
|
|
4670
|
+
});
|
|
4671
|
+
archive.pipe(output);
|
|
4672
|
+
for (const entry of zip.getEntries()) {
|
|
4673
|
+
if (outputError) throw outputError;
|
|
4674
|
+
const stagedPath = stagedParts.get(entry.entryName);
|
|
4675
|
+
if (stagedPath) {
|
|
4676
|
+
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName, output);
|
|
4677
|
+
} else {
|
|
4678
|
+
await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName, output);
|
|
3883
4679
|
}
|
|
3884
|
-
xml += "</definedNames>";
|
|
3885
4680
|
}
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
3896
|
-
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
|
3897
|
-
<Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>`;
|
|
3898
|
-
for (const sheet of this.sheetList) {
|
|
3899
|
-
xml += `<Override PartName="/${sheet.pathInArchive}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
|
4681
|
+
if (outputError) throw outputError;
|
|
4682
|
+
await archive.finalize();
|
|
4683
|
+
await finished(output);
|
|
4684
|
+
if (outputError) throw outputError;
|
|
4685
|
+
if (destinationMode !== void 0) {
|
|
4686
|
+
try {
|
|
4687
|
+
fs4.chmodSync(temporaryPath, destinationMode);
|
|
4688
|
+
} catch {
|
|
4689
|
+
}
|
|
3900
4690
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4691
|
+
const fd = fs4.openSync(temporaryPath, "r+");
|
|
4692
|
+
try {
|
|
4693
|
+
fs4.fsyncSync(fd);
|
|
4694
|
+
} finally {
|
|
4695
|
+
fs4.closeSync(fd);
|
|
4696
|
+
}
|
|
4697
|
+
fs4.renameSync(temporaryPath, target);
|
|
4698
|
+
} catch (error) {
|
|
4699
|
+
try {
|
|
4700
|
+
archive == null ? void 0 : archive.abort();
|
|
4701
|
+
} catch {
|
|
4702
|
+
}
|
|
4703
|
+
throw error;
|
|
4704
|
+
} finally {
|
|
4705
|
+
if (output && !output.closed) {
|
|
4706
|
+
output.destroy();
|
|
4707
|
+
}
|
|
4708
|
+
if (fs4.existsSync(temporaryPath)) {
|
|
4709
|
+
try {
|
|
4710
|
+
fs4.rmSync(temporaryPath);
|
|
4711
|
+
} catch {
|
|
4712
|
+
}
|
|
3914
4713
|
}
|
|
3915
|
-
let nextId = this.sheetList.length + 1;
|
|
3916
|
-
wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>`;
|
|
3917
|
-
wbRels += `<Relationship Id="rId${nextId++}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>`;
|
|
3918
|
-
wbRels += `</Relationships>`;
|
|
3919
|
-
this.archive.append(wbRels, { name: "xl/_rels/workbook.xml.rels" });
|
|
3920
4714
|
}
|
|
3921
|
-
}
|
|
4715
|
+
}
|
|
3922
4716
|
|
|
3923
4717
|
// src/XlsxUpdater.ts
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
4718
|
+
function parseCellRange(ref) {
|
|
4719
|
+
const m = /^\$?([A-Za-z]+)\$?(\d+)(?::\$?([A-Za-z]+)\$?(\d+))?$/.exec(ref.trim());
|
|
4720
|
+
if (!m) return null;
|
|
4721
|
+
const c1 = columnLetterToIndex(m[1].toUpperCase());
|
|
4722
|
+
const r1 = parseInt(m[2], 10);
|
|
4723
|
+
const c2 = m[3] ? columnLetterToIndex(m[3].toUpperCase()) : c1;
|
|
4724
|
+
const r2 = m[4] ? parseInt(m[4], 10) : r1;
|
|
4725
|
+
if (!Number.isFinite(c1) || !Number.isFinite(r1) || !Number.isFinite(c2) || !Number.isFinite(r2)) {
|
|
4726
|
+
return null;
|
|
4727
|
+
}
|
|
4728
|
+
return {
|
|
4729
|
+
c1: Math.min(c1, c2),
|
|
4730
|
+
r1: Math.min(r1, r2),
|
|
4731
|
+
c2: Math.max(c1, c2),
|
|
4732
|
+
r2: Math.max(r1, r2)
|
|
4733
|
+
};
|
|
4734
|
+
}
|
|
4735
|
+
var XlsxUpdater = class _XlsxUpdater {
|
|
3927
4736
|
/**
|
|
3928
4737
|
* @param path Path to an existing `.xlsx` file.
|
|
3929
4738
|
*/
|
|
3930
|
-
constructor(
|
|
4739
|
+
constructor(filePath) {
|
|
4740
|
+
this.temporaryDirectory = null;
|
|
4741
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
3931
4742
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
3932
4743
|
this.pivotCacheDefs = [];
|
|
3933
4744
|
this.pivotTableDefPaths = [];
|
|
@@ -3935,13 +4746,15 @@ var XlsxUpdater = class {
|
|
|
3935
4746
|
this.sharedStringsValues = [];
|
|
3936
4747
|
this.sharedStringsOriginalLength = 0;
|
|
3937
4748
|
this.sharedStringsCount = 0;
|
|
4749
|
+
this.sharedStringsPending = [];
|
|
4750
|
+
this.sharedStringsDirty = false;
|
|
3938
4751
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
3939
|
-
this.sourcePath =
|
|
4752
|
+
this.sourcePath = filePath;
|
|
3940
4753
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3941
|
-
if (!
|
|
3942
|
-
throw new Error(`XlsxUpdater: file not found: ${
|
|
4754
|
+
if (!fs5.existsSync(filePath)) {
|
|
4755
|
+
throw new Error(`XlsxUpdater: file not found: ${filePath}`);
|
|
3943
4756
|
}
|
|
3944
|
-
this.zip = new AdmZip2(
|
|
4757
|
+
this.zip = new AdmZip2(filePath);
|
|
3945
4758
|
if (this.zip.getEntry("xl/workbook.bin")) {
|
|
3946
4759
|
throw new Error(
|
|
3947
4760
|
"XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
|
|
@@ -3975,8 +4788,15 @@ var XlsxUpdater = class {
|
|
|
3975
4788
|
if (!entry) {
|
|
3976
4789
|
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
3977
4790
|
}
|
|
3978
|
-
const sheetXml =
|
|
4791
|
+
const sheetXml = this._entryData(sheetPath).toString("utf8");
|
|
3979
4792
|
this._ensureSharedStringsLoaded();
|
|
4793
|
+
if (this.sharedStringsXml !== null) {
|
|
4794
|
+
this.sharedStringsCount = Math.max(
|
|
4795
|
+
0,
|
|
4796
|
+
this.sharedStringsCount - this._sharedStringRefCount(sheetXml)
|
|
4797
|
+
);
|
|
4798
|
+
this.sharedStringsDirty = true;
|
|
4799
|
+
}
|
|
3980
4800
|
let colStyles = /* @__PURE__ */ new Map();
|
|
3981
4801
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
3982
4802
|
let dateStyle = null;
|
|
@@ -3991,12 +4811,177 @@ var XlsxUpdater = class {
|
|
|
3991
4811
|
const newSheetData = this._buildSheetData(rows, headers, colStyles, headerStyles, dateStyle);
|
|
3992
4812
|
const newXml = this._patchSheetXml(sheetXml, newSheetData, totalRows, lastCol);
|
|
3993
4813
|
this.zip.updateFile(sheetPath, Buffer.from(newXml, "utf8"));
|
|
4814
|
+
this._discardStagedPart(sheetPath);
|
|
3994
4815
|
if (this.sharedStringsXml !== null) {
|
|
3995
4816
|
this._commitSharedStrings();
|
|
3996
4817
|
}
|
|
3997
4818
|
const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
|
|
3998
4819
|
this._updatePivotCaches(sheetName, dimRef, rows.length);
|
|
3999
4820
|
this._addRefreshOnLoadToPivotTables();
|
|
4821
|
+
this._removeCalcChain();
|
|
4822
|
+
this._updateTableRefs(sheetName, dimRef);
|
|
4823
|
+
}
|
|
4824
|
+
/**
|
|
4825
|
+
* Replace worksheet rows from a one-pass source without materialising the
|
|
4826
|
+
* complete result set. Use {@link saveStreaming} to keep the final ZIP
|
|
4827
|
+
* write streaming as well.
|
|
4828
|
+
*/
|
|
4829
|
+
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
4830
|
+
const { headers, styleFallback = "inherit" } = options;
|
|
4831
|
+
if (styleFallback !== "inherit" && styleFallback !== "general") {
|
|
4832
|
+
throw new Error("XlsxUpdater: styleFallback must be 'inherit' or 'general'.");
|
|
4833
|
+
}
|
|
4834
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
4835
|
+
if (!sheetPath) {
|
|
4836
|
+
throw new Error(`XlsxUpdater: sheet "${sheetName}" not found in the workbook.`);
|
|
4837
|
+
}
|
|
4838
|
+
const entry = this.zip.getEntry(sheetPath);
|
|
4839
|
+
if (!entry) {
|
|
4840
|
+
throw new Error(`XlsxUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4841
|
+
}
|
|
4842
|
+
const oldStage = this.stagedParts.get(sheetPath);
|
|
4843
|
+
const beforeValuesLength = this.sharedStringsValues.length;
|
|
4844
|
+
const beforePendingLength = this.sharedStringsPending.length;
|
|
4845
|
+
const beforeSharedStringsCount = this.sharedStringsCount;
|
|
4846
|
+
const beforeSharedStringsDirty = this.sharedStringsDirty;
|
|
4847
|
+
const beforeSharedStringsXml = this.sharedStringsXml;
|
|
4848
|
+
const beforeSharedStringsOriginalLength = this.sharedStringsOriginalLength;
|
|
4849
|
+
const beforeStringIndexMap = new Map(this.stringIndexMap);
|
|
4850
|
+
const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4851
|
+
const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.xml") ? Buffer.from(this._entryData("xl/sharedStrings.xml")) : null;
|
|
4852
|
+
const createdParts = [];
|
|
4853
|
+
const oldSheetXml = this._entryData(sheetPath).toString("utf8");
|
|
4854
|
+
const rowsPath = this._temporaryPath(".rows");
|
|
4855
|
+
const outputPath = this._temporaryPath(".xml");
|
|
4856
|
+
createdParts.push(rowsPath, outputPath);
|
|
4857
|
+
let output = null;
|
|
4858
|
+
try {
|
|
4859
|
+
this._ensureSharedStringsLoaded();
|
|
4860
|
+
if (this.sharedStringsXml !== null) {
|
|
4861
|
+
this.sharedStringsCount = Math.max(
|
|
4862
|
+
0,
|
|
4863
|
+
this.sharedStringsCount - this._sharedStringRefCount(oldSheetXml)
|
|
4864
|
+
);
|
|
4865
|
+
this.sharedStringsDirty = true;
|
|
4866
|
+
}
|
|
4867
|
+
let colStyles = /* @__PURE__ */ new Map();
|
|
4868
|
+
let headerStyles = /* @__PURE__ */ new Map();
|
|
4869
|
+
let dateStyle = null;
|
|
4870
|
+
if (styleFallback !== "general") {
|
|
4871
|
+
const collected = this._collectExistingStyles(oldSheetXml);
|
|
4872
|
+
colStyles = collected.dataStyles;
|
|
4873
|
+
headerStyles = collected.headerStyles;
|
|
4874
|
+
dateStyle = this._findDateStyleIndex();
|
|
4875
|
+
}
|
|
4876
|
+
const prefixMatch = /<([A-Za-z_][\w.-]*:)?sheetData\b/.exec(oldSheetXml);
|
|
4877
|
+
const prefix = (prefixMatch == null ? void 0 : prefixMatch[1]) ?? "";
|
|
4878
|
+
const rowOutput = fs5.createWriteStream(rowsPath);
|
|
4879
|
+
output = rowOutput;
|
|
4880
|
+
let outputError = null;
|
|
4881
|
+
rowOutput.on("error", (error) => {
|
|
4882
|
+
outputError = error;
|
|
4883
|
+
});
|
|
4884
|
+
let nextRow = 1;
|
|
4885
|
+
let width = (headers == null ? void 0 : headers.length) ?? 0;
|
|
4886
|
+
let dataRowsSeen = 0;
|
|
4887
|
+
let keptDataRows = 0;
|
|
4888
|
+
let lastKeptEnd = 0;
|
|
4889
|
+
let pendingEmptyWidth = 0;
|
|
4890
|
+
let writtenBytes = 0;
|
|
4891
|
+
const write = async (data) => {
|
|
4892
|
+
if (outputError) throw outputError;
|
|
4893
|
+
writtenBytes += data.length;
|
|
4894
|
+
if (rowOutput.write(data)) return;
|
|
4895
|
+
await new Promise((resolve6, reject) => {
|
|
4896
|
+
const onDrain = () => {
|
|
4897
|
+
cleanup();
|
|
4898
|
+
resolve6();
|
|
4899
|
+
};
|
|
4900
|
+
const onError = (error) => {
|
|
4901
|
+
cleanup();
|
|
4902
|
+
reject(error);
|
|
4903
|
+
};
|
|
4904
|
+
const cleanup = () => {
|
|
4905
|
+
rowOutput.off("drain", onDrain);
|
|
4906
|
+
rowOutput.off("error", onError);
|
|
4907
|
+
};
|
|
4908
|
+
rowOutput.once("drain", onDrain);
|
|
4909
|
+
rowOutput.once("error", onError);
|
|
4910
|
+
});
|
|
4911
|
+
};
|
|
4912
|
+
if (headers !== void 0) {
|
|
4913
|
+
const cells = headers.map((value, column) => this._valueCell(value, column, nextRow, headerStyles, dateStyle));
|
|
4914
|
+
const header = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
|
|
4915
|
+
await write(Buffer.from(header, "utf8"));
|
|
4916
|
+
lastKeptEnd = writtenBytes;
|
|
4917
|
+
nextRow++;
|
|
4918
|
+
}
|
|
4919
|
+
for await (const sourceRow of rows) {
|
|
4920
|
+
if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
|
|
4921
|
+
throw new TypeError("Each row must be an iterable of cells, not text");
|
|
4922
|
+
}
|
|
4923
|
+
const row = Array.from(sourceRow);
|
|
4924
|
+
const cells = row.map((value, column) => value === null || value === void 0 ? "" : this._valueCell(value, column, nextRow, colStyles, dateStyle));
|
|
4925
|
+
const rowXml = `<${prefix}row r="${nextRow}">${cells.join("")}</${prefix}row>`;
|
|
4926
|
+
await write(Buffer.from(rowXml, "utf8"));
|
|
4927
|
+
dataRowsSeen++;
|
|
4928
|
+
nextRow++;
|
|
4929
|
+
if (row.every((value) => value === null || value === void 0)) {
|
|
4930
|
+
pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
|
|
4931
|
+
} else {
|
|
4932
|
+
width = Math.max(width, pendingEmptyWidth, row.length);
|
|
4933
|
+
pendingEmptyWidth = 0;
|
|
4934
|
+
keptDataRows = dataRowsSeen;
|
|
4935
|
+
lastKeptEnd = writtenBytes;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
await new Promise((resolve6, reject) => {
|
|
4939
|
+
rowOutput.once("close", resolve6);
|
|
4940
|
+
rowOutput.once("error", reject);
|
|
4941
|
+
rowOutput.end();
|
|
4942
|
+
});
|
|
4943
|
+
if (outputError) throw outputError;
|
|
4944
|
+
const rowsFd = fs5.openSync(rowsPath, "r+");
|
|
4945
|
+
try {
|
|
4946
|
+
fs5.ftruncateSync(rowsFd, lastKeptEnd);
|
|
4947
|
+
} finally {
|
|
4948
|
+
fs5.closeSync(rowsFd);
|
|
4949
|
+
}
|
|
4950
|
+
const totalRows = keptDataRows + (headers !== void 0 ? 1 : 0);
|
|
4951
|
+
const lastCol = width - 1;
|
|
4952
|
+
const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
|
|
4953
|
+
this._patchSheetFile(oldSheetXml, rowsPath, outputPath, dimRef);
|
|
4954
|
+
this._stagePart(sheetPath, outputPath);
|
|
4955
|
+
this._commitSharedStrings();
|
|
4956
|
+
this._updatePivotCaches(sheetName, dimRef, keptDataRows);
|
|
4957
|
+
this._addRefreshOnLoadToPivotTables();
|
|
4958
|
+
this._removeCalcChain();
|
|
4959
|
+
this._updateTableRefs(sheetName, dimRef);
|
|
4960
|
+
if (oldStage && oldStage !== outputPath) {
|
|
4961
|
+
this._removeTemporaryFile(oldStage);
|
|
4962
|
+
}
|
|
4963
|
+
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
4964
|
+
} catch (error) {
|
|
4965
|
+
this.sharedStringsValues.length = beforeValuesLength;
|
|
4966
|
+
this.sharedStringsPending.length = beforePendingLength;
|
|
4967
|
+
this.sharedStringsCount = beforeSharedStringsCount;
|
|
4968
|
+
this.sharedStringsDirty = beforeSharedStringsDirty;
|
|
4969
|
+
this.sharedStringsXml = beforeSharedStringsXml;
|
|
4970
|
+
this.sharedStringsOriginalLength = beforeSharedStringsOriginalLength;
|
|
4971
|
+
this.stringIndexMap.clear();
|
|
4972
|
+
for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
|
|
4973
|
+
if (beforeSharedStringsBytes) {
|
|
4974
|
+
this.zip.updateFile("xl/sharedStrings.xml", beforeSharedStringsBytes);
|
|
4975
|
+
}
|
|
4976
|
+
if (oldStage) this.stagedParts.set(sheetPath, oldStage);
|
|
4977
|
+
else this.stagedParts.delete(sheetPath);
|
|
4978
|
+
throw error;
|
|
4979
|
+
} finally {
|
|
4980
|
+
if (output && !output.closed) output.destroy();
|
|
4981
|
+
for (const temporaryPath of createdParts) {
|
|
4982
|
+
this._removeTemporaryFile(temporaryPath);
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4000
4985
|
}
|
|
4001
4986
|
/**
|
|
4002
4987
|
* Write the updated workbook to disk.
|
|
@@ -4004,12 +4989,89 @@ var XlsxUpdater = class {
|
|
|
4004
4989
|
*/
|
|
4005
4990
|
save(outputPath) {
|
|
4006
4991
|
const target = outputPath ?? this.sourcePath;
|
|
4007
|
-
|
|
4992
|
+
this._materializeStagedParts();
|
|
4993
|
+
writeBufferAtomically(this.zip.toBuffer(), target);
|
|
4994
|
+
}
|
|
4995
|
+
/** Save staged streaming replacements without materialising the ZIP. */
|
|
4996
|
+
async saveStreaming(outputPath) {
|
|
4997
|
+
const target = outputPath ?? this.sourcePath;
|
|
4998
|
+
await writeAdmZipStreaming(this.zip, this.stagedParts, target);
|
|
4999
|
+
this._materializeStagedParts();
|
|
4008
5000
|
}
|
|
4009
5001
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4010
5002
|
toBuffer() {
|
|
5003
|
+
this._materializeStagedParts();
|
|
4011
5004
|
return this.zip.toBuffer();
|
|
4012
5005
|
}
|
|
5006
|
+
/**
|
|
5007
|
+
* Release the staging directory and any unsaved staged parts.
|
|
5008
|
+
* The updater remains usable; a new staging directory is created lazily
|
|
5009
|
+
* on the next streaming replacement.
|
|
5010
|
+
*/
|
|
5011
|
+
dispose() {
|
|
5012
|
+
for (const stagedPath of this.stagedParts.values()) {
|
|
5013
|
+
try {
|
|
5014
|
+
fs5.rmSync(stagedPath, { force: true });
|
|
5015
|
+
} catch {
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
this.stagedParts.clear();
|
|
5019
|
+
if (this.temporaryDirectory !== null) {
|
|
5020
|
+
try {
|
|
5021
|
+
fs5.rmSync(this.temporaryDirectory, { recursive: true, force: true });
|
|
5022
|
+
} catch {
|
|
5023
|
+
}
|
|
5024
|
+
this.temporaryDirectory = null;
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
/** Support `using` declarations for automatic staging cleanup. */
|
|
5028
|
+
[Symbol.dispose]() {
|
|
5029
|
+
this.dispose();
|
|
5030
|
+
}
|
|
5031
|
+
_ensureTemporaryDirectory() {
|
|
5032
|
+
if (this.temporaryDirectory === null) {
|
|
5033
|
+
this.temporaryDirectory = fs5.mkdtempSync(path6.join(os.tmpdir(), "spreadsheet-updater-"));
|
|
5034
|
+
} else {
|
|
5035
|
+
try {
|
|
5036
|
+
fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
5037
|
+
} catch {
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
return this.temporaryDirectory;
|
|
5041
|
+
}
|
|
5042
|
+
_temporaryPath(suffix) {
|
|
5043
|
+
return path6.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
5044
|
+
}
|
|
5045
|
+
_removeTemporaryFile(filePath) {
|
|
5046
|
+
try {
|
|
5047
|
+
fs5.rmSync(filePath);
|
|
5048
|
+
} catch (error) {
|
|
5049
|
+
if (error.code !== "ENOENT") throw error;
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
_entryData(name) {
|
|
5053
|
+
const stagedPath = this.stagedParts.get(name);
|
|
5054
|
+
if (stagedPath) return fs5.readFileSync(stagedPath);
|
|
5055
|
+
const entry = this.zip.getEntry(name);
|
|
5056
|
+
if (!entry) throw new Error(`XlsxUpdater: ZIP member missing: ${name}`);
|
|
5057
|
+
return entry.getData();
|
|
5058
|
+
}
|
|
5059
|
+
_stagePart(name, filePath) {
|
|
5060
|
+
this.stagedParts.set(name, filePath);
|
|
5061
|
+
}
|
|
5062
|
+
_discardStagedPart(name) {
|
|
5063
|
+
const stagedPath = this.stagedParts.get(name);
|
|
5064
|
+
if (!stagedPath) return;
|
|
5065
|
+
this._removeTemporaryFile(stagedPath);
|
|
5066
|
+
this.stagedParts.delete(name);
|
|
5067
|
+
}
|
|
5068
|
+
_materializeStagedParts() {
|
|
5069
|
+
for (const [name, stagedPath] of this.stagedParts) {
|
|
5070
|
+
this.zip.updateFile(name, fs5.readFileSync(stagedPath));
|
|
5071
|
+
this._removeTemporaryFile(stagedPath);
|
|
5072
|
+
}
|
|
5073
|
+
this.stagedParts.clear();
|
|
5074
|
+
}
|
|
4013
5075
|
// ------------------------------------------------------------------
|
|
4014
5076
|
// Workbook structure
|
|
4015
5077
|
// ------------------------------------------------------------------
|
|
@@ -4018,10 +5080,15 @@ var XlsxUpdater = class {
|
|
|
4018
5080
|
const rIdToTarget = {};
|
|
4019
5081
|
if (relsEntry) {
|
|
4020
5082
|
const rels = relsEntry.getData().toString("utf8");
|
|
4021
|
-
const
|
|
4022
|
-
let
|
|
4023
|
-
while ((
|
|
4024
|
-
|
|
5083
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5084
|
+
let tagMatch;
|
|
5085
|
+
while ((tagMatch = relTagRe.exec(rels)) !== null) {
|
|
5086
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
5087
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
5088
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
5089
|
+
if (id !== void 0 && target !== void 0) {
|
|
5090
|
+
rIdToTarget[id] = target;
|
|
5091
|
+
}
|
|
4025
5092
|
}
|
|
4026
5093
|
}
|
|
4027
5094
|
const wbEntry = this.zip.getEntry("xl/workbook.xml");
|
|
@@ -4030,21 +5097,23 @@ var XlsxUpdater = class {
|
|
|
4030
5097
|
let match;
|
|
4031
5098
|
while ((match = sheetRe.exec(wbXml)) !== null) {
|
|
4032
5099
|
const tag = match[0];
|
|
4033
|
-
const
|
|
4034
|
-
const
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
const
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
fullPath = fullPath.substring(1);
|
|
4041
|
-
} else if (!fullPath.startsWith("xl/")) {
|
|
4042
|
-
fullPath = "xl/" + fullPath;
|
|
4043
|
-
}
|
|
5100
|
+
const attrs = parseTagAttributes(tag);
|
|
5101
|
+
const rawName = attrs["name"];
|
|
5102
|
+
const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
|
|
5103
|
+
if (rawName === void 0 || rId === void 0) continue;
|
|
5104
|
+
const name = unescapeXml(rawName);
|
|
5105
|
+
const target = rIdToTarget[rId] ?? "";
|
|
5106
|
+
const fullPath = resolveRelTarget(target);
|
|
4044
5107
|
if (fullPath) {
|
|
4045
5108
|
this.sheetNameToPath.set(name, fullPath);
|
|
4046
5109
|
}
|
|
4047
5110
|
}
|
|
5111
|
+
const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbXml);
|
|
5112
|
+
if (wbPrMatch) {
|
|
5113
|
+
const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
|
|
5114
|
+
const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
|
|
5115
|
+
this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
5116
|
+
}
|
|
4048
5117
|
for (const entry of this.zip.getEntries()) {
|
|
4049
5118
|
const name = entry.entryName;
|
|
4050
5119
|
if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
|
|
@@ -4062,47 +5131,59 @@ var XlsxUpdater = class {
|
|
|
4062
5131
|
return;
|
|
4063
5132
|
}
|
|
4064
5133
|
const entry = this.zip.getEntry("xl/sharedStrings.xml");
|
|
4065
|
-
if (!entry) {
|
|
5134
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.xml")) {
|
|
4066
5135
|
this.sharedStringsXml = null;
|
|
4067
5136
|
return;
|
|
4068
5137
|
}
|
|
4069
|
-
this.sharedStringsXml =
|
|
5138
|
+
this.sharedStringsXml = this._entryData("xl/sharedStrings.xml").toString("utf8");
|
|
4070
5139
|
this.sharedStringsValues = parseSharedStringsXml(this.sharedStringsXml);
|
|
4071
|
-
const countMatch = /<sst\b[^>]
|
|
4072
|
-
const countAttr = countMatch ? /count
|
|
4073
|
-
this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) :
|
|
5140
|
+
const countMatch = /<(?:[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(this.sharedStringsXml);
|
|
5141
|
+
const countAttr = countMatch ? /(?:^|\s)count\s*=\s*["'](\d+)["']/.exec(countMatch[0]) : null;
|
|
5142
|
+
this.sharedStringsCount = countAttr ? parseInt(countAttr[1], 10) : this.sharedStringsValues.length;
|
|
4074
5143
|
this.sharedStringsValues.forEach((value, index) => {
|
|
4075
5144
|
if (!this.stringIndexMap.has(value)) {
|
|
4076
5145
|
this.stringIndexMap.set(value, index);
|
|
4077
5146
|
}
|
|
4078
5147
|
});
|
|
4079
5148
|
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
5149
|
+
this.sharedStringsPending = [];
|
|
5150
|
+
this.sharedStringsDirty = false;
|
|
4080
5151
|
}
|
|
4081
5152
|
_commitSharedStrings() {
|
|
4082
|
-
|
|
5153
|
+
var _a;
|
|
5154
|
+
if (this.sharedStringsXml === null || !this.sharedStringsDirty) {
|
|
4083
5155
|
return;
|
|
4084
5156
|
}
|
|
4085
5157
|
let xml = this.sharedStringsXml;
|
|
4086
|
-
const
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
5158
|
+
const sstMatch = /<(?<prefix>[A-Za-z_][\w.-]*:)?sst\b[^>]*>/i.exec(xml);
|
|
5159
|
+
if (!sstMatch || sstMatch.index === void 0) {
|
|
5160
|
+
throw new Error("XlsxUpdater: sharedStrings.xml has no sst element.");
|
|
5161
|
+
}
|
|
5162
|
+
const tag = sstMatch[0];
|
|
5163
|
+
const prefix = ((_a = sstMatch.groups) == null ? void 0 : _a.prefix) ?? "";
|
|
5164
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(
|
|
5165
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(tag, "count", String(this.sharedStringsCount)),
|
|
5166
|
+
"uniqueCount",
|
|
5167
|
+
String(this.sharedStringsValues.length)
|
|
5168
|
+
);
|
|
5169
|
+
xml = xml.substring(0, sstMatch.index) + newTag + xml.substring(sstMatch.index + tag.length);
|
|
5170
|
+
const closingRe = new RegExp(`</${prefix}sst\\s*>\\s*$`, "i");
|
|
5171
|
+
const closingMatch = closingRe.exec(xml);
|
|
5172
|
+
if (!closingMatch || closingMatch.index === void 0) {
|
|
5173
|
+
throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
|
|
4096
5174
|
}
|
|
4097
|
-
|
|
4098
|
-
for (let i = this.sharedStringsOriginalLength; i < this.sharedStringsValues.length; i++) {
|
|
4099
|
-
const txt = this.sharedStringsValues[i];
|
|
5175
|
+
const appended = this.sharedStringsPending.map((txt) => {
|
|
4100
5176
|
const clean = escapeXmlText(txt);
|
|
4101
5177
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
5178
|
+
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
5179
|
+
return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
|
|
5180
|
+
}).join("");
|
|
5181
|
+
xml = xml.substring(0, closingMatch.index) + appended + xml.substring(closingMatch.index);
|
|
4105
5182
|
this.zip.updateFile("xl/sharedStrings.xml", Buffer.from(xml, "utf8"));
|
|
5183
|
+
this.sharedStringsXml = xml;
|
|
5184
|
+
this.sharedStringsOriginalLength = this.sharedStringsValues.length;
|
|
5185
|
+
this.sharedStringsPending = [];
|
|
5186
|
+
this.sharedStringsDirty = false;
|
|
4106
5187
|
}
|
|
4107
5188
|
// ------------------------------------------------------------------
|
|
4108
5189
|
// Styles
|
|
@@ -4227,7 +5308,7 @@ var XlsxUpdater = class {
|
|
|
4227
5308
|
return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
|
|
4228
5309
|
}
|
|
4229
5310
|
if (val instanceof Date) {
|
|
4230
|
-
const oaDate = (val.getTime()
|
|
5311
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
4231
5312
|
if (Number.isFinite(oaDate)) {
|
|
4232
5313
|
const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
|
|
4233
5314
|
return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
|
|
@@ -4253,10 +5334,25 @@ var XlsxUpdater = class {
|
|
|
4253
5334
|
index = this.sharedStringsValues.length;
|
|
4254
5335
|
this.sharedStringsValues.push(text);
|
|
4255
5336
|
this.stringIndexMap.set(text, index);
|
|
5337
|
+
this.sharedStringsPending.push(text);
|
|
4256
5338
|
}
|
|
4257
5339
|
this.sharedStringsCount++;
|
|
5340
|
+
this.sharedStringsDirty = true;
|
|
4258
5341
|
return `<c r="${colRef}${rowNum}" t="s"${styleAttr}><v>${index}</v></c>`;
|
|
4259
5342
|
}
|
|
5343
|
+
static _replaceOrAddXmlAttribute(tag, name, value) {
|
|
5344
|
+
const escaped = escapeXmlText(value);
|
|
5345
|
+
const attribute = new RegExp(`(\\s${name}\\s*=\\s*["'])[^"']*(["'])`, "i");
|
|
5346
|
+
if (attribute.test(tag)) {
|
|
5347
|
+
return tag.replace(attribute, `$1${escaped}$2`);
|
|
5348
|
+
}
|
|
5349
|
+
const insertion = ` ${name}="${escaped}"`;
|
|
5350
|
+
return tag.endsWith("/>") ? `${tag.slice(0, -2)}${insertion}/>` : `${tag.slice(0, -1)}${insertion}>`;
|
|
5351
|
+
}
|
|
5352
|
+
_sharedStringRefCount(sheetXml) {
|
|
5353
|
+
const cellTags = sheetXml.match(/<(?:(?:[A-Za-z_][\w.-]*):)?c\b[^>]*>/gi) ?? [];
|
|
5354
|
+
return cellTags.filter((tag) => /(?:^|\s)t\s*=\s*["']s["']/i.test(tag)).length;
|
|
5355
|
+
}
|
|
4260
5356
|
// ------------------------------------------------------------------
|
|
4261
5357
|
// Worksheet XML patching
|
|
4262
5358
|
// ------------------------------------------------------------------
|
|
@@ -4298,6 +5394,109 @@ var XlsxUpdater = class {
|
|
|
4298
5394
|
}
|
|
4299
5395
|
return xml;
|
|
4300
5396
|
}
|
|
5397
|
+
_patchSheetFile(sheetXml, rowsPath, outputPath, dimensionRef) {
|
|
5398
|
+
var _a, _b, _c, _d;
|
|
5399
|
+
const source = Buffer.from(sheetXml, "utf8");
|
|
5400
|
+
const text = sheetXml;
|
|
5401
|
+
const replacements = [];
|
|
5402
|
+
const emptySheetData = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b(?<attrs>[^>]*)\/>/i.exec(text);
|
|
5403
|
+
if (emptySheetData && emptySheetData.index !== void 0) {
|
|
5404
|
+
const prefix = ((_a = emptySheetData.groups) == null ? void 0 : _a.prefix) ?? "";
|
|
5405
|
+
replacements.push({
|
|
5406
|
+
start: emptySheetData.index,
|
|
5407
|
+
end: emptySheetData.index + emptySheetData[0].length,
|
|
5408
|
+
data: Buffer.from(`<${prefix}sheetData${((_b = emptySheetData.groups) == null ? void 0 : _b.attrs) ?? ""}>`, "utf8"),
|
|
5409
|
+
rows: true,
|
|
5410
|
+
suffix: Buffer.from(`</${prefix}sheetData>`, "utf8")
|
|
5411
|
+
});
|
|
5412
|
+
} else {
|
|
5413
|
+
const opening = /<(?<prefix>[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(text);
|
|
5414
|
+
if (!opening || opening.index === void 0) {
|
|
5415
|
+
throw new Error("XlsxUpdater: <sheetData> element not found in worksheet XML.");
|
|
5416
|
+
}
|
|
5417
|
+
const prefix = ((_c = opening.groups) == null ? void 0 : _c.prefix) ?? "";
|
|
5418
|
+
const closeRe = new RegExp(`</${prefix}sheetData\\s*>`, "i");
|
|
5419
|
+
const closing = closeRe.exec(text.slice(opening.index + opening[0].length));
|
|
5420
|
+
if (!closing || closing.index === void 0) {
|
|
5421
|
+
throw new Error("XlsxUpdater: <sheetData> element not closed in worksheet XML.");
|
|
5422
|
+
}
|
|
5423
|
+
const closingStart = opening.index + opening[0].length + closing.index;
|
|
5424
|
+
replacements.push({
|
|
5425
|
+
start: opening.index + opening[0].length,
|
|
5426
|
+
end: closingStart,
|
|
5427
|
+
rows: true
|
|
5428
|
+
});
|
|
5429
|
+
}
|
|
5430
|
+
const dimension = /<(?<prefix>[A-Za-z_][\w.-]*:)?dimension\b[^>]*>/i.exec(text);
|
|
5431
|
+
if (dimension && dimension.index !== void 0) {
|
|
5432
|
+
replacements.push({
|
|
5433
|
+
start: dimension.index,
|
|
5434
|
+
end: dimension.index + dimension[0].length,
|
|
5435
|
+
data: Buffer.from(
|
|
5436
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(dimension[0], "ref", dimensionRef)
|
|
5437
|
+
)
|
|
5438
|
+
});
|
|
5439
|
+
} else {
|
|
5440
|
+
const root = /<(?<prefix>[A-Za-z_][\w.-]*:)?worksheet\b[^>]*>/i.exec(text);
|
|
5441
|
+
if (root && root.index !== void 0) {
|
|
5442
|
+
const prefix = ((_d = root.groups) == null ? void 0 : _d.prefix) ?? "";
|
|
5443
|
+
const insertAt = root.index + root[0].length;
|
|
5444
|
+
replacements.push({
|
|
5445
|
+
start: insertAt,
|
|
5446
|
+
end: insertAt,
|
|
5447
|
+
data: Buffer.from(`<${prefix}dimension ref="${dimensionRef}"/>`, "utf8")
|
|
5448
|
+
});
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
const autoFilter = /<(?<prefix>[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i.exec(text);
|
|
5452
|
+
if (autoFilter && autoFilter.index !== void 0) {
|
|
5453
|
+
replacements.push({
|
|
5454
|
+
start: autoFilter.index,
|
|
5455
|
+
end: autoFilter.index + autoFilter[0].length,
|
|
5456
|
+
data: Buffer.from(
|
|
5457
|
+
_XlsxUpdater._replaceOrAddXmlAttribute(autoFilter[0], "ref", dimensionRef),
|
|
5458
|
+
"utf8"
|
|
5459
|
+
)
|
|
5460
|
+
});
|
|
5461
|
+
}
|
|
5462
|
+
replacements.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
5463
|
+
const fd = fs5.openSync(outputPath, "w");
|
|
5464
|
+
let cursor = 0;
|
|
5465
|
+
const writeBuffer = (data) => {
|
|
5466
|
+
fs5.writeSync(fd, data);
|
|
5467
|
+
};
|
|
5468
|
+
const copyRows = () => {
|
|
5469
|
+
const rowsFd = fs5.openSync(rowsPath, "r");
|
|
5470
|
+
try {
|
|
5471
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
5472
|
+
const length = fs5.statSync(rowsPath).size;
|
|
5473
|
+
let position = 0;
|
|
5474
|
+
while (position < length) {
|
|
5475
|
+
const count = fs5.readSync(rowsFd, chunk, 0, Math.min(chunk.length, length - position), position);
|
|
5476
|
+
if (count <= 0) throw new Error("XlsxUpdater: failed to read staged rows.");
|
|
5477
|
+
fs5.writeSync(fd, chunk, 0, count);
|
|
5478
|
+
position += count;
|
|
5479
|
+
}
|
|
5480
|
+
} finally {
|
|
5481
|
+
fs5.closeSync(rowsFd);
|
|
5482
|
+
}
|
|
5483
|
+
};
|
|
5484
|
+
try {
|
|
5485
|
+
for (const replacement of replacements) {
|
|
5486
|
+
const startByte = Buffer.byteLength(text.slice(0, replacement.start), "utf8");
|
|
5487
|
+
const endByte = Buffer.byteLength(text.slice(0, replacement.end), "utf8");
|
|
5488
|
+
if (startByte < cursor) throw new Error("XlsxUpdater: overlapping worksheet replacements.");
|
|
5489
|
+
writeBuffer(source.subarray(cursor, startByte));
|
|
5490
|
+
if (replacement.data) writeBuffer(replacement.data);
|
|
5491
|
+
if (replacement.rows) copyRows();
|
|
5492
|
+
if (replacement.suffix) writeBuffer(replacement.suffix);
|
|
5493
|
+
cursor = endByte;
|
|
5494
|
+
}
|
|
5495
|
+
writeBuffer(source.subarray(cursor));
|
|
5496
|
+
} finally {
|
|
5497
|
+
fs5.closeSync(fd);
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
4301
5500
|
// ------------------------------------------------------------------
|
|
4302
5501
|
// Pivot table integration
|
|
4303
5502
|
// ------------------------------------------------------------------
|
|
@@ -4326,93 +5525,251 @@ var XlsxUpdater = class {
|
|
|
4326
5525
|
}
|
|
4327
5526
|
}
|
|
4328
5527
|
_addRefreshOnLoadToPivotTables() {
|
|
4329
|
-
for (const
|
|
4330
|
-
const entry = this.zip.getEntry(
|
|
5528
|
+
for (const path9 of this.pivotTableDefPaths) {
|
|
5529
|
+
const entry = this.zip.getEntry(path9);
|
|
4331
5530
|
if (!entry) continue;
|
|
4332
5531
|
let xml = entry.getData().toString("utf8");
|
|
4333
5532
|
const rootRe = /<pivotTableDefinition\b[^>]*>/;
|
|
4334
5533
|
const rootMatch = rootRe.exec(xml);
|
|
4335
|
-
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
4336
|
-
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
4337
|
-
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
4338
|
-
this.zip.updateFile(
|
|
4339
|
-
}
|
|
4340
|
-
}
|
|
4341
|
-
};
|
|
4342
|
-
|
|
4343
|
-
// src/XlsbUpdater.ts
|
|
4344
|
-
import * as fs4 from "fs";
|
|
4345
|
-
import AdmZip3 from "adm-zip";
|
|
4346
|
-
|
|
4347
|
-
// src/biff12Utils.ts
|
|
4348
|
-
function readVlq(buf, pos) {
|
|
4349
|
-
let value = 0;
|
|
4350
|
-
let shift = 0;
|
|
4351
|
-
let byte;
|
|
4352
|
-
do {
|
|
4353
|
-
byte = buf[pos++];
|
|
4354
|
-
value |= (byte & 127) << shift;
|
|
4355
|
-
shift += 7;
|
|
4356
|
-
} while ((byte & 128) !== 0);
|
|
4357
|
-
return { value, nextPos: pos };
|
|
4358
|
-
}
|
|
4359
|
-
function vlqBytes(value) {
|
|
4360
|
-
const out = [];
|
|
4361
|
-
while (value >= 128) {
|
|
4362
|
-
out.push(value & 127 | 128);
|
|
4363
|
-
value >>>= 7;
|
|
4364
|
-
}
|
|
4365
|
-
out.push(value & 127);
|
|
4366
|
-
return out;
|
|
4367
|
-
}
|
|
4368
|
-
function readRecord(buf, pos, rec) {
|
|
4369
|
-
const idRes = readVlq(buf, pos);
|
|
4370
|
-
if (idRes.nextPos > buf.length) return false;
|
|
4371
|
-
const lenRes = readVlq(buf, idRes.nextPos);
|
|
4372
|
-
if (lenRes.nextPos + lenRes.value > buf.length) return false;
|
|
4373
|
-
rec.headerStart = pos;
|
|
4374
|
-
rec.dataStart = lenRes.nextPos;
|
|
4375
|
-
rec.dataEnd = lenRes.nextPos + lenRes.value;
|
|
4376
|
-
rec.id = idRes.value;
|
|
4377
|
-
rec.len = lenRes.value;
|
|
4378
|
-
return true;
|
|
4379
|
-
}
|
|
4380
|
-
function buildRecord(id, payload) {
|
|
4381
|
-
const idBytes = vlqBytes(id);
|
|
4382
|
-
const lenBytes = vlqBytes(payload.length);
|
|
4383
|
-
const header = Buffer.alloc(idBytes.length + lenBytes.length);
|
|
4384
|
-
idBytes.forEach((b, i) => header[i] = b);
|
|
4385
|
-
lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
|
|
4386
|
-
return Buffer.concat([header, payload]);
|
|
4387
|
-
}
|
|
4388
|
-
function readUtf16(buf, start, charCount) {
|
|
4389
|
-
const end = start + charCount * 2;
|
|
4390
|
-
if (end > buf.length) return "";
|
|
4391
|
-
return buf.toString("utf16le", start, end);
|
|
4392
|
-
}
|
|
4393
|
-
function parseSharedStringsBin(buf) {
|
|
4394
|
-
const values = [];
|
|
4395
|
-
let total = 0;
|
|
4396
|
-
let unique = 0;
|
|
4397
|
-
let endSstOffset = buf.length;
|
|
4398
|
-
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
4399
|
-
let pos = 0;
|
|
4400
|
-
while (readRecord(buf, pos, rec)) {
|
|
4401
|
-
pos = rec.dataEnd;
|
|
4402
|
-
if (rec.id === 159) {
|
|
4403
|
-
total = buf.readUInt32LE(rec.dataStart);
|
|
4404
|
-
unique = buf.readUInt32LE(rec.dataStart + 4);
|
|
4405
|
-
} else if (rec.id === 19) {
|
|
4406
|
-
const cch = buf.readUInt32LE(rec.dataStart + 1);
|
|
4407
|
-
values.push(readUtf16(buf, rec.dataStart + 5, cch));
|
|
4408
|
-
} else if (rec.id === 160) {
|
|
4409
|
-
endSstOffset = rec.headerStart;
|
|
5534
|
+
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
5535
|
+
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
5536
|
+
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
5537
|
+
this.zip.updateFile(path9, Buffer.from(xml, "utf8"));
|
|
4410
5538
|
}
|
|
4411
5539
|
}
|
|
4412
|
-
|
|
4413
|
-
|
|
5540
|
+
/**
|
|
5541
|
+
* Remove the stale calculation chain (xl/calcChain.xml). Excel rebuilds it
|
|
5542
|
+
* on open; keeping stale <c> entries for the replaced sheet would force a
|
|
5543
|
+
* repair prompt. Removes the part, its Content_Types override and its
|
|
5544
|
+
* workbook rels entry (xlspy parity).
|
|
5545
|
+
*/
|
|
5546
|
+
_removeCalcChain() {
|
|
5547
|
+
const calcPath = "xl/calcChain.xml";
|
|
5548
|
+
if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) {
|
|
5549
|
+
this._ensureCalcPrFullCalc();
|
|
5550
|
+
return;
|
|
5551
|
+
}
|
|
5552
|
+
try {
|
|
5553
|
+
this.zip.deleteFile(calcPath);
|
|
5554
|
+
} catch {
|
|
5555
|
+
}
|
|
5556
|
+
const stagedCalc = this.stagedParts.get(calcPath);
|
|
5557
|
+
if (stagedCalc) {
|
|
5558
|
+
this.stagedParts.delete(calcPath);
|
|
5559
|
+
try {
|
|
5560
|
+
fs5.rmSync(stagedCalc, { force: true });
|
|
5561
|
+
} catch {
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
const ctEntry = this.zip.getEntry("[Content_Types].xml");
|
|
5565
|
+
if (ctEntry || this.stagedParts.has("[Content_Types].xml")) {
|
|
5566
|
+
try {
|
|
5567
|
+
let ct = this._entryData("[Content_Types].xml").toString("utf8");
|
|
5568
|
+
const before = ct;
|
|
5569
|
+
ct = ct.replace(/<Override\b[^>]*PartName="\/xl\/calcChain\.xml"[^>]*\/?>/gi, "");
|
|
5570
|
+
if (ct !== before) {
|
|
5571
|
+
this._writePartText("[Content_Types].xml", ct);
|
|
5572
|
+
}
|
|
5573
|
+
} catch {
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
const relsPath = "xl/_rels/workbook.xml.rels";
|
|
5577
|
+
if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
|
|
5578
|
+
try {
|
|
5579
|
+
const rels = this._entryData(relsPath).toString("utf8");
|
|
5580
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5581
|
+
let m;
|
|
5582
|
+
let out = "";
|
|
5583
|
+
let last = 0;
|
|
5584
|
+
let changed = false;
|
|
5585
|
+
while ((m = relTagRe.exec(rels)) !== null) {
|
|
5586
|
+
const attrs = parseTagAttributes(m[0]);
|
|
5587
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
5588
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
5589
|
+
const isCalcChain = type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.xml";
|
|
5590
|
+
if (isCalcChain) {
|
|
5591
|
+
out += rels.substring(last, m.index);
|
|
5592
|
+
last = m.index + m[0].length;
|
|
5593
|
+
changed = true;
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
if (changed) {
|
|
5597
|
+
out += rels.substring(last);
|
|
5598
|
+
this._writePartText(relsPath, out);
|
|
5599
|
+
}
|
|
5600
|
+
} catch {
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
this._ensureCalcPrFullCalc();
|
|
5604
|
+
}
|
|
5605
|
+
_writePartText(name, text) {
|
|
5606
|
+
if (this.stagedParts.has(name)) {
|
|
5607
|
+
fs5.writeFileSync(this.stagedParts.get(name), text, "utf8");
|
|
5608
|
+
} else {
|
|
5609
|
+
this.zip.updateFile(name, Buffer.from(text, "utf8"));
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
_ensureCalcPrFullCalc() {
|
|
5613
|
+
try {
|
|
5614
|
+
const wbPath = "xl/workbook.xml";
|
|
5615
|
+
if (!this.zip.getEntry(wbPath) && !this.stagedParts.has(wbPath)) return;
|
|
5616
|
+
let xml = this._entryData(wbPath).toString("utf8");
|
|
5617
|
+
const calcPrRe = /<(?:[A-Za-z_][\w.-]*:)?calcPr\b[^>]*>/i;
|
|
5618
|
+
const match = calcPrRe.exec(xml);
|
|
5619
|
+
if (match && match.index !== void 0) {
|
|
5620
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(match[0], "fullCalcOnLoad", "1");
|
|
5621
|
+
xml = xml.substring(0, match.index) + newTag + xml.substring(match.index + match[0].length);
|
|
5622
|
+
} else {
|
|
5623
|
+
const insertAt = _XlsxUpdater._calcPrInsertPosition(xml);
|
|
5624
|
+
if (insertAt < 0) return;
|
|
5625
|
+
xml = xml.substring(0, insertAt) + '<calcPr fullCalcOnLoad="1"/>' + xml.substring(insertAt);
|
|
5626
|
+
}
|
|
5627
|
+
this._writePartText(wbPath, xml);
|
|
5628
|
+
} catch {
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
/**
|
|
5632
|
+
* Insertion offset for a missing `<calcPr>`. The element follows
|
|
5633
|
+
* sheets/externalReferences/definedNames in the workbook schema, so insert
|
|
5634
|
+
* after the last of those that exists instead of right after `<workbook>`.
|
|
5635
|
+
*/
|
|
5636
|
+
static _calcPrInsertPosition(xml) {
|
|
5637
|
+
const prefixed = "(?:[A-Za-z_][\\w.-]*:)?";
|
|
5638
|
+
const candidates = [
|
|
5639
|
+
new RegExp(`</${prefixed}sheets\\s*>`, "i"),
|
|
5640
|
+
new RegExp(`<${prefixed}externalReferences\\b[^>]*/>`, "i"),
|
|
5641
|
+
new RegExp(`<${prefixed}externalReferences\\b[^>]*>[\\s\\S]*?</${prefixed}externalReferences\\s*>`, "i"),
|
|
5642
|
+
new RegExp(`<${prefixed}definedNames\\b[^>]*/>`, "i"),
|
|
5643
|
+
new RegExp(`<${prefixed}definedNames\\b[^>]*>[\\s\\S]*?</${prefixed}definedNames\\s*>`, "i")
|
|
5644
|
+
];
|
|
5645
|
+
let insertAt = -1;
|
|
5646
|
+
for (const re of candidates) {
|
|
5647
|
+
const m = re.exec(xml);
|
|
5648
|
+
if (m && m.index !== void 0) {
|
|
5649
|
+
insertAt = Math.max(insertAt, m.index + m[0].length);
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
if (insertAt >= 0) return insertAt;
|
|
5653
|
+
const close = new RegExp(`</${prefixed}workbook\\s*>`, "i").exec(xml);
|
|
5654
|
+
if (close && close.index !== void 0) return close.index;
|
|
5655
|
+
const root = new RegExp(`<${prefixed}workbook\\b[^>]*>`, "i").exec(xml);
|
|
5656
|
+
if (!root || root.index === void 0) return -1;
|
|
5657
|
+
return root.index + root[0].length;
|
|
5658
|
+
}
|
|
5659
|
+
/**
|
|
5660
|
+
* Update ListObject table ranges (xl/tables/*.xml) for the replaced sheet.
|
|
5661
|
+
* Only tables anchored at A1 are updated to the new dimension; tables
|
|
5662
|
+
* elsewhere are left untouched (moving them would corrupt the sheet).
|
|
5663
|
+
* Empty replacements (dimRef "A1") do not shrink multi-column tables.
|
|
5664
|
+
*/
|
|
5665
|
+
_updateTableRefs(sheetName, dimRef) {
|
|
5666
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
5667
|
+
if (!sheetPath) return;
|
|
5668
|
+
const slash = sheetPath.lastIndexOf("/");
|
|
5669
|
+
const dir = slash >= 0 ? sheetPath.substring(0, slash + 1) : "";
|
|
5670
|
+
const base = slash >= 0 ? sheetPath.substring(slash + 1) : sheetPath;
|
|
5671
|
+
const sheetRelsPath = `${dir}_rels/${base}.rels`;
|
|
5672
|
+
const sheetRelsEntry = this.zip.getEntry(sheetRelsPath);
|
|
5673
|
+
const hasStagedRels = this.stagedParts.has(sheetRelsPath);
|
|
5674
|
+
if (!sheetRelsEntry && !hasStagedRels) return;
|
|
5675
|
+
const tableTargets = [];
|
|
5676
|
+
try {
|
|
5677
|
+
const relsXml = this._entryData(sheetRelsPath).toString("utf8");
|
|
5678
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5679
|
+
let m;
|
|
5680
|
+
while ((m = relTagRe.exec(relsXml)) !== null) {
|
|
5681
|
+
const attrs = parseTagAttributes(m[0]);
|
|
5682
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
5683
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
5684
|
+
if (!type.includes("/table") || !target) continue;
|
|
5685
|
+
let tablePath;
|
|
5686
|
+
if (target.startsWith("/")) {
|
|
5687
|
+
tablePath = resolveRelTarget(target);
|
|
5688
|
+
} else {
|
|
5689
|
+
const combined = dir + target;
|
|
5690
|
+
const parts = [];
|
|
5691
|
+
for (const seg of combined.split("/")) {
|
|
5692
|
+
if (seg === "..") parts.pop();
|
|
5693
|
+
else if (seg !== "." && seg !== "") parts.push(seg);
|
|
5694
|
+
}
|
|
5695
|
+
tablePath = parts.join("/");
|
|
5696
|
+
}
|
|
5697
|
+
tableTargets.push(tablePath);
|
|
5698
|
+
}
|
|
5699
|
+
} catch {
|
|
5700
|
+
return;
|
|
5701
|
+
}
|
|
5702
|
+
if (tableTargets.length === 0) return;
|
|
5703
|
+
const tables = [];
|
|
5704
|
+
for (const tablePath of tableTargets) {
|
|
5705
|
+
try {
|
|
5706
|
+
const hasEntry = !!this.zip.getEntry(tablePath);
|
|
5707
|
+
const hasStaged = this.stagedParts.has(tablePath);
|
|
5708
|
+
if (!hasEntry && !hasStaged) continue;
|
|
5709
|
+
const xml = this._entryData(tablePath).toString("utf8");
|
|
5710
|
+
const tableTagRe = /<(?:[A-Za-z_][\w.-]*:)?table\b[^>]*>/i;
|
|
5711
|
+
const tableMatch = tableTagRe.exec(xml);
|
|
5712
|
+
if (!tableMatch || tableMatch.index === void 0) continue;
|
|
5713
|
+
const refMatch = /\bref\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tableMatch[0]);
|
|
5714
|
+
const ref = refMatch ? refMatch[2] ?? refMatch[3] ?? "" : "";
|
|
5715
|
+
tables.push({
|
|
5716
|
+
path: tablePath,
|
|
5717
|
+
xml,
|
|
5718
|
+
tag: tableMatch[0],
|
|
5719
|
+
tagIndex: tableMatch.index,
|
|
5720
|
+
ref,
|
|
5721
|
+
range: parseCellRange(ref),
|
|
5722
|
+
staged: hasStaged
|
|
5723
|
+
});
|
|
5724
|
+
} catch {
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
if (tables.length === 0) return;
|
|
5728
|
+
const dimRange = parseCellRange(dimRef);
|
|
5729
|
+
if (!dimRange) return;
|
|
5730
|
+
for (const table of tables) {
|
|
5731
|
+
try {
|
|
5732
|
+
const origRange = table.range;
|
|
5733
|
+
if (!origRange) continue;
|
|
5734
|
+
if (origRange.r1 !== 1 || origRange.c1 !== 0) continue;
|
|
5735
|
+
if (dimRef === "A1" && !/^A1$/i.test(table.ref)) continue;
|
|
5736
|
+
const origCols = origRange.c2 - origRange.c1 + 1;
|
|
5737
|
+
const newCols = dimRange.c2 - dimRange.c1 + 1;
|
|
5738
|
+
if (newCols !== origCols) continue;
|
|
5739
|
+
let lastRow = dimRange.r2;
|
|
5740
|
+
for (const other of tables) {
|
|
5741
|
+
if (other === table || !other.range) continue;
|
|
5742
|
+
if (other.range.r1 <= 1) continue;
|
|
5743
|
+
if (other.range.c1 > origRange.c2 || other.range.c2 < origRange.c1) continue;
|
|
5744
|
+
lastRow = Math.min(lastRow, other.range.r1 - 1);
|
|
5745
|
+
}
|
|
5746
|
+
if (lastRow < 1) continue;
|
|
5747
|
+
const newRef = origRange.c2 === 0 && lastRow === 1 ? "A1" : `A1:${columnIndexToLetter(origRange.c2)}${lastRow}`;
|
|
5748
|
+
let xml = table.xml;
|
|
5749
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(table.tag, "ref", newRef);
|
|
5750
|
+
xml = xml.substring(0, table.tagIndex) + newTag + xml.substring(table.tagIndex + table.tag.length);
|
|
5751
|
+
const afRe = /<(?:[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i;
|
|
5752
|
+
const afMatch = afRe.exec(xml);
|
|
5753
|
+
if (afMatch && afMatch.index !== void 0) {
|
|
5754
|
+
const newAf = _XlsxUpdater._replaceOrAddXmlAttribute(afMatch[0], "ref", newRef);
|
|
5755
|
+
xml = xml.substring(0, afMatch.index) + newAf + xml.substring(afMatch.index + afMatch[0].length);
|
|
5756
|
+
}
|
|
5757
|
+
if (table.staged) {
|
|
5758
|
+
fs5.writeFileSync(this.stagedParts.get(table.path), xml, "utf8");
|
|
5759
|
+
} else {
|
|
5760
|
+
this.zip.updateFile(table.path, Buffer.from(xml, "utf8"));
|
|
5761
|
+
}
|
|
5762
|
+
} catch {
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5766
|
+
};
|
|
4414
5767
|
|
|
4415
5768
|
// src/XlsbUpdater.ts
|
|
5769
|
+
import * as fs6 from "fs";
|
|
5770
|
+
import * as os2 from "os";
|
|
5771
|
+
import * as path7 from "path";
|
|
5772
|
+
import AdmZip3 from "adm-zip";
|
|
4416
5773
|
var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
|
4417
5774
|
var RK_INT_LOWER = -1 << 29;
|
|
4418
5775
|
var RK_INT_UPPER = (1 << 29) - 1;
|
|
@@ -4420,7 +5777,9 @@ var XlsbUpdater = class {
|
|
|
4420
5777
|
/**
|
|
4421
5778
|
* @param path Path to an existing `.xlsb` file.
|
|
4422
5779
|
*/
|
|
4423
|
-
constructor(
|
|
5780
|
+
constructor(filePath) {
|
|
5781
|
+
this.temporaryDirectory = null;
|
|
5782
|
+
this.stagedParts = /* @__PURE__ */ new Map();
|
|
4424
5783
|
this.sheetNameToPath = /* @__PURE__ */ new Map();
|
|
4425
5784
|
this.pivotCacheDefPaths = [];
|
|
4426
5785
|
this.sharedStringsBuf = null;
|
|
@@ -4429,16 +5788,17 @@ var XlsbUpdater = class {
|
|
|
4429
5788
|
this.sharedStringsUnique = 0;
|
|
4430
5789
|
this.sharedStringsEndSst = 0;
|
|
4431
5790
|
this.stringIndexMap = /* @__PURE__ */ new Map();
|
|
5791
|
+
this.sharedStringsDirty = false;
|
|
4432
5792
|
// ------------------------------------------------------------------
|
|
4433
5793
|
// Shared strings
|
|
4434
5794
|
// ------------------------------------------------------------------
|
|
4435
5795
|
this._appendCount = 0;
|
|
4436
|
-
this.sourcePath =
|
|
5796
|
+
this.sourcePath = filePath;
|
|
4437
5797
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
4438
|
-
if (!
|
|
4439
|
-
throw new Error(`XlsbUpdater: file not found: ${
|
|
5798
|
+
if (!fs6.existsSync(filePath)) {
|
|
5799
|
+
throw new Error(`XlsbUpdater: file not found: ${filePath}`);
|
|
4440
5800
|
}
|
|
4441
|
-
this.zip = new AdmZip3(
|
|
5801
|
+
this.zip = new AdmZip3(filePath);
|
|
4442
5802
|
if (!this.zip.getEntry("xl/workbook.bin")) {
|
|
4443
5803
|
throw new Error(
|
|
4444
5804
|
"XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
|
|
@@ -4469,8 +5829,15 @@ var XlsbUpdater = class {
|
|
|
4469
5829
|
if (!entry) {
|
|
4470
5830
|
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
4471
5831
|
}
|
|
4472
|
-
const sheetBuf =
|
|
5832
|
+
const sheetBuf = this._entryData(sheetPath);
|
|
4473
5833
|
this._ensureSharedStringsLoaded();
|
|
5834
|
+
if (this.sharedStringsBuf !== null) {
|
|
5835
|
+
this.sharedStringsTotal = Math.max(
|
|
5836
|
+
0,
|
|
5837
|
+
this.sharedStringsTotal - this._sharedStringRefCount(sheetBuf)
|
|
5838
|
+
);
|
|
5839
|
+
this.sharedStringsDirty = true;
|
|
5840
|
+
}
|
|
4474
5841
|
let dataStyles = /* @__PURE__ */ new Map();
|
|
4475
5842
|
let headerStyles = /* @__PURE__ */ new Map();
|
|
4476
5843
|
let dateXf = null;
|
|
@@ -4499,12 +5866,222 @@ var XlsbUpdater = class {
|
|
|
4499
5866
|
}
|
|
4500
5867
|
this._patchDimension(newSheet, lastRowIdx, lastCol);
|
|
4501
5868
|
this.zip.updateFile(sheetPath, newSheet);
|
|
4502
|
-
|
|
5869
|
+
this._discardStagedPart(sheetPath);
|
|
5870
|
+
if (this.sharedStringsBuf !== null) {
|
|
4503
5871
|
this._commitSharedStrings();
|
|
4504
5872
|
}
|
|
4505
5873
|
const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
|
|
4506
5874
|
const colLast = lastCol;
|
|
4507
5875
|
this._patchPivotCaches(sheetName, rwLast, colLast);
|
|
5876
|
+
this._removeCalcChain();
|
|
5877
|
+
}
|
|
5878
|
+
/** Replace worksheet rows from a one-pass source. */
|
|
5879
|
+
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
5880
|
+
const { headers, styleFallback = "inherit" } = options;
|
|
5881
|
+
if (styleFallback !== "inherit" && styleFallback !== "general") {
|
|
5882
|
+
throw new Error("XlsbUpdater: styleFallback must be 'inherit' or 'general'.");
|
|
5883
|
+
}
|
|
5884
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
5885
|
+
if (!sheetPath) {
|
|
5886
|
+
throw new Error(`XlsbUpdater: sheet "${sheetName}" not found in the workbook.`);
|
|
5887
|
+
}
|
|
5888
|
+
const entry = this.zip.getEntry(sheetPath);
|
|
5889
|
+
if (!entry) {
|
|
5890
|
+
throw new Error(`XlsbUpdater: worksheet part missing for sheet "${sheetName}".`);
|
|
5891
|
+
}
|
|
5892
|
+
const oldStage = this.stagedParts.get(sheetPath);
|
|
5893
|
+
const beforeValuesLength = this.sharedStringsValues.length;
|
|
5894
|
+
const beforeAppendCount = this._appendCount;
|
|
5895
|
+
const beforeSharedStringsTotal = this.sharedStringsTotal;
|
|
5896
|
+
const beforeSharedStringsUnique = this.sharedStringsUnique;
|
|
5897
|
+
const beforeSharedStringsEndSst = this.sharedStringsEndSst;
|
|
5898
|
+
const beforeSharedStringsDirty = this.sharedStringsDirty;
|
|
5899
|
+
const beforeStringIndexMap = new Map(this.stringIndexMap);
|
|
5900
|
+
const sharedStringsEntry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
5901
|
+
const beforeSharedStringsBytes = sharedStringsEntry || this.stagedParts.has("xl/sharedStrings.bin") ? Buffer.from(this._entryData("xl/sharedStrings.bin")) : null;
|
|
5902
|
+
const beforeSharedStringsBuf = this.sharedStringsBuf ? Buffer.from(this.sharedStringsBuf) : null;
|
|
5903
|
+
const oldSheet = this._entryData(sheetPath);
|
|
5904
|
+
const rowsPath = this._temporaryPath(".rows");
|
|
5905
|
+
const outputPath = this._temporaryPath(".bin");
|
|
5906
|
+
const createdParts = [rowsPath, outputPath];
|
|
5907
|
+
let output = null;
|
|
5908
|
+
try {
|
|
5909
|
+
this._ensureSharedStringsLoaded();
|
|
5910
|
+
if (this.sharedStringsBuf !== null) {
|
|
5911
|
+
this.sharedStringsTotal = Math.max(
|
|
5912
|
+
0,
|
|
5913
|
+
this.sharedStringsTotal - this._sharedStringRefCount(oldSheet)
|
|
5914
|
+
);
|
|
5915
|
+
this.sharedStringsDirty = true;
|
|
5916
|
+
}
|
|
5917
|
+
let dataStyles = /* @__PURE__ */ new Map();
|
|
5918
|
+
let headerStyles = /* @__PURE__ */ new Map();
|
|
5919
|
+
let dateXf = null;
|
|
5920
|
+
if (styleFallback !== "general") {
|
|
5921
|
+
const collected = this._collectStyles(oldSheet);
|
|
5922
|
+
dataStyles = collected.dataStyles;
|
|
5923
|
+
headerStyles = collected.headerStyles;
|
|
5924
|
+
dateXf = this._findDateXf();
|
|
5925
|
+
}
|
|
5926
|
+
const rowOutput = fs6.createWriteStream(rowsPath);
|
|
5927
|
+
output = rowOutput;
|
|
5928
|
+
let outputError = null;
|
|
5929
|
+
rowOutput.on("error", (error) => {
|
|
5930
|
+
outputError = error;
|
|
5931
|
+
});
|
|
5932
|
+
let writtenBytes = 0;
|
|
5933
|
+
let lastKeptEnd = 0;
|
|
5934
|
+
let width = (headers == null ? void 0 : headers.length) ?? 0;
|
|
5935
|
+
let dataRowsSeen = 0;
|
|
5936
|
+
let keptDataRows = 0;
|
|
5937
|
+
let pendingEmptyWidth = 0;
|
|
5938
|
+
let nextRow = 0;
|
|
5939
|
+
const rowHeaderOffsets = [];
|
|
5940
|
+
const write = async (data) => {
|
|
5941
|
+
if (outputError) throw outputError;
|
|
5942
|
+
writtenBytes += data.length;
|
|
5943
|
+
if (rowOutput.write(data)) return;
|
|
5944
|
+
await new Promise((resolve6, reject) => {
|
|
5945
|
+
const onDrain = () => {
|
|
5946
|
+
cleanup();
|
|
5947
|
+
resolve6();
|
|
5948
|
+
};
|
|
5949
|
+
const onError = (error) => {
|
|
5950
|
+
cleanup();
|
|
5951
|
+
reject(error);
|
|
5952
|
+
};
|
|
5953
|
+
const cleanup = () => {
|
|
5954
|
+
rowOutput.off("drain", onDrain);
|
|
5955
|
+
rowOutput.off("error", onError);
|
|
5956
|
+
};
|
|
5957
|
+
rowOutput.once("drain", onDrain);
|
|
5958
|
+
rowOutput.once("error", onError);
|
|
5959
|
+
});
|
|
5960
|
+
};
|
|
5961
|
+
const writeRow = async (rowNumber, cells) => {
|
|
5962
|
+
rowHeaderOffsets.push(writtenBytes);
|
|
5963
|
+
const header = Buffer.alloc(27);
|
|
5964
|
+
header[0] = 0;
|
|
5965
|
+
header[1] = 25;
|
|
5966
|
+
header.writeInt32LE(rowNumber, 2);
|
|
5967
|
+
header[10] = 44;
|
|
5968
|
+
header[11] = 1;
|
|
5969
|
+
header[15] = 1;
|
|
5970
|
+
header.writeInt32LE(0, 19);
|
|
5971
|
+
header.writeInt32LE(0, 23);
|
|
5972
|
+
await write(header);
|
|
5973
|
+
for (const cell of cells) await write(cell);
|
|
5974
|
+
};
|
|
5975
|
+
if (headers !== void 0) {
|
|
5976
|
+
await writeRow(0, headers.map((value, column) => this._stringCellBytes(column, styleFallback === "general" ? 0 : headerStyles.get(column) ?? 0, value)));
|
|
5977
|
+
lastKeptEnd = writtenBytes;
|
|
5978
|
+
nextRow = 1;
|
|
5979
|
+
}
|
|
5980
|
+
for await (const sourceRow of rows) {
|
|
5981
|
+
if (typeof sourceRow === "string" || sourceRow instanceof Uint8Array) {
|
|
5982
|
+
throw new TypeError("Each row must be an iterable of cells, not text");
|
|
5983
|
+
}
|
|
5984
|
+
const row = Array.from(sourceRow);
|
|
5985
|
+
const cells = row.map((value, column) => value === null || value === void 0 ? null : this._valueCellBytes(value, column, dataStyles, dateXf)).filter((cell) => cell !== null);
|
|
5986
|
+
await writeRow(nextRow, cells);
|
|
5987
|
+
dataRowsSeen++;
|
|
5988
|
+
nextRow++;
|
|
5989
|
+
if (row.every((value) => value === null || value === void 0)) {
|
|
5990
|
+
pendingEmptyWidth = Math.max(pendingEmptyWidth, row.length);
|
|
5991
|
+
} else {
|
|
5992
|
+
width = Math.max(width, pendingEmptyWidth, row.length);
|
|
5993
|
+
pendingEmptyWidth = 0;
|
|
5994
|
+
keptDataRows = dataRowsSeen;
|
|
5995
|
+
lastKeptEnd = writtenBytes;
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
await new Promise((resolve6, reject) => {
|
|
5999
|
+
rowOutput.once("close", resolve6);
|
|
6000
|
+
rowOutput.once("error", reject);
|
|
6001
|
+
rowOutput.end();
|
|
6002
|
+
});
|
|
6003
|
+
if (outputError) throw outputError;
|
|
6004
|
+
const rowsFd = fs6.openSync(rowsPath, "r+");
|
|
6005
|
+
try {
|
|
6006
|
+
fs6.ftruncateSync(rowsFd, lastKeptEnd);
|
|
6007
|
+
} finally {
|
|
6008
|
+
fs6.closeSync(rowsFd);
|
|
6009
|
+
}
|
|
6010
|
+
const lastCol = Math.max(0, width - 1);
|
|
6011
|
+
const rowPatchFd = fs6.openSync(rowsPath, "r+");
|
|
6012
|
+
try {
|
|
6013
|
+
const lastColBuffer = Buffer.alloc(4);
|
|
6014
|
+
lastColBuffer.writeInt32LE(lastCol, 0);
|
|
6015
|
+
for (const offset of rowHeaderOffsets) {
|
|
6016
|
+
if (offset >= lastKeptEnd) continue;
|
|
6017
|
+
fs6.writeSync(rowPatchFd, lastColBuffer, 0, lastColBuffer.length, offset + 23);
|
|
6018
|
+
}
|
|
6019
|
+
} finally {
|
|
6020
|
+
fs6.closeSync(rowPatchFd);
|
|
6021
|
+
}
|
|
6022
|
+
const { rowsStart, rowsEnd, autoFilterRanges } = this._findRowsRegion(oldSheet);
|
|
6023
|
+
const oldLength = oldSheet.length;
|
|
6024
|
+
const newRowsLength = fs6.statSync(rowsPath).size;
|
|
6025
|
+
const delta = newRowsLength - (rowsEnd - rowsStart);
|
|
6026
|
+
const outputFd = fs6.openSync(outputPath, "w");
|
|
6027
|
+
try {
|
|
6028
|
+
fs6.writeSync(outputFd, oldSheet, 0, rowsStart, null);
|
|
6029
|
+
const rowsFdRead = fs6.openSync(rowsPath, "r");
|
|
6030
|
+
try {
|
|
6031
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
6032
|
+
let position = 0;
|
|
6033
|
+
while (position < newRowsLength) {
|
|
6034
|
+
const count = fs6.readSync(rowsFdRead, chunk, 0, Math.min(chunk.length, newRowsLength - position), position);
|
|
6035
|
+
if (count <= 0) throw new Error("XlsbUpdater: failed to read staged rows.");
|
|
6036
|
+
fs6.writeSync(outputFd, chunk, 0, count);
|
|
6037
|
+
position += count;
|
|
6038
|
+
}
|
|
6039
|
+
} finally {
|
|
6040
|
+
fs6.closeSync(rowsFdRead);
|
|
6041
|
+
}
|
|
6042
|
+
fs6.writeSync(outputFd, oldSheet, rowsEnd, oldLength - rowsEnd, rowsStart + newRowsLength);
|
|
6043
|
+
} finally {
|
|
6044
|
+
fs6.closeSync(outputFd);
|
|
6045
|
+
}
|
|
6046
|
+
this._patchSheetMetadataFile(
|
|
6047
|
+
outputPath,
|
|
6048
|
+
oldSheet,
|
|
6049
|
+
rowsEnd,
|
|
6050
|
+
delta,
|
|
6051
|
+
autoFilterRanges,
|
|
6052
|
+
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
6053
|
+
lastCol
|
|
6054
|
+
);
|
|
6055
|
+
this._stagePart(sheetPath, outputPath);
|
|
6056
|
+
this._commitSharedStrings();
|
|
6057
|
+
this._patchPivotCaches(
|
|
6058
|
+
sheetName,
|
|
6059
|
+
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
6060
|
+
lastCol
|
|
6061
|
+
);
|
|
6062
|
+
this._removeCalcChain();
|
|
6063
|
+
if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
|
|
6064
|
+
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
6065
|
+
} catch (error) {
|
|
6066
|
+
this.sharedStringsValues.length = beforeValuesLength;
|
|
6067
|
+
this._appendCount = beforeAppendCount;
|
|
6068
|
+
this.sharedStringsTotal = beforeSharedStringsTotal;
|
|
6069
|
+
this.sharedStringsUnique = beforeSharedStringsUnique;
|
|
6070
|
+
this.sharedStringsEndSst = beforeSharedStringsEndSst;
|
|
6071
|
+
this.sharedStringsDirty = beforeSharedStringsDirty;
|
|
6072
|
+
this.sharedStringsBuf = beforeSharedStringsBuf;
|
|
6073
|
+
this.stringIndexMap.clear();
|
|
6074
|
+
for (const [value, index] of beforeStringIndexMap) this.stringIndexMap.set(value, index);
|
|
6075
|
+
if (beforeSharedStringsBytes) {
|
|
6076
|
+
this.zip.updateFile("xl/sharedStrings.bin", beforeSharedStringsBytes);
|
|
6077
|
+
}
|
|
6078
|
+
if (oldStage) this.stagedParts.set(sheetPath, oldStage);
|
|
6079
|
+
else this.stagedParts.delete(sheetPath);
|
|
6080
|
+
throw error;
|
|
6081
|
+
} finally {
|
|
6082
|
+
if (output && !output.closed) output.destroy();
|
|
6083
|
+
for (const temporaryPath of createdParts) this._removeTemporaryFile(temporaryPath);
|
|
6084
|
+
}
|
|
4508
6085
|
}
|
|
4509
6086
|
/**
|
|
4510
6087
|
* Write the updated workbook to disk.
|
|
@@ -4512,12 +6089,89 @@ var XlsbUpdater = class {
|
|
|
4512
6089
|
*/
|
|
4513
6090
|
save(outputPath) {
|
|
4514
6091
|
const target = outputPath ?? this.sourcePath;
|
|
4515
|
-
|
|
6092
|
+
this._materializeStagedParts();
|
|
6093
|
+
writeBufferAtomically(this.zip.toBuffer(), target);
|
|
6094
|
+
}
|
|
6095
|
+
/** Save staged streaming replacements without materialising the ZIP. */
|
|
6096
|
+
async saveStreaming(outputPath) {
|
|
6097
|
+
const target = outputPath ?? this.sourcePath;
|
|
6098
|
+
await writeAdmZipStreaming(this.zip, this.stagedParts, target);
|
|
6099
|
+
this._materializeStagedParts();
|
|
4516
6100
|
}
|
|
4517
6101
|
/** The updated workbook as an in-memory ZIP buffer. */
|
|
4518
6102
|
toBuffer() {
|
|
6103
|
+
this._materializeStagedParts();
|
|
4519
6104
|
return this.zip.toBuffer();
|
|
4520
6105
|
}
|
|
6106
|
+
/**
|
|
6107
|
+
* Release the staging directory and any unsaved staged parts.
|
|
6108
|
+
* The updater remains usable; a new staging directory is created lazily
|
|
6109
|
+
* on the next streaming replacement.
|
|
6110
|
+
*/
|
|
6111
|
+
dispose() {
|
|
6112
|
+
for (const stagedPath of this.stagedParts.values()) {
|
|
6113
|
+
try {
|
|
6114
|
+
fs6.rmSync(stagedPath, { force: true });
|
|
6115
|
+
} catch {
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
this.stagedParts.clear();
|
|
6119
|
+
if (this.temporaryDirectory !== null) {
|
|
6120
|
+
try {
|
|
6121
|
+
fs6.rmSync(this.temporaryDirectory, { recursive: true, force: true });
|
|
6122
|
+
} catch {
|
|
6123
|
+
}
|
|
6124
|
+
this.temporaryDirectory = null;
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
/** Support `using` declarations for automatic staging cleanup. */
|
|
6128
|
+
[Symbol.dispose]() {
|
|
6129
|
+
this.dispose();
|
|
6130
|
+
}
|
|
6131
|
+
_ensureTemporaryDirectory() {
|
|
6132
|
+
if (this.temporaryDirectory === null) {
|
|
6133
|
+
this.temporaryDirectory = fs6.mkdtempSync(path7.join(os2.tmpdir(), "spreadsheet-updater-"));
|
|
6134
|
+
} else {
|
|
6135
|
+
try {
|
|
6136
|
+
fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
6137
|
+
} catch {
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
return this.temporaryDirectory;
|
|
6141
|
+
}
|
|
6142
|
+
_temporaryPath(suffix) {
|
|
6143
|
+
return path7.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
6144
|
+
}
|
|
6145
|
+
_removeTemporaryFile(filePath) {
|
|
6146
|
+
try {
|
|
6147
|
+
fs6.rmSync(filePath);
|
|
6148
|
+
} catch (error) {
|
|
6149
|
+
if (error.code !== "ENOENT") throw error;
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
_entryData(name) {
|
|
6153
|
+
const stagedPath = this.stagedParts.get(name);
|
|
6154
|
+
if (stagedPath) return fs6.readFileSync(stagedPath);
|
|
6155
|
+
const entry = this.zip.getEntry(name);
|
|
6156
|
+
if (!entry) throw new Error(`XlsbUpdater: ZIP member missing: ${name}`);
|
|
6157
|
+
return entry.getData();
|
|
6158
|
+
}
|
|
6159
|
+
_stagePart(name, filePath) {
|
|
6160
|
+
this.stagedParts.set(name, filePath);
|
|
6161
|
+
}
|
|
6162
|
+
_discardStagedPart(name) {
|
|
6163
|
+
const stagedPath = this.stagedParts.get(name);
|
|
6164
|
+
if (!stagedPath) return;
|
|
6165
|
+
this._removeTemporaryFile(stagedPath);
|
|
6166
|
+
this.stagedParts.delete(name);
|
|
6167
|
+
}
|
|
6168
|
+
_materializeStagedParts() {
|
|
6169
|
+
for (const [name, stagedPath] of this.stagedParts) {
|
|
6170
|
+
this.zip.updateFile(name, fs6.readFileSync(stagedPath));
|
|
6171
|
+
this._removeTemporaryFile(stagedPath);
|
|
6172
|
+
}
|
|
6173
|
+
this.stagedParts.clear();
|
|
6174
|
+
}
|
|
4521
6175
|
// ------------------------------------------------------------------
|
|
4522
6176
|
// Workbook structure
|
|
4523
6177
|
// ------------------------------------------------------------------
|
|
@@ -4526,10 +6180,15 @@ var XlsbUpdater = class {
|
|
|
4526
6180
|
const rIdToTarget = {};
|
|
4527
6181
|
if (relsEntry) {
|
|
4528
6182
|
const rels = relsEntry.getData().toString("utf8");
|
|
4529
|
-
const
|
|
4530
|
-
let
|
|
4531
|
-
while ((
|
|
4532
|
-
|
|
6183
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
6184
|
+
let tagMatch;
|
|
6185
|
+
while ((tagMatch = relTagRe.exec(rels)) !== null) {
|
|
6186
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
6187
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
6188
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
6189
|
+
if (id !== void 0 && target !== void 0) {
|
|
6190
|
+
rIdToTarget[id] = target;
|
|
6191
|
+
}
|
|
4533
6192
|
}
|
|
4534
6193
|
}
|
|
4535
6194
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
@@ -4549,12 +6208,24 @@ var XlsbUpdater = class {
|
|
|
4549
6208
|
o += 4;
|
|
4550
6209
|
const name = readUtf16(wbBuf, o, nameLen);
|
|
4551
6210
|
let target = rIdToTarget[rId] ?? "";
|
|
4552
|
-
|
|
4553
|
-
else if (!target.startsWith("xl/")) target = "xl/" + target;
|
|
6211
|
+
target = resolveRelTarget(target);
|
|
4554
6212
|
if (target) {
|
|
4555
6213
|
this.sheetNameToPath.set(name, target);
|
|
4556
6214
|
}
|
|
4557
6215
|
}
|
|
6216
|
+
try {
|
|
6217
|
+
const rec1904 = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
6218
|
+
let p1904 = 0;
|
|
6219
|
+
while (readRecord(wbBuf, p1904, rec1904)) {
|
|
6220
|
+
if (rec1904.id === 153 && rec1904.len >= 4) {
|
|
6221
|
+
const is1904 = (wbBuf[rec1904.dataStart] & 1) !== 0;
|
|
6222
|
+
this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
6223
|
+
break;
|
|
6224
|
+
}
|
|
6225
|
+
p1904 = rec1904.dataEnd;
|
|
6226
|
+
}
|
|
6227
|
+
} catch {
|
|
6228
|
+
}
|
|
4558
6229
|
}
|
|
4559
6230
|
for (const e of this.zip.getEntries()) {
|
|
4560
6231
|
if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
|
|
@@ -4567,11 +6238,11 @@ var XlsbUpdater = class {
|
|
|
4567
6238
|
return;
|
|
4568
6239
|
}
|
|
4569
6240
|
const entry = this.zip.getEntry("xl/sharedStrings.bin");
|
|
4570
|
-
if (!entry) {
|
|
6241
|
+
if (!entry && !this.stagedParts.has("xl/sharedStrings.bin")) {
|
|
4571
6242
|
this.sharedStringsBuf = null;
|
|
4572
6243
|
return;
|
|
4573
6244
|
}
|
|
4574
|
-
this.sharedStringsBuf =
|
|
6245
|
+
this.sharedStringsBuf = this._entryData("xl/sharedStrings.bin");
|
|
4575
6246
|
const parsed = parseSharedStringsBin(this.sharedStringsBuf);
|
|
4576
6247
|
this.sharedStringsValues = parsed.values;
|
|
4577
6248
|
this.sharedStringsTotal = parsed.total;
|
|
@@ -4583,9 +6254,10 @@ var XlsbUpdater = class {
|
|
|
4583
6254
|
}
|
|
4584
6255
|
});
|
|
4585
6256
|
this._appendCount = 0;
|
|
6257
|
+
this.sharedStringsDirty = false;
|
|
4586
6258
|
}
|
|
4587
6259
|
_commitSharedStrings() {
|
|
4588
|
-
if (this.sharedStringsBuf === null || this.
|
|
6260
|
+
if (this.sharedStringsBuf === null || !this.sharedStringsDirty) return;
|
|
4589
6261
|
const originalValuesLength = this.sharedStringsValues.length - this._appendCount;
|
|
4590
6262
|
let newItems = Buffer.alloc(0);
|
|
4591
6263
|
for (let i = originalValuesLength; i < this.sharedStringsValues.length; i++) {
|
|
@@ -4611,6 +6283,9 @@ var XlsbUpdater = class {
|
|
|
4611
6283
|
pos = rec.dataEnd;
|
|
4612
6284
|
}
|
|
4613
6285
|
this.zip.updateFile("xl/sharedStrings.bin", out);
|
|
6286
|
+
this.sharedStringsBuf = out;
|
|
6287
|
+
this._appendCount = 0;
|
|
6288
|
+
this.sharedStringsDirty = false;
|
|
4614
6289
|
}
|
|
4615
6290
|
// ------------------------------------------------------------------
|
|
4616
6291
|
// Styles
|
|
@@ -4659,27 +6334,15 @@ var XlsbUpdater = class {
|
|
|
4659
6334
|
_findDateXf() {
|
|
4660
6335
|
const entry = this.zip.getEntry("xl/styles.bin");
|
|
4661
6336
|
if (!entry) return null;
|
|
4662
|
-
const
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
if (
|
|
4670
|
-
|
|
4671
|
-
xfIndex = 0;
|
|
4672
|
-
continue;
|
|
4673
|
-
}
|
|
4674
|
-
if (rec.id === 618) {
|
|
4675
|
-
inCellXfs = false;
|
|
4676
|
-
continue;
|
|
4677
|
-
}
|
|
4678
|
-
if (inCellXfs && rec.id === 47 && rec.len >= 4) {
|
|
4679
|
-
const numFmtId = stylesBuf.readUInt16LE(rec.dataStart + 2);
|
|
4680
|
-
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47;
|
|
4681
|
-
if (isDate) return xfIndex;
|
|
4682
|
-
xfIndex++;
|
|
6337
|
+
const reader = new BiffReaderWriter(entry.getData());
|
|
6338
|
+
while (reader.readStyles()) {
|
|
6339
|
+
}
|
|
6340
|
+
const numFmtIds = reader._xfIndexToNumFmtId;
|
|
6341
|
+
const customDates = reader._customNumFmts;
|
|
6342
|
+
for (let i = 0; i < numFmtIds.length; i++) {
|
|
6343
|
+
const numFmtId = numFmtIds[i];
|
|
6344
|
+
if (numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || customDates.has(numFmtId)) {
|
|
6345
|
+
return i;
|
|
4683
6346
|
}
|
|
4684
6347
|
}
|
|
4685
6348
|
return null;
|
|
@@ -4759,7 +6422,7 @@ var XlsbUpdater = class {
|
|
|
4759
6422
|
return buildRecord(4, payload);
|
|
4760
6423
|
}
|
|
4761
6424
|
if (val instanceof Date) {
|
|
4762
|
-
const oaDate = (val.getTime()
|
|
6425
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
4763
6426
|
if (Number.isFinite(oaDate)) {
|
|
4764
6427
|
const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
|
|
4765
6428
|
const payload = Buffer.alloc(16);
|
|
@@ -4793,12 +6456,51 @@ var XlsbUpdater = class {
|
|
|
4793
6456
|
this._appendCount++;
|
|
4794
6457
|
}
|
|
4795
6458
|
this.sharedStringsTotal++;
|
|
6459
|
+
this.sharedStringsDirty = true;
|
|
4796
6460
|
const payload = Buffer.alloc(12);
|
|
4797
6461
|
payload.writeUInt32LE(col, 0);
|
|
4798
6462
|
payload.writeUInt32LE(style, 4);
|
|
4799
6463
|
payload.writeUInt32LE(index, 8);
|
|
4800
6464
|
return buildRecord(7, payload);
|
|
4801
6465
|
}
|
|
6466
|
+
_sharedStringRefCount(sheetBuf) {
|
|
6467
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
6468
|
+
let pos = 0;
|
|
6469
|
+
let count = 0;
|
|
6470
|
+
while (readRecord(sheetBuf, pos, rec)) {
|
|
6471
|
+
if (rec.id === 7) count++;
|
|
6472
|
+
pos = rec.dataEnd;
|
|
6473
|
+
}
|
|
6474
|
+
return count;
|
|
6475
|
+
}
|
|
6476
|
+
_patchSheetMetadataFile(filePath, originalSheet, rowsEnd, delta, autoFilterRanges, lastRowIdx, lastCol) {
|
|
6477
|
+
const fd = fs6.openSync(filePath, "r+");
|
|
6478
|
+
try {
|
|
6479
|
+
const patchInt32 = (offset, value) => {
|
|
6480
|
+
const bytes = Buffer.alloc(4);
|
|
6481
|
+
bytes.writeInt32LE(value, 0);
|
|
6482
|
+
fs6.writeSync(fd, bytes, 0, bytes.length, offset);
|
|
6483
|
+
};
|
|
6484
|
+
for (const filterOffset of autoFilterRanges) {
|
|
6485
|
+
const newOffset = filterOffset >= rowsEnd ? filterOffset + delta : filterOffset;
|
|
6486
|
+
patchInt32(newOffset + 4, lastRowIdx);
|
|
6487
|
+
patchInt32(newOffset + 12, lastCol);
|
|
6488
|
+
}
|
|
6489
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
6490
|
+
let pos = 0;
|
|
6491
|
+
while (readRecord(originalSheet, pos, rec)) {
|
|
6492
|
+
if (rec.id === 148 && rec.len >= 16) {
|
|
6493
|
+
const shift = rec.dataStart >= rowsEnd ? delta : 0;
|
|
6494
|
+
patchInt32(rec.dataStart + shift + 4, lastRowIdx);
|
|
6495
|
+
patchInt32(rec.dataStart + shift + 12, lastCol);
|
|
6496
|
+
break;
|
|
6497
|
+
}
|
|
6498
|
+
pos = rec.dataEnd;
|
|
6499
|
+
}
|
|
6500
|
+
} finally {
|
|
6501
|
+
fs6.closeSync(fd);
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
4802
6504
|
// ------------------------------------------------------------------
|
|
4803
6505
|
// Rows region detection
|
|
4804
6506
|
// ------------------------------------------------------------------
|
|
@@ -4829,8 +6531,8 @@ var XlsbUpdater = class {
|
|
|
4829
6531
|
return { rowsStart, rowsEnd, autoFilterRanges };
|
|
4830
6532
|
}
|
|
4831
6533
|
/**
|
|
4832
|
-
* Keep the sheet dimension record (
|
|
4833
|
-
* Excel uses its rwLast (payload offset
|
|
6534
|
+
* Keep the sheet dimension record (0x94) in sync with the new data.
|
|
6535
|
+
* Excel uses its rwLast (payload offset 4) and colLast (payload offset 12)
|
|
4834
6536
|
* fields to read the full row range; stale values make the last written row
|
|
4835
6537
|
* invisible to Excel (and drop it from pivot refreshes).
|
|
4836
6538
|
*/
|
|
@@ -4838,9 +6540,9 @@ var XlsbUpdater = class {
|
|
|
4838
6540
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
4839
6541
|
let pos = 0;
|
|
4840
6542
|
while (readRecord(sheetBuf, pos, rec)) {
|
|
4841
|
-
if (rec.id ===
|
|
4842
|
-
sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart +
|
|
4843
|
-
sheetBuf.writeInt32LE(lastCol, rec.dataStart +
|
|
6543
|
+
if (rec.id === 148 && rec.len >= 16) {
|
|
6544
|
+
sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 4);
|
|
6545
|
+
sheetBuf.writeInt32LE(lastCol, rec.dataStart + 12);
|
|
4844
6546
|
return;
|
|
4845
6547
|
}
|
|
4846
6548
|
pos = rec.dataEnd;
|
|
@@ -4850,8 +6552,8 @@ var XlsbUpdater = class {
|
|
|
4850
6552
|
// Pivot cache integration
|
|
4851
6553
|
// ------------------------------------------------------------------
|
|
4852
6554
|
_patchPivotCaches(sheetName, rwLast, colLast) {
|
|
4853
|
-
for (const
|
|
4854
|
-
const entry = this.zip.getEntry(
|
|
6555
|
+
for (const path9 of this.pivotCacheDefPaths) {
|
|
6556
|
+
const entry = this.zip.getEntry(path9);
|
|
4855
6557
|
if (!entry) continue;
|
|
4856
6558
|
const buf = Buffer.from(entry.getData());
|
|
4857
6559
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
@@ -4883,84 +6585,85 @@ var XlsbUpdater = class {
|
|
|
4883
6585
|
}
|
|
4884
6586
|
}
|
|
4885
6587
|
if (patched) {
|
|
4886
|
-
this.zip.updateFile(
|
|
6588
|
+
this.zip.updateFile(path9, buf);
|
|
4887
6589
|
}
|
|
4888
6590
|
}
|
|
4889
6591
|
}
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
// src/atomicFile.ts
|
|
4899
|
-
import * as fs5 from "fs";
|
|
4900
|
-
import { randomUUID } from "crypto";
|
|
4901
|
-
var realFileOperations = {
|
|
4902
|
-
exists: (filePath) => fs5.existsSync(filePath),
|
|
4903
|
-
rename: (sourcePath, destinationPath) => fs5.renameSync(sourcePath, destinationPath),
|
|
4904
|
-
remove: (filePath) => fs5.rmSync(filePath)
|
|
4905
|
-
};
|
|
4906
|
-
function errorMessage(error) {
|
|
4907
|
-
return error instanceof Error ? error.message : String(error);
|
|
4908
|
-
}
|
|
4909
|
-
function destinationExistsError(destinationPath) {
|
|
4910
|
-
return new Error(
|
|
4911
|
-
`Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
|
|
4912
|
-
);
|
|
4913
|
-
}
|
|
4914
|
-
function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
|
|
4915
|
-
const backupPath = `${destinationPath}.${randomUUID()}.replacement-backup`;
|
|
4916
|
-
let backupCreated = false;
|
|
4917
|
-
let installed = false;
|
|
4918
|
-
if (operations.exists(destinationPath)) {
|
|
4919
|
-
if (!overwrite) {
|
|
4920
|
-
throw destinationExistsError(destinationPath);
|
|
4921
|
-
}
|
|
4922
|
-
operations.rename(destinationPath, backupPath);
|
|
4923
|
-
backupCreated = true;
|
|
4924
|
-
}
|
|
4925
|
-
try {
|
|
6592
|
+
/**
|
|
6593
|
+
* Remove the stale calculation chain (xl/calcChain.bin). Excel rebuilds it
|
|
6594
|
+
* on open. Removes the part, its Content_Types override and its workbook
|
|
6595
|
+
* rels entry. XLSB tables are not patched (documented limitation).
|
|
6596
|
+
*/
|
|
6597
|
+
_removeCalcChain() {
|
|
6598
|
+
const calcPath = "xl/calcChain.bin";
|
|
6599
|
+
if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) return;
|
|
4926
6600
|
try {
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
}
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
}
|
|
4936
|
-
throw error;
|
|
6601
|
+
this.zip.deleteFile(calcPath);
|
|
6602
|
+
} catch {
|
|
6603
|
+
}
|
|
6604
|
+
const stagedCalc = this.stagedParts.get(calcPath);
|
|
6605
|
+
if (stagedCalc) {
|
|
6606
|
+
this.stagedParts.delete(calcPath);
|
|
6607
|
+
try {
|
|
6608
|
+
fs6.rmSync(stagedCalc, { force: true });
|
|
6609
|
+
} catch {
|
|
4937
6610
|
}
|
|
4938
6611
|
}
|
|
4939
|
-
|
|
4940
|
-
|
|
6612
|
+
const ctPath = "[Content_Types].xml";
|
|
6613
|
+
if (this.zip.getEntry(ctPath) || this.stagedParts.has(ctPath)) {
|
|
6614
|
+
try {
|
|
6615
|
+
const ct = this._entryData(ctPath).toString("utf8");
|
|
6616
|
+
const updated = ct.replace(
|
|
6617
|
+
/<Override\b[^>]*PartName="\/xl\/calcChain\.bin"[^>]*\/?>/gi,
|
|
6618
|
+
""
|
|
6619
|
+
);
|
|
6620
|
+
if (updated !== ct) this._writePartText(ctPath, updated);
|
|
6621
|
+
} catch {
|
|
6622
|
+
}
|
|
4941
6623
|
}
|
|
4942
|
-
|
|
4943
|
-
if (
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
6624
|
+
const relsPath = "xl/_rels/workbook.bin.rels";
|
|
6625
|
+
if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
|
|
6626
|
+
try {
|
|
6627
|
+
const rels = this._entryData(relsPath).toString("utf8");
|
|
6628
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
6629
|
+
let m;
|
|
6630
|
+
let out = "";
|
|
6631
|
+
let last = 0;
|
|
6632
|
+
let changed = false;
|
|
6633
|
+
while ((m = relTagRe.exec(rels)) !== null) {
|
|
6634
|
+
const attrs = parseTagAttributes(m[0]);
|
|
6635
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
6636
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
6637
|
+
if (type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.bin") {
|
|
6638
|
+
out += rels.substring(last, m.index);
|
|
6639
|
+
last = m.index + m[0].length;
|
|
6640
|
+
changed = true;
|
|
6641
|
+
}
|
|
4952
6642
|
}
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
6643
|
+
if (changed) {
|
|
6644
|
+
out += rels.substring(last);
|
|
6645
|
+
this._writePartText(relsPath, out);
|
|
6646
|
+
}
|
|
6647
|
+
} catch {
|
|
4957
6648
|
}
|
|
4958
6649
|
}
|
|
4959
|
-
throw error;
|
|
4960
6650
|
}
|
|
4961
|
-
|
|
6651
|
+
/** Write an XML/text part to its staged file when present, else into the ZIP. */
|
|
6652
|
+
_writePartText(name, text) {
|
|
6653
|
+
const stagedPath = this.stagedParts.get(name);
|
|
6654
|
+
if (stagedPath) {
|
|
6655
|
+
fs6.writeFileSync(stagedPath, text, "utf8");
|
|
6656
|
+
} else {
|
|
6657
|
+
this.zip.updateFile(name, Buffer.from(text, "utf8"));
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
};
|
|
4962
6661
|
|
|
4963
6662
|
// src/ExcelConverter.ts
|
|
6663
|
+
import * as fs7 from "fs";
|
|
6664
|
+
import * as path8 from "path";
|
|
6665
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6666
|
+
import { spawn } from "child_process";
|
|
4964
6667
|
var POWERSHELL_CONVERSION_SCRIPT = `
|
|
4965
6668
|
$ErrorActionPreference = 'Stop'
|
|
4966
6669
|
$inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
|
|
@@ -5005,29 +6708,29 @@ function errorMessage2(error) {
|
|
|
5005
6708
|
return error instanceof Error ? error.message : String(error);
|
|
5006
6709
|
}
|
|
5007
6710
|
function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
|
|
5008
|
-
const input =
|
|
5009
|
-
const output =
|
|
5010
|
-
if (
|
|
6711
|
+
const input = path8.resolve(inputPath);
|
|
6712
|
+
const output = path8.resolve(outputPath);
|
|
6713
|
+
if (path8.extname(input).toLowerCase() !== inputExtension) {
|
|
5011
6714
|
throw new Error(
|
|
5012
6715
|
`Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
|
|
5013
6716
|
);
|
|
5014
6717
|
}
|
|
5015
|
-
if (
|
|
6718
|
+
if (path8.extname(output).toLowerCase() !== outputExtension) {
|
|
5016
6719
|
throw new Error(
|
|
5017
6720
|
`Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
|
|
5018
6721
|
);
|
|
5019
6722
|
}
|
|
5020
|
-
if (!
|
|
6723
|
+
if (!fs7.existsSync(input)) {
|
|
5021
6724
|
throw new Error(`Excel conversion: source file not found: ${inputPath}`);
|
|
5022
6725
|
}
|
|
5023
|
-
if (!
|
|
6726
|
+
if (!fs7.statSync(input).isFile()) {
|
|
5024
6727
|
throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
|
|
5025
6728
|
}
|
|
5026
|
-
if (!
|
|
5027
|
-
throw new Error(`Excel conversion: destination directory not found: ${
|
|
6729
|
+
if (!fs7.existsSync(path8.dirname(output))) {
|
|
6730
|
+
throw new Error(`Excel conversion: destination directory not found: ${path8.dirname(output)}`);
|
|
5028
6731
|
}
|
|
5029
|
-
if (
|
|
5030
|
-
if (
|
|
6732
|
+
if (fs7.existsSync(output)) {
|
|
6733
|
+
if (fs7.statSync(output).isDirectory()) {
|
|
5031
6734
|
throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
|
|
5032
6735
|
}
|
|
5033
6736
|
if (!overwrite) {
|
|
@@ -5049,7 +6752,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5049
6752
|
JUSTYBASE_EXCEL_OUTPUT: outputPath,
|
|
5050
6753
|
JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
|
|
5051
6754
|
};
|
|
5052
|
-
return new Promise((
|
|
6755
|
+
return new Promise((resolve6, reject) => {
|
|
5053
6756
|
var _a, _b, _c, _d;
|
|
5054
6757
|
let stdout = "";
|
|
5055
6758
|
let stderr = "";
|
|
@@ -5084,7 +6787,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5084
6787
|
if (settled) return;
|
|
5085
6788
|
settled = true;
|
|
5086
6789
|
if (code === 0) {
|
|
5087
|
-
|
|
6790
|
+
resolve6();
|
|
5088
6791
|
return;
|
|
5089
6792
|
}
|
|
5090
6793
|
const details = (stderr || stdout).replace(/\s+/g, " ").trim();
|
|
@@ -5105,22 +6808,22 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
|
|
|
5105
6808
|
if (process.platform !== "win32") {
|
|
5106
6809
|
throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
|
|
5107
6810
|
}
|
|
5108
|
-
const temporaryOutput =
|
|
5109
|
-
|
|
5110
|
-
`.${
|
|
6811
|
+
const temporaryOutput = path8.join(
|
|
6812
|
+
path8.dirname(output),
|
|
6813
|
+
`.${path8.basename(output, outputExtension)}.${randomUUID5()}.conversion${outputExtension}`
|
|
5111
6814
|
);
|
|
5112
6815
|
try {
|
|
5113
6816
|
await runPowerShellConversion(input, temporaryOutput, fileFormat);
|
|
5114
|
-
if (!
|
|
6817
|
+
if (!fs7.existsSync(temporaryOutput)) {
|
|
5115
6818
|
throw new Error("Excel conversion completed without creating the destination file.");
|
|
5116
6819
|
}
|
|
5117
6820
|
installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
|
|
5118
6821
|
} catch (error) {
|
|
5119
6822
|
throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
|
|
5120
6823
|
} finally {
|
|
5121
|
-
if (
|
|
6824
|
+
if (fs7.existsSync(temporaryOutput)) {
|
|
5122
6825
|
try {
|
|
5123
|
-
|
|
6826
|
+
fs7.rmSync(temporaryOutput);
|
|
5124
6827
|
} catch {
|
|
5125
6828
|
}
|
|
5126
6829
|
}
|
|
@@ -5138,22 +6841,38 @@ export {
|
|
|
5138
6841
|
ExcelReaderAbstract,
|
|
5139
6842
|
F,
|
|
5140
6843
|
INVALID_SHEET_NAME_CHARS,
|
|
6844
|
+
MAX_CELL_CHARS,
|
|
6845
|
+
MAX_COLS,
|
|
6846
|
+
MAX_ROWS,
|
|
5141
6847
|
ReaderFactory,
|
|
5142
6848
|
StreamingSheetState,
|
|
5143
6849
|
XlsbReader,
|
|
5144
6850
|
XlsbUpdater,
|
|
5145
6851
|
XlsbWriter,
|
|
6852
|
+
XlsxUpdater as XlsmUpdater,
|
|
5146
6853
|
XlsxReader,
|
|
5147
6854
|
XlsxUpdater,
|
|
5148
6855
|
XlsxWriter,
|
|
5149
6856
|
applyHeaderWidths,
|
|
6857
|
+
assertCellTextLength,
|
|
6858
|
+
assertColumnCount,
|
|
6859
|
+
assertRowCount,
|
|
6860
|
+
assertRowFitsColumns,
|
|
6861
|
+
columnIndexToLetter,
|
|
6862
|
+
columnLetterToIndex,
|
|
5150
6863
|
convertXlsbToXlsx,
|
|
5151
6864
|
convertXlsxToXlsb,
|
|
5152
6865
|
defaultColWidth,
|
|
6866
|
+
escapeXmlText,
|
|
5153
6867
|
getFormat,
|
|
5154
6868
|
initColWidths,
|
|
5155
6869
|
isFormattedCell,
|
|
6870
|
+
parseSharedStringsXml,
|
|
6871
|
+
parseTagAttributes,
|
|
6872
|
+
resolveRelTarget,
|
|
5156
6873
|
sanitizeSheetName,
|
|
6874
|
+
trimTrailingEmptyRows,
|
|
6875
|
+
unescapeXml,
|
|
5157
6876
|
unwrapCell,
|
|
5158
6877
|
updateColWidthsFromRows
|
|
5159
6878
|
};
|