@cj-tech-master/excelts 1.1.0 → 1.4.1
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/dist/browser/excelts.iife.js +1089 -568
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +18 -18
- package/dist/cjs/doc/data-validations.js +29 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/utils/cell-format.js +815 -0
- package/dist/cjs/utils/parse-sax.js +2 -2
- package/dist/cjs/utils/{extra-utils.js → sheet-utils.js} +114 -89
- package/dist/cjs/utils/stream-buf.js +15 -4
- package/dist/cjs/utils/unzip/parse.js +82 -1
- package/dist/cjs/utils/utils.js +13 -17
- package/dist/cjs/utils/zip-stream.js +20 -32
- package/dist/cjs/xlsx/xform/sheet/data-validations-xform.js +46 -7
- package/dist/cjs/xlsx/xlsx.js +1 -2
- package/dist/esm/doc/data-validations.js +29 -1
- package/dist/esm/index.browser.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/cell-format.js +810 -0
- package/dist/esm/utils/parse-sax.js +1 -1
- package/dist/esm/utils/{extra-utils.js → sheet-utils.js} +97 -72
- package/dist/esm/utils/stream-buf.js +15 -4
- package/dist/esm/utils/unzip/parse.js +83 -2
- package/dist/esm/utils/utils.js +12 -16
- package/dist/esm/utils/zip-stream.js +20 -32
- package/dist/esm/xlsx/xform/sheet/data-validations-xform.js +46 -7
- package/dist/esm/xlsx/xlsx.js +1 -2
- package/dist/types/index.browser.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/cell-format.d.ts +32 -0
- package/dist/types/utils/{extra-utils.d.ts → sheet-utils.d.ts} +51 -52
- package/dist/types/utils/utils.d.ts +5 -2
- package/package.json +5 -5
- package/dist/cjs/utils/browser-buffer-decode.js +0 -13
- package/dist/cjs/utils/browser-buffer-encode.js +0 -13
- package/dist/cjs/utils/browser.js +0 -6
- package/dist/esm/utils/browser-buffer-decode.js +0 -11
- package/dist/esm/utils/browser-buffer-encode.js +0 -11
- package/dist/esm/utils/browser.js +0 -3
- package/dist/types/utils/browser-buffer-decode.d.ts +0 -2
- package/dist/types/utils/browser-buffer-encode.d.ts +0 -2
- package/dist/types/utils/browser.d.ts +0 -1
|
@@ -1,87 +1,112 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for ExcelTS
|
|
3
3
|
* Provides convenient helper functions for common spreadsheet operations
|
|
4
|
-
* compatible with xlsx library's utils API but built on excelts native types
|
|
5
4
|
*/
|
|
6
5
|
import { Workbook } from "../doc/workbook.js";
|
|
7
6
|
import { colCache } from "./col-cache.js";
|
|
7
|
+
import { dateToExcel } from "./utils.js";
|
|
8
|
+
import { format as cellFormat } from "./cell-format.js";
|
|
9
|
+
/**
|
|
10
|
+
* Get formatted display text for a cell value
|
|
11
|
+
* Returns the value formatted according to the cell's numFmt
|
|
12
|
+
* This matches Excel's display exactly
|
|
13
|
+
*/
|
|
14
|
+
function getCellDisplayText(cell) {
|
|
15
|
+
const value = cell.value;
|
|
16
|
+
const fmt = cell.numFmt || "General";
|
|
17
|
+
// Null/undefined
|
|
18
|
+
if (value == null) {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
// Date object - convert to Excel serial number
|
|
22
|
+
if (value instanceof Date) {
|
|
23
|
+
const serial = dateToExcel(value, false);
|
|
24
|
+
return cellFormat(fmt, serial);
|
|
25
|
+
}
|
|
26
|
+
// Number/Boolean/String - let cellFormat handle it
|
|
27
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
28
|
+
return cellFormat(fmt, value);
|
|
29
|
+
}
|
|
30
|
+
// Fallback to cell.text for other types (rich text, hyperlink, error, formula, etc.)
|
|
31
|
+
return cell.text;
|
|
32
|
+
}
|
|
8
33
|
// =============================================================================
|
|
9
34
|
// Cell Address Encoding/Decoding
|
|
10
35
|
// =============================================================================
|
|
11
36
|
/**
|
|
12
37
|
* Decode column string to 0-indexed number
|
|
13
|
-
* @example
|
|
38
|
+
* @example decodeCol("A") => 0, decodeCol("Z") => 25, decodeCol("AA") => 26
|
|
14
39
|
*/
|
|
15
|
-
export function
|
|
40
|
+
export function decodeCol(colstr) {
|
|
16
41
|
return colCache.l2n(colstr.toUpperCase()) - 1;
|
|
17
42
|
}
|
|
18
43
|
/**
|
|
19
44
|
* Encode 0-indexed column number to string
|
|
20
|
-
* @example
|
|
45
|
+
* @example encodeCol(0) => "A", encodeCol(25) => "Z", encodeCol(26) => "AA"
|
|
21
46
|
*/
|
|
22
|
-
export function
|
|
47
|
+
export function encodeCol(col) {
|
|
23
48
|
return colCache.n2l(col + 1);
|
|
24
49
|
}
|
|
25
50
|
/**
|
|
26
51
|
* Decode row string to 0-indexed number
|
|
27
|
-
* @example
|
|
52
|
+
* @example decodeRow("1") => 0, decodeRow("10") => 9
|
|
28
53
|
*/
|
|
29
|
-
export function
|
|
54
|
+
export function decodeRow(rowstr) {
|
|
30
55
|
return parseInt(rowstr, 10) - 1;
|
|
31
56
|
}
|
|
32
57
|
/**
|
|
33
58
|
* Encode 0-indexed row number to string
|
|
34
|
-
* @example
|
|
59
|
+
* @example encodeRow(0) => "1", encodeRow(9) => "10"
|
|
35
60
|
*/
|
|
36
|
-
export function
|
|
61
|
+
export function encodeRow(row) {
|
|
37
62
|
return String(row + 1);
|
|
38
63
|
}
|
|
39
64
|
/**
|
|
40
65
|
* Decode cell address string to CellAddress object
|
|
41
|
-
* @example
|
|
66
|
+
* @example decodeCell("A1") => {c: 0, r: 0}, decodeCell("B2") => {c: 1, r: 1}
|
|
42
67
|
*/
|
|
43
|
-
export function
|
|
68
|
+
export function decodeCell(cstr) {
|
|
44
69
|
const addr = colCache.decodeAddress(cstr.toUpperCase());
|
|
45
70
|
return { c: addr.col - 1, r: addr.row - 1 };
|
|
46
71
|
}
|
|
47
72
|
/**
|
|
48
73
|
* Encode CellAddress object to cell address string
|
|
49
|
-
* @example
|
|
74
|
+
* @example encodeCell({c: 0, r: 0}) => "A1", encodeCell({c: 1, r: 1}) => "B2"
|
|
50
75
|
*/
|
|
51
|
-
export function
|
|
76
|
+
export function encodeCell(cell) {
|
|
52
77
|
return colCache.encodeAddress(cell.r + 1, cell.c + 1);
|
|
53
78
|
}
|
|
54
79
|
/**
|
|
55
80
|
* Decode range string to Range object
|
|
56
|
-
* @example
|
|
81
|
+
* @example decodeRange("A1:B2") => {s: {c: 0, r: 0}, e: {c: 1, r: 1}}
|
|
57
82
|
*/
|
|
58
|
-
export function
|
|
83
|
+
export function decodeRange(range) {
|
|
59
84
|
const idx = range.indexOf(":");
|
|
60
85
|
if (idx === -1) {
|
|
61
|
-
const cell =
|
|
86
|
+
const cell = decodeCell(range);
|
|
62
87
|
return { s: cell, e: { ...cell } };
|
|
63
88
|
}
|
|
64
89
|
return {
|
|
65
|
-
s:
|
|
66
|
-
e:
|
|
90
|
+
s: decodeCell(range.slice(0, idx)),
|
|
91
|
+
e: decodeCell(range.slice(idx + 1))
|
|
67
92
|
};
|
|
68
93
|
}
|
|
69
|
-
export function
|
|
94
|
+
export function encodeRange(startOrRange, end) {
|
|
70
95
|
if (end === undefined) {
|
|
71
96
|
const range = startOrRange;
|
|
72
|
-
return
|
|
97
|
+
return encodeRange(range.s, range.e);
|
|
73
98
|
}
|
|
74
99
|
const start = startOrRange;
|
|
75
|
-
const startStr =
|
|
76
|
-
const endStr =
|
|
100
|
+
const startStr = encodeCell(start);
|
|
101
|
+
const endStr = encodeCell(end);
|
|
77
102
|
return startStr === endStr ? startStr : `${startStr}:${endStr}`;
|
|
78
103
|
}
|
|
79
104
|
/**
|
|
80
105
|
* Create a worksheet from an array of objects (xlsx compatible)
|
|
81
106
|
* @example
|
|
82
|
-
* const ws =
|
|
107
|
+
* const ws = jsonToSheet([{name: "Alice", age: 30}, {name: "Bob", age: 25}])
|
|
83
108
|
*/
|
|
84
|
-
export function
|
|
109
|
+
export function jsonToSheet(data, opts) {
|
|
85
110
|
const o = opts || {};
|
|
86
111
|
// Create a temporary workbook to get a worksheet
|
|
87
112
|
const tempWb = new Workbook();
|
|
@@ -127,7 +152,7 @@ export function json_to_sheet(data, opts) {
|
|
|
127
152
|
/**
|
|
128
153
|
* Add data from an array of objects to an existing worksheet (xlsx compatible)
|
|
129
154
|
*/
|
|
130
|
-
export function
|
|
155
|
+
export function sheetAddJson(worksheet, data, opts) {
|
|
131
156
|
const o = opts || {};
|
|
132
157
|
if (data.length === 0) {
|
|
133
158
|
return worksheet;
|
|
@@ -137,7 +162,7 @@ export function sheet_add_json(worksheet, data, opts) {
|
|
|
137
162
|
let startCol = 1;
|
|
138
163
|
if (o.origin !== undefined) {
|
|
139
164
|
if (typeof o.origin === "string") {
|
|
140
|
-
const addr =
|
|
165
|
+
const addr = decodeCell(o.origin);
|
|
141
166
|
startRow = addr.r + 1;
|
|
142
167
|
startCol = addr.c + 1;
|
|
143
168
|
}
|
|
@@ -193,18 +218,18 @@ export function sheet_add_json(worksheet, data, opts) {
|
|
|
193
218
|
* Convert worksheet to JSON array (xlsx compatible)
|
|
194
219
|
* @example
|
|
195
220
|
* // Default: array of objects with first row as headers
|
|
196
|
-
* const data =
|
|
221
|
+
* const data = sheetToJson(worksheet)
|
|
197
222
|
* // => [{name: "Alice", age: 30}, {name: "Bob", age: 25}]
|
|
198
223
|
*
|
|
199
224
|
* // Array of arrays
|
|
200
|
-
* const aoa =
|
|
225
|
+
* const aoa = sheetToJson(worksheet, { header: 1 })
|
|
201
226
|
* // => [["name", "age"], ["Alice", 30], ["Bob", 25]]
|
|
202
227
|
*
|
|
203
228
|
* // Column letters as keys
|
|
204
|
-
* const cols =
|
|
229
|
+
* const cols = sheetToJson(worksheet, { header: "A" })
|
|
205
230
|
* // => [{A: "name", B: "age"}, {A: "Alice", B: 30}]
|
|
206
231
|
*/
|
|
207
|
-
export function
|
|
232
|
+
export function sheetToJson(worksheet, opts) {
|
|
208
233
|
const o = opts || {};
|
|
209
234
|
// Determine range
|
|
210
235
|
let startRow = 1;
|
|
@@ -216,7 +241,7 @@ export function sheet_to_json(worksheet, opts) {
|
|
|
216
241
|
startRow = o.range + 1; // 0-indexed to 1-indexed
|
|
217
242
|
}
|
|
218
243
|
else if (typeof o.range === "string") {
|
|
219
|
-
const r =
|
|
244
|
+
const r = decodeRange(o.range);
|
|
220
245
|
startRow = r.s.r + 1;
|
|
221
246
|
endRow = r.e.r + 1;
|
|
222
247
|
startCol = r.s.c + 1;
|
|
@@ -238,8 +263,8 @@ export function sheet_to_json(worksheet, opts) {
|
|
|
238
263
|
let isEmpty = true;
|
|
239
264
|
for (let col = startCol; col <= endCol; col++) {
|
|
240
265
|
const cell = worksheet.getCell(row, col);
|
|
241
|
-
const val = cell.value;
|
|
242
|
-
if (val != null) {
|
|
266
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
267
|
+
if (val != null && val !== "") {
|
|
243
268
|
rowData[col - startCol] = val;
|
|
244
269
|
isEmpty = false;
|
|
245
270
|
}
|
|
@@ -266,9 +291,9 @@ export function sheet_to_json(worksheet, opts) {
|
|
|
266
291
|
let isEmpty = true;
|
|
267
292
|
for (let col = startCol; col <= endCol; col++) {
|
|
268
293
|
const cell = worksheet.getCell(row, col);
|
|
269
|
-
const val = cell.value;
|
|
270
|
-
const key =
|
|
271
|
-
if (val != null) {
|
|
294
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
295
|
+
const key = encodeCol(col - 1); // 0-indexed for encodeCol
|
|
296
|
+
if (val != null && val !== "") {
|
|
272
297
|
rowData[key] = val;
|
|
273
298
|
isEmpty = false;
|
|
274
299
|
}
|
|
@@ -293,8 +318,8 @@ export function sheet_to_json(worksheet, opts) {
|
|
|
293
318
|
const colIdx = col - startCol;
|
|
294
319
|
const key = headerOpt[colIdx] ?? `__EMPTY_${colIdx}`;
|
|
295
320
|
const cell = worksheet.getCell(row, col);
|
|
296
|
-
const val = cell.value;
|
|
297
|
-
if (val != null) {
|
|
321
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
322
|
+
if (val != null && val !== "") {
|
|
298
323
|
rowData[key] = val;
|
|
299
324
|
isEmpty = false;
|
|
300
325
|
}
|
|
@@ -335,9 +360,9 @@ export function sheet_to_json(worksheet, opts) {
|
|
|
335
360
|
let isEmpty = true;
|
|
336
361
|
for (let col = startCol; col <= endCol; col++) {
|
|
337
362
|
const cell = worksheet.getCell(row, col);
|
|
338
|
-
const val = cell.value;
|
|
363
|
+
const val = o.raw === false ? getCellDisplayText(cell).trim() : cell.value;
|
|
339
364
|
const key = headers[col - startCol];
|
|
340
|
-
if (val != null) {
|
|
365
|
+
if (val != null && val !== "") {
|
|
341
366
|
rowData[key] = val;
|
|
342
367
|
isEmpty = false;
|
|
343
368
|
}
|
|
@@ -354,7 +379,7 @@ export function sheet_to_json(worksheet, opts) {
|
|
|
354
379
|
/**
|
|
355
380
|
* Convert worksheet to CSV string
|
|
356
381
|
*/
|
|
357
|
-
export function
|
|
382
|
+
export function sheetToCsv(worksheet, opts) {
|
|
358
383
|
const o = opts || {};
|
|
359
384
|
const FS = o.FS ?? ",";
|
|
360
385
|
const RS = o.RS ?? "\n";
|
|
@@ -415,17 +440,17 @@ export function sheet_to_csv(worksheet, opts) {
|
|
|
415
440
|
/**
|
|
416
441
|
* Create a new workbook
|
|
417
442
|
*/
|
|
418
|
-
export function
|
|
443
|
+
export function bookNew() {
|
|
419
444
|
return new Workbook();
|
|
420
445
|
}
|
|
421
446
|
/**
|
|
422
447
|
* Append worksheet to workbook (xlsx compatible)
|
|
423
448
|
* @example
|
|
424
|
-
* const wb =
|
|
425
|
-
* const ws =
|
|
426
|
-
*
|
|
449
|
+
* const wb = bookNew();
|
|
450
|
+
* const ws = jsonToSheet([{a: 1, b: 2}]);
|
|
451
|
+
* bookAppendSheet(wb, ws, "Sheet1");
|
|
427
452
|
*/
|
|
428
|
-
export function
|
|
453
|
+
export function bookAppendSheet(workbook, worksheet, name) {
|
|
429
454
|
// Copy the worksheet data to a new sheet in the workbook
|
|
430
455
|
const newWs = workbook.addWorksheet(name);
|
|
431
456
|
// Copy all cells from source worksheet
|
|
@@ -450,9 +475,9 @@ export function book_append_sheet(workbook, worksheet, name) {
|
|
|
450
475
|
/**
|
|
451
476
|
* Create a worksheet from an array of arrays (xlsx compatible)
|
|
452
477
|
* @example
|
|
453
|
-
* const ws =
|
|
478
|
+
* const ws = aoaToSheet([["Name", "Age"], ["Alice", 30], ["Bob", 25]])
|
|
454
479
|
*/
|
|
455
|
-
export function
|
|
480
|
+
export function aoaToSheet(data, opts) {
|
|
456
481
|
const tempWb = new Workbook();
|
|
457
482
|
const worksheet = tempWb.addWorksheet("Sheet1");
|
|
458
483
|
if (data.length === 0) {
|
|
@@ -463,7 +488,7 @@ export function aoa_to_sheet(data, opts) {
|
|
|
463
488
|
let startCol = 1;
|
|
464
489
|
if (opts?.origin !== undefined) {
|
|
465
490
|
if (typeof opts.origin === "string") {
|
|
466
|
-
const addr =
|
|
491
|
+
const addr = decodeCell(opts.origin);
|
|
467
492
|
startRow = addr.r + 1;
|
|
468
493
|
startCol = addr.c + 1;
|
|
469
494
|
}
|
|
@@ -490,7 +515,7 @@ export function aoa_to_sheet(data, opts) {
|
|
|
490
515
|
/**
|
|
491
516
|
* Add data from an array of arrays to an existing worksheet (xlsx compatible)
|
|
492
517
|
*/
|
|
493
|
-
export function
|
|
518
|
+
export function sheetAddAoa(worksheet, data, opts) {
|
|
494
519
|
if (data.length === 0) {
|
|
495
520
|
return worksheet;
|
|
496
521
|
}
|
|
@@ -499,7 +524,7 @@ export function sheet_add_aoa(worksheet, data, opts) {
|
|
|
499
524
|
let startCol = 1;
|
|
500
525
|
if (opts?.origin !== undefined) {
|
|
501
526
|
if (typeof opts.origin === "string") {
|
|
502
|
-
const addr =
|
|
527
|
+
const addr = decodeCell(opts.origin);
|
|
503
528
|
startRow = addr.r + 1;
|
|
504
529
|
startCol = addr.c + 1;
|
|
505
530
|
}
|
|
@@ -532,7 +557,7 @@ export function sheet_add_aoa(worksheet, data, opts) {
|
|
|
532
557
|
/**
|
|
533
558
|
* Convert worksheet to array of arrays
|
|
534
559
|
*/
|
|
535
|
-
export function
|
|
560
|
+
export function sheetToAoa(worksheet) {
|
|
536
561
|
const result = [];
|
|
537
562
|
worksheet.eachRow({ includeEmpty: true }, (row, rowNumber) => {
|
|
538
563
|
const rowData = [];
|
|
@@ -547,24 +572,24 @@ export function sheet_to_aoa(worksheet) {
|
|
|
547
572
|
// Export utils object
|
|
548
573
|
// =============================================================================
|
|
549
574
|
export const utils = {
|
|
550
|
-
// Cell encoding/decoding
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
// Sheet/JSON conversion
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
// Workbook functions
|
|
568
|
-
|
|
569
|
-
|
|
575
|
+
// Cell encoding/decoding (camelCase)
|
|
576
|
+
decodeCol,
|
|
577
|
+
encodeCol,
|
|
578
|
+
decodeRow,
|
|
579
|
+
encodeRow,
|
|
580
|
+
decodeCell,
|
|
581
|
+
encodeCell,
|
|
582
|
+
decodeRange,
|
|
583
|
+
encodeRange,
|
|
584
|
+
// Sheet/JSON conversion (camelCase)
|
|
585
|
+
jsonToSheet,
|
|
586
|
+
sheetAddJson,
|
|
587
|
+
sheetToJson,
|
|
588
|
+
sheetToCsv,
|
|
589
|
+
aoaToSheet,
|
|
590
|
+
sheetAddAoa,
|
|
591
|
+
sheetToAoa,
|
|
592
|
+
// Workbook functions (camelCase)
|
|
593
|
+
bookNew,
|
|
594
|
+
bookAppendSheet
|
|
570
595
|
};
|
|
@@ -198,14 +198,24 @@ inherits(StreamBuf, Duplex, {
|
|
|
198
198
|
if (data instanceof StringBuf || (data && data.constructor?.name === "StringBuf")) {
|
|
199
199
|
chunk = new StringBufChunk(data);
|
|
200
200
|
}
|
|
201
|
-
else if (data
|
|
201
|
+
else if (Buffer.isBuffer(data)) {
|
|
202
|
+
// Use Buffer.isBuffer() instead of instanceof for cross-realm compatibility
|
|
203
|
+
// (e.g., Web Workers where Buffer polyfill instances may differ)
|
|
202
204
|
chunk = new BufferChunk(data);
|
|
203
205
|
}
|
|
204
|
-
else if (
|
|
206
|
+
else if (ArrayBuffer.isView(data)) {
|
|
207
|
+
// Handle typed arrays (Uint8Array, Int8Array, etc.) - cross-realm safe
|
|
208
|
+
chunk = new BufferChunk(Buffer.from(data.buffer, data.byteOffset, data.byteLength));
|
|
209
|
+
}
|
|
210
|
+
else if (data instanceof ArrayBuffer) {
|
|
211
|
+
// Handle ArrayBuffer - convert to Buffer
|
|
212
|
+
chunk = new BufferChunk(Buffer.from(data));
|
|
213
|
+
}
|
|
214
|
+
else if (typeof data === "string" || data instanceof String) {
|
|
205
215
|
chunk = new StringChunk(String(data), encoding);
|
|
206
216
|
}
|
|
207
217
|
else {
|
|
208
|
-
throw new Error("Chunk must be one of type String, Buffer or StringBuf.");
|
|
218
|
+
throw new Error("Chunk must be one of type String, Buffer, Uint8Array, ArrayBuffer or StringBuf.");
|
|
209
219
|
}
|
|
210
220
|
// now, do something with the chunk
|
|
211
221
|
if (this.pipes.length) {
|
|
@@ -221,7 +231,8 @@ inherits(StreamBuf, Duplex, {
|
|
|
221
231
|
}
|
|
222
232
|
else {
|
|
223
233
|
this._writeToBuffers(chunk);
|
|
224
|
-
|
|
234
|
+
// Use queueMicrotask for cross-platform compatibility (ES2020+)
|
|
235
|
+
queueMicrotask(() => callback());
|
|
225
236
|
}
|
|
226
237
|
}
|
|
227
238
|
else {
|
|
@@ -6,13 +6,94 @@
|
|
|
6
6
|
* Commits in this fork are (c) Ziggy Jonsson (ziggy.jonsson.nyc@gmail.com)
|
|
7
7
|
*/
|
|
8
8
|
import zlib from "zlib";
|
|
9
|
-
import { PassThrough, pipeline } from "stream";
|
|
9
|
+
import { PassThrough, Transform, pipeline } from "stream";
|
|
10
10
|
import { PullStream } from "./pull-stream.js";
|
|
11
11
|
import { NoopStream } from "./noop-stream.js";
|
|
12
12
|
import { bufferStream } from "./buffer-stream.js";
|
|
13
13
|
import { parseExtraField } from "./parse-extra-field.js";
|
|
14
14
|
import { parseDateTime } from "./parse-datetime.js";
|
|
15
15
|
import { parse as parseBuffer } from "./parse-buffer.js";
|
|
16
|
+
// Check if native zlib is available (Node.js environment)
|
|
17
|
+
// In browser with polyfill, createInflateRaw may not exist or may not work properly
|
|
18
|
+
const hasNativeZlib = typeof zlib?.createInflateRaw === "function" &&
|
|
19
|
+
typeof process !== "undefined" &&
|
|
20
|
+
process.versions?.node;
|
|
21
|
+
/**
|
|
22
|
+
* A Transform stream that wraps browser's native DecompressionStream.
|
|
23
|
+
* Used when native zlib is not available (browser environment).
|
|
24
|
+
*/
|
|
25
|
+
class BrowserInflateRawStream extends Transform {
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.chunks = [];
|
|
29
|
+
this.totalLength = 0;
|
|
30
|
+
}
|
|
31
|
+
_transform(chunk, _encoding, callback) {
|
|
32
|
+
// Avoid unnecessary copy - Buffer extends Uint8Array
|
|
33
|
+
this.chunks.push(chunk);
|
|
34
|
+
this.totalLength += chunk.length;
|
|
35
|
+
callback();
|
|
36
|
+
}
|
|
37
|
+
_flush(callback) {
|
|
38
|
+
try {
|
|
39
|
+
// Use pre-calculated totalLength for better performance
|
|
40
|
+
const combined = new Uint8Array(this.totalLength);
|
|
41
|
+
let offset = 0;
|
|
42
|
+
for (const chunk of this.chunks) {
|
|
43
|
+
combined.set(chunk, offset);
|
|
44
|
+
offset += chunk.length;
|
|
45
|
+
}
|
|
46
|
+
// Clear chunks to free memory
|
|
47
|
+
this.chunks = [];
|
|
48
|
+
// Use native DecompressionStream
|
|
49
|
+
const ds = new DecompressionStream("deflate-raw");
|
|
50
|
+
const writer = ds.writable.getWriter();
|
|
51
|
+
const reader = ds.readable.getReader();
|
|
52
|
+
// Optimized read loop - collect chunks and concatenate at the end
|
|
53
|
+
const readAll = async () => {
|
|
54
|
+
const results = [];
|
|
55
|
+
let total = 0;
|
|
56
|
+
while (true) {
|
|
57
|
+
const { done, value } = await reader.read();
|
|
58
|
+
if (done) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
results.push(value);
|
|
62
|
+
total += value.length;
|
|
63
|
+
}
|
|
64
|
+
// Single allocation for final result
|
|
65
|
+
const result = Buffer.allocUnsafe(total);
|
|
66
|
+
let off = 0;
|
|
67
|
+
for (const r of results) {
|
|
68
|
+
result.set(r, off);
|
|
69
|
+
off += r.length;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
writer.write(combined);
|
|
74
|
+
writer.close();
|
|
75
|
+
readAll()
|
|
76
|
+
.then(decompressed => {
|
|
77
|
+
this.push(decompressed);
|
|
78
|
+
callback();
|
|
79
|
+
})
|
|
80
|
+
.catch(callback);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
callback(err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates an InflateRaw stream.
|
|
89
|
+
* Uses native zlib in Node.js for best performance, falls back to DecompressionStream in browser.
|
|
90
|
+
*/
|
|
91
|
+
function createInflateRaw() {
|
|
92
|
+
if (hasNativeZlib) {
|
|
93
|
+
return zlib.createInflateRaw();
|
|
94
|
+
}
|
|
95
|
+
return new BrowserInflateRawStream();
|
|
96
|
+
}
|
|
16
97
|
const endDirectorySignature = Buffer.alloc(4);
|
|
17
98
|
endDirectorySignature.writeUInt32LE(0x06054b50, 0);
|
|
18
99
|
export class Parse extends PullStream {
|
|
@@ -168,7 +249,7 @@ export class Parse extends PullStream {
|
|
|
168
249
|
const fileSizeKnown = !((vars.flags || 0) & 0x08) || vars.compressedSize > 0;
|
|
169
250
|
let eof;
|
|
170
251
|
entry.__autodraining = __autodraining; // expose __autodraining for test purposes
|
|
171
|
-
const inflater = vars.compressionMethod && !__autodraining ?
|
|
252
|
+
const inflater = vars.compressionMethod && !__autodraining ? createInflateRaw() : new PassThrough();
|
|
172
253
|
if (fileSizeKnown) {
|
|
173
254
|
entry.size = vars.uncompressedSize;
|
|
174
255
|
eof = vars.compressedSize;
|
package/dist/esm/utils/utils.js
CHANGED
|
@@ -3,21 +3,6 @@ export function delay(ms) {
|
|
|
3
3
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
4
4
|
}
|
|
5
5
|
export function nop() { }
|
|
6
|
-
export function promiseImmediate(value) {
|
|
7
|
-
return new Promise(resolve => {
|
|
8
|
-
if (global.setImmediate) {
|
|
9
|
-
setImmediate(() => {
|
|
10
|
-
resolve(value);
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
// poorman's setImmediate - must wait at least 1ms
|
|
15
|
-
setTimeout(() => {
|
|
16
|
-
resolve(value);
|
|
17
|
-
}, 1);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
6
|
// useful stuff
|
|
22
7
|
export const inherits = function (cls, superCtor, statics, prototype) {
|
|
23
8
|
cls.super_ = superCtor;
|
|
@@ -189,7 +174,6 @@ export function objectFromProps(props, value = null) {
|
|
|
189
174
|
/** @deprecated Import functions directly instead of using the utils object */
|
|
190
175
|
export const utils = {
|
|
191
176
|
nop,
|
|
192
|
-
promiseImmediate,
|
|
193
177
|
inherits,
|
|
194
178
|
dateToExcel,
|
|
195
179
|
excelToDate,
|
|
@@ -208,3 +192,15 @@ export const utils = {
|
|
|
208
192
|
toSortedArray,
|
|
209
193
|
objectFromProps
|
|
210
194
|
};
|
|
195
|
+
// TextDecoder is available in ES2020+ and all modern browsers/Node.js
|
|
196
|
+
const textDecoder = new TextDecoder("utf-8");
|
|
197
|
+
/**
|
|
198
|
+
* Convert a Buffer or ArrayBuffer to a UTF-8 string
|
|
199
|
+
* Works in both Node.js and browser environments
|
|
200
|
+
*/
|
|
201
|
+
export function bufferToString(chunk) {
|
|
202
|
+
if (typeof chunk === "string") {
|
|
203
|
+
return chunk;
|
|
204
|
+
}
|
|
205
|
+
return textDecoder.decode(chunk);
|
|
206
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import events from "events";
|
|
2
2
|
import { Zip, ZipPassThrough, ZipDeflate } from "fflate";
|
|
3
3
|
import { StreamBuf } from "./stream-buf.js";
|
|
4
|
-
import { stringToBuffer } from "./browser-buffer-encode.js";
|
|
5
|
-
import { isBrowser } from "./browser.js";
|
|
6
4
|
// =============================================================================
|
|
7
5
|
// The ZipWriter class
|
|
8
6
|
// Packs streamed data into an output zip stream
|
|
@@ -36,39 +34,29 @@ class ZipWriter extends events.EventEmitter {
|
|
|
36
34
|
append(data, options) {
|
|
37
35
|
let buffer;
|
|
38
36
|
if (Object.prototype.hasOwnProperty.call(options, "base64") && options.base64) {
|
|
39
|
-
//
|
|
37
|
+
// Decode base64 data - Buffer.from works in both Node.js and browser (via polyfill)
|
|
40
38
|
const base64Data = typeof data === "string" ? data : data.toString();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
buffer = Buffer.from(base64Data, "base64");
|
|
40
|
+
}
|
|
41
|
+
else if (typeof data === "string") {
|
|
42
|
+
// Convert string to Buffer - works in both environments
|
|
43
|
+
buffer = Buffer.from(data, "utf8");
|
|
44
|
+
}
|
|
45
|
+
else if (Buffer.isBuffer(data)) {
|
|
46
|
+
// Buffer extends Uint8Array, fflate can use it directly - no copy needed
|
|
47
|
+
buffer = data;
|
|
48
|
+
}
|
|
49
|
+
else if (ArrayBuffer.isView(data)) {
|
|
50
|
+
// Handle typed arrays - create view without copy
|
|
51
|
+
buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
52
|
+
}
|
|
53
|
+
else if (data instanceof ArrayBuffer) {
|
|
54
|
+
// Handle ArrayBuffer directly
|
|
55
|
+
buffer = new Uint8Array(data);
|
|
55
56
|
}
|
|
56
57
|
else {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (isBrowser) {
|
|
60
|
-
buffer = stringToBuffer(data);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
buffer = Buffer.from(data, "utf8");
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else if (Buffer.isBuffer(data)) {
|
|
67
|
-
buffer = new Uint8Array(data);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
buffer = data;
|
|
71
|
-
}
|
|
58
|
+
// Assume it's already a Uint8Array or compatible type
|
|
59
|
+
buffer = data;
|
|
72
60
|
}
|
|
73
61
|
// Add file to zip using streaming API
|
|
74
62
|
// Use ZipDeflate for compression or ZipPassThrough for no compression
|