@cj-tech-master/excelts 1.4.5-canary.20251212143538.45665af → 1.4.5-canary.20251212160853.7621827

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.
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Simple ZIP extraction utilities
3
3
  * Provides easy-to-use Promise-based API for extracting ZIP files
4
+ * Works in both Node.js and browser environments
4
5
  */
5
- import { Readable } from "stream";
6
- import { createParse } from "./parse.js";
6
+ import { ZipParser } from "./zip-parser.js";
7
7
  /**
8
8
  * Extract all files from a ZIP buffer
9
9
  *
10
- * @param zipData - ZIP file data as Buffer or Uint8Array
10
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
11
11
  * @returns Map of file paths to their content
12
12
  *
13
13
  * @example
@@ -24,40 +24,24 @@ import { createParse } from "./parse.js";
24
24
  */
25
25
  export async function extractAll(zipData) {
26
26
  const files = new Map();
27
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
28
- const parse = createParse({ forceStream: true });
29
- const stream = Readable.from([buffer]);
30
- stream.pipe(parse);
31
- for await (const entry of parse) {
32
- const zipEntry = entry;
33
- const isDirectory = zipEntry.type === "Directory";
34
- if (isDirectory) {
35
- files.set(zipEntry.path, {
36
- path: zipEntry.path,
37
- data: Buffer.alloc(0),
38
- isDirectory: true,
39
- size: 0
40
- });
41
- zipEntry.autodrain();
42
- }
43
- else {
44
- const data = await zipEntry.buffer();
45
- files.set(zipEntry.path, {
46
- path: zipEntry.path,
47
- data,
48
- isDirectory: false,
49
- size: data.length
50
- });
51
- }
27
+ const parser = new ZipParser(zipData);
28
+ for (const entry of parser.getEntries()) {
29
+ const data = await parser.extract(entry.path);
30
+ files.set(entry.path, {
31
+ path: entry.path,
32
+ data: data || new Uint8Array(0),
33
+ isDirectory: entry.isDirectory,
34
+ size: entry.uncompressedSize
35
+ });
52
36
  }
53
37
  return files;
54
38
  }
55
39
  /**
56
40
  * Extract a single file from a ZIP buffer
57
41
  *
58
- * @param zipData - ZIP file data as Buffer or Uint8Array
42
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
59
43
  * @param filePath - Path of the file to extract
60
- * @returns File content as Buffer, or null if not found
44
+ * @returns File content as Uint8Array, or null if not found
61
45
  *
62
46
  * @example
63
47
  * ```ts
@@ -66,31 +50,18 @@ export async function extractAll(zipData) {
66
50
  * const zipData = fs.readFileSync("archive.zip");
67
51
  * const content = await extractFile(zipData, "readme.txt");
68
52
  * if (content) {
69
- * console.log(content.toString("utf-8"));
53
+ * console.log(new TextDecoder().decode(content));
70
54
  * }
71
55
  * ```
72
56
  */
73
57
  export async function extractFile(zipData, filePath) {
74
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
75
- const parse = createParse({ forceStream: true });
76
- const stream = Readable.from([buffer]);
77
- stream.pipe(parse);
78
- for await (const entry of parse) {
79
- const zipEntry = entry;
80
- if (zipEntry.path === filePath) {
81
- if (zipEntry.type === "Directory") {
82
- return Buffer.alloc(0);
83
- }
84
- return zipEntry.buffer();
85
- }
86
- zipEntry.autodrain();
87
- }
88
- return null;
58
+ const parser = new ZipParser(zipData);
59
+ return parser.extract(filePath);
89
60
  }
90
61
  /**
91
62
  * List all file paths in a ZIP buffer (without extracting content)
92
63
  *
93
- * @param zipData - ZIP file data as Buffer or Uint8Array
64
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
94
65
  * @returns Array of file paths
95
66
  *
96
67
  * @example
@@ -103,22 +74,13 @@ export async function extractFile(zipData, filePath) {
103
74
  * ```
104
75
  */
105
76
  export async function listFiles(zipData) {
106
- const paths = [];
107
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
108
- const parse = createParse({ forceStream: true });
109
- const stream = Readable.from([buffer]);
110
- stream.pipe(parse);
111
- for await (const entry of parse) {
112
- const zipEntry = entry;
113
- paths.push(zipEntry.path);
114
- zipEntry.autodrain();
115
- }
116
- return paths;
77
+ const parser = new ZipParser(zipData);
78
+ return parser.listFiles();
117
79
  }
118
80
  /**
119
81
  * Iterate over ZIP entries with a callback (memory efficient for large ZIPs)
120
82
  *
121
- * @param zipData - ZIP file data as Buffer or Uint8Array
83
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
122
84
  * @param callback - Async callback for each entry, return false to stop iteration
123
85
  *
124
86
  * @example
@@ -128,33 +90,17 @@ export async function listFiles(zipData) {
128
90
  * await forEachEntry(zipData, async (path, getData) => {
129
91
  * if (path.endsWith(".xml")) {
130
92
  * const content = await getData();
131
- * console.log(content.toString("utf-8"));
93
+ * console.log(new TextDecoder().decode(content));
132
94
  * }
133
95
  * return true; // continue iteration
134
96
  * });
135
97
  * ```
136
98
  */
137
99
  export async function forEachEntry(zipData, callback) {
138
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
139
- const parse = createParse({ forceStream: true });
140
- const stream = Readable.from([buffer]);
141
- stream.pipe(parse);
142
- for await (const entry of parse) {
143
- const zipEntry = entry;
144
- let dataPromise = null;
145
- const getData = () => {
146
- if (!dataPromise) {
147
- dataPromise = zipEntry.buffer();
148
- }
149
- return dataPromise;
150
- };
151
- const shouldContinue = await callback(zipEntry.path, getData, zipEntry);
152
- // If callback didn't read data, drain it
153
- if (!dataPromise) {
154
- zipEntry.autodrain();
155
- }
156
- if (shouldContinue === false) {
157
- break;
158
- }
159
- }
100
+ const parser = new ZipParser(zipData);
101
+ await parser.forEach(async (entry, getData) => {
102
+ return callback(entry.path, getData, entry);
103
+ });
160
104
  }
105
+ // Re-export ZipParser for advanced usage
106
+ export { ZipParser } from "./zip-parser.js";
@@ -1,8 +1,23 @@
1
1
  /**
2
2
  * Unzip utilities for parsing ZIP archives
3
+ *
4
+ * Two APIs are provided:
5
+ *
6
+ * 1. **Stream-based API** (Node.js only):
7
+ * - `Parse`, `createParse` - Parse ZIP files as a stream
8
+ * - Best for large files where you don't want to load entire file into memory
9
+ * - Requires Node.js `stream` module
10
+ *
11
+ * 2. **Buffer-based API** (Browser + Node.js):
12
+ * - `extractAll`, `extractFile`, `listFiles`, `forEachEntry`, `ZipParser`
13
+ * - Works in both Node.js and browser environments
14
+ * - Uses native `DecompressionStream` in browser, `zlib` in Node.js
15
+ * - Best for files already loaded into memory (ArrayBuffer, Uint8Array)
16
+ *
3
17
  * Original source: https://github.com/ZJONSSON/node-unzipper
4
18
  * License: MIT
5
19
  */
20
+ // Stream-based API (Node.js only - requires stream module)
6
21
  export { Parse, createParse } from "./parse.js";
7
22
  export { PullStream } from "./pull-stream.js";
8
23
  export { NoopStream } from "./noop-stream.js";
@@ -10,5 +25,5 @@ export { bufferStream } from "./buffer-stream.js";
10
25
  export { parse as parseBuffer } from "./parse-buffer.js";
11
26
  export { parseDateTime } from "./parse-datetime.js";
12
27
  export { parseExtraField } from "./parse-extra-field.js";
13
- // Simple extraction API
14
- export { extractAll, extractFile, listFiles, forEachEntry } from "./extract.js";
28
+ // Buffer-based API (Browser + Node.js - cross-platform)
29
+ export { extractAll, extractFile, listFiles, forEachEntry, ZipParser } from "./extract.js";
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Pure Uint8Array-based ZIP parser
3
+ * Works in both Node.js and browser environments
4
+ * No dependency on Node.js stream module
5
+ */
6
+ import { decompress, decompressSync } from "../zip/compress.js";
7
+ // ZIP file signatures
8
+ const LOCAL_FILE_HEADER_SIG = 0x04034b50;
9
+ const CENTRAL_DIR_HEADER_SIG = 0x02014b50;
10
+ const END_OF_CENTRAL_DIR_SIG = 0x06054b50;
11
+ const ZIP64_END_OF_CENTRAL_DIR_SIG = 0x06064b50;
12
+ const ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50;
13
+ // Compression methods
14
+ const COMPRESSION_STORED = 0;
15
+ const COMPRESSION_DEFLATE = 8;
16
+ /**
17
+ * Parse DOS date/time format to JavaScript Date
18
+ * Dates in zip file entries are stored as DosDateTime
19
+ * Spec: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime
20
+ */
21
+ function parseDateTime(date, time) {
22
+ const day = date & 0x1f;
23
+ const month = (date >> 5) & 0x0f;
24
+ const year = ((date >> 9) & 0x7f) + 1980;
25
+ const seconds = time ? (time & 0x1f) * 2 : 0;
26
+ const minutes = time ? (time >> 5) & 0x3f : 0;
27
+ const hours = time ? time >> 11 : 0;
28
+ return new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds));
29
+ }
30
+ /**
31
+ * Parse ZIP64 extra field
32
+ */
33
+ function parseZip64ExtraField(extraField, compressedSize, uncompressedSize, localHeaderOffset) {
34
+ const view = new DataView(extraField.buffer, extraField.byteOffset, extraField.byteLength);
35
+ let offset = 0;
36
+ while (offset + 4 <= extraField.length) {
37
+ const signature = view.getUint16(offset, true);
38
+ const partSize = view.getUint16(offset + 2, true);
39
+ if (signature === 0x0001) {
40
+ // ZIP64 extended information
41
+ let fieldOffset = offset + 4;
42
+ if (uncompressedSize === 0xffffffff && fieldOffset + 8 <= offset + 4 + partSize) {
43
+ uncompressedSize = Number(view.getBigUint64(fieldOffset, true));
44
+ fieldOffset += 8;
45
+ }
46
+ if (compressedSize === 0xffffffff && fieldOffset + 8 <= offset + 4 + partSize) {
47
+ compressedSize = Number(view.getBigUint64(fieldOffset, true));
48
+ fieldOffset += 8;
49
+ }
50
+ if (localHeaderOffset === 0xffffffff && fieldOffset + 8 <= offset + 4 + partSize) {
51
+ localHeaderOffset = Number(view.getBigUint64(fieldOffset, true));
52
+ }
53
+ break;
54
+ }
55
+ offset += 4 + partSize;
56
+ }
57
+ return { compressedSize, uncompressedSize, localHeaderOffset };
58
+ }
59
+ /**
60
+ * DataView helper for reading little-endian values
61
+ */
62
+ class BinaryReader {
63
+ constructor(data, offset = 0) {
64
+ this.data = data;
65
+ this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
66
+ this.offset = offset;
67
+ }
68
+ get position() {
69
+ return this.offset;
70
+ }
71
+ set position(value) {
72
+ this.offset = value;
73
+ }
74
+ get remaining() {
75
+ return this.data.length - this.offset;
76
+ }
77
+ readUint8() {
78
+ const value = this.view.getUint8(this.offset);
79
+ this.offset += 1;
80
+ return value;
81
+ }
82
+ readUint16() {
83
+ const value = this.view.getUint16(this.offset, true);
84
+ this.offset += 2;
85
+ return value;
86
+ }
87
+ readUint32() {
88
+ const value = this.view.getUint32(this.offset, true);
89
+ this.offset += 4;
90
+ return value;
91
+ }
92
+ readBigUint64() {
93
+ const value = this.view.getBigUint64(this.offset, true);
94
+ this.offset += 8;
95
+ return value;
96
+ }
97
+ readBytes(length) {
98
+ const bytes = this.data.subarray(this.offset, this.offset + length);
99
+ this.offset += length;
100
+ return bytes;
101
+ }
102
+ readString(length, utf8 = true) {
103
+ const bytes = this.readBytes(length);
104
+ if (utf8) {
105
+ return new TextDecoder("utf-8").decode(bytes);
106
+ }
107
+ // Fallback to ASCII/Latin-1
108
+ return String.fromCharCode(...bytes);
109
+ }
110
+ skip(length) {
111
+ this.offset += length;
112
+ }
113
+ slice(start, end) {
114
+ return this.data.subarray(start, end);
115
+ }
116
+ peekUint32(offset) {
117
+ return this.view.getUint32(offset, true);
118
+ }
119
+ }
120
+ /**
121
+ * Find the End of Central Directory record
122
+ * Searches backwards from the end of the file
123
+ */
124
+ function findEndOfCentralDir(data) {
125
+ // EOCD is at least 22 bytes, search backwards
126
+ // Comment can be up to 65535 bytes
127
+ const minOffset = Math.max(0, data.length - 65557);
128
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
129
+ for (let i = data.length - 22; i >= minOffset; i--) {
130
+ if (view.getUint32(i, true) === END_OF_CENTRAL_DIR_SIG) {
131
+ return i;
132
+ }
133
+ }
134
+ return -1;
135
+ }
136
+ /**
137
+ * Find ZIP64 End of Central Directory Locator
138
+ */
139
+ function findZip64EOCDLocator(data, eocdOffset) {
140
+ // ZIP64 EOCD Locator is 20 bytes and appears right before EOCD
141
+ const locatorOffset = eocdOffset - 20;
142
+ if (locatorOffset < 0) {
143
+ return -1;
144
+ }
145
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
146
+ if (view.getUint32(locatorOffset, true) === ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) {
147
+ return locatorOffset;
148
+ }
149
+ return -1;
150
+ }
151
+ /**
152
+ * Parse ZIP file entries from Central Directory
153
+ */
154
+ export function parseZipEntries(data, options = {}) {
155
+ const { decodeStrings = true } = options;
156
+ const entries = [];
157
+ // Find End of Central Directory
158
+ const eocdOffset = findEndOfCentralDir(data);
159
+ if (eocdOffset === -1) {
160
+ throw new Error("Invalid ZIP file: End of Central Directory not found");
161
+ }
162
+ const reader = new BinaryReader(data, eocdOffset);
163
+ // Read EOCD
164
+ // Offset Size Description
165
+ // 0 4 EOCD signature (0x06054b50)
166
+ // 4 2 Number of this disk
167
+ // 6 2 Disk where central directory starts
168
+ // 8 2 Number of central directory records on this disk
169
+ // 10 2 Total number of central directory records
170
+ // 12 4 Size of central directory (bytes)
171
+ // 16 4 Offset of start of central directory
172
+ // 20 2 Comment length
173
+ reader.skip(4); // signature
174
+ reader.skip(2); // disk number
175
+ reader.skip(2); // disk where central dir starts
176
+ reader.skip(2); // entries on this disk
177
+ let totalEntries = reader.readUint16(); // total entries
178
+ let centralDirSize = reader.readUint32();
179
+ let centralDirOffset = reader.readUint32();
180
+ // Check for ZIP64
181
+ const zip64LocatorOffset = findZip64EOCDLocator(data, eocdOffset);
182
+ if (zip64LocatorOffset !== -1) {
183
+ const locatorReader = new BinaryReader(data, zip64LocatorOffset);
184
+ locatorReader.skip(4); // signature
185
+ locatorReader.skip(4); // disk number with ZIP64 EOCD
186
+ const zip64EOCDOffset = Number(locatorReader.readBigUint64());
187
+ // Read ZIP64 EOCD
188
+ const zip64Reader = new BinaryReader(data, zip64EOCDOffset);
189
+ const zip64Sig = zip64Reader.readUint32();
190
+ if (zip64Sig === ZIP64_END_OF_CENTRAL_DIR_SIG) {
191
+ zip64Reader.skip(8); // size of ZIP64 EOCD
192
+ zip64Reader.skip(2); // version made by
193
+ zip64Reader.skip(2); // version needed
194
+ zip64Reader.skip(4); // disk number
195
+ zip64Reader.skip(4); // disk with central dir
196
+ const zip64TotalEntries = Number(zip64Reader.readBigUint64());
197
+ const zip64CentralDirSize = Number(zip64Reader.readBigUint64());
198
+ const zip64CentralDirOffset = Number(zip64Reader.readBigUint64());
199
+ // Use ZIP64 values if standard values are maxed out
200
+ if (totalEntries === 0xffff) {
201
+ totalEntries = zip64TotalEntries;
202
+ }
203
+ if (centralDirSize === 0xffffffff) {
204
+ centralDirSize = zip64CentralDirSize;
205
+ }
206
+ if (centralDirOffset === 0xffffffff) {
207
+ centralDirOffset = zip64CentralDirOffset;
208
+ }
209
+ }
210
+ }
211
+ // Read Central Directory entries
212
+ const centralReader = new BinaryReader(data, centralDirOffset);
213
+ for (let i = 0; i < totalEntries; i++) {
214
+ const sig = centralReader.readUint32();
215
+ if (sig !== CENTRAL_DIR_HEADER_SIG) {
216
+ throw new Error(`Invalid Central Directory header signature at entry ${i}`);
217
+ }
218
+ // Central Directory File Header format:
219
+ // Offset Size Description
220
+ // 0 4 Central directory file header signature (0x02014b50)
221
+ // 4 2 Version made by
222
+ // 6 2 Version needed to extract
223
+ // 8 2 General purpose bit flag
224
+ // 10 2 Compression method
225
+ // 12 2 File last modification time
226
+ // 14 2 File last modification date
227
+ // 16 4 CRC-32
228
+ // 20 4 Compressed size
229
+ // 24 4 Uncompressed size
230
+ // 28 2 File name length
231
+ // 30 2 Extra field length
232
+ // 32 2 File comment length
233
+ // 34 2 Disk number where file starts
234
+ // 36 2 Internal file attributes
235
+ // 38 4 External file attributes
236
+ // 42 4 Relative offset of local file header
237
+ // 46 n File name
238
+ // 46+n m Extra field
239
+ // 46+n+m k File comment
240
+ centralReader.skip(2); // version made by
241
+ centralReader.skip(2); // version needed
242
+ const flags = centralReader.readUint16();
243
+ const compressionMethod = centralReader.readUint16();
244
+ const lastModTime = centralReader.readUint16();
245
+ const lastModDate = centralReader.readUint16();
246
+ const crc32 = centralReader.readUint32();
247
+ let compressedSize = centralReader.readUint32();
248
+ let uncompressedSize = centralReader.readUint32();
249
+ const fileNameLength = centralReader.readUint16();
250
+ const extraFieldLength = centralReader.readUint16();
251
+ const commentLength = centralReader.readUint16();
252
+ centralReader.skip(2); // disk number start
253
+ centralReader.skip(2); // internal attributes
254
+ const externalAttributes = centralReader.readUint32();
255
+ let localHeaderOffset = centralReader.readUint32();
256
+ // Check for UTF-8 flag (bit 11)
257
+ const isUtf8 = (flags & 0x800) !== 0;
258
+ const useUtf8 = decodeStrings && isUtf8;
259
+ const fileName = centralReader.readString(fileNameLength, useUtf8);
260
+ const extraField = centralReader.readBytes(extraFieldLength);
261
+ const comment = centralReader.readString(commentLength, useUtf8);
262
+ // Parse extra field for ZIP64 values
263
+ if (extraFieldLength > 0) {
264
+ const parsed = parseZip64ExtraField(extraField, compressedSize, uncompressedSize, localHeaderOffset);
265
+ compressedSize = parsed.compressedSize;
266
+ uncompressedSize = parsed.uncompressedSize;
267
+ localHeaderOffset = parsed.localHeaderOffset;
268
+ }
269
+ const isDirectory = fileName.endsWith("/") || (externalAttributes & 0x10) !== 0;
270
+ const isEncrypted = (flags & 0x01) !== 0;
271
+ entries.push({
272
+ path: fileName,
273
+ isDirectory,
274
+ compressedSize,
275
+ uncompressedSize,
276
+ compressionMethod,
277
+ crc32,
278
+ lastModified: parseDateTime(lastModDate, lastModTime),
279
+ localHeaderOffset,
280
+ comment,
281
+ externalAttributes,
282
+ isEncrypted
283
+ });
284
+ }
285
+ return entries;
286
+ }
287
+ /**
288
+ * Extract file data for a specific entry
289
+ */
290
+ export async function extractEntryData(data, entry) {
291
+ if (entry.isDirectory) {
292
+ return new Uint8Array(0);
293
+ }
294
+ if (entry.isEncrypted) {
295
+ throw new Error(`File "${entry.path}" is encrypted and cannot be extracted`);
296
+ }
297
+ const reader = new BinaryReader(data, entry.localHeaderOffset);
298
+ // Read local file header
299
+ const sig = reader.readUint32();
300
+ if (sig !== LOCAL_FILE_HEADER_SIG) {
301
+ throw new Error(`Invalid local file header signature for "${entry.path}"`);
302
+ }
303
+ reader.skip(2); // version needed
304
+ reader.skip(2); // flags
305
+ reader.skip(2); // compression method
306
+ reader.skip(2); // last mod time
307
+ reader.skip(2); // last mod date
308
+ reader.skip(4); // crc32
309
+ reader.skip(4); // compressed size
310
+ reader.skip(4); // uncompressed size
311
+ const fileNameLength = reader.readUint16();
312
+ const extraFieldLength = reader.readUint16();
313
+ reader.skip(fileNameLength);
314
+ reader.skip(extraFieldLength);
315
+ // Extract compressed data
316
+ const compressedData = reader.readBytes(entry.compressedSize);
317
+ // Decompress if needed
318
+ if (entry.compressionMethod === COMPRESSION_STORED) {
319
+ return compressedData;
320
+ }
321
+ else if (entry.compressionMethod === COMPRESSION_DEFLATE) {
322
+ return decompress(compressedData);
323
+ }
324
+ else {
325
+ throw new Error(`Unsupported compression method: ${entry.compressionMethod}`);
326
+ }
327
+ }
328
+ /**
329
+ * Extract file data synchronously (Node.js only)
330
+ */
331
+ export function extractEntryDataSync(data, entry) {
332
+ if (entry.isDirectory) {
333
+ return new Uint8Array(0);
334
+ }
335
+ if (entry.isEncrypted) {
336
+ throw new Error(`File "${entry.path}" is encrypted and cannot be extracted`);
337
+ }
338
+ const reader = new BinaryReader(data, entry.localHeaderOffset);
339
+ // Read local file header
340
+ const sig = reader.readUint32();
341
+ if (sig !== LOCAL_FILE_HEADER_SIG) {
342
+ throw new Error(`Invalid local file header signature for "${entry.path}"`);
343
+ }
344
+ reader.skip(2); // version needed
345
+ reader.skip(2); // flags
346
+ reader.skip(2); // compression method
347
+ reader.skip(2); // last mod time
348
+ reader.skip(2); // last mod date
349
+ reader.skip(4); // crc32
350
+ reader.skip(4); // compressed size
351
+ reader.skip(4); // uncompressed size
352
+ const fileNameLength = reader.readUint16();
353
+ const extraFieldLength = reader.readUint16();
354
+ reader.skip(fileNameLength);
355
+ reader.skip(extraFieldLength);
356
+ // Extract compressed data
357
+ const compressedData = reader.readBytes(entry.compressedSize);
358
+ // Decompress if needed
359
+ if (entry.compressionMethod === COMPRESSION_STORED) {
360
+ return compressedData;
361
+ }
362
+ else if (entry.compressionMethod === COMPRESSION_DEFLATE) {
363
+ return decompressSync(compressedData);
364
+ }
365
+ else {
366
+ throw new Error(`Unsupported compression method: ${entry.compressionMethod}`);
367
+ }
368
+ }
369
+ /**
370
+ * High-level ZIP parser class
371
+ */
372
+ export class ZipParser {
373
+ constructor(data, options = {}) {
374
+ this.data = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
375
+ this.entries = parseZipEntries(this.data, options);
376
+ this.entryMap = new Map(this.entries.map(e => [e.path, e]));
377
+ }
378
+ /**
379
+ * Get all entries in the ZIP file
380
+ */
381
+ getEntries() {
382
+ return this.entries;
383
+ }
384
+ /**
385
+ * Get entry by path
386
+ */
387
+ getEntry(path) {
388
+ return this.entryMap.get(path);
389
+ }
390
+ /**
391
+ * Check if entry exists
392
+ */
393
+ hasEntry(path) {
394
+ return this.entryMap.has(path);
395
+ }
396
+ /**
397
+ * List all file paths
398
+ */
399
+ listFiles() {
400
+ return this.entries.map(e => e.path);
401
+ }
402
+ /**
403
+ * Extract a single file (async)
404
+ */
405
+ async extract(path) {
406
+ const entry = this.entryMap.get(path);
407
+ if (!entry) {
408
+ return null;
409
+ }
410
+ return extractEntryData(this.data, entry);
411
+ }
412
+ /**
413
+ * Extract a single file (sync, Node.js only)
414
+ */
415
+ extractSync(path) {
416
+ const entry = this.entryMap.get(path);
417
+ if (!entry) {
418
+ return null;
419
+ }
420
+ return extractEntryDataSync(this.data, entry);
421
+ }
422
+ /**
423
+ * Extract all files (async)
424
+ */
425
+ async extractAll() {
426
+ const result = new Map();
427
+ for (const entry of this.entries) {
428
+ const data = await extractEntryData(this.data, entry);
429
+ result.set(entry.path, data);
430
+ }
431
+ return result;
432
+ }
433
+ /**
434
+ * Iterate over entries with async callback
435
+ */
436
+ async forEach(callback) {
437
+ for (const entry of this.entries) {
438
+ let dataPromise = null;
439
+ const getData = () => {
440
+ if (!dataPromise) {
441
+ dataPromise = extractEntryData(this.data, entry);
442
+ }
443
+ return dataPromise;
444
+ };
445
+ const shouldContinue = await callback(entry, getData);
446
+ if (shouldContinue === false) {
447
+ break;
448
+ }
449
+ }
450
+ }
451
+ }