@justybase/spreadsheet-tasks 2.2.0 → 3.1.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 +90 -0
- package/README.md +11 -0
- package/browser/browser-spreadsheet.js +163 -60
- package/browser/justybase-spreadsheet.min.js +13 -13
- package/dist/cjs/index.d.ts +134 -7
- package/dist/cjs/index.js +1381 -464
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1364 -464
- package/dist/esm/index.js.map +1 -1
- package/docs/API.md +66 -1
- package/package.json +4 -3
package/dist/esm/index.js
CHANGED
|
@@ -112,6 +112,153 @@ 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
|
+
function cellRefAttr(col, prevCol, colRef, rowNum) {
|
|
252
|
+
return col === prevCol + 1 ? "" : ` r="${colRef}${rowNum}"`;
|
|
253
|
+
}
|
|
254
|
+
var DEFAULT_COMPRESSION_LEVEL = 6;
|
|
255
|
+
function assertCompressionLevel(level) {
|
|
256
|
+
if (!Number.isInteger(level) || level < 0 || level > 9) {
|
|
257
|
+
throw new RangeError(`compressionLevel must be an integer from 0 to 9, got ${level}`);
|
|
258
|
+
}
|
|
259
|
+
return level;
|
|
260
|
+
}
|
|
261
|
+
|
|
115
262
|
// src/ExcelReaderAbstract.ts
|
|
116
263
|
var ExcelReaderAbstract = class {
|
|
117
264
|
constructor() {
|
|
@@ -126,8 +273,7 @@ var ExcelReaderAbstract = class {
|
|
|
126
273
|
dispose() {
|
|
127
274
|
}
|
|
128
275
|
getDateTimeFromOaDate(oaDate) {
|
|
129
|
-
|
|
130
|
-
return new Date(ms);
|
|
276
|
+
return new Date(oaDateToTime(oaDate, this._oaEpoch));
|
|
131
277
|
}
|
|
132
278
|
};
|
|
133
279
|
|
|
@@ -387,6 +533,216 @@ var BiffReaderWriter = class {
|
|
|
387
533
|
|
|
388
534
|
// src/XlsbReader.ts
|
|
389
535
|
import AdmZip from "adm-zip";
|
|
536
|
+
|
|
537
|
+
// src/xmlUtils.ts
|
|
538
|
+
function hasInvalidControlChars(str) {
|
|
539
|
+
for (let i = 0; i < str.length; i++) {
|
|
540
|
+
const c = str.charCodeAt(i);
|
|
541
|
+
if (c < 32 && c !== 9 && c !== 10 && c !== 13) {
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
function stripInvalidControlChars(str) {
|
|
548
|
+
let out = "";
|
|
549
|
+
for (let i = 0; i < str.length; i++) {
|
|
550
|
+
const c = str.charCodeAt(i);
|
|
551
|
+
if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
|
|
552
|
+
out += str[i];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return out;
|
|
556
|
+
}
|
|
557
|
+
function escapeXmlText(str) {
|
|
558
|
+
if (!/[&<>"'\r]/.test(str) && !hasInvalidControlChars(str)) {
|
|
559
|
+
return str;
|
|
560
|
+
}
|
|
561
|
+
let out = str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r/g, " ");
|
|
562
|
+
if (hasInvalidControlChars(out)) {
|
|
563
|
+
out = stripInvalidControlChars(out);
|
|
564
|
+
}
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
function unescapeXml(str) {
|
|
568
|
+
if (!str || str.indexOf("&") === -1) return str;
|
|
569
|
+
return str.replace(/&(?:amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);/g, (entity) => {
|
|
570
|
+
switch (entity) {
|
|
571
|
+
case "&":
|
|
572
|
+
return "&";
|
|
573
|
+
case "<":
|
|
574
|
+
return "<";
|
|
575
|
+
case ">":
|
|
576
|
+
return ">";
|
|
577
|
+
case """:
|
|
578
|
+
return '"';
|
|
579
|
+
case "'":
|
|
580
|
+
return "'";
|
|
581
|
+
default: {
|
|
582
|
+
if (entity[1] === "#") {
|
|
583
|
+
const body = entity.slice(2, -1);
|
|
584
|
+
const code = body[0] === "x" || body[0] === "X" ? parseInt(body.slice(1), 16) : parseInt(body, 10);
|
|
585
|
+
if (Number.isFinite(code)) {
|
|
586
|
+
try {
|
|
587
|
+
return String.fromCodePoint(code);
|
|
588
|
+
} catch {
|
|
589
|
+
return entity;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return entity;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
function escapeXlsxText(str) {
|
|
599
|
+
return escapeXmlText(str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_"));
|
|
600
|
+
}
|
|
601
|
+
function unescapeXmlText(str) {
|
|
602
|
+
if (!str) return str;
|
|
603
|
+
const text = unescapeXml(str.indexOf("\r") === -1 ? str : str.replace(/\r\n?/g, "\n"));
|
|
604
|
+
return text.indexOf("_x") === -1 ? text : text.replace(/_x([0-9A-Fa-f]{4})_/g, (_match, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
605
|
+
}
|
|
606
|
+
function parseTagAttributes(tag) {
|
|
607
|
+
const attrs = {};
|
|
608
|
+
const re = /([A-Za-z_][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
609
|
+
let m;
|
|
610
|
+
while ((m = re.exec(tag)) !== null) {
|
|
611
|
+
const name = m[1];
|
|
612
|
+
const value = m[3] !== void 0 ? m[3] : m[4];
|
|
613
|
+
attrs[name] = value;
|
|
614
|
+
const colon = name.indexOf(":");
|
|
615
|
+
if (colon !== -1) {
|
|
616
|
+
const local = name.slice(colon + 1);
|
|
617
|
+
if (!(local in attrs)) attrs[local] = value;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return attrs;
|
|
621
|
+
}
|
|
622
|
+
function resolveRelTarget(target) {
|
|
623
|
+
if (!target) return target;
|
|
624
|
+
if (target.startsWith("/")) return target.slice(1);
|
|
625
|
+
if (target.startsWith("xl/")) return target;
|
|
626
|
+
return "xl/" + target;
|
|
627
|
+
}
|
|
628
|
+
function columnIndexToLetter(colIndex) {
|
|
629
|
+
let n = colIndex + 1;
|
|
630
|
+
let result = "";
|
|
631
|
+
while (n > 0) {
|
|
632
|
+
const rem = (n - 1) % 26;
|
|
633
|
+
result = String.fromCharCode(65 + rem) + result;
|
|
634
|
+
n = Math.floor((n - 1) / 26);
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
637
|
+
}
|
|
638
|
+
function columnLetterToIndex(letter) {
|
|
639
|
+
let column = 0;
|
|
640
|
+
const length = letter.length;
|
|
641
|
+
for (let i = 0; i < length; i++) {
|
|
642
|
+
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
|
|
643
|
+
}
|
|
644
|
+
return column - 1;
|
|
645
|
+
}
|
|
646
|
+
function trimTrailingEmptyRows(rows) {
|
|
647
|
+
let end = rows.length;
|
|
648
|
+
while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
|
|
649
|
+
end--;
|
|
650
|
+
}
|
|
651
|
+
return end === rows.length ? rows : rows.slice(0, end);
|
|
652
|
+
}
|
|
653
|
+
function parseSharedStringsXml(xml) {
|
|
654
|
+
const result = [];
|
|
655
|
+
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>|<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*\/>/gi;
|
|
656
|
+
let itemMatch;
|
|
657
|
+
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
658
|
+
const full = itemMatch[0];
|
|
659
|
+
if (full.endsWith("/>") && itemMatch[1] === void 0) {
|
|
660
|
+
result.push("");
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
let content = itemMatch[1] ?? "";
|
|
664
|
+
content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*>[\s\S]*?<\/(?:(?:[A-Za-z_][\w.-]*):)?rPh\s*>/gi, "");
|
|
665
|
+
content = content.replace(/<(?:(?:[A-Za-z_][\w.-]*):)?rPh\b[^>]*\/>/gi, "");
|
|
666
|
+
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
667
|
+
let value = "";
|
|
668
|
+
let textMatch;
|
|
669
|
+
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
670
|
+
value += textMatch[1];
|
|
671
|
+
}
|
|
672
|
+
result.push(unescapeXmlText(value));
|
|
673
|
+
}
|
|
674
|
+
return result;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/biff12Utils.ts
|
|
678
|
+
function readVlq(buf, pos) {
|
|
679
|
+
let value = 0;
|
|
680
|
+
let shift = 0;
|
|
681
|
+
let byte;
|
|
682
|
+
do {
|
|
683
|
+
byte = buf[pos++];
|
|
684
|
+
value |= (byte & 127) << shift;
|
|
685
|
+
shift += 7;
|
|
686
|
+
} while ((byte & 128) !== 0);
|
|
687
|
+
return { value, nextPos: pos };
|
|
688
|
+
}
|
|
689
|
+
function vlqBytes(value) {
|
|
690
|
+
const out = [];
|
|
691
|
+
while (value >= 128) {
|
|
692
|
+
out.push(value & 127 | 128);
|
|
693
|
+
value >>>= 7;
|
|
694
|
+
}
|
|
695
|
+
out.push(value & 127);
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
function readRecord(buf, pos, rec) {
|
|
699
|
+
const idRes = readVlq(buf, pos);
|
|
700
|
+
if (idRes.nextPos > buf.length) return false;
|
|
701
|
+
const lenRes = readVlq(buf, idRes.nextPos);
|
|
702
|
+
if (lenRes.nextPos + lenRes.value > buf.length) return false;
|
|
703
|
+
rec.headerStart = pos;
|
|
704
|
+
rec.dataStart = lenRes.nextPos;
|
|
705
|
+
rec.dataEnd = lenRes.nextPos + lenRes.value;
|
|
706
|
+
rec.id = idRes.value;
|
|
707
|
+
rec.len = lenRes.value;
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
function buildRecord(id, payload) {
|
|
711
|
+
const idBytes = vlqBytes(id);
|
|
712
|
+
const lenBytes = vlqBytes(payload.length);
|
|
713
|
+
const header = Buffer.alloc(idBytes.length + lenBytes.length);
|
|
714
|
+
idBytes.forEach((b, i) => header[i] = b);
|
|
715
|
+
lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
|
|
716
|
+
return Buffer.concat([header, payload]);
|
|
717
|
+
}
|
|
718
|
+
function readUtf16(buf, start, charCount) {
|
|
719
|
+
const end = start + charCount * 2;
|
|
720
|
+
if (end > buf.length) return "";
|
|
721
|
+
return buf.toString("utf16le", start, end);
|
|
722
|
+
}
|
|
723
|
+
function parseSharedStringsBin(buf) {
|
|
724
|
+
const values = [];
|
|
725
|
+
let total = 0;
|
|
726
|
+
let unique = 0;
|
|
727
|
+
let endSstOffset = buf.length;
|
|
728
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
729
|
+
let pos = 0;
|
|
730
|
+
while (readRecord(buf, pos, rec)) {
|
|
731
|
+
pos = rec.dataEnd;
|
|
732
|
+
if (rec.id === 159) {
|
|
733
|
+
total = buf.readUInt32LE(rec.dataStart);
|
|
734
|
+
unique = buf.readUInt32LE(rec.dataStart + 4);
|
|
735
|
+
} else if (rec.id === 19) {
|
|
736
|
+
const cch = buf.readUInt32LE(rec.dataStart + 1);
|
|
737
|
+
values.push(readUtf16(buf, rec.dataStart + 5, cch));
|
|
738
|
+
} else if (rec.id === 160) {
|
|
739
|
+
endSstOffset = rec.headerStart;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return { values, total, unique, endSstOffset };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/XlsbReader.ts
|
|
390
746
|
var XlsbReader = class extends ExcelReaderAbstract {
|
|
391
747
|
constructor() {
|
|
392
748
|
super();
|
|
@@ -398,12 +754,29 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
398
754
|
this._reader = null;
|
|
399
755
|
this._currentRow = [];
|
|
400
756
|
this._pendingRowIndex = -1;
|
|
757
|
+
this._nextExpectedRow = 0;
|
|
401
758
|
this._eof = false;
|
|
402
759
|
this.xfIdToNumFmtId = [];
|
|
403
760
|
this.customDateFormats = null;
|
|
404
761
|
}
|
|
405
|
-
async open(
|
|
406
|
-
this.
|
|
762
|
+
async open(path9, readSharedStrings = true) {
|
|
763
|
+
await this.close();
|
|
764
|
+
this.sheetNames = [];
|
|
765
|
+
this.sheets = [];
|
|
766
|
+
this.sharedStrings = [];
|
|
767
|
+
this.xfIdToNumFmtId = [];
|
|
768
|
+
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
769
|
+
this.fieldCount = 0;
|
|
770
|
+
this.resultsCount = 0;
|
|
771
|
+
this.actualSheetName = "";
|
|
772
|
+
this._currentSheetIndex = -1;
|
|
773
|
+
this._currentRow = [];
|
|
774
|
+
this._pendingRowIndex = -1;
|
|
775
|
+
this._nextExpectedRow = 0;
|
|
776
|
+
this._eof = false;
|
|
777
|
+
this._reader = null;
|
|
778
|
+
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
779
|
+
this.zip = new AdmZip(path9);
|
|
407
780
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
408
781
|
if (wbEntry) {
|
|
409
782
|
const buf = wbEntry.getData();
|
|
@@ -420,20 +793,39 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
420
793
|
const relsEntry = this.zip.getEntry("xl/_rels/workbook.bin.rels");
|
|
421
794
|
if (relsEntry) {
|
|
422
795
|
const xml = relsEntry.getData().toString("utf8");
|
|
423
|
-
const
|
|
424
|
-
let
|
|
796
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
797
|
+
let tagMatch;
|
|
425
798
|
const rIdToTarget = {};
|
|
426
|
-
while ((
|
|
427
|
-
|
|
799
|
+
while ((tagMatch = relTagRe.exec(xml)) !== null) {
|
|
800
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
801
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
802
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
803
|
+
if (id !== void 0 && target !== void 0) {
|
|
804
|
+
rIdToTarget[id] = target;
|
|
805
|
+
}
|
|
428
806
|
}
|
|
429
807
|
for (const sheet of this.sheets) {
|
|
430
|
-
|
|
808
|
+
const target = rIdToTarget[sheet.rId];
|
|
431
809
|
if (target) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
810
|
+
sheet.path = resolveRelTarget(target);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
try {
|
|
815
|
+
const raw = wbEntry ? wbEntry.getData() : null;
|
|
816
|
+
if (raw) {
|
|
817
|
+
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
818
|
+
let pos = 0;
|
|
819
|
+
while (readRecord(raw, pos, rec)) {
|
|
820
|
+
if (rec.id === 153 && rec.len >= 4) {
|
|
821
|
+
const is1904 = (raw[rec.dataStart] & 1) !== 0;
|
|
822
|
+
this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
pos = rec.dataEnd;
|
|
435
826
|
}
|
|
436
827
|
}
|
|
828
|
+
} catch {
|
|
437
829
|
}
|
|
438
830
|
if (readSharedStrings) {
|
|
439
831
|
const ssPath = "xl/sharedStrings.bin";
|
|
@@ -448,8 +840,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
448
840
|
this.resultsCount = this.sheets.length;
|
|
449
841
|
this._currentSheetIndex = -1;
|
|
450
842
|
}
|
|
451
|
-
_readSharedStrings(
|
|
452
|
-
const entry = this.zip.getEntry(
|
|
843
|
+
_readSharedStrings(path9) {
|
|
844
|
+
const entry = this.zip.getEntry(path9);
|
|
453
845
|
if (!entry) return;
|
|
454
846
|
const reader = new BiffReaderWriter(entry.getData());
|
|
455
847
|
while (reader.readSharedStrings()) {
|
|
@@ -458,8 +850,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
458
850
|
}
|
|
459
851
|
}
|
|
460
852
|
}
|
|
461
|
-
_readStyles(
|
|
462
|
-
const entry = this.zip.getEntry(
|
|
853
|
+
_readStyles(path9) {
|
|
854
|
+
const entry = this.zip.getEntry(path9);
|
|
463
855
|
if (!entry) return;
|
|
464
856
|
const reader = new BiffReaderWriter(entry.getData());
|
|
465
857
|
while (reader.readStyles()) {
|
|
@@ -470,21 +862,54 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
470
862
|
getSheetNames() {
|
|
471
863
|
return this.sheetNames;
|
|
472
864
|
}
|
|
865
|
+
async selectSheet(nameOrIndex) {
|
|
866
|
+
let index = -1;
|
|
867
|
+
if (typeof nameOrIndex === "number") {
|
|
868
|
+
index = nameOrIndex;
|
|
869
|
+
} else {
|
|
870
|
+
index = this.sheetNames.indexOf(nameOrIndex);
|
|
871
|
+
if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
|
|
872
|
+
}
|
|
873
|
+
if (index < 0 || index >= this.sheets.length) {
|
|
874
|
+
throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
|
|
875
|
+
}
|
|
876
|
+
this._currentSheetIndex = index;
|
|
877
|
+
if (!this._initSheet(index)) {
|
|
878
|
+
throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[index]}'.`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async nextResult() {
|
|
882
|
+
const next = this._currentSheetIndex + 1;
|
|
883
|
+
if (next >= this.sheets.length) return false;
|
|
884
|
+
this._currentSheetIndex = next;
|
|
885
|
+
if (!this._initSheet(next)) {
|
|
886
|
+
throw new Error(`XlsbReader: worksheet part missing for sheet '${this.sheetNames[next]}'.`);
|
|
887
|
+
}
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
473
890
|
async read() {
|
|
474
891
|
if (this._currentSheetIndex === -1) {
|
|
475
892
|
this._currentSheetIndex = 0;
|
|
476
893
|
if (!this._initSheet(0)) return false;
|
|
477
894
|
}
|
|
478
895
|
if (this._eof) return false;
|
|
896
|
+
if (this._pendingRowIndex > this._nextExpectedRow && this._pendingRowIndex < MAX_ROWS) {
|
|
897
|
+
this._currentRow = [];
|
|
898
|
+
this._nextExpectedRow++;
|
|
899
|
+
return true;
|
|
900
|
+
}
|
|
479
901
|
this._currentRow = [];
|
|
480
902
|
while (true) {
|
|
481
903
|
const hasRecord = this._reader.readWorksheet();
|
|
482
904
|
if (!hasRecord) {
|
|
483
905
|
this._eof = true;
|
|
906
|
+
this._nextExpectedRow = this._pendingRowIndex + 1;
|
|
484
907
|
return true;
|
|
485
908
|
}
|
|
486
909
|
if (this._reader._rowIndex !== -1 && this._reader._rowIndex !== this._pendingRowIndex) {
|
|
910
|
+
const completed = this._pendingRowIndex;
|
|
487
911
|
this._pendingRowIndex = this._reader._rowIndex;
|
|
912
|
+
this._nextExpectedRow = completed + 1;
|
|
488
913
|
return true;
|
|
489
914
|
}
|
|
490
915
|
if (this._reader._readCell) {
|
|
@@ -525,13 +950,17 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
525
950
|
}
|
|
526
951
|
}
|
|
527
952
|
_initSheet(index) {
|
|
528
|
-
if (index >= this.sheets.length) return false;
|
|
953
|
+
if (index < 0 || index >= this.sheets.length) return false;
|
|
529
954
|
const sheet = this.sheets[index];
|
|
530
|
-
const entry = this.zip.getEntry(sheet.path);
|
|
955
|
+
const entry = sheet.path ? this.zip.getEntry(sheet.path) : void 0;
|
|
531
956
|
if (!entry) return false;
|
|
532
957
|
this._reader = new BiffReaderWriter(entry.getData());
|
|
533
958
|
this._eof = false;
|
|
534
959
|
this._pendingRowIndex = -1;
|
|
960
|
+
this._nextExpectedRow = 0;
|
|
961
|
+
this._currentRow = [];
|
|
962
|
+
this.fieldCount = 0;
|
|
963
|
+
this.actualSheetName = sheet.name;
|
|
535
964
|
while (this._reader.readWorksheet()) {
|
|
536
965
|
if (this._reader._rowIndex !== -1) {
|
|
537
966
|
this._pendingRowIndex = this._reader._rowIndex;
|
|
@@ -539,7 +968,7 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
539
968
|
}
|
|
540
969
|
}
|
|
541
970
|
this._eof = true;
|
|
542
|
-
return
|
|
971
|
+
return true;
|
|
543
972
|
}
|
|
544
973
|
getValue(i) {
|
|
545
974
|
if (i < 0 || i >= this._currentRow.length) return null;
|
|
@@ -549,84 +978,15 @@ var XlsbReader = class extends ExcelReaderAbstract {
|
|
|
549
978
|
|
|
550
979
|
// src/XlsxReader.ts
|
|
551
980
|
import yauzl from "yauzl";
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
function stripInvalidControlChars(str) {
|
|
564
|
-
let out = "";
|
|
565
|
-
for (let i = 0; i < str.length; i++) {
|
|
566
|
-
const c = str.charCodeAt(i);
|
|
567
|
-
if (c === 9 || c === 10 || c === 13 || c >= 32 && c <= 55295 || c >= 57344 && c <= 65533) {
|
|
568
|
-
out += str[i];
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
return out;
|
|
572
|
-
}
|
|
573
|
-
function escapeXmlText(str) {
|
|
574
|
-
if (!/&|<|>|"|'/.test(str) && !hasInvalidControlChars(str)) {
|
|
575
|
-
return str;
|
|
576
|
-
}
|
|
577
|
-
let out = str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
578
|
-
if (hasInvalidControlChars(out)) {
|
|
579
|
-
out = stripInvalidControlChars(out);
|
|
580
|
-
}
|
|
581
|
-
return out;
|
|
582
|
-
}
|
|
583
|
-
function unescapeXml(str) {
|
|
584
|
-
if (!str || str.indexOf("&") === -1) return str;
|
|
585
|
-
return str.replace(/&#x([0-9a-f]+);/gi, (_match, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/&#(\d+);/g, (_match, decimal) => String.fromCodePoint(parseInt(decimal, 10))).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'");
|
|
586
|
-
}
|
|
587
|
-
function columnIndexToLetter(colIndex) {
|
|
588
|
-
let n = colIndex + 1;
|
|
589
|
-
let result = "";
|
|
590
|
-
while (n > 0) {
|
|
591
|
-
const rem = (n - 1) % 26;
|
|
592
|
-
result = String.fromCharCode(65 + rem) + result;
|
|
593
|
-
n = Math.floor((n - 1) / 26);
|
|
594
|
-
}
|
|
595
|
-
return result;
|
|
596
|
-
}
|
|
597
|
-
function columnLetterToIndex(letter) {
|
|
598
|
-
let column = 0;
|
|
599
|
-
const length = letter.length;
|
|
600
|
-
for (let i = 0; i < length; i++) {
|
|
601
|
-
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
|
|
602
|
-
}
|
|
603
|
-
return column - 1;
|
|
604
|
-
}
|
|
605
|
-
function trimTrailingEmptyRows(rows) {
|
|
606
|
-
let end = rows.length;
|
|
607
|
-
while (end > 0 && rows[end - 1].every((v) => v === null || v === void 0)) {
|
|
608
|
-
end--;
|
|
609
|
-
}
|
|
610
|
-
return end === rows.length ? rows : rows.slice(0, end);
|
|
611
|
-
}
|
|
612
|
-
function parseSharedStringsXml(xml) {
|
|
613
|
-
const result = [];
|
|
614
|
-
const itemRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?si\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?si\s*>/gi;
|
|
615
|
-
let itemMatch;
|
|
616
|
-
while ((itemMatch = itemRegex.exec(xml)) !== null) {
|
|
617
|
-
const content = itemMatch[1];
|
|
618
|
-
const textRegex = /<(?:(?:[A-Za-z_][\w.-]*):)?t\b[^>]*>([\s\S]*?)<\/(?:(?:[A-Za-z_][\w.-]*):)?t\s*>/gi;
|
|
619
|
-
let value = "";
|
|
620
|
-
let textMatch;
|
|
621
|
-
while ((textMatch = textRegex.exec(content)) !== null) {
|
|
622
|
-
value += textMatch[1];
|
|
623
|
-
}
|
|
624
|
-
result.push(unescapeXml(value));
|
|
625
|
-
}
|
|
626
|
-
return result;
|
|
981
|
+
function parseIsoDateUtc(value) {
|
|
982
|
+
let text = value.trim().replace(" ", "T");
|
|
983
|
+
if (!text) return null;
|
|
984
|
+
if (!/z$|[+-]\d{2}:?\d{2}$/i.test(text) && text.includes("T")) {
|
|
985
|
+
text += "Z";
|
|
986
|
+
}
|
|
987
|
+
const date = new Date(text);
|
|
988
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
627
989
|
}
|
|
628
|
-
|
|
629
|
-
// src/XlsxReader.ts
|
|
630
990
|
var XlsxReader = class extends ExcelReaderAbstract {
|
|
631
991
|
constructor() {
|
|
632
992
|
super();
|
|
@@ -638,13 +998,34 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
638
998
|
this._currentSheetIndex = -1;
|
|
639
999
|
this._sheetXml = null;
|
|
640
1000
|
this._xmlPos = 0;
|
|
1001
|
+
/** End of the sheetData element; row tags must not be searched past it. */
|
|
1002
|
+
this._xmlEnd = 0;
|
|
641
1003
|
this._currentRow = [];
|
|
1004
|
+
this._expectedRowNum = 1;
|
|
1005
|
+
this._pendingEmptyCount = 0;
|
|
642
1006
|
this.xfIdToNumFmtId = [];
|
|
643
1007
|
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
644
1008
|
}
|
|
645
|
-
async open(
|
|
646
|
-
|
|
647
|
-
|
|
1009
|
+
async open(path9, readSharedStrings = true) {
|
|
1010
|
+
await this.close();
|
|
1011
|
+
this.sheetNames = [];
|
|
1012
|
+
this.sheets = [];
|
|
1013
|
+
this.sharedStrings = [];
|
|
1014
|
+
this.xfIdToNumFmtId = [];
|
|
1015
|
+
this.customDateFormats = /* @__PURE__ */ new Set();
|
|
1016
|
+
this.fieldCount = 0;
|
|
1017
|
+
this.resultsCount = 0;
|
|
1018
|
+
this.actualSheetName = "";
|
|
1019
|
+
this._currentSheetIndex = -1;
|
|
1020
|
+
this._sheetXml = null;
|
|
1021
|
+
this._xmlPos = 0;
|
|
1022
|
+
this._xmlEnd = 0;
|
|
1023
|
+
this._currentRow = [];
|
|
1024
|
+
this._expectedRowNum = 1;
|
|
1025
|
+
this._pendingEmptyCount = 0;
|
|
1026
|
+
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
1027
|
+
return new Promise((resolve6, reject) => {
|
|
1028
|
+
yauzl.open(path9, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
|
|
648
1029
|
if (err) return reject(err);
|
|
649
1030
|
this.zipfile = zipfile;
|
|
650
1031
|
this.entries = /* @__PURE__ */ new Map();
|
|
@@ -657,28 +1038,40 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
657
1038
|
const wbRelsContent = await this._readZipEntryContent("xl/_rels/workbook.xml.rels");
|
|
658
1039
|
const rIdToTarget = {};
|
|
659
1040
|
if (wbRelsContent) {
|
|
660
|
-
const
|
|
661
|
-
let
|
|
662
|
-
while ((
|
|
663
|
-
|
|
1041
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
1042
|
+
let tagMatch;
|
|
1043
|
+
while ((tagMatch = relTagRe.exec(wbRelsContent)) !== null) {
|
|
1044
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
1045
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
1046
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
1047
|
+
if (id !== void 0 && target !== void 0) {
|
|
1048
|
+
rIdToTarget[id] = target;
|
|
1049
|
+
}
|
|
664
1050
|
}
|
|
665
1051
|
}
|
|
666
1052
|
const wbContent = await this._readZipEntryContent("xl/workbook.xml");
|
|
667
1053
|
if (wbContent) {
|
|
668
|
-
const
|
|
669
|
-
let
|
|
670
|
-
while ((
|
|
671
|
-
const
|
|
672
|
-
const
|
|
673
|
-
const
|
|
1054
|
+
const sheetTagRe = /<sheet\b[^>]*>/g;
|
|
1055
|
+
let tagMatch;
|
|
1056
|
+
while ((tagMatch = sheetTagRe.exec(wbContent)) !== null) {
|
|
1057
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
1058
|
+
const rawName = attrs["name"];
|
|
1059
|
+
const sheetId = attrs["sheetId"] ?? "";
|
|
1060
|
+
const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
|
|
1061
|
+
if (rawName === void 0 || rId === void 0) continue;
|
|
1062
|
+
const name = unescapeXml(rawName);
|
|
674
1063
|
const target = rIdToTarget[rId];
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
fullPath = "xl/" + fullPath;
|
|
678
|
-
}
|
|
1064
|
+
if (!target) continue;
|
|
1065
|
+
const fullPath = resolveRelTarget(target);
|
|
679
1066
|
this.sheetNames.push(name);
|
|
680
1067
|
this.sheets.push({ name, sheetId, rId, path: fullPath });
|
|
681
1068
|
}
|
|
1069
|
+
const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbContent);
|
|
1070
|
+
if (wbPrMatch) {
|
|
1071
|
+
const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
|
|
1072
|
+
const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
|
|
1073
|
+
this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
1074
|
+
}
|
|
682
1075
|
}
|
|
683
1076
|
if (readSharedStrings) {
|
|
684
1077
|
const ssContent = await this._readZipEntryContent("xl/sharedStrings.xml");
|
|
@@ -692,7 +1085,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
692
1085
|
}
|
|
693
1086
|
this.resultsCount = this.sheets.length;
|
|
694
1087
|
this._currentSheetIndex = -1;
|
|
695
|
-
|
|
1088
|
+
resolve6();
|
|
696
1089
|
} catch (e) {
|
|
697
1090
|
reject(e);
|
|
698
1091
|
}
|
|
@@ -706,16 +1099,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
706
1099
|
this.zipfile.close();
|
|
707
1100
|
}
|
|
708
1101
|
}
|
|
709
|
-
async _readZipEntryContent(
|
|
710
|
-
const entry = this.entries.get(
|
|
1102
|
+
async _readZipEntryContent(path9) {
|
|
1103
|
+
const entry = this.entries.get(path9);
|
|
711
1104
|
if (!entry) return null;
|
|
712
|
-
return new Promise((
|
|
1105
|
+
return new Promise((resolve6, reject) => {
|
|
713
1106
|
this.zipfile.openReadStream(entry, (err, readStream) => {
|
|
714
1107
|
if (err) return reject(err);
|
|
715
1108
|
const chunks = [];
|
|
716
1109
|
readStream.on("data", (chunk) => chunks.push(chunk));
|
|
717
1110
|
readStream.on("end", () => {
|
|
718
|
-
|
|
1111
|
+
resolve6(Buffer.concat(chunks).toString("utf8"));
|
|
719
1112
|
});
|
|
720
1113
|
readStream.on("error", reject);
|
|
721
1114
|
});
|
|
@@ -763,6 +1156,27 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
763
1156
|
return false;
|
|
764
1157
|
}
|
|
765
1158
|
}
|
|
1159
|
+
async selectSheet(nameOrIndex) {
|
|
1160
|
+
let index = -1;
|
|
1161
|
+
if (typeof nameOrIndex === "number") {
|
|
1162
|
+
index = nameOrIndex;
|
|
1163
|
+
} else {
|
|
1164
|
+
index = this.sheetNames.indexOf(nameOrIndex);
|
|
1165
|
+
if (index === -1) throw new Error(`Sheet '${nameOrIndex}' not found`);
|
|
1166
|
+
}
|
|
1167
|
+
if (index < 0 || index >= this.sheets.length) {
|
|
1168
|
+
throw new Error(`Sheet index ${String(nameOrIndex)} out of range`);
|
|
1169
|
+
}
|
|
1170
|
+
this._currentSheetIndex = index;
|
|
1171
|
+
await this._initSheet(index);
|
|
1172
|
+
}
|
|
1173
|
+
async nextResult() {
|
|
1174
|
+
const next = this._currentSheetIndex + 1;
|
|
1175
|
+
if (next >= this.sheets.length) return false;
|
|
1176
|
+
this._currentSheetIndex = next;
|
|
1177
|
+
await this._initSheet(next);
|
|
1178
|
+
return true;
|
|
1179
|
+
}
|
|
766
1180
|
async _initSheet(index) {
|
|
767
1181
|
if (index >= this.sheets.length) return;
|
|
768
1182
|
const sheet = this.sheets[index];
|
|
@@ -772,23 +1186,106 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
772
1186
|
this._sheetXml = "";
|
|
773
1187
|
}
|
|
774
1188
|
this._xmlPos = 0;
|
|
1189
|
+
this._xmlEnd = this._sheetXml.length;
|
|
1190
|
+
this._expectedRowNum = 1;
|
|
1191
|
+
this._pendingEmptyCount = 0;
|
|
1192
|
+
this._currentRow = [];
|
|
1193
|
+
this.fieldCount = 0;
|
|
775
1194
|
const sd = this._sheetXml.indexOf("<sheetData>");
|
|
776
|
-
if (sd !== -1)
|
|
1195
|
+
if (sd !== -1) {
|
|
1196
|
+
this._xmlPos = sd + 11;
|
|
1197
|
+
const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
|
|
1198
|
+
if (se !== -1) this._xmlEnd = se;
|
|
1199
|
+
} else {
|
|
1200
|
+
const m = /<(?:[A-Za-z_][\w.-]*:)?sheetData\b[^>]*>/i.exec(this._sheetXml);
|
|
1201
|
+
if (m && m.index !== void 0) {
|
|
1202
|
+
this._xmlPos = m.index + m[0].length;
|
|
1203
|
+
if (/\/>\s*$/.test(m[0])) {
|
|
1204
|
+
this._xmlEnd = this._xmlPos;
|
|
1205
|
+
} else {
|
|
1206
|
+
const se = this._sheetXml.indexOf("</sheetData>", this._xmlPos);
|
|
1207
|
+
if (se !== -1) this._xmlEnd = se;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
777
1211
|
}
|
|
778
1212
|
_readNextRow() {
|
|
779
1213
|
if (!this._sheetXml) return false;
|
|
780
|
-
|
|
1214
|
+
if (this._pendingEmptyCount > 0) {
|
|
1215
|
+
this._pendingEmptyCount--;
|
|
1216
|
+
this._currentRow = [];
|
|
1217
|
+
this._expectedRowNum++;
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
const rowStart = this._findRowTagStart(this._xmlPos);
|
|
781
1221
|
if (rowStart === -1) return false;
|
|
1222
|
+
const rowTagEnd = this._sheetXml.indexOf(">", rowStart);
|
|
1223
|
+
if (rowTagEnd === -1) return false;
|
|
1224
|
+
const rowTag = this._sheetXml.substring(rowStart, rowTagEnd + 1);
|
|
1225
|
+
const isSelfClosing = this._sheetXml.charCodeAt(rowTagEnd - 1) === 47;
|
|
1226
|
+
const rowAttrs = parseTagAttributes(rowTag);
|
|
1227
|
+
const rRaw = rowAttrs["r"];
|
|
1228
|
+
let rowNum = this._expectedRowNum;
|
|
1229
|
+
if (rRaw !== void 0) {
|
|
1230
|
+
const parsed = parseInt(rRaw, 10);
|
|
1231
|
+
if (Number.isFinite(parsed) && parsed >= 1 && parsed <= MAX_ROWS) rowNum = parsed;
|
|
1232
|
+
}
|
|
1233
|
+
if (rowNum > this._expectedRowNum) {
|
|
1234
|
+
const gap = rowNum - this._expectedRowNum;
|
|
1235
|
+
this._pendingEmptyCount = gap - 1;
|
|
1236
|
+
this._currentRow = [];
|
|
1237
|
+
this._expectedRowNum++;
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
if (rowNum < this._expectedRowNum) {
|
|
1241
|
+
if (isSelfClosing) {
|
|
1242
|
+
this._xmlPos = rowTagEnd + 1;
|
|
1243
|
+
this._currentRow = [];
|
|
1244
|
+
return true;
|
|
1245
|
+
}
|
|
1246
|
+
const rowEnd2 = this._sheetXml.indexOf("</row>", rowStart);
|
|
1247
|
+
if (rowEnd2 === -1) return false;
|
|
1248
|
+
this._xmlPos = rowEnd2 + 6;
|
|
1249
|
+
this._parseRowByIndex(rowStart, rowEnd2);
|
|
1250
|
+
this._expectedRowNum = Math.max(this._expectedRowNum + 1, rowNum + 1);
|
|
1251
|
+
return true;
|
|
1252
|
+
}
|
|
1253
|
+
if (isSelfClosing) {
|
|
1254
|
+
this._xmlPos = rowTagEnd + 1;
|
|
1255
|
+
this._currentRow = [];
|
|
1256
|
+
this._expectedRowNum = rowNum + 1;
|
|
1257
|
+
return true;
|
|
1258
|
+
}
|
|
782
1259
|
const rowEnd = this._sheetXml.indexOf("</row>", rowStart);
|
|
783
1260
|
if (rowEnd === -1) return false;
|
|
784
1261
|
this._xmlPos = rowEnd + 6;
|
|
785
1262
|
this._parseRowByIndex(rowStart, rowEnd);
|
|
1263
|
+
this._expectedRowNum = rowNum + 1;
|
|
786
1264
|
return true;
|
|
787
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Find the next `<row …>` start, ignoring similarly named elements such as
|
|
1268
|
+
* `<rowBreaks>` that follow `</sheetData>`.
|
|
1269
|
+
*/
|
|
1270
|
+
_findRowTagStart(from) {
|
|
1271
|
+
const xml = this._sheetXml;
|
|
1272
|
+
let pos = from;
|
|
1273
|
+
while (pos < this._xmlEnd) {
|
|
1274
|
+
const idx = xml.indexOf("<row", pos);
|
|
1275
|
+
if (idx === -1 || idx >= this._xmlEnd) return -1;
|
|
1276
|
+
const next = xml.charCodeAt(idx + 4);
|
|
1277
|
+
if (next === 62 || next === 47 || next === 32 || next === 9 || next === 10 || next === 13) {
|
|
1278
|
+
return idx;
|
|
1279
|
+
}
|
|
1280
|
+
pos = idx + 4;
|
|
1281
|
+
}
|
|
1282
|
+
return -1;
|
|
1283
|
+
}
|
|
788
1284
|
_parseRowByIndex(rowStart, rowEnd) {
|
|
789
1285
|
this._currentRow = [];
|
|
790
1286
|
const xml = this._sheetXml;
|
|
791
1287
|
let pos = xml.indexOf(">", rowStart) + 1;
|
|
1288
|
+
let nextCol = 0;
|
|
792
1289
|
while (pos < rowEnd) {
|
|
793
1290
|
const cStart = xml.indexOf("<c", pos);
|
|
794
1291
|
if (cStart === -1 || cStart >= rowEnd) break;
|
|
@@ -822,6 +1319,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
822
1319
|
}
|
|
823
1320
|
}
|
|
824
1321
|
let val = null;
|
|
1322
|
+
let inlineVals = null;
|
|
825
1323
|
if (xml.charCodeAt(nextTagClose - 1) === 47) {
|
|
826
1324
|
pos = nextTagClose + 1;
|
|
827
1325
|
} else {
|
|
@@ -836,27 +1334,41 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
836
1334
|
}
|
|
837
1335
|
} else {
|
|
838
1336
|
if (type === "inlineStr") {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1337
|
+
inlineVals = [];
|
|
1338
|
+
let tSearch = nextTagClose;
|
|
1339
|
+
while (true) {
|
|
1340
|
+
const tStart = xml.indexOf("<t", tSearch);
|
|
1341
|
+
if (tStart === -1 || tStart >= cellContentEnd) break;
|
|
1342
|
+
const tClose = xml.indexOf(">", tStart);
|
|
1343
|
+
if (tClose === -1 || tClose >= cellContentEnd) break;
|
|
1344
|
+
const tContentStart = tClose + 1;
|
|
842
1345
|
const tEnd = xml.indexOf("</t>", tContentStart);
|
|
843
|
-
if (tEnd
|
|
844
|
-
|
|
845
|
-
|
|
1346
|
+
if (tEnd === -1 || tEnd > cellContentEnd) break;
|
|
1347
|
+
inlineVals.push(xml.substring(tContentStart, tEnd));
|
|
1348
|
+
tSearch = tEnd + 4;
|
|
846
1349
|
}
|
|
1350
|
+
if (inlineVals.length === 0) inlineVals = null;
|
|
847
1351
|
}
|
|
848
1352
|
}
|
|
849
1353
|
pos = cellContentEnd + 4;
|
|
850
1354
|
}
|
|
851
1355
|
let finalVal = null;
|
|
852
|
-
if (
|
|
1356
|
+
if (inlineVals !== null) {
|
|
1357
|
+
finalVal = unescapeXmlText(inlineVals.join(""));
|
|
1358
|
+
} else if (val !== null) {
|
|
853
1359
|
if (type === "s") {
|
|
854
1360
|
const idx = parseInt(val, 10);
|
|
855
1361
|
finalVal = this.sharedStrings[idx];
|
|
856
1362
|
} else if (type === "b") {
|
|
857
1363
|
finalVal = val === "1" || val === "true";
|
|
858
1364
|
} else if (type === "inlineStr") {
|
|
1365
|
+
finalVal = unescapeXmlText(val);
|
|
1366
|
+
} else if (type === "str") {
|
|
1367
|
+
finalVal = unescapeXmlText(val);
|
|
1368
|
+
} else if (type === "e") {
|
|
859
1369
|
finalVal = unescapeXml(val);
|
|
1370
|
+
} else if (type === "d") {
|
|
1371
|
+
finalVal = parseIsoDateUtc(val) ?? unescapeXmlText(val);
|
|
860
1372
|
} else if (type === "n" || type === "") {
|
|
861
1373
|
finalVal = parseFloat(val);
|
|
862
1374
|
if (styleIndex > 0 && this.xfIdToNumFmtId) {
|
|
@@ -874,10 +1386,10 @@ var XlsxReader = class extends ExcelReaderAbstract {
|
|
|
874
1386
|
}
|
|
875
1387
|
}
|
|
876
1388
|
}
|
|
877
|
-
if (colIndex
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1389
|
+
if (colIndex === -1) colIndex = nextCol;
|
|
1390
|
+
nextCol = colIndex + 1;
|
|
1391
|
+
this._currentRow[colIndex] = finalVal;
|
|
1392
|
+
if (colIndex >= this.fieldCount) this.fieldCount = colIndex + 1;
|
|
881
1393
|
}
|
|
882
1394
|
}
|
|
883
1395
|
getValue(i) {
|
|
@@ -907,99 +1419,11 @@ var ReaderFactory = class {
|
|
|
907
1419
|
|
|
908
1420
|
// src/XlsbWriter.ts
|
|
909
1421
|
import * as fs from "fs";
|
|
1422
|
+
import * as path2 from "path";
|
|
1423
|
+
import { randomUUID } from "crypto";
|
|
910
1424
|
import archiver from "archiver";
|
|
911
1425
|
import { Readable } from "stream";
|
|
912
1426
|
|
|
913
|
-
// src/Formats.ts
|
|
914
|
-
var F = {
|
|
915
|
-
THOUSANDS_SEP: "#,##0",
|
|
916
|
-
CURRENCY_PLN: '#,##0.00 "z\u0142"',
|
|
917
|
-
CURRENCY_EUR: "#,##0.00 \u20AC",
|
|
918
|
-
PERCENTAGE: "0%",
|
|
919
|
-
SCIENTIFIC: "0.00E+00",
|
|
920
|
-
TWO_DECIMALS: "#,##0.00",
|
|
921
|
-
TEXT: "@",
|
|
922
|
-
LEADING_ZEROS: "000000000",
|
|
923
|
-
DATE_SHORT: "dd.mm.yyyy",
|
|
924
|
-
DATE_LONG: "d mmmm yyyy",
|
|
925
|
-
DATE_DAY_MONTH_YEAR: "dd-mm-yyyy",
|
|
926
|
-
DATE_ISO: "yyyy-mm-dd",
|
|
927
|
-
DATE_MONTH_YEAR: "mmmm yyyy",
|
|
928
|
-
DATE_WEEKDAY: "dddd, d mmmm yyyy",
|
|
929
|
-
DATE_DAY_MONTH: "d mmmm",
|
|
930
|
-
DATE_YEAR_ONLY: "yyyy",
|
|
931
|
-
DATETIME_SHORT: "dd.mm.yyyy hh:mm",
|
|
932
|
-
DATETIME_LONG: "d mmmm yyyy hh:mm:ss",
|
|
933
|
-
TIME_HH_MM: "hh:mm",
|
|
934
|
-
TIME_HH_MM_SS: "hh:mm:ss",
|
|
935
|
-
TIME_12H: "h:mm AM/PM",
|
|
936
|
-
DATETIME_24H: "dd.mm.yyyy hh:mm:ss",
|
|
937
|
-
DATETIME_ISO: 'yyyy-mm-dd"T"hh:mm:ss',
|
|
938
|
-
TIME_MS: "hh:mm:ss.000"
|
|
939
|
-
};
|
|
940
|
-
function isFormattedCell(val) {
|
|
941
|
-
return val !== null && val !== void 0 && typeof val === "object" && "format" in val && typeof val.format === "string";
|
|
942
|
-
}
|
|
943
|
-
function unwrapCell(val) {
|
|
944
|
-
if (isFormattedCell(val)) {
|
|
945
|
-
return val.value;
|
|
946
|
-
}
|
|
947
|
-
return val;
|
|
948
|
-
}
|
|
949
|
-
function getFormat(val) {
|
|
950
|
-
if (isFormattedCell(val)) {
|
|
951
|
-
return val.format;
|
|
952
|
-
}
|
|
953
|
-
return null;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// src/writerHelpers.ts
|
|
957
|
-
var INVALID_SHEET_NAME_CHARS = /[\\/*?[\]:]/g;
|
|
958
|
-
function sanitizeSheetName(name, sheetCount) {
|
|
959
|
-
if (!name || typeof name !== "string") {
|
|
960
|
-
return `Sheet${sheetCount + 1}`;
|
|
961
|
-
}
|
|
962
|
-
let sanitized = name.replace(INVALID_SHEET_NAME_CHARS, "_");
|
|
963
|
-
if (sanitized.length > 31) {
|
|
964
|
-
sanitized = sanitized.substring(0, 31);
|
|
965
|
-
}
|
|
966
|
-
if (sanitized.trim().length === 0) {
|
|
967
|
-
sanitized = `Sheet${sheetCount + 1}`;
|
|
968
|
-
}
|
|
969
|
-
return sanitized;
|
|
970
|
-
}
|
|
971
|
-
function initColWidths(columnCount) {
|
|
972
|
-
return new Array(columnCount).fill(-1);
|
|
973
|
-
}
|
|
974
|
-
function applyHeaderWidths(colWidths, headers, columnCount) {
|
|
975
|
-
for (let i = 0; i < columnCount; i++) {
|
|
976
|
-
const len = headers[i] ? headers[i].length + 1 : 0;
|
|
977
|
-
let width = 1.25 * len + 2;
|
|
978
|
-
if (width > 80) width = 80;
|
|
979
|
-
if (colWidths[i] < width) colWidths[i] = width;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
function updateColWidthsFromRows(colWidths, rows, sampleLimit = 100) {
|
|
983
|
-
for (let r = 0; r < Math.min(rows.length, sampleLimit); r++) {
|
|
984
|
-
const row = rows[r];
|
|
985
|
-
for (let c = 0; c < row.length; c++) {
|
|
986
|
-
const raw = row[c];
|
|
987
|
-
if (raw === null || raw === void 0) continue;
|
|
988
|
-
const val = isFormattedCell(raw) ? raw.value : raw;
|
|
989
|
-
const len = val instanceof Date ? 20 : String(val).length + 1;
|
|
990
|
-
let width = 1.25 * len + 2;
|
|
991
|
-
if (width > 80) width = 80;
|
|
992
|
-
if (colWidths[c] < width) colWidths[c] = width;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
function defaultColWidth(width, floor = false) {
|
|
997
|
-
if (width > 0) {
|
|
998
|
-
return floor ? Math.floor(width) : width;
|
|
999
|
-
}
|
|
1000
|
-
return 10;
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
1427
|
// src/StreamingSheetState.ts
|
|
1004
1428
|
var StreamingSheetState = class {
|
|
1005
1429
|
constructor() {
|
|
@@ -1051,7 +1475,13 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
1051
1475
|
/**
|
|
1052
1476
|
* @param filePath Destination `.xlsb` path on disk.
|
|
1053
1477
|
*/
|
|
1054
|
-
|
|
1478
|
+
/**
|
|
1479
|
+
* @param filePath Destination `.xlsb` path on disk.
|
|
1480
|
+
* @param options.compressionLevel Deflate level 0-9 (default 6).
|
|
1481
|
+
*/
|
|
1482
|
+
constructor(filePath, options = {}) {
|
|
1483
|
+
this._outputError = null;
|
|
1484
|
+
this._finalized = false;
|
|
1055
1485
|
this.sheetCount = 0;
|
|
1056
1486
|
this.sheetList = [];
|
|
1057
1487
|
this.sstDic = /* @__PURE__ */ new Map();
|
|
@@ -1062,8 +1492,21 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
1062
1492
|
this._nextXfIdx = 4;
|
|
1063
1493
|
this._stFmtRecords = /* @__PURE__ */ new Map();
|
|
1064
1494
|
this.stream = new StreamingSheetState();
|
|
1065
|
-
|
|
1066
|
-
this.
|
|
1495
|
+
const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
|
|
1496
|
+
this._targetPath = path2.resolve(filePath);
|
|
1497
|
+
fs.mkdirSync(path2.dirname(this._targetPath), { recursive: true });
|
|
1498
|
+
this._tempPath = path2.join(
|
|
1499
|
+
path2.dirname(this._targetPath),
|
|
1500
|
+
`.${path2.basename(this._targetPath)}.${randomUUID()}.tmp`
|
|
1501
|
+
);
|
|
1502
|
+
this.output = fs.createWriteStream(this._tempPath);
|
|
1503
|
+
this.output.on("error", (err) => {
|
|
1504
|
+
this._outputError = err;
|
|
1505
|
+
});
|
|
1506
|
+
this.archive = archiver("zip", { zlib: { level: compressionLevel } });
|
|
1507
|
+
this.archive.on("error", (err) => {
|
|
1508
|
+
this._outputError = err;
|
|
1509
|
+
});
|
|
1067
1510
|
this.archive.pipe(this.output);
|
|
1068
1511
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
1069
1512
|
this._sheet1Bytes = Buffer.from([
|
|
@@ -2933,6 +3376,11 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
2933
3376
|
* @param options Optional settings (hidden, doAutofilter)
|
|
2934
3377
|
*/
|
|
2935
3378
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
3379
|
+
assertColumnCount(columnCount);
|
|
3380
|
+
if (headers !== void 0) {
|
|
3381
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
3382
|
+
for (const h of headers) assertCellTextLength(h);
|
|
3383
|
+
}
|
|
2936
3384
|
const { hidden = false, doAutofilter = true, sampleRows } = options;
|
|
2937
3385
|
this.addSheet(sheetName, hidden);
|
|
2938
3386
|
this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
|
|
@@ -2998,6 +3446,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
2998
3446
|
`Row length mismatch. Expected ${this.stream.endCol - this.stream.startCol} columns, got ${row.length}`
|
|
2999
3447
|
);
|
|
3000
3448
|
}
|
|
3449
|
+
assertRowCount(this.stream.rowNum + 1);
|
|
3001
3450
|
this.createRowHeader(bigBuf, this.stream.rowNum, this.stream.startCol, this.stream.endCol);
|
|
3002
3451
|
for (let c = 0; c < row.length; c++) {
|
|
3003
3452
|
const raw = row[c];
|
|
@@ -3020,7 +3469,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3020
3469
|
} else if (typeof val === "boolean") {
|
|
3021
3470
|
this.writeBool(bigBuf, val, c);
|
|
3022
3471
|
} else if (val instanceof Date) {
|
|
3023
|
-
const oaDate = (val.getTime()
|
|
3472
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
3024
3473
|
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
3025
3474
|
} else if (val !== null && val !== void 0) {
|
|
3026
3475
|
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
|
|
@@ -3077,6 +3526,16 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3077
3526
|
} else if (headers) {
|
|
3078
3527
|
columnCount = headers.length;
|
|
3079
3528
|
}
|
|
3529
|
+
if (columnCount > 0) assertColumnCount(columnCount);
|
|
3530
|
+
const totalRowsCheck = rows.length + (headers ? 1 : 0);
|
|
3531
|
+
assertRowCount(totalRowsCheck);
|
|
3532
|
+
if (headers) {
|
|
3533
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
3534
|
+
for (const h of headers) assertCellTextLength(h);
|
|
3535
|
+
}
|
|
3536
|
+
for (let r = 0; r < rows.length; r++) {
|
|
3537
|
+
assertRowFitsColumns("row", rows[r].length, columnCount);
|
|
3538
|
+
}
|
|
3080
3539
|
const colWidths = initColWidths(columnCount);
|
|
3081
3540
|
if (headers) {
|
|
3082
3541
|
applyHeaderWidths(colWidths, headers, columnCount);
|
|
@@ -3152,7 +3611,7 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3152
3611
|
} else if (typeof val === "boolean") {
|
|
3153
3612
|
this.writeBool(bigBuf, val, c);
|
|
3154
3613
|
} else if (val instanceof Date) {
|
|
3155
|
-
const oaDate = (val.getTime()
|
|
3614
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
3156
3615
|
this.writeDouble(bigBuf, oaDate, c, fmtString !== null ? styleNum : 1);
|
|
3157
3616
|
} else if (val !== null && val !== void 0) {
|
|
3158
3617
|
this.writeString(bigBuf, val.toString(), c, false, fmtString !== null ? styleNum : void 0);
|
|
@@ -3235,13 +3694,14 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3235
3694
|
bigBuf.writeUnsafeByte(val ? 1 : 0);
|
|
3236
3695
|
}
|
|
3237
3696
|
writeDateTime(bigBuf, date, colNum) {
|
|
3238
|
-
const oaDate = (date.getTime()
|
|
3697
|
+
const oaDate = timeToOaDate(date.getTime(), this._oaEpoch);
|
|
3239
3698
|
this.writeDouble(bigBuf, oaDate, colNum, 1);
|
|
3240
3699
|
}
|
|
3241
3700
|
_getDateWidth(_date) {
|
|
3242
3701
|
return 19;
|
|
3243
3702
|
}
|
|
3244
3703
|
writeString(bigBuf, val, colNum, bolded = false, styleOverride) {
|
|
3704
|
+
assertCellTextLength(val);
|
|
3245
3705
|
let index;
|
|
3246
3706
|
if (this.sstDic.has(val)) {
|
|
3247
3707
|
index = this.sstDic.get(val);
|
|
@@ -3267,8 +3727,8 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3267
3727
|
bigBuf.writeByte(159);
|
|
3268
3728
|
bigBuf.writeByte(1);
|
|
3269
3729
|
bigBuf.writeByte(8);
|
|
3270
|
-
bigBuf.writeInt32LE(this.sstCntUnique);
|
|
3271
3730
|
bigBuf.writeInt32LE(this.sstCntAll);
|
|
3731
|
+
bigBuf.writeInt32LE(this.sstCntUnique);
|
|
3272
3732
|
for (const [txt] of this.sstDic) {
|
|
3273
3733
|
const txtLen = txt.length;
|
|
3274
3734
|
bigBuf.writeByte(19);
|
|
@@ -3321,8 +3781,61 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3321
3781
|
* Must be awaited before the file is fully written.
|
|
3322
3782
|
*/
|
|
3323
3783
|
finalize() {
|
|
3324
|
-
return new Promise((
|
|
3784
|
+
return new Promise((resolve6, reject) => {
|
|
3785
|
+
if (this._finalized) {
|
|
3786
|
+
reject(new Error("XlsbWriter: already finalized"));
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
this._finalized = true;
|
|
3790
|
+
let settled = false;
|
|
3791
|
+
const cleanupTemp = () => {
|
|
3792
|
+
try {
|
|
3793
|
+
if (fs.existsSync(this._tempPath)) fs.rmSync(this._tempPath);
|
|
3794
|
+
} catch {
|
|
3795
|
+
}
|
|
3796
|
+
};
|
|
3797
|
+
const fail = (err) => {
|
|
3798
|
+
if (settled) return;
|
|
3799
|
+
settled = true;
|
|
3800
|
+
try {
|
|
3801
|
+
this.archive.abort();
|
|
3802
|
+
} catch {
|
|
3803
|
+
}
|
|
3804
|
+
try {
|
|
3805
|
+
this.output.destroy();
|
|
3806
|
+
} catch {
|
|
3807
|
+
}
|
|
3808
|
+
cleanupTemp();
|
|
3809
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3810
|
+
};
|
|
3811
|
+
const succeed = () => {
|
|
3812
|
+
if (settled) return;
|
|
3813
|
+
if (this._outputError) {
|
|
3814
|
+
fail(this._outputError);
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
3817
|
+
settled = true;
|
|
3818
|
+
try {
|
|
3819
|
+
const fd = fs.openSync(this._tempPath, "r+");
|
|
3820
|
+
try {
|
|
3821
|
+
fs.fsyncSync(fd);
|
|
3822
|
+
} finally {
|
|
3823
|
+
fs.closeSync(fd);
|
|
3824
|
+
}
|
|
3825
|
+
try {
|
|
3826
|
+
const existing = fs.statSync(this._targetPath);
|
|
3827
|
+
fs.chmodSync(this._tempPath, existing.mode & 511);
|
|
3828
|
+
} catch {
|
|
3829
|
+
}
|
|
3830
|
+
fs.renameSync(this._tempPath, this._targetPath);
|
|
3831
|
+
resolve6();
|
|
3832
|
+
} catch (err) {
|
|
3833
|
+
cleanupTemp();
|
|
3834
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3835
|
+
}
|
|
3836
|
+
};
|
|
3325
3837
|
try {
|
|
3838
|
+
if (this._outputError) throw this._outputError;
|
|
3326
3839
|
this.saveSst();
|
|
3327
3840
|
this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
|
|
3328
3841
|
const wbBuffers = [];
|
|
@@ -3419,11 +3932,12 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3419
3932
|
</Relationships>`;
|
|
3420
3933
|
this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
|
|
3421
3934
|
}
|
|
3422
|
-
this.output.
|
|
3423
|
-
this.
|
|
3935
|
+
this.output.once("close", succeed);
|
|
3936
|
+
this.output.once("error", fail);
|
|
3937
|
+
this.archive.once("error", fail);
|
|
3424
3938
|
this.archive.finalize();
|
|
3425
3939
|
} catch (err) {
|
|
3426
|
-
|
|
3940
|
+
fail(err);
|
|
3427
3941
|
}
|
|
3428
3942
|
});
|
|
3429
3943
|
}
|
|
@@ -3431,27 +3945,21 @@ var XlsbWriter = class _XlsbWriter {
|
|
|
3431
3945
|
|
|
3432
3946
|
// src/XlsxWriter.ts
|
|
3433
3947
|
import * as fs2 from "fs";
|
|
3948
|
+
import * as path3 from "path";
|
|
3949
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3434
3950
|
import archiver2 from "archiver";
|
|
3435
3951
|
import { Readable as Readable2 } from "stream";
|
|
3436
|
-
var COLUMN_LETTERS = (() => {
|
|
3437
|
-
const letters = [];
|
|
3438
|
-
for (let i = 65; i < 91; i++) {
|
|
3439
|
-
letters.push(String.fromCharCode(i));
|
|
3440
|
-
}
|
|
3441
|
-
const temp = [];
|
|
3442
|
-
for (const p of letters) {
|
|
3443
|
-
for (let i = 65; i < 91; i++) {
|
|
3444
|
-
temp.push(p + String.fromCharCode(i));
|
|
3445
|
-
}
|
|
3446
|
-
}
|
|
3447
|
-
letters.push(...temp);
|
|
3448
|
-
return letters;
|
|
3449
|
-
})();
|
|
3450
3952
|
var XlsxWriter = class {
|
|
3451
3953
|
/**
|
|
3452
3954
|
* @param filePath Destination `.xlsx` path on disk.
|
|
3453
3955
|
*/
|
|
3454
|
-
|
|
3956
|
+
/**
|
|
3957
|
+
* @param filePath Destination `.xlsx` path on disk.
|
|
3958
|
+
* @param options.compressionLevel Deflate level 0-9 (default 6).
|
|
3959
|
+
*/
|
|
3960
|
+
constructor(filePath, options = {}) {
|
|
3961
|
+
this._outputError = null;
|
|
3962
|
+
this._finalized = false;
|
|
3455
3963
|
this.sheetCount = 0;
|
|
3456
3964
|
this.sheetList = [];
|
|
3457
3965
|
this.sstArray = [];
|
|
@@ -3465,13 +3973,26 @@ var XlsxWriter = class {
|
|
|
3465
3973
|
this._nextXfIndex = 4;
|
|
3466
3974
|
this.stream = new StreamingSheetState();
|
|
3467
3975
|
this.currentColLetters = [];
|
|
3468
|
-
|
|
3469
|
-
this.
|
|
3976
|
+
const compressionLevel = assertCompressionLevel(options.compressionLevel ?? DEFAULT_COMPRESSION_LEVEL);
|
|
3977
|
+
this._targetPath = path3.resolve(filePath);
|
|
3978
|
+
fs2.mkdirSync(path3.dirname(this._targetPath), { recursive: true });
|
|
3979
|
+
this._tempPath = path3.join(
|
|
3980
|
+
path3.dirname(this._targetPath),
|
|
3981
|
+
`.${path3.basename(this._targetPath)}.${randomUUID2()}.tmp`
|
|
3982
|
+
);
|
|
3983
|
+
this.output = fs2.createWriteStream(this._tempPath);
|
|
3984
|
+
this.output.on("error", (err) => {
|
|
3985
|
+
this._outputError = err;
|
|
3986
|
+
});
|
|
3987
|
+
this.archive = archiver2("zip", { zlib: { level: compressionLevel } });
|
|
3988
|
+
this.archive.on("error", (err) => {
|
|
3989
|
+
this._outputError = err;
|
|
3990
|
+
});
|
|
3470
3991
|
this.archive.pipe(this.output);
|
|
3471
3992
|
this._oaEpoch = Date.UTC(1899, 11, 30);
|
|
3472
3993
|
}
|
|
3473
3994
|
_getColumnLetter(colIndex) {
|
|
3474
|
-
return colIndex
|
|
3995
|
+
return columnIndexToLetter(colIndex);
|
|
3475
3996
|
}
|
|
3476
3997
|
/** @deprecated Internal; kept for unit-test access. Prefer sanitizeSheetName(). */
|
|
3477
3998
|
_sanitizeSheetName(name) {
|
|
@@ -3508,7 +4029,7 @@ var XlsxWriter = class {
|
|
|
3508
4029
|
_needsEscape(str) {
|
|
3509
4030
|
for (let i = 0; i < str.length; i++) {
|
|
3510
4031
|
const c = str.charCodeAt(i);
|
|
3511
|
-
if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
|
|
4032
|
+
if (c === 38 || c === 60 || c === 62 || c === 34 || c === 39 || c === 13 || c >= 0 && c <= 8 || c === 11 || c === 12 || c >= 14 && c <= 31) {
|
|
3512
4033
|
return true;
|
|
3513
4034
|
}
|
|
3514
4035
|
}
|
|
@@ -3516,13 +4037,19 @@ var XlsxWriter = class {
|
|
|
3516
4037
|
}
|
|
3517
4038
|
_escape(str) {
|
|
3518
4039
|
if (typeof str !== "string") return str;
|
|
4040
|
+
str = str.replace(/_x([0-9A-Fa-f]{4})_/g, "_x005F_x$1_");
|
|
3519
4041
|
if (!this._needsEscape(str)) return str;
|
|
3520
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
|
|
4042
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r/g, " ").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
|
|
3521
4043
|
}
|
|
3522
4044
|
/**
|
|
3523
4045
|
* Start streaming a sheet. Call `writeRow` for each row, then `endSheet`.
|
|
3524
4046
|
*/
|
|
3525
4047
|
startSheet(sheetName, columnCount, headers, options = {}) {
|
|
4048
|
+
assertColumnCount(columnCount);
|
|
4049
|
+
if (headers !== void 0) {
|
|
4050
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
4051
|
+
for (const h of headers) assertCellTextLength(h);
|
|
4052
|
+
}
|
|
3526
4053
|
const { hidden = false, doAutofilter = true, sampleRows } = options;
|
|
3527
4054
|
this.addSheet(sheetName, hidden);
|
|
3528
4055
|
this.stream.begin(columnCount, doAutofilter && headers !== void 0, new BigBuffer());
|
|
@@ -3565,7 +4092,7 @@ var XlsxWriter = class {
|
|
|
3565
4092
|
this.stream.rowNum++;
|
|
3566
4093
|
bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
|
|
3567
4094
|
for (let c = 0; c < headers.length; c++) {
|
|
3568
|
-
this._writeStringCell(bigBuf, headers[c],
|
|
4095
|
+
this._writeStringCell(bigBuf, headers[c], "", 3);
|
|
3569
4096
|
}
|
|
3570
4097
|
bigBuf.writeString("</row>");
|
|
3571
4098
|
}
|
|
@@ -3579,7 +4106,9 @@ var XlsxWriter = class {
|
|
|
3579
4106
|
);
|
|
3580
4107
|
}
|
|
3581
4108
|
this.stream.rowNum++;
|
|
4109
|
+
assertRowCount(this.stream.rowNum);
|
|
3582
4110
|
bigBuf.writeString(`<row r="${this.stream.rowNum}">`);
|
|
4111
|
+
let prevCol = -1;
|
|
3583
4112
|
for (let c = 0; c < row.length; c++) {
|
|
3584
4113
|
const raw = row[c];
|
|
3585
4114
|
if (raw === null || raw === void 0) continue;
|
|
@@ -3587,27 +4116,28 @@ var XlsxWriter = class {
|
|
|
3587
4116
|
const val = fmtString !== null ? unwrapCell(raw) : raw;
|
|
3588
4117
|
const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
|
|
3589
4118
|
const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
|
|
3590
|
-
const
|
|
4119
|
+
const ref = cellRefAttr(c, prevCol, this.currentColLetters[c], this.stream.rowNum);
|
|
4120
|
+
prevCol = c;
|
|
3591
4121
|
if (typeof val === "number") {
|
|
3592
4122
|
if (Number.isFinite(val)) {
|
|
3593
|
-
bigBuf.writeString(`<c
|
|
4123
|
+
bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
|
|
3594
4124
|
} else {
|
|
3595
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4125
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3596
4126
|
}
|
|
3597
4127
|
} else if (typeof val === "bigint") {
|
|
3598
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4128
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3599
4129
|
} else if (typeof val === "boolean") {
|
|
3600
|
-
bigBuf.writeString(`<c
|
|
4130
|
+
bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
|
|
3601
4131
|
} else if (val instanceof Date) {
|
|
3602
4132
|
const oaDate = this._toOADate(val);
|
|
3603
4133
|
if (Number.isFinite(oaDate)) {
|
|
3604
4134
|
const finalStyle = xfIdx >= 0 ? xfIdx : 1;
|
|
3605
|
-
bigBuf.writeString(`<c
|
|
4135
|
+
bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
|
|
3606
4136
|
} else {
|
|
3607
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4137
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3608
4138
|
}
|
|
3609
4139
|
} else if (val !== null && val !== void 0) {
|
|
3610
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4140
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3611
4141
|
}
|
|
3612
4142
|
}
|
|
3613
4143
|
bigBuf.writeString("</row>");
|
|
@@ -3642,6 +4172,16 @@ var XlsxWriter = class {
|
|
|
3642
4172
|
} else if (headers) {
|
|
3643
4173
|
columnCount = headers.length;
|
|
3644
4174
|
}
|
|
4175
|
+
if (columnCount > 0) assertColumnCount(columnCount);
|
|
4176
|
+
const totalRows = rows.length + (headers ? 1 : 0);
|
|
4177
|
+
assertRowCount(totalRows);
|
|
4178
|
+
if (headers) {
|
|
4179
|
+
assertRowFitsColumns("header", headers.length, columnCount);
|
|
4180
|
+
for (const h of headers) assertCellTextLength(h);
|
|
4181
|
+
}
|
|
4182
|
+
for (let r = 0; r < rows.length; r++) {
|
|
4183
|
+
assertRowFitsColumns("row", rows[r].length, columnCount);
|
|
4184
|
+
}
|
|
3645
4185
|
const colWidths = initColWidths(columnCount);
|
|
3646
4186
|
const colLetters = new Array(columnCount);
|
|
3647
4187
|
for (let i = 0; i < columnCount; i++) {
|
|
@@ -3653,7 +4193,6 @@ var XlsxWriter = class {
|
|
|
3653
4193
|
updateColWidthsFromRows(colWidths, rows);
|
|
3654
4194
|
bigBuf.writeString('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>');
|
|
3655
4195
|
bigBuf.writeString('<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">');
|
|
3656
|
-
const totalRows = rows.length + (headers ? 1 : 0);
|
|
3657
4196
|
if (totalRows > 0 && columnCount > 0) {
|
|
3658
4197
|
bigBuf.writeString(`<dimension ref="A1:${colLetters[columnCount - 1]}${totalRows}"/>`);
|
|
3659
4198
|
} else {
|
|
@@ -3684,7 +4223,7 @@ var XlsxWriter = class {
|
|
|
3684
4223
|
rowNum++;
|
|
3685
4224
|
bigBuf.writeString(`<row r="${rowNum}">`);
|
|
3686
4225
|
for (let c = 0; c < headers.length; c++) {
|
|
3687
|
-
this._writeStringCell(bigBuf, headers[c],
|
|
4226
|
+
this._writeStringCell(bigBuf, headers[c], "", 3);
|
|
3688
4227
|
}
|
|
3689
4228
|
bigBuf.writeString("</row>");
|
|
3690
4229
|
}
|
|
@@ -3692,6 +4231,7 @@ var XlsxWriter = class {
|
|
|
3692
4231
|
rowNum++;
|
|
3693
4232
|
bigBuf.writeString(`<row r="${rowNum}">`);
|
|
3694
4233
|
const row = rows[r];
|
|
4234
|
+
let prevCol = -1;
|
|
3695
4235
|
for (let c = 0; c < row.length; c++) {
|
|
3696
4236
|
const raw = row[c];
|
|
3697
4237
|
if (raw === null || raw === void 0) continue;
|
|
@@ -3699,27 +4239,28 @@ var XlsxWriter = class {
|
|
|
3699
4239
|
const val = fmtString !== null ? unwrapCell(raw) : raw;
|
|
3700
4240
|
const xfIdx = fmtString !== null ? this._registerFormat(fmtString) : -1;
|
|
3701
4241
|
const styleAttr = xfIdx >= 0 ? ` s="${xfIdx}"` : "";
|
|
3702
|
-
const
|
|
4242
|
+
const ref = cellRefAttr(c, prevCol, colLetters[c], rowNum);
|
|
4243
|
+
prevCol = c;
|
|
3703
4244
|
if (typeof val === "number") {
|
|
3704
4245
|
if (Number.isFinite(val)) {
|
|
3705
|
-
bigBuf.writeString(`<c
|
|
4246
|
+
bigBuf.writeString(`<c${ref}${styleAttr}><v>${val}</v></c>`);
|
|
3706
4247
|
} else {
|
|
3707
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4248
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3708
4249
|
}
|
|
3709
4250
|
} else if (typeof val === "bigint") {
|
|
3710
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4251
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3711
4252
|
} else if (typeof val === "boolean") {
|
|
3712
|
-
bigBuf.writeString(`<c
|
|
4253
|
+
bigBuf.writeString(`<c${ref} t="b"${styleAttr}><v>${val ? 1 : 0}</v></c>`);
|
|
3713
4254
|
} else if (val instanceof Date) {
|
|
3714
4255
|
const oaDate = this._toOADate(val);
|
|
3715
4256
|
if (Number.isFinite(oaDate)) {
|
|
3716
4257
|
const finalStyle = xfIdx >= 0 ? xfIdx : 1;
|
|
3717
|
-
bigBuf.writeString(`<c
|
|
4258
|
+
bigBuf.writeString(`<c${ref} s="${finalStyle}"><v>${oaDate}</v></c>`);
|
|
3718
4259
|
} else {
|
|
3719
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4260
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3720
4261
|
}
|
|
3721
4262
|
} else if (val !== null && val !== void 0) {
|
|
3722
|
-
this._writeStringCell(bigBuf, val.toString(),
|
|
4263
|
+
this._writeStringCell(bigBuf, val.toString(), ref, xfIdx >= 0 ? xfIdx : void 0);
|
|
3723
4264
|
}
|
|
3724
4265
|
}
|
|
3725
4266
|
bigBuf.writeString("</row>");
|
|
@@ -3738,7 +4279,9 @@ var XlsxWriter = class {
|
|
|
3738
4279
|
name: this.sheetList[this.sheetCount - 1].pathInArchive
|
|
3739
4280
|
});
|
|
3740
4281
|
}
|
|
3741
|
-
|
|
4282
|
+
/** `ref` is the cell's `r` attribute from `cellRefAttr`, or '' when omitted. */
|
|
4283
|
+
_writeStringCell(bigBuf, val, ref, styleOverride) {
|
|
4284
|
+
assertCellTextLength(val);
|
|
3742
4285
|
let index = this.sstMap.get(val);
|
|
3743
4286
|
if (index === void 0) {
|
|
3744
4287
|
index = this.sstArray.length;
|
|
@@ -3747,7 +4290,7 @@ var XlsxWriter = class {
|
|
|
3747
4290
|
}
|
|
3748
4291
|
this.sstCntAll++;
|
|
3749
4292
|
const styleAttr = styleOverride !== void 0 ? ` s="${styleOverride}"` : "";
|
|
3750
|
-
bigBuf.writeString(`<c
|
|
4293
|
+
bigBuf.writeString(`<c${ref} t="s"${styleAttr}><v>${index}</v></c>`);
|
|
3751
4294
|
}
|
|
3752
4295
|
_registerFormat(fmtString) {
|
|
3753
4296
|
if (this._formatXfMap.has(fmtString)) {
|
|
@@ -3760,29 +4303,79 @@ var XlsxWriter = class {
|
|
|
3760
4303
|
return xfIndex;
|
|
3761
4304
|
}
|
|
3762
4305
|
_toOADate(date) {
|
|
3763
|
-
return (date.getTime()
|
|
4306
|
+
return timeToOaDate(date.getTime(), this._oaEpoch);
|
|
3764
4307
|
}
|
|
3765
4308
|
_getDateWidth(_date) {
|
|
3766
4309
|
return 19;
|
|
3767
4310
|
}
|
|
3768
4311
|
/** Finalize the ZIP package and close the output stream. */
|
|
3769
4312
|
finalize() {
|
|
3770
|
-
return new Promise((
|
|
4313
|
+
return new Promise((resolve6, reject) => {
|
|
4314
|
+
if (this._finalized) {
|
|
4315
|
+
reject(new Error("XlsxWriter: already finalized"));
|
|
4316
|
+
return;
|
|
4317
|
+
}
|
|
4318
|
+
this._finalized = true;
|
|
4319
|
+
let settled = false;
|
|
4320
|
+
const cleanupTemp = () => {
|
|
4321
|
+
try {
|
|
4322
|
+
if (fs2.existsSync(this._tempPath)) fs2.rmSync(this._tempPath);
|
|
4323
|
+
} catch {
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
const fail = (err) => {
|
|
4327
|
+
if (settled) return;
|
|
4328
|
+
settled = true;
|
|
4329
|
+
try {
|
|
4330
|
+
this.archive.abort();
|
|
4331
|
+
} catch {
|
|
4332
|
+
}
|
|
4333
|
+
try {
|
|
4334
|
+
this.output.destroy();
|
|
4335
|
+
} catch {
|
|
4336
|
+
}
|
|
4337
|
+
cleanupTemp();
|
|
4338
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4339
|
+
};
|
|
4340
|
+
const succeed = () => {
|
|
4341
|
+
if (settled) return;
|
|
4342
|
+
if (this._outputError) {
|
|
4343
|
+
fail(this._outputError);
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
settled = true;
|
|
4347
|
+
try {
|
|
4348
|
+
const fd = fs2.openSync(this._tempPath, "r+");
|
|
4349
|
+
try {
|
|
4350
|
+
fs2.fsyncSync(fd);
|
|
4351
|
+
} finally {
|
|
4352
|
+
fs2.closeSync(fd);
|
|
4353
|
+
}
|
|
4354
|
+
try {
|
|
4355
|
+
const existing = fs2.statSync(this._targetPath);
|
|
4356
|
+
fs2.chmodSync(this._tempPath, existing.mode & 511);
|
|
4357
|
+
} catch {
|
|
4358
|
+
}
|
|
4359
|
+
fs2.renameSync(this._tempPath, this._targetPath);
|
|
4360
|
+
resolve6();
|
|
4361
|
+
} catch (err) {
|
|
4362
|
+
cleanupTemp();
|
|
4363
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
4364
|
+
}
|
|
4365
|
+
};
|
|
3771
4366
|
try {
|
|
4367
|
+
if (this._outputError) throw this._outputError;
|
|
3772
4368
|
this._writeSharedStrings();
|
|
3773
4369
|
this._writeStyles();
|
|
3774
4370
|
this._writeWorkbook();
|
|
3775
4371
|
this._writeContentTypes();
|
|
3776
4372
|
this._writeRels();
|
|
3777
|
-
this.output.
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
this.archive.on("error", (err) => {
|
|
3781
|
-
reject(err);
|
|
3782
|
-
});
|
|
4373
|
+
this.output.once("close", succeed);
|
|
4374
|
+
this.output.once("error", fail);
|
|
4375
|
+
this.archive.once("error", fail);
|
|
3783
4376
|
this.archive.finalize();
|
|
3784
4377
|
} catch (err) {
|
|
3785
|
-
|
|
4378
|
+
fail(err);
|
|
3786
4379
|
}
|
|
3787
4380
|
});
|
|
3788
4381
|
}
|
|
@@ -3914,13 +4507,13 @@ ${xfEntries.join("\n")}
|
|
|
3914
4507
|
// src/XlsxUpdater.ts
|
|
3915
4508
|
import * as fs5 from "fs";
|
|
3916
4509
|
import * as os from "os";
|
|
3917
|
-
import * as
|
|
4510
|
+
import * as path6 from "path";
|
|
3918
4511
|
import AdmZip2 from "adm-zip";
|
|
3919
4512
|
|
|
3920
4513
|
// src/atomicFile.ts
|
|
3921
4514
|
import * as fs3 from "fs";
|
|
3922
|
-
import * as
|
|
3923
|
-
import { randomUUID } from "crypto";
|
|
4515
|
+
import * as path4 from "path";
|
|
4516
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3924
4517
|
var realFileOperations = {
|
|
3925
4518
|
exists: (filePath) => fs3.existsSync(filePath),
|
|
3926
4519
|
rename: (sourcePath, destinationPath) => fs3.renameSync(sourcePath, destinationPath),
|
|
@@ -3943,7 +4536,7 @@ function readDestinationMode(target) {
|
|
|
3943
4536
|
}
|
|
3944
4537
|
}
|
|
3945
4538
|
function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
|
|
3946
|
-
const backupPath = `${destinationPath}.${
|
|
4539
|
+
const backupPath = `${destinationPath}.${randomUUID3()}.replacement-backup`;
|
|
3947
4540
|
let backupCreated = false;
|
|
3948
4541
|
let installed = false;
|
|
3949
4542
|
if (operations.exists(destinationPath)) {
|
|
@@ -3991,11 +4584,11 @@ function installTemporaryOutput(temporaryPath, destinationPath, overwrite, opera
|
|
|
3991
4584
|
}
|
|
3992
4585
|
}
|
|
3993
4586
|
function writeBufferAtomically(buffer, destinationPath) {
|
|
3994
|
-
const target =
|
|
3995
|
-
fs3.mkdirSync(
|
|
3996
|
-
const temporaryPath =
|
|
3997
|
-
|
|
3998
|
-
`.${
|
|
4587
|
+
const target = path4.resolve(destinationPath);
|
|
4588
|
+
fs3.mkdirSync(path4.dirname(target), { recursive: true });
|
|
4589
|
+
const temporaryPath = path4.join(
|
|
4590
|
+
path4.dirname(target),
|
|
4591
|
+
`.${path4.basename(target)}.${randomUUID3()}.tmp`
|
|
3999
4592
|
);
|
|
4000
4593
|
const destinationMode = readDestinationMode(target);
|
|
4001
4594
|
try {
|
|
@@ -4031,8 +4624,8 @@ function writeBufferAtomically(buffer, destinationPath) {
|
|
|
4031
4624
|
|
|
4032
4625
|
// src/streamingZip.ts
|
|
4033
4626
|
import * as fs4 from "fs";
|
|
4034
|
-
import * as
|
|
4035
|
-
import { randomUUID as
|
|
4627
|
+
import * as path5 from "path";
|
|
4628
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4036
4629
|
import { finished } from "stream/promises";
|
|
4037
4630
|
import { Readable as Readable3 } from "stream";
|
|
4038
4631
|
import archiver3 from "archiver";
|
|
@@ -4044,8 +4637,8 @@ function readDestinationMode2(target) {
|
|
|
4044
4637
|
return void 0;
|
|
4045
4638
|
}
|
|
4046
4639
|
}
|
|
4047
|
-
function appendAndWait(archive, source, name) {
|
|
4048
|
-
return new Promise((
|
|
4640
|
+
function appendAndWait(archive, source, name, output) {
|
|
4641
|
+
return new Promise((resolve6, reject) => {
|
|
4049
4642
|
const cleanup = () => {
|
|
4050
4643
|
archive.off("entry", onEntry);
|
|
4051
4644
|
archive.off("error", onError);
|
|
@@ -4053,10 +4646,13 @@ function appendAndWait(archive, source, name) {
|
|
|
4053
4646
|
if (typeof maybeEmitter2.off === "function") {
|
|
4054
4647
|
maybeEmitter2.off("error", onError);
|
|
4055
4648
|
}
|
|
4649
|
+
if (output) {
|
|
4650
|
+
output.off("error", onError);
|
|
4651
|
+
}
|
|
4056
4652
|
};
|
|
4057
4653
|
const onEntry = () => {
|
|
4058
4654
|
cleanup();
|
|
4059
|
-
|
|
4655
|
+
resolve6();
|
|
4060
4656
|
};
|
|
4061
4657
|
const onError = (error) => {
|
|
4062
4658
|
cleanup();
|
|
@@ -4064,6 +4660,9 @@ function appendAndWait(archive, source, name) {
|
|
|
4064
4660
|
};
|
|
4065
4661
|
archive.once("entry", onEntry);
|
|
4066
4662
|
archive.once("error", onError);
|
|
4663
|
+
if (output) {
|
|
4664
|
+
output.once("error", onError);
|
|
4665
|
+
}
|
|
4067
4666
|
const maybeEmitter = source;
|
|
4068
4667
|
if (typeof maybeEmitter.once === "function") {
|
|
4069
4668
|
maybeEmitter.once("error", onError);
|
|
@@ -4077,11 +4676,11 @@ function appendAndWait(archive, source, name) {
|
|
|
4077
4676
|
});
|
|
4078
4677
|
}
|
|
4079
4678
|
async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
4080
|
-
const target =
|
|
4081
|
-
fs4.mkdirSync(
|
|
4082
|
-
const temporaryPath =
|
|
4083
|
-
|
|
4084
|
-
`.${
|
|
4679
|
+
const target = path5.resolve(destinationPath);
|
|
4680
|
+
fs4.mkdirSync(path5.dirname(target), { recursive: true });
|
|
4681
|
+
const temporaryPath = path5.join(
|
|
4682
|
+
path5.dirname(target),
|
|
4683
|
+
`.${path5.basename(target)}.${randomUUID4()}.tmp`
|
|
4085
4684
|
);
|
|
4086
4685
|
const destinationMode = readDestinationMode2(target);
|
|
4087
4686
|
let output = null;
|
|
@@ -4091,18 +4690,32 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
|
4091
4690
|
temporaryPath,
|
|
4092
4691
|
destinationMode !== void 0 ? { mode: destinationMode } : {}
|
|
4093
4692
|
);
|
|
4094
|
-
|
|
4693
|
+
let outputError = null;
|
|
4694
|
+
output.on("error", (err) => {
|
|
4695
|
+
outputError = err;
|
|
4696
|
+
try {
|
|
4697
|
+
archive == null ? void 0 : archive.abort();
|
|
4698
|
+
} catch {
|
|
4699
|
+
}
|
|
4700
|
+
});
|
|
4701
|
+
archive = archiver3("zip", { zlib: { level: DEFAULT_COMPRESSION_LEVEL } });
|
|
4702
|
+
archive.on("error", (err) => {
|
|
4703
|
+
outputError = err;
|
|
4704
|
+
});
|
|
4095
4705
|
archive.pipe(output);
|
|
4096
4706
|
for (const entry of zip.getEntries()) {
|
|
4707
|
+
if (outputError) throw outputError;
|
|
4097
4708
|
const stagedPath = stagedParts.get(entry.entryName);
|
|
4098
4709
|
if (stagedPath) {
|
|
4099
|
-
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName);
|
|
4710
|
+
await appendAndWait(archive, fs4.createReadStream(stagedPath), entry.entryName, output);
|
|
4100
4711
|
} else {
|
|
4101
|
-
await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName);
|
|
4712
|
+
await appendAndWait(archive, Readable3.from([entry.getData()]), entry.entryName, output);
|
|
4102
4713
|
}
|
|
4103
4714
|
}
|
|
4715
|
+
if (outputError) throw outputError;
|
|
4104
4716
|
await archive.finalize();
|
|
4105
4717
|
await finished(output);
|
|
4718
|
+
if (outputError) throw outputError;
|
|
4106
4719
|
if (destinationMode !== void 0) {
|
|
4107
4720
|
try {
|
|
4108
4721
|
fs4.chmodSync(temporaryPath, destinationMode);
|
|
@@ -4136,6 +4749,23 @@ async function writeAdmZipStreaming(zip, stagedParts, destinationPath) {
|
|
|
4136
4749
|
}
|
|
4137
4750
|
|
|
4138
4751
|
// src/XlsxUpdater.ts
|
|
4752
|
+
function parseCellRange(ref) {
|
|
4753
|
+
const m = /^\$?([A-Za-z]+)\$?(\d+)(?::\$?([A-Za-z]+)\$?(\d+))?$/.exec(ref.trim());
|
|
4754
|
+
if (!m) return null;
|
|
4755
|
+
const c1 = columnLetterToIndex(m[1].toUpperCase());
|
|
4756
|
+
const r1 = parseInt(m[2], 10);
|
|
4757
|
+
const c2 = m[3] ? columnLetterToIndex(m[3].toUpperCase()) : c1;
|
|
4758
|
+
const r2 = m[4] ? parseInt(m[4], 10) : r1;
|
|
4759
|
+
if (!Number.isFinite(c1) || !Number.isFinite(r1) || !Number.isFinite(c2) || !Number.isFinite(r2)) {
|
|
4760
|
+
return null;
|
|
4761
|
+
}
|
|
4762
|
+
return {
|
|
4763
|
+
c1: Math.min(c1, c2),
|
|
4764
|
+
r1: Math.min(r1, r2),
|
|
4765
|
+
c2: Math.max(c1, c2),
|
|
4766
|
+
r2: Math.max(r1, r2)
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4139
4769
|
var XlsxUpdater = class _XlsxUpdater {
|
|
4140
4770
|
/**
|
|
4141
4771
|
* @param path Path to an existing `.xlsx` file.
|
|
@@ -4222,6 +4852,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4222
4852
|
const dimRef = totalRows > 0 && lastCol >= 0 ? `A1:${columnIndexToLetter(lastCol)}${totalRows}` : "A1";
|
|
4223
4853
|
this._updatePivotCaches(sheetName, dimRef, rows.length);
|
|
4224
4854
|
this._addRefreshOnLoadToPivotTables();
|
|
4855
|
+
this._removeCalcChain();
|
|
4856
|
+
this._updateTableRefs(sheetName, dimRef);
|
|
4225
4857
|
}
|
|
4226
4858
|
/**
|
|
4227
4859
|
* Replace worksheet rows from a one-pass source without materialising the
|
|
@@ -4294,10 +4926,10 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4294
4926
|
if (outputError) throw outputError;
|
|
4295
4927
|
writtenBytes += data.length;
|
|
4296
4928
|
if (rowOutput.write(data)) return;
|
|
4297
|
-
await new Promise((
|
|
4929
|
+
await new Promise((resolve6, reject) => {
|
|
4298
4930
|
const onDrain = () => {
|
|
4299
4931
|
cleanup();
|
|
4300
|
-
|
|
4932
|
+
resolve6();
|
|
4301
4933
|
};
|
|
4302
4934
|
const onError = (error) => {
|
|
4303
4935
|
cleanup();
|
|
@@ -4337,8 +4969,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4337
4969
|
lastKeptEnd = writtenBytes;
|
|
4338
4970
|
}
|
|
4339
4971
|
}
|
|
4340
|
-
await new Promise((
|
|
4341
|
-
rowOutput.once("close",
|
|
4972
|
+
await new Promise((resolve6, reject) => {
|
|
4973
|
+
rowOutput.once("close", resolve6);
|
|
4342
4974
|
rowOutput.once("error", reject);
|
|
4343
4975
|
rowOutput.end();
|
|
4344
4976
|
});
|
|
@@ -4357,6 +4989,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4357
4989
|
this._commitSharedStrings();
|
|
4358
4990
|
this._updatePivotCaches(sheetName, dimRef, keptDataRows);
|
|
4359
4991
|
this._addRefreshOnLoadToPivotTables();
|
|
4992
|
+
this._removeCalcChain();
|
|
4993
|
+
this._updateTableRefs(sheetName, dimRef);
|
|
4360
4994
|
if (oldStage && oldStage !== outputPath) {
|
|
4361
4995
|
this._removeTemporaryFile(oldStage);
|
|
4362
4996
|
}
|
|
@@ -4430,7 +5064,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4430
5064
|
}
|
|
4431
5065
|
_ensureTemporaryDirectory() {
|
|
4432
5066
|
if (this.temporaryDirectory === null) {
|
|
4433
|
-
this.temporaryDirectory = fs5.mkdtempSync(
|
|
5067
|
+
this.temporaryDirectory = fs5.mkdtempSync(path6.join(os.tmpdir(), "spreadsheet-updater-"));
|
|
4434
5068
|
} else {
|
|
4435
5069
|
try {
|
|
4436
5070
|
fs5.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
@@ -4440,7 +5074,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4440
5074
|
return this.temporaryDirectory;
|
|
4441
5075
|
}
|
|
4442
5076
|
_temporaryPath(suffix) {
|
|
4443
|
-
return
|
|
5077
|
+
return path6.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
4444
5078
|
}
|
|
4445
5079
|
_removeTemporaryFile(filePath) {
|
|
4446
5080
|
try {
|
|
@@ -4480,10 +5114,15 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4480
5114
|
const rIdToTarget = {};
|
|
4481
5115
|
if (relsEntry) {
|
|
4482
5116
|
const rels = relsEntry.getData().toString("utf8");
|
|
4483
|
-
const
|
|
4484
|
-
let
|
|
4485
|
-
while ((
|
|
4486
|
-
|
|
5117
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5118
|
+
let tagMatch;
|
|
5119
|
+
while ((tagMatch = relTagRe.exec(rels)) !== null) {
|
|
5120
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
5121
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
5122
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
5123
|
+
if (id !== void 0 && target !== void 0) {
|
|
5124
|
+
rIdToTarget[id] = target;
|
|
5125
|
+
}
|
|
4487
5126
|
}
|
|
4488
5127
|
}
|
|
4489
5128
|
const wbEntry = this.zip.getEntry("xl/workbook.xml");
|
|
@@ -4492,21 +5131,23 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4492
5131
|
let match;
|
|
4493
5132
|
while ((match = sheetRe.exec(wbXml)) !== null) {
|
|
4494
5133
|
const tag = match[0];
|
|
4495
|
-
const
|
|
4496
|
-
const
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
const
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
fullPath = fullPath.substring(1);
|
|
4503
|
-
} else if (!fullPath.startsWith("xl/")) {
|
|
4504
|
-
fullPath = "xl/" + fullPath;
|
|
4505
|
-
}
|
|
5134
|
+
const attrs = parseTagAttributes(tag);
|
|
5135
|
+
const rawName = attrs["name"];
|
|
5136
|
+
const rId = attrs["r:id"] ?? attrs["id"] ?? attrs["rId"];
|
|
5137
|
+
if (rawName === void 0 || rId === void 0) continue;
|
|
5138
|
+
const name = unescapeXml(rawName);
|
|
5139
|
+
const target = rIdToTarget[rId] ?? "";
|
|
5140
|
+
const fullPath = resolveRelTarget(target);
|
|
4506
5141
|
if (fullPath) {
|
|
4507
5142
|
this.sheetNameToPath.set(name, fullPath);
|
|
4508
5143
|
}
|
|
4509
5144
|
}
|
|
5145
|
+
const wbPrMatch = /<workbookPr\b[^>]*>/i.exec(wbXml);
|
|
5146
|
+
if (wbPrMatch) {
|
|
5147
|
+
const wbPrAttrs = parseTagAttributes(wbPrMatch[0]);
|
|
5148
|
+
const v = (wbPrAttrs["date1904"] ?? "").trim().toLowerCase();
|
|
5149
|
+
this._oaEpoch = v === "1" || v === "true" ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
5150
|
+
}
|
|
4510
5151
|
for (const entry of this.zip.getEntries()) {
|
|
4511
5152
|
const name = entry.entryName;
|
|
4512
5153
|
if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.xml$/.test(name)) {
|
|
@@ -4566,7 +5207,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4566
5207
|
throw new Error("XlsxUpdater: sharedStrings.xml has no closing sst element.");
|
|
4567
5208
|
}
|
|
4568
5209
|
const appended = this.sharedStringsPending.map((txt) => {
|
|
4569
|
-
const clean =
|
|
5210
|
+
const clean = escapeXlsxText(txt);
|
|
4570
5211
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4571
5212
|
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
4572
5213
|
return `<${prefix}si><${prefix}t${tAttr}>${clean}</${prefix}t></${prefix}si>`;
|
|
@@ -4589,14 +5230,25 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4589
5230
|
const sdEnd = sheetXml.indexOf("</sheetData>", sdStart);
|
|
4590
5231
|
if (sdEnd === -1) return { dataStyles: /* @__PURE__ */ new Map(), headerStyles };
|
|
4591
5232
|
const region = sheetXml.substring(sdStart, sdEnd);
|
|
4592
|
-
const cellRe = /<c\b[^>]*>/g;
|
|
5233
|
+
const cellRe = /<row\b[^>]*>|<c\b[^>]*>/g;
|
|
4593
5234
|
let m;
|
|
5235
|
+
let row = 0;
|
|
5236
|
+
let nextCol = 0;
|
|
4594
5237
|
while ((m = cellRe.exec(region)) !== null) {
|
|
4595
5238
|
const tag = m[0];
|
|
5239
|
+
if (tag.startsWith("<row")) {
|
|
5240
|
+
const rowAttr = /\br="(\d+)"/.exec(tag);
|
|
5241
|
+
row = rowAttr ? parseInt(rowAttr[1], 10) : row + 1;
|
|
5242
|
+
nextCol = 0;
|
|
5243
|
+
continue;
|
|
5244
|
+
}
|
|
4596
5245
|
const rMatch = /r="([A-Z]+)(\d+)"/.exec(tag);
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
5246
|
+
let col = nextCol;
|
|
5247
|
+
if (rMatch) {
|
|
5248
|
+
col = columnLetterToIndex(rMatch[1]);
|
|
5249
|
+
row = parseInt(rMatch[2], 10);
|
|
5250
|
+
}
|
|
5251
|
+
nextCol = col + 1;
|
|
4600
5252
|
const sMatch = /s="(\d+)"/.exec(tag);
|
|
4601
5253
|
if (!sMatch) continue;
|
|
4602
5254
|
const s = parseInt(sMatch[1], 10);
|
|
@@ -4701,7 +5353,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4701
5353
|
return `<c r="${colRef}${rowNum}" t="b"${colStyle > 0 ? ` s="${colStyle}"` : ""}><v>${val ? 1 : 0}</v></c>`;
|
|
4702
5354
|
}
|
|
4703
5355
|
if (val instanceof Date) {
|
|
4704
|
-
const oaDate = (val.getTime()
|
|
5356
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
4705
5357
|
if (Number.isFinite(oaDate)) {
|
|
4706
5358
|
const style = colStyle > 0 ? colStyle : dateStyle !== null ? dateStyle : 0;
|
|
4707
5359
|
return `<c r="${colRef}${rowNum}"${style > 0 ? ` s="${style}"` : ""}><v>${oaDate}</v></c>`;
|
|
@@ -4717,7 +5369,7 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4717
5369
|
const colRef = columnIndexToLetter(col);
|
|
4718
5370
|
const styleAttr = style > 0 ? ` s="${style}"` : "";
|
|
4719
5371
|
if (this.sharedStringsXml === null) {
|
|
4720
|
-
const clean =
|
|
5372
|
+
const clean = escapeXlsxText(text);
|
|
4721
5373
|
const preserve = clean.length > 0 && (clean[0] === " " || clean[clean.length - 1] === " " || /[\t\n\r]/.test(clean));
|
|
4722
5374
|
const tAttr = preserve ? ' xml:space="preserve"' : "";
|
|
4723
5375
|
return `<c r="${colRef}${rowNum}" t="inlineStr"${styleAttr}><is><t${tAttr}>${clean}</t></is></c>`;
|
|
@@ -4918,8 +5570,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4918
5570
|
}
|
|
4919
5571
|
}
|
|
4920
5572
|
_addRefreshOnLoadToPivotTables() {
|
|
4921
|
-
for (const
|
|
4922
|
-
const entry = this.zip.getEntry(
|
|
5573
|
+
for (const path9 of this.pivotTableDefPaths) {
|
|
5574
|
+
const entry = this.zip.getEntry(path9);
|
|
4923
5575
|
if (!entry) continue;
|
|
4924
5576
|
let xml = entry.getData().toString("utf8");
|
|
4925
5577
|
const rootRe = /<pivotTableDefinition\b[^>]*>/;
|
|
@@ -4927,7 +5579,233 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4927
5579
|
if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
|
|
4928
5580
|
const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
|
|
4929
5581
|
xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
|
|
4930
|
-
this.zip.updateFile(
|
|
5582
|
+
this.zip.updateFile(path9, Buffer.from(xml, "utf8"));
|
|
5583
|
+
}
|
|
5584
|
+
}
|
|
5585
|
+
/**
|
|
5586
|
+
* Remove the stale calculation chain (xl/calcChain.xml). Excel rebuilds it
|
|
5587
|
+
* on open; keeping stale <c> entries for the replaced sheet would force a
|
|
5588
|
+
* repair prompt. Removes the part, its Content_Types override and its
|
|
5589
|
+
* workbook rels entry (xlspy parity).
|
|
5590
|
+
*/
|
|
5591
|
+
_removeCalcChain() {
|
|
5592
|
+
const calcPath = "xl/calcChain.xml";
|
|
5593
|
+
if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) {
|
|
5594
|
+
this._ensureCalcPrFullCalc();
|
|
5595
|
+
return;
|
|
5596
|
+
}
|
|
5597
|
+
try {
|
|
5598
|
+
this.zip.deleteFile(calcPath);
|
|
5599
|
+
} catch {
|
|
5600
|
+
}
|
|
5601
|
+
const stagedCalc = this.stagedParts.get(calcPath);
|
|
5602
|
+
if (stagedCalc) {
|
|
5603
|
+
this.stagedParts.delete(calcPath);
|
|
5604
|
+
try {
|
|
5605
|
+
fs5.rmSync(stagedCalc, { force: true });
|
|
5606
|
+
} catch {
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
const ctEntry = this.zip.getEntry("[Content_Types].xml");
|
|
5610
|
+
if (ctEntry || this.stagedParts.has("[Content_Types].xml")) {
|
|
5611
|
+
try {
|
|
5612
|
+
let ct = this._entryData("[Content_Types].xml").toString("utf8");
|
|
5613
|
+
const before = ct;
|
|
5614
|
+
ct = ct.replace(/<Override\b[^>]*PartName="\/xl\/calcChain\.xml"[^>]*\/?>/gi, "");
|
|
5615
|
+
if (ct !== before) {
|
|
5616
|
+
this._writePartText("[Content_Types].xml", ct);
|
|
5617
|
+
}
|
|
5618
|
+
} catch {
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
const relsPath = "xl/_rels/workbook.xml.rels";
|
|
5622
|
+
if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
|
|
5623
|
+
try {
|
|
5624
|
+
const rels = this._entryData(relsPath).toString("utf8");
|
|
5625
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5626
|
+
let m;
|
|
5627
|
+
let out = "";
|
|
5628
|
+
let last = 0;
|
|
5629
|
+
let changed = false;
|
|
5630
|
+
while ((m = relTagRe.exec(rels)) !== null) {
|
|
5631
|
+
const attrs = parseTagAttributes(m[0]);
|
|
5632
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
5633
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
5634
|
+
const isCalcChain = type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.xml";
|
|
5635
|
+
if (isCalcChain) {
|
|
5636
|
+
out += rels.substring(last, m.index);
|
|
5637
|
+
last = m.index + m[0].length;
|
|
5638
|
+
changed = true;
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
if (changed) {
|
|
5642
|
+
out += rels.substring(last);
|
|
5643
|
+
this._writePartText(relsPath, out);
|
|
5644
|
+
}
|
|
5645
|
+
} catch {
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
this._ensureCalcPrFullCalc();
|
|
5649
|
+
}
|
|
5650
|
+
_writePartText(name, text) {
|
|
5651
|
+
if (this.stagedParts.has(name)) {
|
|
5652
|
+
fs5.writeFileSync(this.stagedParts.get(name), text, "utf8");
|
|
5653
|
+
} else {
|
|
5654
|
+
this.zip.updateFile(name, Buffer.from(text, "utf8"));
|
|
5655
|
+
}
|
|
5656
|
+
}
|
|
5657
|
+
_ensureCalcPrFullCalc() {
|
|
5658
|
+
try {
|
|
5659
|
+
const wbPath = "xl/workbook.xml";
|
|
5660
|
+
if (!this.zip.getEntry(wbPath) && !this.stagedParts.has(wbPath)) return;
|
|
5661
|
+
let xml = this._entryData(wbPath).toString("utf8");
|
|
5662
|
+
const calcPrRe = /<(?:[A-Za-z_][\w.-]*:)?calcPr\b[^>]*>/i;
|
|
5663
|
+
const match = calcPrRe.exec(xml);
|
|
5664
|
+
if (match && match.index !== void 0) {
|
|
5665
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(match[0], "fullCalcOnLoad", "1");
|
|
5666
|
+
xml = xml.substring(0, match.index) + newTag + xml.substring(match.index + match[0].length);
|
|
5667
|
+
} else {
|
|
5668
|
+
const insertAt = _XlsxUpdater._calcPrInsertPosition(xml);
|
|
5669
|
+
if (insertAt < 0) return;
|
|
5670
|
+
xml = xml.substring(0, insertAt) + '<calcPr fullCalcOnLoad="1"/>' + xml.substring(insertAt);
|
|
5671
|
+
}
|
|
5672
|
+
this._writePartText(wbPath, xml);
|
|
5673
|
+
} catch {
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
/**
|
|
5677
|
+
* Insertion offset for a missing `<calcPr>`. The element follows
|
|
5678
|
+
* sheets/externalReferences/definedNames in the workbook schema, so insert
|
|
5679
|
+
* after the last of those that exists instead of right after `<workbook>`.
|
|
5680
|
+
*/
|
|
5681
|
+
static _calcPrInsertPosition(xml) {
|
|
5682
|
+
const prefixed = "(?:[A-Za-z_][\\w.-]*:)?";
|
|
5683
|
+
const candidates = [
|
|
5684
|
+
new RegExp(`</${prefixed}sheets\\s*>`, "i"),
|
|
5685
|
+
new RegExp(`<${prefixed}externalReferences\\b[^>]*/>`, "i"),
|
|
5686
|
+
new RegExp(`<${prefixed}externalReferences\\b[^>]*>[\\s\\S]*?</${prefixed}externalReferences\\s*>`, "i"),
|
|
5687
|
+
new RegExp(`<${prefixed}definedNames\\b[^>]*/>`, "i"),
|
|
5688
|
+
new RegExp(`<${prefixed}definedNames\\b[^>]*>[\\s\\S]*?</${prefixed}definedNames\\s*>`, "i")
|
|
5689
|
+
];
|
|
5690
|
+
let insertAt = -1;
|
|
5691
|
+
for (const re of candidates) {
|
|
5692
|
+
const m = re.exec(xml);
|
|
5693
|
+
if (m && m.index !== void 0) {
|
|
5694
|
+
insertAt = Math.max(insertAt, m.index + m[0].length);
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
if (insertAt >= 0) return insertAt;
|
|
5698
|
+
const close = new RegExp(`</${prefixed}workbook\\s*>`, "i").exec(xml);
|
|
5699
|
+
if (close && close.index !== void 0) return close.index;
|
|
5700
|
+
const root = new RegExp(`<${prefixed}workbook\\b[^>]*>`, "i").exec(xml);
|
|
5701
|
+
if (!root || root.index === void 0) return -1;
|
|
5702
|
+
return root.index + root[0].length;
|
|
5703
|
+
}
|
|
5704
|
+
/**
|
|
5705
|
+
* Update ListObject table ranges (xl/tables/*.xml) for the replaced sheet.
|
|
5706
|
+
* Only tables anchored at A1 are updated to the new dimension; tables
|
|
5707
|
+
* elsewhere are left untouched (moving them would corrupt the sheet).
|
|
5708
|
+
* Empty replacements (dimRef "A1") do not shrink multi-column tables.
|
|
5709
|
+
*/
|
|
5710
|
+
_updateTableRefs(sheetName, dimRef) {
|
|
5711
|
+
const sheetPath = this.sheetNameToPath.get(sheetName);
|
|
5712
|
+
if (!sheetPath) return;
|
|
5713
|
+
const slash = sheetPath.lastIndexOf("/");
|
|
5714
|
+
const dir = slash >= 0 ? sheetPath.substring(0, slash + 1) : "";
|
|
5715
|
+
const base = slash >= 0 ? sheetPath.substring(slash + 1) : sheetPath;
|
|
5716
|
+
const sheetRelsPath = `${dir}_rels/${base}.rels`;
|
|
5717
|
+
const sheetRelsEntry = this.zip.getEntry(sheetRelsPath);
|
|
5718
|
+
const hasStagedRels = this.stagedParts.has(sheetRelsPath);
|
|
5719
|
+
if (!sheetRelsEntry && !hasStagedRels) return;
|
|
5720
|
+
const tableTargets = [];
|
|
5721
|
+
try {
|
|
5722
|
+
const relsXml = this._entryData(sheetRelsPath).toString("utf8");
|
|
5723
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
5724
|
+
let m;
|
|
5725
|
+
while ((m = relTagRe.exec(relsXml)) !== null) {
|
|
5726
|
+
const attrs = parseTagAttributes(m[0]);
|
|
5727
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
5728
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
5729
|
+
if (!type.includes("/table") || !target) continue;
|
|
5730
|
+
let tablePath;
|
|
5731
|
+
if (target.startsWith("/")) {
|
|
5732
|
+
tablePath = resolveRelTarget(target);
|
|
5733
|
+
} else {
|
|
5734
|
+
const combined = dir + target;
|
|
5735
|
+
const parts = [];
|
|
5736
|
+
for (const seg of combined.split("/")) {
|
|
5737
|
+
if (seg === "..") parts.pop();
|
|
5738
|
+
else if (seg !== "." && seg !== "") parts.push(seg);
|
|
5739
|
+
}
|
|
5740
|
+
tablePath = parts.join("/");
|
|
5741
|
+
}
|
|
5742
|
+
tableTargets.push(tablePath);
|
|
5743
|
+
}
|
|
5744
|
+
} catch {
|
|
5745
|
+
return;
|
|
5746
|
+
}
|
|
5747
|
+
if (tableTargets.length === 0) return;
|
|
5748
|
+
const tables = [];
|
|
5749
|
+
for (const tablePath of tableTargets) {
|
|
5750
|
+
try {
|
|
5751
|
+
const hasEntry = !!this.zip.getEntry(tablePath);
|
|
5752
|
+
const hasStaged = this.stagedParts.has(tablePath);
|
|
5753
|
+
if (!hasEntry && !hasStaged) continue;
|
|
5754
|
+
const xml = this._entryData(tablePath).toString("utf8");
|
|
5755
|
+
const tableTagRe = /<(?:[A-Za-z_][\w.-]*:)?table\b[^>]*>/i;
|
|
5756
|
+
const tableMatch = tableTagRe.exec(xml);
|
|
5757
|
+
if (!tableMatch || tableMatch.index === void 0) continue;
|
|
5758
|
+
const refMatch = /\bref\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tableMatch[0]);
|
|
5759
|
+
const ref = refMatch ? refMatch[2] ?? refMatch[3] ?? "" : "";
|
|
5760
|
+
tables.push({
|
|
5761
|
+
path: tablePath,
|
|
5762
|
+
xml,
|
|
5763
|
+
tag: tableMatch[0],
|
|
5764
|
+
tagIndex: tableMatch.index,
|
|
5765
|
+
ref,
|
|
5766
|
+
range: parseCellRange(ref),
|
|
5767
|
+
staged: hasStaged
|
|
5768
|
+
});
|
|
5769
|
+
} catch {
|
|
5770
|
+
}
|
|
5771
|
+
}
|
|
5772
|
+
if (tables.length === 0) return;
|
|
5773
|
+
const dimRange = parseCellRange(dimRef);
|
|
5774
|
+
if (!dimRange) return;
|
|
5775
|
+
for (const table of tables) {
|
|
5776
|
+
try {
|
|
5777
|
+
const origRange = table.range;
|
|
5778
|
+
if (!origRange) continue;
|
|
5779
|
+
if (origRange.r1 !== 1 || origRange.c1 !== 0) continue;
|
|
5780
|
+
if (dimRef === "A1" && !/^A1$/i.test(table.ref)) continue;
|
|
5781
|
+
const origCols = origRange.c2 - origRange.c1 + 1;
|
|
5782
|
+
const newCols = dimRange.c2 - dimRange.c1 + 1;
|
|
5783
|
+
if (newCols !== origCols) continue;
|
|
5784
|
+
let lastRow = dimRange.r2;
|
|
5785
|
+
for (const other of tables) {
|
|
5786
|
+
if (other === table || !other.range) continue;
|
|
5787
|
+
if (other.range.r1 <= 1) continue;
|
|
5788
|
+
if (other.range.c1 > origRange.c2 || other.range.c2 < origRange.c1) continue;
|
|
5789
|
+
lastRow = Math.min(lastRow, other.range.r1 - 1);
|
|
5790
|
+
}
|
|
5791
|
+
if (lastRow < 1) continue;
|
|
5792
|
+
const newRef = origRange.c2 === 0 && lastRow === 1 ? "A1" : `A1:${columnIndexToLetter(origRange.c2)}${lastRow}`;
|
|
5793
|
+
let xml = table.xml;
|
|
5794
|
+
const newTag = _XlsxUpdater._replaceOrAddXmlAttribute(table.tag, "ref", newRef);
|
|
5795
|
+
xml = xml.substring(0, table.tagIndex) + newTag + xml.substring(table.tagIndex + table.tag.length);
|
|
5796
|
+
const afRe = /<(?:[A-Za-z_][\w.-]*:)?autoFilter\b[^>]*>/i;
|
|
5797
|
+
const afMatch = afRe.exec(xml);
|
|
5798
|
+
if (afMatch && afMatch.index !== void 0) {
|
|
5799
|
+
const newAf = _XlsxUpdater._replaceOrAddXmlAttribute(afMatch[0], "ref", newRef);
|
|
5800
|
+
xml = xml.substring(0, afMatch.index) + newAf + xml.substring(afMatch.index + afMatch[0].length);
|
|
5801
|
+
}
|
|
5802
|
+
if (table.staged) {
|
|
5803
|
+
fs5.writeFileSync(this.stagedParts.get(table.path), xml, "utf8");
|
|
5804
|
+
} else {
|
|
5805
|
+
this.zip.updateFile(table.path, Buffer.from(xml, "utf8"));
|
|
5806
|
+
}
|
|
5807
|
+
} catch {
|
|
5808
|
+
}
|
|
4931
5809
|
}
|
|
4932
5810
|
}
|
|
4933
5811
|
};
|
|
@@ -4935,78 +5813,8 @@ var XlsxUpdater = class _XlsxUpdater {
|
|
|
4935
5813
|
// src/XlsbUpdater.ts
|
|
4936
5814
|
import * as fs6 from "fs";
|
|
4937
5815
|
import * as os2 from "os";
|
|
4938
|
-
import * as
|
|
5816
|
+
import * as path7 from "path";
|
|
4939
5817
|
import AdmZip3 from "adm-zip";
|
|
4940
|
-
|
|
4941
|
-
// src/biff12Utils.ts
|
|
4942
|
-
function readVlq(buf, pos) {
|
|
4943
|
-
let value = 0;
|
|
4944
|
-
let shift = 0;
|
|
4945
|
-
let byte;
|
|
4946
|
-
do {
|
|
4947
|
-
byte = buf[pos++];
|
|
4948
|
-
value |= (byte & 127) << shift;
|
|
4949
|
-
shift += 7;
|
|
4950
|
-
} while ((byte & 128) !== 0);
|
|
4951
|
-
return { value, nextPos: pos };
|
|
4952
|
-
}
|
|
4953
|
-
function vlqBytes(value) {
|
|
4954
|
-
const out = [];
|
|
4955
|
-
while (value >= 128) {
|
|
4956
|
-
out.push(value & 127 | 128);
|
|
4957
|
-
value >>>= 7;
|
|
4958
|
-
}
|
|
4959
|
-
out.push(value & 127);
|
|
4960
|
-
return out;
|
|
4961
|
-
}
|
|
4962
|
-
function readRecord(buf, pos, rec) {
|
|
4963
|
-
const idRes = readVlq(buf, pos);
|
|
4964
|
-
if (idRes.nextPos > buf.length) return false;
|
|
4965
|
-
const lenRes = readVlq(buf, idRes.nextPos);
|
|
4966
|
-
if (lenRes.nextPos + lenRes.value > buf.length) return false;
|
|
4967
|
-
rec.headerStart = pos;
|
|
4968
|
-
rec.dataStart = lenRes.nextPos;
|
|
4969
|
-
rec.dataEnd = lenRes.nextPos + lenRes.value;
|
|
4970
|
-
rec.id = idRes.value;
|
|
4971
|
-
rec.len = lenRes.value;
|
|
4972
|
-
return true;
|
|
4973
|
-
}
|
|
4974
|
-
function buildRecord(id, payload) {
|
|
4975
|
-
const idBytes = vlqBytes(id);
|
|
4976
|
-
const lenBytes = vlqBytes(payload.length);
|
|
4977
|
-
const header = Buffer.alloc(idBytes.length + lenBytes.length);
|
|
4978
|
-
idBytes.forEach((b, i) => header[i] = b);
|
|
4979
|
-
lenBytes.forEach((b, i) => header[idBytes.length + i] = b);
|
|
4980
|
-
return Buffer.concat([header, payload]);
|
|
4981
|
-
}
|
|
4982
|
-
function readUtf16(buf, start, charCount) {
|
|
4983
|
-
const end = start + charCount * 2;
|
|
4984
|
-
if (end > buf.length) return "";
|
|
4985
|
-
return buf.toString("utf16le", start, end);
|
|
4986
|
-
}
|
|
4987
|
-
function parseSharedStringsBin(buf) {
|
|
4988
|
-
const values = [];
|
|
4989
|
-
let total = 0;
|
|
4990
|
-
let unique = 0;
|
|
4991
|
-
let endSstOffset = buf.length;
|
|
4992
|
-
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
4993
|
-
let pos = 0;
|
|
4994
|
-
while (readRecord(buf, pos, rec)) {
|
|
4995
|
-
pos = rec.dataEnd;
|
|
4996
|
-
if (rec.id === 159) {
|
|
4997
|
-
total = buf.readUInt32LE(rec.dataStart);
|
|
4998
|
-
unique = buf.readUInt32LE(rec.dataStart + 4);
|
|
4999
|
-
} else if (rec.id === 19) {
|
|
5000
|
-
const cch = buf.readUInt32LE(rec.dataStart + 1);
|
|
5001
|
-
values.push(readUtf16(buf, rec.dataStart + 5, cch));
|
|
5002
|
-
} else if (rec.id === 160) {
|
|
5003
|
-
endSstOffset = rec.headerStart;
|
|
5004
|
-
}
|
|
5005
|
-
}
|
|
5006
|
-
return { values, total, unique, endSstOffset };
|
|
5007
|
-
}
|
|
5008
|
-
|
|
5009
|
-
// src/XlsbUpdater.ts
|
|
5010
5818
|
var CELL_RECORDS = /* @__PURE__ */ new Set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
|
5011
5819
|
var RK_INT_LOWER = -1 << 29;
|
|
5012
5820
|
var RK_INT_UPPER = (1 << 29) - 1;
|
|
@@ -5110,6 +5918,7 @@ var XlsbUpdater = class {
|
|
|
5110
5918
|
const rwLast = Math.max(0, rows.length + (headers ? 1 : 0) - 1);
|
|
5111
5919
|
const colLast = lastCol;
|
|
5112
5920
|
this._patchPivotCaches(sheetName, rwLast, colLast);
|
|
5921
|
+
this._removeCalcChain();
|
|
5113
5922
|
}
|
|
5114
5923
|
/** Replace worksheet rows from a one-pass source. */
|
|
5115
5924
|
async replaceSheetDataStream(sheetName, rows, options = {}) {
|
|
@@ -5177,10 +5986,10 @@ var XlsbUpdater = class {
|
|
|
5177
5986
|
if (outputError) throw outputError;
|
|
5178
5987
|
writtenBytes += data.length;
|
|
5179
5988
|
if (rowOutput.write(data)) return;
|
|
5180
|
-
await new Promise((
|
|
5989
|
+
await new Promise((resolve6, reject) => {
|
|
5181
5990
|
const onDrain = () => {
|
|
5182
5991
|
cleanup();
|
|
5183
|
-
|
|
5992
|
+
resolve6();
|
|
5184
5993
|
};
|
|
5185
5994
|
const onError = (error) => {
|
|
5186
5995
|
cleanup();
|
|
@@ -5231,8 +6040,8 @@ var XlsbUpdater = class {
|
|
|
5231
6040
|
lastKeptEnd = writtenBytes;
|
|
5232
6041
|
}
|
|
5233
6042
|
}
|
|
5234
|
-
await new Promise((
|
|
5235
|
-
rowOutput.once("close",
|
|
6043
|
+
await new Promise((resolve6, reject) => {
|
|
6044
|
+
rowOutput.once("close", resolve6);
|
|
5236
6045
|
rowOutput.once("error", reject);
|
|
5237
6046
|
rowOutput.end();
|
|
5238
6047
|
});
|
|
@@ -5295,6 +6104,7 @@ var XlsbUpdater = class {
|
|
|
5295
6104
|
Math.max(0, keptDataRows + (headers !== void 0 ? 1 : 0) - 1),
|
|
5296
6105
|
lastCol
|
|
5297
6106
|
);
|
|
6107
|
+
this._removeCalcChain();
|
|
5298
6108
|
if (oldStage && oldStage !== outputPath) this._removeTemporaryFile(oldStage);
|
|
5299
6109
|
createdParts.splice(createdParts.indexOf(outputPath), 1);
|
|
5300
6110
|
} catch (error) {
|
|
@@ -5365,7 +6175,7 @@ var XlsbUpdater = class {
|
|
|
5365
6175
|
}
|
|
5366
6176
|
_ensureTemporaryDirectory() {
|
|
5367
6177
|
if (this.temporaryDirectory === null) {
|
|
5368
|
-
this.temporaryDirectory = fs6.mkdtempSync(
|
|
6178
|
+
this.temporaryDirectory = fs6.mkdtempSync(path7.join(os2.tmpdir(), "spreadsheet-updater-"));
|
|
5369
6179
|
} else {
|
|
5370
6180
|
try {
|
|
5371
6181
|
fs6.mkdirSync(this.temporaryDirectory, { recursive: true });
|
|
@@ -5375,7 +6185,7 @@ var XlsbUpdater = class {
|
|
|
5375
6185
|
return this.temporaryDirectory;
|
|
5376
6186
|
}
|
|
5377
6187
|
_temporaryPath(suffix) {
|
|
5378
|
-
return
|
|
6188
|
+
return path7.join(this._ensureTemporaryDirectory(), `part-${Date.now()}-${Math.random().toString(16).slice(2)}${suffix}`);
|
|
5379
6189
|
}
|
|
5380
6190
|
_removeTemporaryFile(filePath) {
|
|
5381
6191
|
try {
|
|
@@ -5415,10 +6225,15 @@ var XlsbUpdater = class {
|
|
|
5415
6225
|
const rIdToTarget = {};
|
|
5416
6226
|
if (relsEntry) {
|
|
5417
6227
|
const rels = relsEntry.getData().toString("utf8");
|
|
5418
|
-
const
|
|
5419
|
-
let
|
|
5420
|
-
while ((
|
|
5421
|
-
|
|
6228
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
6229
|
+
let tagMatch;
|
|
6230
|
+
while ((tagMatch = relTagRe.exec(rels)) !== null) {
|
|
6231
|
+
const attrs = parseTagAttributes(tagMatch[0]);
|
|
6232
|
+
const id = attrs["Id"] ?? attrs["id"];
|
|
6233
|
+
const target = attrs["Target"] ?? attrs["target"];
|
|
6234
|
+
if (id !== void 0 && target !== void 0) {
|
|
6235
|
+
rIdToTarget[id] = target;
|
|
6236
|
+
}
|
|
5422
6237
|
}
|
|
5423
6238
|
}
|
|
5424
6239
|
const wbEntry = this.zip.getEntry("xl/workbook.bin");
|
|
@@ -5438,12 +6253,24 @@ var XlsbUpdater = class {
|
|
|
5438
6253
|
o += 4;
|
|
5439
6254
|
const name = readUtf16(wbBuf, o, nameLen);
|
|
5440
6255
|
let target = rIdToTarget[rId] ?? "";
|
|
5441
|
-
|
|
5442
|
-
else if (!target.startsWith("xl/")) target = "xl/" + target;
|
|
6256
|
+
target = resolveRelTarget(target);
|
|
5443
6257
|
if (target) {
|
|
5444
6258
|
this.sheetNameToPath.set(name, target);
|
|
5445
6259
|
}
|
|
5446
6260
|
}
|
|
6261
|
+
try {
|
|
6262
|
+
const rec1904 = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
6263
|
+
let p1904 = 0;
|
|
6264
|
+
while (readRecord(wbBuf, p1904, rec1904)) {
|
|
6265
|
+
if (rec1904.id === 153 && rec1904.len >= 4) {
|
|
6266
|
+
const is1904 = (wbBuf[rec1904.dataStart] & 1) !== 0;
|
|
6267
|
+
this._oaEpoch = is1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30);
|
|
6268
|
+
break;
|
|
6269
|
+
}
|
|
6270
|
+
p1904 = rec1904.dataEnd;
|
|
6271
|
+
}
|
|
6272
|
+
} catch {
|
|
6273
|
+
}
|
|
5447
6274
|
}
|
|
5448
6275
|
for (const e of this.zip.getEntries()) {
|
|
5449
6276
|
if (/^xl\/pivotCache\/pivotCacheDefinition\d*\.bin$/.test(e.entryName)) {
|
|
@@ -5552,27 +6379,15 @@ var XlsbUpdater = class {
|
|
|
5552
6379
|
_findDateXf() {
|
|
5553
6380
|
const entry = this.zip.getEntry("xl/styles.bin");
|
|
5554
6381
|
if (!entry) return null;
|
|
5555
|
-
const
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
if (
|
|
5563
|
-
|
|
5564
|
-
xfIndex = 0;
|
|
5565
|
-
continue;
|
|
5566
|
-
}
|
|
5567
|
-
if (rec.id === 618) {
|
|
5568
|
-
inCellXfs = false;
|
|
5569
|
-
continue;
|
|
5570
|
-
}
|
|
5571
|
-
if (inCellXfs && rec.id === 47 && rec.len >= 4) {
|
|
5572
|
-
const numFmtId = stylesBuf.readUInt16LE(rec.dataStart + 2);
|
|
5573
|
-
const isDate = numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47;
|
|
5574
|
-
if (isDate) return xfIndex;
|
|
5575
|
-
xfIndex++;
|
|
6382
|
+
const reader = new BiffReaderWriter(entry.getData());
|
|
6383
|
+
while (reader.readStyles()) {
|
|
6384
|
+
}
|
|
6385
|
+
const numFmtIds = reader._xfIndexToNumFmtId;
|
|
6386
|
+
const customDates = reader._customNumFmts;
|
|
6387
|
+
for (let i = 0; i < numFmtIds.length; i++) {
|
|
6388
|
+
const numFmtId = numFmtIds[i];
|
|
6389
|
+
if (numFmtId >= 14 && numFmtId <= 22 || numFmtId >= 45 && numFmtId <= 47 || customDates.has(numFmtId)) {
|
|
6390
|
+
return i;
|
|
5576
6391
|
}
|
|
5577
6392
|
}
|
|
5578
6393
|
return null;
|
|
@@ -5652,7 +6467,7 @@ var XlsbUpdater = class {
|
|
|
5652
6467
|
return buildRecord(4, payload);
|
|
5653
6468
|
}
|
|
5654
6469
|
if (val instanceof Date) {
|
|
5655
|
-
const oaDate = (val.getTime()
|
|
6470
|
+
const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
|
|
5656
6471
|
if (Number.isFinite(oaDate)) {
|
|
5657
6472
|
const style = colStyle > 0 ? colStyle : dateXf !== null ? dateXf : 0;
|
|
5658
6473
|
const payload = Buffer.alloc(16);
|
|
@@ -5719,10 +6534,10 @@ var XlsbUpdater = class {
|
|
|
5719
6534
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5720
6535
|
let pos = 0;
|
|
5721
6536
|
while (readRecord(originalSheet, pos, rec)) {
|
|
5722
|
-
if (rec.id ===
|
|
6537
|
+
if (rec.id === 148 && rec.len >= 16) {
|
|
5723
6538
|
const shift = rec.dataStart >= rowsEnd ? delta : 0;
|
|
5724
|
-
patchInt32(rec.dataStart + shift +
|
|
5725
|
-
patchInt32(rec.dataStart + shift +
|
|
6539
|
+
patchInt32(rec.dataStart + shift + 4, lastRowIdx);
|
|
6540
|
+
patchInt32(rec.dataStart + shift + 12, lastCol);
|
|
5726
6541
|
break;
|
|
5727
6542
|
}
|
|
5728
6543
|
pos = rec.dataEnd;
|
|
@@ -5761,8 +6576,8 @@ var XlsbUpdater = class {
|
|
|
5761
6576
|
return { rowsStart, rowsEnd, autoFilterRanges };
|
|
5762
6577
|
}
|
|
5763
6578
|
/**
|
|
5764
|
-
* Keep the sheet dimension record (
|
|
5765
|
-
* Excel uses its rwLast (payload offset
|
|
6579
|
+
* Keep the sheet dimension record (0x94) in sync with the new data.
|
|
6580
|
+
* Excel uses its rwLast (payload offset 4) and colLast (payload offset 12)
|
|
5766
6581
|
* fields to read the full row range; stale values make the last written row
|
|
5767
6582
|
* invisible to Excel (and drop it from pivot refreshes).
|
|
5768
6583
|
*/
|
|
@@ -5770,9 +6585,9 @@ var XlsbUpdater = class {
|
|
|
5770
6585
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
5771
6586
|
let pos = 0;
|
|
5772
6587
|
while (readRecord(sheetBuf, pos, rec)) {
|
|
5773
|
-
if (rec.id ===
|
|
5774
|
-
sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart +
|
|
5775
|
-
sheetBuf.writeInt32LE(lastCol, rec.dataStart +
|
|
6588
|
+
if (rec.id === 148 && rec.len >= 16) {
|
|
6589
|
+
sheetBuf.writeInt32LE(lastRowIdx, rec.dataStart + 4);
|
|
6590
|
+
sheetBuf.writeInt32LE(lastCol, rec.dataStart + 12);
|
|
5776
6591
|
return;
|
|
5777
6592
|
}
|
|
5778
6593
|
pos = rec.dataEnd;
|
|
@@ -5782,8 +6597,8 @@ var XlsbUpdater = class {
|
|
|
5782
6597
|
// Pivot cache integration
|
|
5783
6598
|
// ------------------------------------------------------------------
|
|
5784
6599
|
_patchPivotCaches(sheetName, rwLast, colLast) {
|
|
5785
|
-
for (const
|
|
5786
|
-
const entry = this.zip.getEntry(
|
|
6600
|
+
for (const path9 of this.pivotCacheDefPaths) {
|
|
6601
|
+
const entry = this.zip.getEntry(path9);
|
|
5787
6602
|
if (!entry) continue;
|
|
5788
6603
|
const buf = Buffer.from(entry.getData());
|
|
5789
6604
|
const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
|
|
@@ -5815,16 +6630,84 @@ var XlsbUpdater = class {
|
|
|
5815
6630
|
}
|
|
5816
6631
|
}
|
|
5817
6632
|
if (patched) {
|
|
5818
|
-
this.zip.updateFile(
|
|
6633
|
+
this.zip.updateFile(path9, buf);
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
/**
|
|
6638
|
+
* Remove the stale calculation chain (xl/calcChain.bin). Excel rebuilds it
|
|
6639
|
+
* on open. Removes the part, its Content_Types override and its workbook
|
|
6640
|
+
* rels entry. XLSB tables are not patched (documented limitation).
|
|
6641
|
+
*/
|
|
6642
|
+
_removeCalcChain() {
|
|
6643
|
+
const calcPath = "xl/calcChain.bin";
|
|
6644
|
+
if (!this.zip.getEntry(calcPath) && !this.stagedParts.has(calcPath)) return;
|
|
6645
|
+
try {
|
|
6646
|
+
this.zip.deleteFile(calcPath);
|
|
6647
|
+
} catch {
|
|
6648
|
+
}
|
|
6649
|
+
const stagedCalc = this.stagedParts.get(calcPath);
|
|
6650
|
+
if (stagedCalc) {
|
|
6651
|
+
this.stagedParts.delete(calcPath);
|
|
6652
|
+
try {
|
|
6653
|
+
fs6.rmSync(stagedCalc, { force: true });
|
|
6654
|
+
} catch {
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
const ctPath = "[Content_Types].xml";
|
|
6658
|
+
if (this.zip.getEntry(ctPath) || this.stagedParts.has(ctPath)) {
|
|
6659
|
+
try {
|
|
6660
|
+
const ct = this._entryData(ctPath).toString("utf8");
|
|
6661
|
+
const updated = ct.replace(
|
|
6662
|
+
/<Override\b[^>]*PartName="\/xl\/calcChain\.bin"[^>]*\/?>/gi,
|
|
6663
|
+
""
|
|
6664
|
+
);
|
|
6665
|
+
if (updated !== ct) this._writePartText(ctPath, updated);
|
|
6666
|
+
} catch {
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
const relsPath = "xl/_rels/workbook.bin.rels";
|
|
6670
|
+
if (this.zip.getEntry(relsPath) || this.stagedParts.has(relsPath)) {
|
|
6671
|
+
try {
|
|
6672
|
+
const rels = this._entryData(relsPath).toString("utf8");
|
|
6673
|
+
const relTagRe = /<Relationship\b[^>]*>/g;
|
|
6674
|
+
let m;
|
|
6675
|
+
let out = "";
|
|
6676
|
+
let last = 0;
|
|
6677
|
+
let changed = false;
|
|
6678
|
+
while ((m = relTagRe.exec(rels)) !== null) {
|
|
6679
|
+
const attrs = parseTagAttributes(m[0]);
|
|
6680
|
+
const type = attrs["Type"] ?? attrs["type"] ?? "";
|
|
6681
|
+
const target = attrs["Target"] ?? attrs["target"] ?? "";
|
|
6682
|
+
if (type.endsWith("/calcChain") && resolveRelTarget(target) === "xl/calcChain.bin") {
|
|
6683
|
+
out += rels.substring(last, m.index);
|
|
6684
|
+
last = m.index + m[0].length;
|
|
6685
|
+
changed = true;
|
|
6686
|
+
}
|
|
6687
|
+
}
|
|
6688
|
+
if (changed) {
|
|
6689
|
+
out += rels.substring(last);
|
|
6690
|
+
this._writePartText(relsPath, out);
|
|
6691
|
+
}
|
|
6692
|
+
} catch {
|
|
5819
6693
|
}
|
|
5820
6694
|
}
|
|
5821
6695
|
}
|
|
6696
|
+
/** Write an XML/text part to its staged file when present, else into the ZIP. */
|
|
6697
|
+
_writePartText(name, text) {
|
|
6698
|
+
const stagedPath = this.stagedParts.get(name);
|
|
6699
|
+
if (stagedPath) {
|
|
6700
|
+
fs6.writeFileSync(stagedPath, text, "utf8");
|
|
6701
|
+
} else {
|
|
6702
|
+
this.zip.updateFile(name, Buffer.from(text, "utf8"));
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
5822
6705
|
};
|
|
5823
6706
|
|
|
5824
6707
|
// src/ExcelConverter.ts
|
|
5825
6708
|
import * as fs7 from "fs";
|
|
5826
|
-
import * as
|
|
5827
|
-
import { randomUUID as
|
|
6709
|
+
import * as path8 from "path";
|
|
6710
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
5828
6711
|
import { spawn } from "child_process";
|
|
5829
6712
|
var POWERSHELL_CONVERSION_SCRIPT = `
|
|
5830
6713
|
$ErrorActionPreference = 'Stop'
|
|
@@ -5870,14 +6753,14 @@ function errorMessage2(error) {
|
|
|
5870
6753
|
return error instanceof Error ? error.message : String(error);
|
|
5871
6754
|
}
|
|
5872
6755
|
function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
|
|
5873
|
-
const input =
|
|
5874
|
-
const output =
|
|
5875
|
-
if (
|
|
6756
|
+
const input = path8.resolve(inputPath);
|
|
6757
|
+
const output = path8.resolve(outputPath);
|
|
6758
|
+
if (path8.extname(input).toLowerCase() !== inputExtension) {
|
|
5876
6759
|
throw new Error(
|
|
5877
6760
|
`Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
|
|
5878
6761
|
);
|
|
5879
6762
|
}
|
|
5880
|
-
if (
|
|
6763
|
+
if (path8.extname(output).toLowerCase() !== outputExtension) {
|
|
5881
6764
|
throw new Error(
|
|
5882
6765
|
`Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
|
|
5883
6766
|
);
|
|
@@ -5888,8 +6771,8 @@ function validateConversionPaths(inputPath, outputPath, inputExtension, outputEx
|
|
|
5888
6771
|
if (!fs7.statSync(input).isFile()) {
|
|
5889
6772
|
throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
|
|
5890
6773
|
}
|
|
5891
|
-
if (!fs7.existsSync(
|
|
5892
|
-
throw new Error(`Excel conversion: destination directory not found: ${
|
|
6774
|
+
if (!fs7.existsSync(path8.dirname(output))) {
|
|
6775
|
+
throw new Error(`Excel conversion: destination directory not found: ${path8.dirname(output)}`);
|
|
5893
6776
|
}
|
|
5894
6777
|
if (fs7.existsSync(output)) {
|
|
5895
6778
|
if (fs7.statSync(output).isDirectory()) {
|
|
@@ -5914,7 +6797,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5914
6797
|
JUSTYBASE_EXCEL_OUTPUT: outputPath,
|
|
5915
6798
|
JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
|
|
5916
6799
|
};
|
|
5917
|
-
return new Promise((
|
|
6800
|
+
return new Promise((resolve6, reject) => {
|
|
5918
6801
|
var _a, _b, _c, _d;
|
|
5919
6802
|
let stdout = "";
|
|
5920
6803
|
let stderr = "";
|
|
@@ -5949,7 +6832,7 @@ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
|
|
|
5949
6832
|
if (settled) return;
|
|
5950
6833
|
settled = true;
|
|
5951
6834
|
if (code === 0) {
|
|
5952
|
-
|
|
6835
|
+
resolve6();
|
|
5953
6836
|
return;
|
|
5954
6837
|
}
|
|
5955
6838
|
const details = (stderr || stdout).replace(/\s+/g, " ").trim();
|
|
@@ -5970,9 +6853,9 @@ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExt
|
|
|
5970
6853
|
if (process.platform !== "win32") {
|
|
5971
6854
|
throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
|
|
5972
6855
|
}
|
|
5973
|
-
const temporaryOutput =
|
|
5974
|
-
|
|
5975
|
-
`.${
|
|
6856
|
+
const temporaryOutput = path8.join(
|
|
6857
|
+
path8.dirname(output),
|
|
6858
|
+
`.${path8.basename(output, outputExtension)}.${randomUUID5()}.conversion${outputExtension}`
|
|
5976
6859
|
);
|
|
5977
6860
|
try {
|
|
5978
6861
|
await runPowerShellConversion(input, temporaryOutput, fileFormat);
|
|
@@ -6003,6 +6886,9 @@ export {
|
|
|
6003
6886
|
ExcelReaderAbstract,
|
|
6004
6887
|
F,
|
|
6005
6888
|
INVALID_SHEET_NAME_CHARS,
|
|
6889
|
+
MAX_CELL_CHARS,
|
|
6890
|
+
MAX_COLS,
|
|
6891
|
+
MAX_ROWS,
|
|
6006
6892
|
ReaderFactory,
|
|
6007
6893
|
StreamingSheetState,
|
|
6008
6894
|
XlsbReader,
|
|
@@ -6013,13 +6899,27 @@ export {
|
|
|
6013
6899
|
XlsxUpdater,
|
|
6014
6900
|
XlsxWriter,
|
|
6015
6901
|
applyHeaderWidths,
|
|
6902
|
+
assertCellTextLength,
|
|
6903
|
+
assertColumnCount,
|
|
6904
|
+
assertRowCount,
|
|
6905
|
+
assertRowFitsColumns,
|
|
6906
|
+
columnIndexToLetter,
|
|
6907
|
+
columnLetterToIndex,
|
|
6016
6908
|
convertXlsbToXlsx,
|
|
6017
6909
|
convertXlsxToXlsb,
|
|
6018
6910
|
defaultColWidth,
|
|
6911
|
+
escapeXlsxText,
|
|
6912
|
+
escapeXmlText,
|
|
6019
6913
|
getFormat,
|
|
6020
6914
|
initColWidths,
|
|
6021
6915
|
isFormattedCell,
|
|
6916
|
+
parseSharedStringsXml,
|
|
6917
|
+
parseTagAttributes,
|
|
6918
|
+
resolveRelTarget,
|
|
6022
6919
|
sanitizeSheetName,
|
|
6920
|
+
trimTrailingEmptyRows,
|
|
6921
|
+
unescapeXml,
|
|
6922
|
+
unescapeXmlText,
|
|
6023
6923
|
unwrapCell,
|
|
6024
6924
|
updateColWidthsFromRows
|
|
6025
6925
|
};
|