@cj-tech-master/excelts 1.4.3 → 1.4.5-canary.20251212053535.13d32d8
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/README.md +3 -3
- package/README_zh.md +3 -3
- package/dist/browser/excelts.iife.js +13026 -7610
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +87 -24
- package/dist/cjs/doc/anchor.js +25 -11
- package/dist/cjs/doc/cell.js +75 -43
- package/dist/cjs/doc/column.js +39 -16
- package/dist/cjs/doc/defined-names.js +53 -7
- package/dist/cjs/doc/image.js +11 -8
- package/dist/cjs/doc/range.js +64 -28
- package/dist/cjs/doc/row.js +33 -17
- package/dist/cjs/doc/table.js +3 -5
- package/dist/cjs/doc/workbook.js +5 -4
- package/dist/cjs/doc/worksheet.js +24 -20
- package/dist/cjs/stream/xlsx/workbook-writer.js +3 -2
- package/dist/cjs/utils/sheet-utils.js +3 -1
- package/dist/cjs/utils/unzip/extract.js +166 -0
- package/dist/cjs/utils/unzip/index.js +7 -1
- package/dist/cjs/utils/xml-stream.js +25 -3
- package/dist/cjs/utils/zip/compress.js +261 -0
- package/dist/cjs/utils/zip/crc32.js +154 -0
- package/dist/cjs/utils/zip/index.js +70 -0
- package/dist/cjs/utils/zip/zip-builder.js +378 -0
- package/dist/cjs/utils/zip-stream.js +30 -34
- package/dist/cjs/xlsx/xform/book/defined-name-xform.js +36 -2
- package/dist/cjs/xlsx/xform/list-xform.js +6 -0
- package/dist/cjs/xlsx/xform/sheet/cell-xform.js +6 -1
- package/dist/cjs/xlsx/xform/sheet/row-xform.js +24 -2
- package/dist/cjs/xlsx/xform/table/filter-column-xform.js +4 -0
- package/dist/esm/doc/anchor.js +25 -11
- package/dist/esm/doc/cell.js +75 -43
- package/dist/esm/doc/column.js +39 -16
- package/dist/esm/doc/defined-names.js +53 -7
- package/dist/esm/doc/image.js +11 -8
- package/dist/esm/doc/range.js +64 -28
- package/dist/esm/doc/row.js +33 -17
- package/dist/esm/doc/table.js +3 -5
- package/dist/esm/doc/workbook.js +5 -4
- package/dist/esm/doc/worksheet.js +24 -20
- package/dist/esm/stream/xlsx/workbook-writer.js +3 -2
- package/dist/esm/utils/sheet-utils.js +3 -1
- package/dist/esm/utils/unzip/extract.js +160 -0
- package/dist/esm/utils/unzip/index.js +2 -0
- package/dist/esm/utils/xml-stream.js +25 -3
- package/dist/esm/utils/zip/compress.js +220 -0
- package/dist/esm/utils/zip/crc32.js +116 -0
- package/dist/esm/utils/zip/index.js +55 -0
- package/dist/esm/utils/zip/zip-builder.js +372 -0
- package/dist/esm/utils/zip-stream.js +30 -34
- package/dist/esm/xlsx/xform/book/defined-name-xform.js +36 -2
- package/dist/esm/xlsx/xform/list-xform.js +6 -0
- package/dist/esm/xlsx/xform/sheet/cell-xform.js +6 -1
- package/dist/esm/xlsx/xform/sheet/row-xform.js +24 -2
- package/dist/esm/xlsx/xform/table/filter-column-xform.js +4 -0
- package/dist/types/doc/anchor.d.ts +14 -7
- package/dist/types/doc/cell.d.ts +85 -40
- package/dist/types/doc/column.d.ts +39 -34
- package/dist/types/doc/defined-names.d.ts +11 -8
- package/dist/types/doc/image.d.ts +29 -12
- package/dist/types/doc/pivot-table.d.ts +1 -1
- package/dist/types/doc/range.d.ts +15 -4
- package/dist/types/doc/row.d.ts +34 -40
- package/dist/types/doc/table.d.ts +21 -36
- package/dist/types/doc/workbook.d.ts +30 -33
- package/dist/types/doc/worksheet.d.ts +105 -80
- package/dist/types/stream/xlsx/worksheet-reader.d.ts +3 -5
- package/dist/types/types.d.ts +86 -26
- package/dist/types/utils/col-cache.d.ts +11 -8
- package/dist/types/utils/unzip/extract.d.ts +92 -0
- package/dist/types/utils/unzip/index.d.ts +1 -0
- package/dist/types/utils/xml-stream.d.ts +2 -0
- package/dist/types/utils/zip/compress.d.ts +83 -0
- package/dist/types/utils/zip/crc32.d.ts +55 -0
- package/dist/types/utils/zip/index.d.ts +52 -0
- package/dist/types/utils/zip/zip-builder.d.ts +110 -0
- package/dist/types/utils/zip-stream.d.ts +6 -12
- package/dist/types/xlsx/xform/list-xform.d.ts +1 -0
- package/dist/types/xlsx/xform/sheet/row-xform.d.ts +2 -0
- package/package.json +8 -8
package/dist/cjs/doc/row.js
CHANGED
|
@@ -83,12 +83,12 @@ class Row {
|
|
|
83
83
|
cDst = this.getCell(i);
|
|
84
84
|
cDst.value = cSrc.value;
|
|
85
85
|
cDst.style = cSrc.style;
|
|
86
|
-
cDst.
|
|
86
|
+
cDst.comment = cSrc.comment;
|
|
87
87
|
}
|
|
88
88
|
else if (cDst) {
|
|
89
89
|
cDst.value = null;
|
|
90
90
|
cDst.style = {};
|
|
91
|
-
cDst.
|
|
91
|
+
cDst.comment = undefined;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -100,7 +100,7 @@ class Row {
|
|
|
100
100
|
cDst = this.getCell(i + nExpand);
|
|
101
101
|
cDst.value = cSrc.value;
|
|
102
102
|
cDst.style = cSrc.style;
|
|
103
|
-
cDst.
|
|
103
|
+
cDst.comment = cSrc.comment;
|
|
104
104
|
}
|
|
105
105
|
else {
|
|
106
106
|
this._cells[i + nExpand - 1] = undefined;
|
|
@@ -112,13 +112,18 @@ class Row {
|
|
|
112
112
|
cDst = this.getCell(start + i);
|
|
113
113
|
cDst.value = inserts[i];
|
|
114
114
|
cDst.style = {};
|
|
115
|
-
cDst.
|
|
115
|
+
cDst.comment = undefined;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
eachCell(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
eachCell(optionsOrIteratee, maybeIteratee) {
|
|
119
|
+
let options = null;
|
|
120
|
+
let iteratee;
|
|
121
|
+
if (typeof optionsOrIteratee === "function") {
|
|
122
|
+
iteratee = optionsOrIteratee;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
options = optionsOrIteratee;
|
|
126
|
+
iteratee = maybeIteratee;
|
|
122
127
|
}
|
|
123
128
|
if (options && options.includeEmpty) {
|
|
124
129
|
const n = this._cells.length;
|
|
@@ -198,7 +203,7 @@ class Row {
|
|
|
198
203
|
}
|
|
199
204
|
// returns true if the row includes at least one cell with a value
|
|
200
205
|
get hasValues() {
|
|
201
|
-
return this._cells.some(
|
|
206
|
+
return this._cells.some(cell => cell && cell.type !== enums_js_1.Enums.ValueType.Null);
|
|
202
207
|
}
|
|
203
208
|
get cellCount() {
|
|
204
209
|
return this._cells.length;
|
|
@@ -237,46 +242,57 @@ class Row {
|
|
|
237
242
|
this.style[name] = value;
|
|
238
243
|
this._cells.forEach(cell => {
|
|
239
244
|
if (cell) {
|
|
240
|
-
cell[name] = value;
|
|
245
|
+
cell.style[name] = value;
|
|
241
246
|
}
|
|
242
247
|
});
|
|
243
|
-
return value;
|
|
244
248
|
}
|
|
245
249
|
get numFmt() {
|
|
246
250
|
return this.style.numFmt;
|
|
247
251
|
}
|
|
248
252
|
set numFmt(value) {
|
|
249
|
-
|
|
253
|
+
if (value !== undefined) {
|
|
254
|
+
this._applyStyle("numFmt", value);
|
|
255
|
+
}
|
|
250
256
|
}
|
|
251
257
|
get font() {
|
|
252
258
|
return this.style.font;
|
|
253
259
|
}
|
|
254
260
|
set font(value) {
|
|
255
|
-
|
|
261
|
+
if (value !== undefined) {
|
|
262
|
+
this._applyStyle("font", value);
|
|
263
|
+
}
|
|
256
264
|
}
|
|
257
265
|
get alignment() {
|
|
258
266
|
return this.style.alignment;
|
|
259
267
|
}
|
|
260
268
|
set alignment(value) {
|
|
261
|
-
|
|
269
|
+
if (value !== undefined) {
|
|
270
|
+
this._applyStyle("alignment", value);
|
|
271
|
+
}
|
|
262
272
|
}
|
|
263
273
|
get protection() {
|
|
264
274
|
return this.style.protection;
|
|
265
275
|
}
|
|
266
276
|
set protection(value) {
|
|
267
|
-
|
|
277
|
+
if (value !== undefined) {
|
|
278
|
+
this._applyStyle("protection", value);
|
|
279
|
+
}
|
|
268
280
|
}
|
|
269
281
|
get border() {
|
|
270
282
|
return this.style.border;
|
|
271
283
|
}
|
|
272
284
|
set border(value) {
|
|
273
|
-
|
|
285
|
+
if (value !== undefined) {
|
|
286
|
+
this._applyStyle("border", value);
|
|
287
|
+
}
|
|
274
288
|
}
|
|
275
289
|
get fill() {
|
|
276
290
|
return this.style.fill;
|
|
277
291
|
}
|
|
278
292
|
set fill(value) {
|
|
279
|
-
|
|
293
|
+
if (value !== undefined) {
|
|
294
|
+
this._applyStyle("fill", value);
|
|
295
|
+
}
|
|
280
296
|
}
|
|
281
297
|
get hidden() {
|
|
282
298
|
return !!this._hidden;
|
package/dist/cjs/doc/table.js
CHANGED
|
@@ -157,9 +157,7 @@ class Table {
|
|
|
157
157
|
// the sheet...
|
|
158
158
|
const assignStyle = (cell, style) => {
|
|
159
159
|
if (style) {
|
|
160
|
-
Object.
|
|
161
|
-
cell.style[key] = style[key];
|
|
162
|
-
});
|
|
160
|
+
Object.assign(cell.style, style);
|
|
163
161
|
}
|
|
164
162
|
};
|
|
165
163
|
const { worksheet, table } = this;
|
|
@@ -377,10 +375,10 @@ class Table {
|
|
|
377
375
|
this._assign(this.table, "totalsRow", value);
|
|
378
376
|
}
|
|
379
377
|
get theme() {
|
|
380
|
-
return this.table.style.
|
|
378
|
+
return this.table.style.theme;
|
|
381
379
|
}
|
|
382
380
|
set theme(value) {
|
|
383
|
-
this.table.style.
|
|
381
|
+
this.table.style.theme = value;
|
|
384
382
|
}
|
|
385
383
|
get showFirstColumn() {
|
|
386
384
|
return this.table.style.showFirstColumn;
|
package/dist/cjs/doc/workbook.js
CHANGED
|
@@ -53,12 +53,13 @@ class Workbook {
|
|
|
53
53
|
addWorksheet(name, options) {
|
|
54
54
|
const id = this.nextId;
|
|
55
55
|
const lastOrderNo = this._worksheets.reduce((acc, ws) => ((ws && ws.orderNo) > acc ? ws.orderNo : acc), 0);
|
|
56
|
-
const worksheetOptions =
|
|
56
|
+
const worksheetOptions = {
|
|
57
|
+
...options,
|
|
57
58
|
id,
|
|
58
59
|
name,
|
|
59
60
|
orderNo: lastOrderNo + 1,
|
|
60
61
|
workbook: this
|
|
61
|
-
}
|
|
62
|
+
};
|
|
62
63
|
const worksheet = new worksheet_js_1.Worksheet(worksheetOptions);
|
|
63
64
|
this._worksheets[id] = worksheet;
|
|
64
65
|
return worksheet;
|
|
@@ -106,11 +107,11 @@ class Workbook {
|
|
|
106
107
|
addImage(image) {
|
|
107
108
|
// TODO: validation?
|
|
108
109
|
const id = this.media.length;
|
|
109
|
-
this.media.push(
|
|
110
|
+
this.media.push({ ...image, type: "image" });
|
|
110
111
|
return id;
|
|
111
112
|
}
|
|
112
113
|
getImage(id) {
|
|
113
|
-
return this.media[id];
|
|
114
|
+
return this.media[Number(id)];
|
|
114
115
|
}
|
|
115
116
|
get model() {
|
|
116
117
|
return {
|
|
@@ -128,7 +128,7 @@ class Worksheet {
|
|
|
128
128
|
}
|
|
129
129
|
name = name.substring(0, 31);
|
|
130
130
|
}
|
|
131
|
-
if (this._workbook.
|
|
131
|
+
if (this._workbook.worksheets.find(ws => ws && ws.name.toLowerCase() === name.toLowerCase())) {
|
|
132
132
|
throw new Error(`Worksheet name already exists: ${name}`);
|
|
133
133
|
}
|
|
134
134
|
this._name = name;
|
|
@@ -164,7 +164,7 @@ class Worksheet {
|
|
|
164
164
|
set columns(value) {
|
|
165
165
|
// calculate max header row count
|
|
166
166
|
this._headerRowCount = value.reduce((pv, cv) => {
|
|
167
|
-
const headerCount = (cv.header
|
|
167
|
+
const headerCount = Array.isArray(cv.header) ? cv.header.length : cv.header ? 1 : 0;
|
|
168
168
|
return Math.max(pv, headerCount);
|
|
169
169
|
}, 0);
|
|
170
170
|
// construct Column objects
|
|
@@ -220,12 +220,9 @@ class Worksheet {
|
|
|
220
220
|
if (inserts.length > 0) {
|
|
221
221
|
// must iterate over all rows whether they exist yet or not
|
|
222
222
|
for (let i = 0; i < nRows; i++) {
|
|
223
|
-
const
|
|
224
|
-
inserts.forEach(insert => {
|
|
225
|
-
rowArguments.push(insert[i] || null);
|
|
226
|
-
});
|
|
223
|
+
const insertValues = inserts.map(insert => insert[i] || null);
|
|
227
224
|
const row = this.getRow(i + 1);
|
|
228
|
-
row.splice(...
|
|
225
|
+
row.splice(start, count, ...insertValues);
|
|
229
226
|
}
|
|
230
227
|
}
|
|
231
228
|
else {
|
|
@@ -251,7 +248,7 @@ class Worksheet {
|
|
|
251
248
|
}
|
|
252
249
|
}
|
|
253
250
|
for (let i = start; i < start + inserts.length; i++) {
|
|
254
|
-
this.getColumn(i).defn =
|
|
251
|
+
this.getColumn(i).defn = undefined;
|
|
255
252
|
}
|
|
256
253
|
// account for defined names
|
|
257
254
|
this.workbook.definedNames.spliceColumns(this.name, start, count, inserts.length);
|
|
@@ -261,7 +258,7 @@ class Worksheet {
|
|
|
261
258
|
}
|
|
262
259
|
get columnCount() {
|
|
263
260
|
let maxCount = 0;
|
|
264
|
-
this.eachRow(
|
|
261
|
+
this.eachRow(row => {
|
|
265
262
|
maxCount = Math.max(maxCount, row.cellCount);
|
|
266
263
|
});
|
|
267
264
|
return maxCount;
|
|
@@ -270,7 +267,7 @@ class Worksheet {
|
|
|
270
267
|
// performance nightmare - for each row, counts all the columns used
|
|
271
268
|
const counts = [];
|
|
272
269
|
let count = 0;
|
|
273
|
-
this.eachRow(
|
|
270
|
+
this.eachRow(row => {
|
|
274
271
|
row.eachCell(({ col }) => {
|
|
275
272
|
if (!counts[col]) {
|
|
276
273
|
counts[col] = true;
|
|
@@ -451,10 +448,10 @@ class Worksheet {
|
|
|
451
448
|
rSrc.eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
452
449
|
rDst.getCell(colNumber).style = cell.style;
|
|
453
450
|
// remerge cells accounting for insert offset
|
|
454
|
-
if (cell.
|
|
455
|
-
const cellToBeMerged = this.getRow(cell.
|
|
456
|
-
const prevMaster = cell.
|
|
457
|
-
const newMaster = this.getRow(prevMaster.
|
|
451
|
+
if (cell.type === enums_js_1.Enums.ValueType.Merge) {
|
|
452
|
+
const cellToBeMerged = this.getRow(cell.row + nInserts).getCell(colNumber);
|
|
453
|
+
const prevMaster = cell.master;
|
|
454
|
+
const newMaster = this.getRow(prevMaster.row + nInserts).getCell(prevMaster.col);
|
|
458
455
|
cellToBeMerged.merge(newMaster);
|
|
459
456
|
}
|
|
460
457
|
});
|
|
@@ -473,10 +470,15 @@ class Worksheet {
|
|
|
473
470
|
// account for defined names
|
|
474
471
|
this.workbook.definedNames.spliceRows(this.name, start, count, nInserts);
|
|
475
472
|
}
|
|
476
|
-
eachRow(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
473
|
+
eachRow(optionsOrIteratee, maybeIteratee) {
|
|
474
|
+
let options;
|
|
475
|
+
let iteratee;
|
|
476
|
+
if (typeof optionsOrIteratee === "function") {
|
|
477
|
+
iteratee = optionsOrIteratee;
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
options = optionsOrIteratee;
|
|
481
|
+
iteratee = maybeIteratee;
|
|
480
482
|
}
|
|
481
483
|
if (options && options.includeEmpty) {
|
|
482
484
|
const n = this._rows.length;
|
|
@@ -614,12 +616,14 @@ class Worksheet {
|
|
|
614
616
|
for (let r = top; r <= bottom; r++) {
|
|
615
617
|
for (let c = left; c <= right; c++) {
|
|
616
618
|
if (first) {
|
|
617
|
-
this.getCell(r, c)
|
|
619
|
+
const cell = this.getCell(r, c);
|
|
620
|
+
const formulaValue = {
|
|
618
621
|
shareType,
|
|
619
622
|
formula,
|
|
620
623
|
ref: range,
|
|
621
624
|
result: getResult(r, c)
|
|
622
625
|
};
|
|
626
|
+
cell.value = formulaValue;
|
|
623
627
|
first = false;
|
|
624
628
|
}
|
|
625
629
|
else {
|
|
@@ -755,7 +759,7 @@ Please leave feedback at https://github.com/excelts/excelts/discussions/2575`);
|
|
|
755
759
|
};
|
|
756
760
|
// =================================================
|
|
757
761
|
// columns
|
|
758
|
-
model.cols = column_js_1.Column.toModel(this.columns);
|
|
762
|
+
model.cols = column_js_1.Column.toModel(this.columns || []);
|
|
759
763
|
// ==========================================================
|
|
760
764
|
// Rows
|
|
761
765
|
const rows = (model.rows = []);
|
|
@@ -37,8 +37,9 @@ class WorkbookWriter {
|
|
|
37
37
|
this.views = [];
|
|
38
38
|
this.zipOptions = options.zip;
|
|
39
39
|
// Extract compression level from zip options (supports both zlib.level and compressionOptions.level)
|
|
40
|
-
// Default compression level is
|
|
41
|
-
|
|
40
|
+
// Default compression level is 1 (fast compression with good ratio)
|
|
41
|
+
// Level 1 is ~2x faster than level 6 with only ~7% larger files
|
|
42
|
+
const level = options.zip?.zlib?.level ?? options.zip?.compressionOptions?.level ?? 1;
|
|
42
43
|
this.compressionLevel = Math.max(0, Math.min(9, level));
|
|
43
44
|
this.media = [];
|
|
44
45
|
this.commentRefs = [];
|
|
@@ -132,7 +132,9 @@ function formatValue(value, fmt, dateFormat) {
|
|
|
132
132
|
*/
|
|
133
133
|
function getCellDisplayText(cell, dateFormat) {
|
|
134
134
|
const value = cell.value;
|
|
135
|
-
const
|
|
135
|
+
const numFmt = cell.numFmt;
|
|
136
|
+
// Extract format code string from numFmt (which can be string or NumFmt object)
|
|
137
|
+
const fmt = typeof numFmt === "string" ? numFmt : (numFmt?.formatCode ?? "General");
|
|
136
138
|
// Null/undefined
|
|
137
139
|
if (value == null) {
|
|
138
140
|
return "";
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Simple ZIP extraction utilities
|
|
4
|
+
* Provides easy-to-use Promise-based API for extracting ZIP files
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.extractAll = extractAll;
|
|
8
|
+
exports.extractFile = extractFile;
|
|
9
|
+
exports.listFiles = listFiles;
|
|
10
|
+
exports.forEachEntry = forEachEntry;
|
|
11
|
+
const stream_1 = require("stream");
|
|
12
|
+
const parse_js_1 = require("./parse");
|
|
13
|
+
/**
|
|
14
|
+
* Extract all files from a ZIP buffer
|
|
15
|
+
*
|
|
16
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
17
|
+
* @returns Map of file paths to their content
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { extractAll } from "./utils/unzip/extract.js";
|
|
22
|
+
*
|
|
23
|
+
* const zipData = fs.readFileSync("archive.zip");
|
|
24
|
+
* const files = await extractAll(zipData);
|
|
25
|
+
*
|
|
26
|
+
* for (const [path, file] of files) {
|
|
27
|
+
* console.log(`${path}: ${file.data.length} bytes`);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
async function extractAll(zipData) {
|
|
32
|
+
const files = new Map();
|
|
33
|
+
const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
|
|
34
|
+
const parse = (0, parse_js_1.createParse)({ forceStream: true });
|
|
35
|
+
const stream = stream_1.Readable.from([buffer]);
|
|
36
|
+
stream.pipe(parse);
|
|
37
|
+
for await (const entry of parse) {
|
|
38
|
+
const zipEntry = entry;
|
|
39
|
+
const isDirectory = zipEntry.type === "Directory";
|
|
40
|
+
if (isDirectory) {
|
|
41
|
+
files.set(zipEntry.path, {
|
|
42
|
+
path: zipEntry.path,
|
|
43
|
+
data: Buffer.alloc(0),
|
|
44
|
+
isDirectory: true,
|
|
45
|
+
size: 0
|
|
46
|
+
});
|
|
47
|
+
zipEntry.autodrain();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const data = await zipEntry.buffer();
|
|
51
|
+
files.set(zipEntry.path, {
|
|
52
|
+
path: zipEntry.path,
|
|
53
|
+
data,
|
|
54
|
+
isDirectory: false,
|
|
55
|
+
size: data.length
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract a single file from a ZIP buffer
|
|
63
|
+
*
|
|
64
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
65
|
+
* @param filePath - Path of the file to extract
|
|
66
|
+
* @returns File content as Buffer, or null if not found
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { extractFile } from "./utils/unzip/extract.js";
|
|
71
|
+
*
|
|
72
|
+
* const zipData = fs.readFileSync("archive.zip");
|
|
73
|
+
* const content = await extractFile(zipData, "readme.txt");
|
|
74
|
+
* if (content) {
|
|
75
|
+
* console.log(content.toString("utf-8"));
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
async function extractFile(zipData, filePath) {
|
|
80
|
+
const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
|
|
81
|
+
const parse = (0, parse_js_1.createParse)({ forceStream: true });
|
|
82
|
+
const stream = stream_1.Readable.from([buffer]);
|
|
83
|
+
stream.pipe(parse);
|
|
84
|
+
for await (const entry of parse) {
|
|
85
|
+
const zipEntry = entry;
|
|
86
|
+
if (zipEntry.path === filePath) {
|
|
87
|
+
if (zipEntry.type === "Directory") {
|
|
88
|
+
return Buffer.alloc(0);
|
|
89
|
+
}
|
|
90
|
+
return zipEntry.buffer();
|
|
91
|
+
}
|
|
92
|
+
zipEntry.autodrain();
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* List all file paths in a ZIP buffer (without extracting content)
|
|
98
|
+
*
|
|
99
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
100
|
+
* @returns Array of file paths
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { listFiles } from "./utils/unzip/extract.js";
|
|
105
|
+
*
|
|
106
|
+
* const zipData = fs.readFileSync("archive.zip");
|
|
107
|
+
* const paths = await listFiles(zipData);
|
|
108
|
+
* console.log(paths); // ["file1.txt", "folder/file2.txt", ...]
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
async function listFiles(zipData) {
|
|
112
|
+
const paths = [];
|
|
113
|
+
const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
|
|
114
|
+
const parse = (0, parse_js_1.createParse)({ forceStream: true });
|
|
115
|
+
const stream = stream_1.Readable.from([buffer]);
|
|
116
|
+
stream.pipe(parse);
|
|
117
|
+
for await (const entry of parse) {
|
|
118
|
+
const zipEntry = entry;
|
|
119
|
+
paths.push(zipEntry.path);
|
|
120
|
+
zipEntry.autodrain();
|
|
121
|
+
}
|
|
122
|
+
return paths;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Iterate over ZIP entries with a callback (memory efficient for large ZIPs)
|
|
126
|
+
*
|
|
127
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
128
|
+
* @param callback - Async callback for each entry, return false to stop iteration
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* import { forEachEntry } from "./utils/unzip/extract.js";
|
|
133
|
+
*
|
|
134
|
+
* await forEachEntry(zipData, async (path, getData) => {
|
|
135
|
+
* if (path.endsWith(".xml")) {
|
|
136
|
+
* const content = await getData();
|
|
137
|
+
* console.log(content.toString("utf-8"));
|
|
138
|
+
* }
|
|
139
|
+
* return true; // continue iteration
|
|
140
|
+
* });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
async function forEachEntry(zipData, callback) {
|
|
144
|
+
const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
|
|
145
|
+
const parse = (0, parse_js_1.createParse)({ forceStream: true });
|
|
146
|
+
const stream = stream_1.Readable.from([buffer]);
|
|
147
|
+
stream.pipe(parse);
|
|
148
|
+
for await (const entry of parse) {
|
|
149
|
+
const zipEntry = entry;
|
|
150
|
+
let dataPromise = null;
|
|
151
|
+
const getData = () => {
|
|
152
|
+
if (!dataPromise) {
|
|
153
|
+
dataPromise = zipEntry.buffer();
|
|
154
|
+
}
|
|
155
|
+
return dataPromise;
|
|
156
|
+
};
|
|
157
|
+
const shouldContinue = await callback(zipEntry.path, getData, zipEntry);
|
|
158
|
+
// If callback didn't read data, drain it
|
|
159
|
+
if (!dataPromise) {
|
|
160
|
+
zipEntry.autodrain();
|
|
161
|
+
}
|
|
162
|
+
if (shouldContinue === false) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* License: MIT
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.parseExtraField = exports.parseDateTime = exports.parseBuffer = exports.bufferStream = exports.NoopStream = exports.PullStream = exports.createParse = exports.Parse = void 0;
|
|
8
|
+
exports.forEachEntry = exports.listFiles = exports.extractFile = exports.extractAll = exports.parseExtraField = exports.parseDateTime = exports.parseBuffer = exports.bufferStream = exports.NoopStream = exports.PullStream = exports.createParse = exports.Parse = void 0;
|
|
9
9
|
var parse_js_1 = require("./parse");
|
|
10
10
|
Object.defineProperty(exports, "Parse", { enumerable: true, get: function () { return parse_js_1.Parse; } });
|
|
11
11
|
Object.defineProperty(exports, "createParse", { enumerable: true, get: function () { return parse_js_1.createParse; } });
|
|
@@ -21,3 +21,9 @@ var parse_datetime_js_1 = require("./parse-datetime");
|
|
|
21
21
|
Object.defineProperty(exports, "parseDateTime", { enumerable: true, get: function () { return parse_datetime_js_1.parseDateTime; } });
|
|
22
22
|
var parse_extra_field_js_1 = require("./parse-extra-field");
|
|
23
23
|
Object.defineProperty(exports, "parseExtraField", { enumerable: true, get: function () { return parse_extra_field_js_1.parseExtraField; } });
|
|
24
|
+
// Simple extraction API
|
|
25
|
+
var extract_js_1 = require("./extract");
|
|
26
|
+
Object.defineProperty(exports, "extractAll", { enumerable: true, get: function () { return extract_js_1.extractAll; } });
|
|
27
|
+
Object.defineProperty(exports, "extractFile", { enumerable: true, get: function () { return extract_js_1.extractFile; } });
|
|
28
|
+
Object.defineProperty(exports, "listFiles", { enumerable: true, get: function () { return extract_js_1.listFiles; } });
|
|
29
|
+
Object.defineProperty(exports, "forEachEntry", { enumerable: true, get: function () { return extract_js_1.forEachEntry; } });
|
|
@@ -7,6 +7,8 @@ const OPEN_ANGLE = "<";
|
|
|
7
7
|
const CLOSE_ANGLE = ">";
|
|
8
8
|
const OPEN_ANGLE_SLASH = "</";
|
|
9
9
|
const CLOSE_SLASH_ANGLE = "/>";
|
|
10
|
+
// Chunk size for periodic consolidation (reduces final join overhead)
|
|
11
|
+
const CHUNK_SIZE = 10000;
|
|
10
12
|
function pushAttribute(xml, name, value) {
|
|
11
13
|
xml.push(` ${name}="${(0, utils_js_1.xmlEncode)(value.toString())}"`);
|
|
12
14
|
}
|
|
@@ -24,15 +26,23 @@ function pushAttributes(xml, attributes) {
|
|
|
24
26
|
class XmlStream {
|
|
25
27
|
constructor() {
|
|
26
28
|
this._xml = [];
|
|
29
|
+
this._chunks = [];
|
|
27
30
|
this._stack = [];
|
|
28
31
|
this._rollbacks = [];
|
|
29
32
|
}
|
|
33
|
+
_consolidate() {
|
|
34
|
+
// Periodically join small strings into larger chunks to reduce final join overhead
|
|
35
|
+
if (this._xml.length >= CHUNK_SIZE) {
|
|
36
|
+
this._chunks.push(this._xml.join(""));
|
|
37
|
+
this._xml = [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
30
40
|
get tos() {
|
|
31
41
|
return this._stack.length ? this._stack[this._stack.length - 1] : undefined;
|
|
32
42
|
}
|
|
33
43
|
get cursor() {
|
|
34
44
|
// handy way to track whether anything has been added
|
|
35
|
-
return this._xml.length;
|
|
45
|
+
return this._chunks.length * CHUNK_SIZE + this._xml.length;
|
|
36
46
|
}
|
|
37
47
|
openXml(docAttributes) {
|
|
38
48
|
const xml = this._xml;
|
|
@@ -99,6 +109,7 @@ class XmlStream {
|
|
|
99
109
|
}
|
|
100
110
|
this.open = false;
|
|
101
111
|
this.leaf = false;
|
|
112
|
+
this._consolidate();
|
|
102
113
|
}
|
|
103
114
|
leafNode(name, attributes, text) {
|
|
104
115
|
this.openNode(name, attributes);
|
|
@@ -118,7 +129,8 @@ class XmlStream {
|
|
|
118
129
|
xml: this._xml.length,
|
|
119
130
|
stack: this._stack.length,
|
|
120
131
|
leaf: this.leaf,
|
|
121
|
-
open: this.open
|
|
132
|
+
open: this.open,
|
|
133
|
+
chunksLength: this._chunks.length
|
|
122
134
|
});
|
|
123
135
|
return this.cursor;
|
|
124
136
|
}
|
|
@@ -133,12 +145,22 @@ class XmlStream {
|
|
|
133
145
|
if (this._stack.length > r.stack) {
|
|
134
146
|
this._stack.splice(r.stack, this._stack.length - r.stack);
|
|
135
147
|
}
|
|
148
|
+
if (this._chunks.length > r.chunksLength) {
|
|
149
|
+
this._chunks.splice(r.chunksLength, this._chunks.length - r.chunksLength);
|
|
150
|
+
}
|
|
136
151
|
this.leaf = r.leaf;
|
|
137
152
|
this.open = r.open;
|
|
138
153
|
}
|
|
139
154
|
get xml() {
|
|
140
155
|
this.closeAll();
|
|
141
|
-
|
|
156
|
+
// Join chunks first, then remaining xml array
|
|
157
|
+
if (this._chunks.length === 0) {
|
|
158
|
+
return this._xml.join("");
|
|
159
|
+
}
|
|
160
|
+
if (this._xml.length > 0) {
|
|
161
|
+
this._chunks.push(this._xml.join(""));
|
|
162
|
+
}
|
|
163
|
+
return this._chunks.join("");
|
|
142
164
|
}
|
|
143
165
|
}
|
|
144
166
|
exports.XmlStream = XmlStream;
|