@cj-tech-master/excelts 1.4.2 → 1.4.4
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 +8135 -2722
- package/dist/browser/excelts.iife.js.map +1 -1
- package/dist/browser/excelts.iife.min.js +86 -23
- package/dist/cjs/stream/xlsx/workbook-writer.js +3 -2
- package/dist/cjs/utils/cell-format.js +13 -9
- package/dist/cjs/utils/sheet-utils.js +125 -15
- 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/stream/xlsx/workbook-writer.js +3 -2
- package/dist/esm/utils/cell-format.js +13 -9
- package/dist/esm/utils/sheet-utils.js +125 -15
- 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/utils/sheet-utils.d.ts +8 -2
- 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 +1 -1
|
@@ -42,16 +42,50 @@ class DefinedNamesXform extends BaseXform {
|
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
// Regex to validate cell range format:
|
|
46
|
+
// - Cell: $A$1 or A1
|
|
47
|
+
// - Range: $A$1:$B$10 or A1:B10
|
|
48
|
+
// - Row range: $1:$2 (for print titles)
|
|
49
|
+
// - Column range: $A:$B (for print titles)
|
|
50
|
+
const cellRangeRegexp = /^[$]?[A-Za-z]{1,3}[$]?\d+(:[$]?[A-Za-z]{1,3}[$]?\d+)?$/;
|
|
51
|
+
const rowRangeRegexp = /^[$]?\d+:[$]?\d+$/;
|
|
52
|
+
const colRangeRegexp = /^[$]?[A-Za-z]{1,3}:[$]?[A-Za-z]{1,3}$/;
|
|
45
53
|
function isValidRange(range) {
|
|
54
|
+
// Skip array constants wrapped in {} - these are not valid cell ranges
|
|
55
|
+
// e.g., {"'Sheet1'!$A$1:$B$10"} or {#N/A,#N/A,FALSE,"text"}
|
|
56
|
+
if (range.startsWith("{") || range.endsWith("}")) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// Extract the cell reference part (after the sheet name if present)
|
|
60
|
+
const cellRef = range.split("!").pop() || "";
|
|
61
|
+
// Must match one of the valid patterns
|
|
62
|
+
if (!cellRangeRegexp.test(cellRef) &&
|
|
63
|
+
!rowRangeRegexp.test(cellRef) &&
|
|
64
|
+
!colRangeRegexp.test(cellRef)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
46
67
|
try {
|
|
47
|
-
colCache.decodeEx(range);
|
|
48
|
-
|
|
68
|
+
const decoded = colCache.decodeEx(range);
|
|
69
|
+
// For cell ranges: row/col or top/bottom/left/right should be valid numbers
|
|
70
|
+
// For row ranges ($1:$2): top/bottom are numbers, left/right are null
|
|
71
|
+
// For column ranges ($A:$B): left/right are numbers, top/bottom are null
|
|
72
|
+
if (("row" in decoded && typeof decoded.row === "number") ||
|
|
73
|
+
("top" in decoded && typeof decoded.top === "number") ||
|
|
74
|
+
("left" in decoded && typeof decoded.left === "number")) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
49
78
|
}
|
|
50
79
|
catch {
|
|
51
80
|
return false;
|
|
52
81
|
}
|
|
53
82
|
}
|
|
54
83
|
function extractRanges(parsedText) {
|
|
84
|
+
// Skip if the entire text is wrapped in {} (array constant)
|
|
85
|
+
const trimmed = parsedText.trim();
|
|
86
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
55
89
|
const ranges = [];
|
|
56
90
|
let quotesOpened = false;
|
|
57
91
|
let last = "";
|
|
@@ -407,7 +407,12 @@ class CellXform extends BaseXform {
|
|
|
407
407
|
}
|
|
408
408
|
break;
|
|
409
409
|
case Enums.ValueType.Formula:
|
|
410
|
-
|
|
410
|
+
// Only convert formula result to date if the result is a number
|
|
411
|
+
// String results (t="str") should not be converted even if the cell has a date format
|
|
412
|
+
if (model.result !== undefined &&
|
|
413
|
+
typeof model.result === "number" &&
|
|
414
|
+
style &&
|
|
415
|
+
isDateFmt(style.numFmt)) {
|
|
411
416
|
model.result = excelToDate(model.result, options.date1904);
|
|
412
417
|
}
|
|
413
418
|
if (model.shareType === "shared") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseXform } from "../base-xform.js";
|
|
2
2
|
import { CellXform } from "./cell-xform.js";
|
|
3
3
|
import { parseBoolean } from "../../../utils/utils.js";
|
|
4
|
+
import { colCache } from "../../../utils/col-cache.js";
|
|
4
5
|
class RowXform extends BaseXform {
|
|
5
6
|
constructor(options) {
|
|
6
7
|
super();
|
|
@@ -12,6 +13,11 @@ class RowXform extends BaseXform {
|
|
|
12
13
|
get tag() {
|
|
13
14
|
return "row";
|
|
14
15
|
}
|
|
16
|
+
reset() {
|
|
17
|
+
super.reset();
|
|
18
|
+
this.numRowsSeen = 0;
|
|
19
|
+
this.lastCellCol = 0;
|
|
20
|
+
}
|
|
15
21
|
prepare(model, options) {
|
|
16
22
|
const styleId = options.styles.addStyleModel(model.style);
|
|
17
23
|
if (styleId) {
|
|
@@ -62,11 +68,15 @@ class RowXform extends BaseXform {
|
|
|
62
68
|
}
|
|
63
69
|
if (node.name === "row") {
|
|
64
70
|
this.numRowsSeen += 1;
|
|
71
|
+
// Reset lastCellCol for each new row
|
|
72
|
+
this.lastCellCol = 0;
|
|
65
73
|
const spans = node.attributes.spans
|
|
66
74
|
? node.attributes.spans.split(":").map((span) => parseInt(span, 10))
|
|
67
75
|
: [undefined, undefined];
|
|
76
|
+
// If r attribute is missing, use numRowsSeen as the row number
|
|
77
|
+
const rowNumber = node.attributes.r ? parseInt(node.attributes.r, 10) : this.numRowsSeen;
|
|
68
78
|
const model = (this.model = {
|
|
69
|
-
number:
|
|
79
|
+
number: rowNumber,
|
|
70
80
|
min: spans[0],
|
|
71
81
|
max: spans[1],
|
|
72
82
|
cells: []
|
|
@@ -106,7 +116,19 @@ class RowXform extends BaseXform {
|
|
|
106
116
|
parseClose(name) {
|
|
107
117
|
if (this.parser) {
|
|
108
118
|
if (!this.parser.parseClose(name)) {
|
|
109
|
-
this.
|
|
119
|
+
const cellModel = this.parser.model;
|
|
120
|
+
// If cell has address, extract column number from it
|
|
121
|
+
// Otherwise, calculate address based on position
|
|
122
|
+
if (cellModel.address) {
|
|
123
|
+
const decoded = colCache.decodeAddress(cellModel.address);
|
|
124
|
+
this.lastCellCol = decoded.col;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// No r attribute, calculate address from position
|
|
128
|
+
this.lastCellCol += 1;
|
|
129
|
+
cellModel.address = colCache.encodeAddress(this.model.number, this.lastCellCol);
|
|
130
|
+
}
|
|
131
|
+
this.model.cells.push(cellModel);
|
|
110
132
|
if (this.maxItems && this.model.cells.length > this.maxItems) {
|
|
111
133
|
throw new Error(`Max column count (${this.maxItems}) exceeded`);
|
|
112
134
|
}
|
|
@@ -54,6 +54,10 @@ class FilterColumnXform extends BaseXform {
|
|
|
54
54
|
filterButton: attributes.hiddenButton === "0"
|
|
55
55
|
};
|
|
56
56
|
return true;
|
|
57
|
+
case "dynamicFilter":
|
|
58
|
+
// Ignore dynamicFilter nodes - we don't need to preserve them for reading
|
|
59
|
+
// See: https://github.com/exceljs/exceljs/issues/2972
|
|
60
|
+
return true;
|
|
57
61
|
default:
|
|
58
62
|
this.parser = this.map[node.name];
|
|
59
63
|
if (this.parser) {
|
|
@@ -73,7 +73,7 @@ export interface JSON2SheetOpts {
|
|
|
73
73
|
/** Use specified field order (default Object.keys) */
|
|
74
74
|
header?: string[];
|
|
75
75
|
/** Use specified date format in string output */
|
|
76
|
-
|
|
76
|
+
dateFormat?: string;
|
|
77
77
|
/** Store dates as type d (default is n) */
|
|
78
78
|
cellDates?: boolean;
|
|
79
79
|
/** If true, do not include header row in output */
|
|
@@ -117,6 +117,12 @@ export interface Sheet2JSONOpts {
|
|
|
117
117
|
defval?: CellValue;
|
|
118
118
|
/** Include blank lines in the output */
|
|
119
119
|
blankrows?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Override format for date values (not applied to time-only formats).
|
|
122
|
+
* When provided, Date values will be formatted using this format string.
|
|
123
|
+
* @example "dd/mm/yyyy" or "yyyy-mm-dd"
|
|
124
|
+
*/
|
|
125
|
+
dateFormat?: string;
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
122
128
|
* Convert worksheet to JSON array (xlsx compatible)
|
|
@@ -164,7 +170,7 @@ export interface AOA2SheetOpts {
|
|
|
164
170
|
/** Use specified cell as starting point */
|
|
165
171
|
origin?: Origin;
|
|
166
172
|
/** Use specified date format in string output */
|
|
167
|
-
|
|
173
|
+
dateFormat?: string;
|
|
168
174
|
/** Store dates as type d (default is n) */
|
|
169
175
|
cellDates?: boolean;
|
|
170
176
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple ZIP extraction utilities
|
|
3
|
+
* Provides easy-to-use Promise-based API for extracting ZIP files
|
|
4
|
+
*/
|
|
5
|
+
import { type ZipEntry } from "./parse.js";
|
|
6
|
+
/**
|
|
7
|
+
* Extracted file entry
|
|
8
|
+
*/
|
|
9
|
+
export interface ExtractedFile {
|
|
10
|
+
/** File path within the ZIP */
|
|
11
|
+
path: string;
|
|
12
|
+
/** File content as Buffer */
|
|
13
|
+
data: Buffer;
|
|
14
|
+
/** Whether this is a directory */
|
|
15
|
+
isDirectory: boolean;
|
|
16
|
+
/** Uncompressed size */
|
|
17
|
+
size: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Extract all files from a ZIP buffer
|
|
21
|
+
*
|
|
22
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
23
|
+
* @returns Map of file paths to their content
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { extractAll } from "./utils/unzip/extract.js";
|
|
28
|
+
*
|
|
29
|
+
* const zipData = fs.readFileSync("archive.zip");
|
|
30
|
+
* const files = await extractAll(zipData);
|
|
31
|
+
*
|
|
32
|
+
* for (const [path, file] of files) {
|
|
33
|
+
* console.log(`${path}: ${file.data.length} bytes`);
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function extractAll(zipData: Buffer | Uint8Array): Promise<Map<string, ExtractedFile>>;
|
|
38
|
+
/**
|
|
39
|
+
* Extract a single file from a ZIP buffer
|
|
40
|
+
*
|
|
41
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
42
|
+
* @param filePath - Path of the file to extract
|
|
43
|
+
* @returns File content as Buffer, or null if not found
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { extractFile } from "./utils/unzip/extract.js";
|
|
48
|
+
*
|
|
49
|
+
* const zipData = fs.readFileSync("archive.zip");
|
|
50
|
+
* const content = await extractFile(zipData, "readme.txt");
|
|
51
|
+
* if (content) {
|
|
52
|
+
* console.log(content.toString("utf-8"));
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function extractFile(zipData: Buffer | Uint8Array, filePath: string): Promise<Buffer | null>;
|
|
57
|
+
/**
|
|
58
|
+
* List all file paths in a ZIP buffer (without extracting content)
|
|
59
|
+
*
|
|
60
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
61
|
+
* @returns Array of file paths
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* import { listFiles } from "./utils/unzip/extract.js";
|
|
66
|
+
*
|
|
67
|
+
* const zipData = fs.readFileSync("archive.zip");
|
|
68
|
+
* const paths = await listFiles(zipData);
|
|
69
|
+
* console.log(paths); // ["file1.txt", "folder/file2.txt", ...]
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function listFiles(zipData: Buffer | Uint8Array): Promise<string[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Iterate over ZIP entries with a callback (memory efficient for large ZIPs)
|
|
75
|
+
*
|
|
76
|
+
* @param zipData - ZIP file data as Buffer or Uint8Array
|
|
77
|
+
* @param callback - Async callback for each entry, return false to stop iteration
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { forEachEntry } from "./utils/unzip/extract.js";
|
|
82
|
+
*
|
|
83
|
+
* await forEachEntry(zipData, async (path, getData) => {
|
|
84
|
+
* if (path.endsWith(".xml")) {
|
|
85
|
+
* const content = await getData();
|
|
86
|
+
* console.log(content.toString("utf-8"));
|
|
87
|
+
* }
|
|
88
|
+
* return true; // continue iteration
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function forEachEntry(zipData: Buffer | Uint8Array, callback: (path: string, getData: () => Promise<Buffer>, entry: ZipEntry) => Promise<boolean | void>): Promise<void>;
|
|
@@ -10,3 +10,4 @@ export { bufferStream } from "./buffer-stream.js";
|
|
|
10
10
|
export { parse as parseBuffer } from "./parse-buffer.js";
|
|
11
11
|
export { parseDateTime } from "./parse-datetime.js";
|
|
12
12
|
export { parseExtraField, type ExtraField, type ZipVars } from "./parse-extra-field.js";
|
|
13
|
+
export { extractAll, extractFile, listFiles, forEachEntry, type ExtractedFile } from "./extract.js";
|
|
@@ -3,11 +3,13 @@ interface Attributes {
|
|
|
3
3
|
}
|
|
4
4
|
declare class XmlStream {
|
|
5
5
|
private _xml;
|
|
6
|
+
private _chunks;
|
|
6
7
|
private _stack;
|
|
7
8
|
private _rollbacks;
|
|
8
9
|
leaf?: boolean;
|
|
9
10
|
open?: boolean;
|
|
10
11
|
constructor();
|
|
12
|
+
private _consolidate;
|
|
11
13
|
get tos(): string | undefined;
|
|
12
14
|
get cursor(): number;
|
|
13
15
|
openXml(docAttributes?: Attributes): void;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native compression utilities using platform APIs
|
|
3
|
+
*
|
|
4
|
+
* - Node.js: Uses native zlib module (C++ implementation, fastest)
|
|
5
|
+
* - Browser: Uses CompressionStream API (Chrome 80+, Firefox 113+, Safari 16.4+)
|
|
6
|
+
*
|
|
7
|
+
* Both use "deflate-raw" format which is required for ZIP files
|
|
8
|
+
* (raw DEFLATE without zlib header/trailer)
|
|
9
|
+
*/
|
|
10
|
+
import type * as zlibType from "zlib";
|
|
11
|
+
/**
|
|
12
|
+
* Compression options
|
|
13
|
+
*/
|
|
14
|
+
export interface CompressOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Compression level (0-9)
|
|
17
|
+
* - 0: No compression (STORE)
|
|
18
|
+
* - 1: Fastest compression
|
|
19
|
+
* - 6: Default compression (good balance)
|
|
20
|
+
* - 9: Best compression (slowest)
|
|
21
|
+
*
|
|
22
|
+
* Note: CompressionStream does not support level configuration,
|
|
23
|
+
* it uses a fixed level (~6)
|
|
24
|
+
*/
|
|
25
|
+
level?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Ensure zlib is loaded (Node.js only)
|
|
29
|
+
* Call this before using sync methods if you need to guarantee availability
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureZlib(): Promise<typeof zlibType | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Check if native zlib is available (Node.js)
|
|
34
|
+
*/
|
|
35
|
+
export declare function hasNativeZlib(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Check if CompressionStream is available (Browser/Node.js 17+)
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasCompressionStream(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Compress data using the best available native method
|
|
42
|
+
*
|
|
43
|
+
* Priority:
|
|
44
|
+
* 1. Node.js zlib (if available) - fastest, supports compression levels
|
|
45
|
+
* 2. CompressionStream (browser/Node.js 17+) - no level support
|
|
46
|
+
* 3. Return uncompressed data (fallback)
|
|
47
|
+
*
|
|
48
|
+
* @param data - Data to compress
|
|
49
|
+
* @param options - Compression options
|
|
50
|
+
* @returns Compressed data
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const data = new TextEncoder().encode("Hello, World!");
|
|
55
|
+
* const compressed = await compress(data, { level: 6 });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function compress(data: Uint8Array, options?: CompressOptions): Promise<Uint8Array>;
|
|
59
|
+
/**
|
|
60
|
+
* Compress data synchronously using Node.js zlib
|
|
61
|
+
* Only available in Node.js environment
|
|
62
|
+
*
|
|
63
|
+
* @param data - Data to compress
|
|
64
|
+
* @param options - Compression options
|
|
65
|
+
* @returns Compressed data
|
|
66
|
+
* @throws Error if not in Node.js environment
|
|
67
|
+
*/
|
|
68
|
+
export declare function compressSync(data: Uint8Array, options?: CompressOptions): Uint8Array;
|
|
69
|
+
/**
|
|
70
|
+
* Decompress data using the best available native method
|
|
71
|
+
*
|
|
72
|
+
* @param data - Compressed data (deflate-raw format)
|
|
73
|
+
* @returns Decompressed data
|
|
74
|
+
*/
|
|
75
|
+
export declare function decompress(data: Uint8Array): Promise<Uint8Array>;
|
|
76
|
+
/**
|
|
77
|
+
* Decompress data synchronously using Node.js zlib
|
|
78
|
+
*
|
|
79
|
+
* @param data - Compressed data (deflate-raw format)
|
|
80
|
+
* @returns Decompressed data
|
|
81
|
+
* @throws Error if not in Node.js environment
|
|
82
|
+
*/
|
|
83
|
+
export declare function decompressSync(data: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC32 calculation utility for ZIP files
|
|
3
|
+
*
|
|
4
|
+
* - Node.js: Uses native zlib.crc32 (C++ implementation, ~100x faster)
|
|
5
|
+
* - Browser: Uses lookup table optimization
|
|
6
|
+
*
|
|
7
|
+
* The polynomial used is the standard CRC-32 IEEE 802.3:
|
|
8
|
+
* x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
|
|
9
|
+
* Represented as 0xEDB88320 in reversed (LSB-first) form
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Calculate CRC32 checksum for the given data
|
|
13
|
+
* Uses native zlib.crc32 in Node.js for ~100x better performance
|
|
14
|
+
*
|
|
15
|
+
* @param data - Input data as Uint8Array or Buffer
|
|
16
|
+
* @returns CRC32 checksum as unsigned 32-bit integer
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const data = new TextEncoder().encode("Hello, World!");
|
|
21
|
+
* const checksum = crc32(data);
|
|
22
|
+
* console.log(checksum.toString(16)); // "ec4ac3d0"
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function crc32(data: Uint8Array): number;
|
|
26
|
+
/**
|
|
27
|
+
* Ensure zlib is loaded (for use before calling crc32)
|
|
28
|
+
*/
|
|
29
|
+
export declare function ensureCrc32(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate CRC32 incrementally (useful for streaming)
|
|
32
|
+
* Call with initial crc of 0xffffffff, then finalize with crc32Finalize
|
|
33
|
+
* Note: This always uses JS implementation for consistency in streaming
|
|
34
|
+
*
|
|
35
|
+
* @param crc - Current CRC value (start with 0xffffffff)
|
|
36
|
+
* @param data - Input data chunk
|
|
37
|
+
* @returns Updated CRC value (not finalized)
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* let crc = 0xffffffff;
|
|
42
|
+
* crc = crc32Update(crc, chunk1);
|
|
43
|
+
* crc = crc32Update(crc, chunk2);
|
|
44
|
+
* const checksum = crc32Finalize(crc);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function crc32Update(crc: number, data: Uint8Array): number;
|
|
48
|
+
/**
|
|
49
|
+
* Finalize CRC32 calculation
|
|
50
|
+
* XOR with 0xffffffff and convert to unsigned 32-bit
|
|
51
|
+
*
|
|
52
|
+
* @param crc - CRC value from crc32Update
|
|
53
|
+
* @returns Final CRC32 checksum
|
|
54
|
+
*/
|
|
55
|
+
export declare function crc32Finalize(crc: number): number;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native ZIP utilities - Pure native implementation without third-party dependencies
|
|
3
|
+
*
|
|
4
|
+
* This module provides ZIP file creation using only native platform APIs:
|
|
5
|
+
* - Node.js: Uses native zlib module (C++ implementation, fastest)
|
|
6
|
+
* - Browser: Uses CompressionStream API (Chrome 80+, Firefox 113+, Safari 16.4+)
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Full ZIP format support (Local File Headers, Central Directory, EOCD)
|
|
10
|
+
* - DEFLATE compression (level 0-9 on Node.js, fixed level on browser)
|
|
11
|
+
* - STORE mode (no compression)
|
|
12
|
+
* - UTF-8 filename support
|
|
13
|
+
* - File comments and ZIP comments
|
|
14
|
+
* - Streaming API for large files
|
|
15
|
+
* - Both sync (Node.js) and async APIs
|
|
16
|
+
*
|
|
17
|
+
* @example Basic usage
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { createZip } from "./utils/zip/index.js";
|
|
20
|
+
*
|
|
21
|
+
* const zipData = await createZip([
|
|
22
|
+
* { name: "hello.txt", data: new TextEncoder().encode("Hello!") },
|
|
23
|
+
* { name: "folder/nested.txt", data: new TextEncoder().encode("Nested file") }
|
|
24
|
+
* ], { level: 6 });
|
|
25
|
+
*
|
|
26
|
+
* // Write to file (Node.js)
|
|
27
|
+
* fs.writeFileSync("output.zip", zipData);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Streaming usage
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { ZipBuilder } from "./utils/zip/index.js";
|
|
33
|
+
*
|
|
34
|
+
* const builder = new ZipBuilder({ level: 1 });
|
|
35
|
+
*
|
|
36
|
+
* // Add files one by one
|
|
37
|
+
* const [header1, data1] = await builder.addFile({
|
|
38
|
+
* name: "file1.txt",
|
|
39
|
+
* data: new TextEncoder().encode("File 1 content")
|
|
40
|
+
* });
|
|
41
|
+
* stream.write(header1);
|
|
42
|
+
* stream.write(data1);
|
|
43
|
+
*
|
|
44
|
+
* // Finalize and write central directory
|
|
45
|
+
* for (const chunk of builder.finalize()) {
|
|
46
|
+
* stream.write(chunk);
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export { crc32, crc32Update, crc32Finalize } from "./crc32.js";
|
|
51
|
+
export { compress, compressSync, decompress, decompressSync, hasNativeZlib, hasCompressionStream, type CompressOptions } from "./compress.js";
|
|
52
|
+
export { createZip, createZipSync, ZipBuilder, type ZipEntry, type ZipOptions } from "./zip-builder.js";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIP file format builder
|
|
3
|
+
*
|
|
4
|
+
* Implements ZIP file structure according to PKWARE's APPNOTE.TXT specification
|
|
5
|
+
* https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
|
|
6
|
+
*
|
|
7
|
+
* ZIP file structure:
|
|
8
|
+
* ┌──────────────────────────┐
|
|
9
|
+
* │ Local File Header 1 │
|
|
10
|
+
* │ File Data 1 │
|
|
11
|
+
* ├──────────────────────────┤
|
|
12
|
+
* │ Local File Header 2 │
|
|
13
|
+
* │ File Data 2 │
|
|
14
|
+
* ├──────────────────────────┤
|
|
15
|
+
* │ ... │
|
|
16
|
+
* ├──────────────────────────┤
|
|
17
|
+
* │ Central Directory 1 │
|
|
18
|
+
* │ Central Directory 2 │
|
|
19
|
+
* │ ... │
|
|
20
|
+
* ├──────────────────────────┤
|
|
21
|
+
* │ End of Central Directory │
|
|
22
|
+
* └──────────────────────────┘
|
|
23
|
+
*/
|
|
24
|
+
import { type CompressOptions } from "./compress.js";
|
|
25
|
+
/**
|
|
26
|
+
* ZIP file entry
|
|
27
|
+
*/
|
|
28
|
+
export interface ZipEntry {
|
|
29
|
+
/** File name (can include directory path, use forward slashes) */
|
|
30
|
+
name: string;
|
|
31
|
+
/** File data (will be compressed unless level=0) */
|
|
32
|
+
data: Uint8Array;
|
|
33
|
+
/** File modification time (optional, defaults to current time) */
|
|
34
|
+
modTime?: Date;
|
|
35
|
+
/** File comment (optional) */
|
|
36
|
+
comment?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* ZIP builder options
|
|
40
|
+
*/
|
|
41
|
+
export interface ZipOptions extends CompressOptions {
|
|
42
|
+
/** ZIP file comment (optional) */
|
|
43
|
+
comment?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a ZIP file from entries (async)
|
|
47
|
+
*
|
|
48
|
+
* @param entries - Files to include in ZIP
|
|
49
|
+
* @param options - ZIP options
|
|
50
|
+
* @returns ZIP file as Uint8Array
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const zip = await createZip([
|
|
55
|
+
* { name: "hello.txt", data: new TextEncoder().encode("Hello!") },
|
|
56
|
+
* { name: "folder/file.txt", data: new TextEncoder().encode("Nested!") }
|
|
57
|
+
* ], { level: 6 });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function createZip(entries: ZipEntry[], options?: ZipOptions): Promise<Uint8Array>;
|
|
61
|
+
/**
|
|
62
|
+
* Create a ZIP file from entries (sync, Node.js only)
|
|
63
|
+
*
|
|
64
|
+
* @param entries - Files to include in ZIP
|
|
65
|
+
* @param options - ZIP options
|
|
66
|
+
* @returns ZIP file as Uint8Array
|
|
67
|
+
* @throws Error if not in Node.js environment
|
|
68
|
+
*/
|
|
69
|
+
export declare function createZipSync(entries: ZipEntry[], options?: ZipOptions): Uint8Array;
|
|
70
|
+
/**
|
|
71
|
+
* Streaming ZIP builder for large files
|
|
72
|
+
* Writes chunks to a callback as they are generated
|
|
73
|
+
*/
|
|
74
|
+
export declare class ZipBuilder {
|
|
75
|
+
private entries;
|
|
76
|
+
private currentOffset;
|
|
77
|
+
private level;
|
|
78
|
+
private zipComment;
|
|
79
|
+
private finalized;
|
|
80
|
+
/**
|
|
81
|
+
* Create a new ZIP builder
|
|
82
|
+
* @param options - ZIP options
|
|
83
|
+
*/
|
|
84
|
+
constructor(options?: ZipOptions);
|
|
85
|
+
/**
|
|
86
|
+
* Add a file to the ZIP (async)
|
|
87
|
+
* @param entry - File entry
|
|
88
|
+
* @returns Local file header and compressed data chunks
|
|
89
|
+
*/
|
|
90
|
+
addFile(entry: ZipEntry): Promise<Uint8Array[]>;
|
|
91
|
+
/**
|
|
92
|
+
* Add a file to the ZIP (sync, Node.js only)
|
|
93
|
+
* @param entry - File entry
|
|
94
|
+
* @returns Local file header and compressed data chunks
|
|
95
|
+
*/
|
|
96
|
+
addFileSync(entry: ZipEntry): Uint8Array[];
|
|
97
|
+
/**
|
|
98
|
+
* Finalize the ZIP and return central directory + end record
|
|
99
|
+
* @returns Central directory and end of central directory chunks
|
|
100
|
+
*/
|
|
101
|
+
finalize(): Uint8Array[];
|
|
102
|
+
/**
|
|
103
|
+
* Get current number of entries
|
|
104
|
+
*/
|
|
105
|
+
get entryCount(): number;
|
|
106
|
+
/**
|
|
107
|
+
* Get current ZIP data size (without central directory)
|
|
108
|
+
*/
|
|
109
|
+
get dataSize(): number;
|
|
110
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import events from "events";
|
|
2
|
-
import { Zip } from "fflate";
|
|
3
2
|
interface ZipWriterOptions {
|
|
4
|
-
|
|
3
|
+
/** Compression method: "DEFLATE" (default) or "STORE" (no compression) */
|
|
5
4
|
compression?: "DEFLATE" | "STORE";
|
|
6
5
|
compressionOptions?: {
|
|
6
|
+
/** Compression level 0-9: 0=none, 1=fast (default), 9=best */
|
|
7
7
|
level?: number;
|
|
8
8
|
};
|
|
9
9
|
}
|
|
@@ -11,17 +11,11 @@ interface AppendOptions {
|
|
|
11
11
|
name: string;
|
|
12
12
|
base64?: boolean;
|
|
13
13
|
}
|
|
14
|
-
interface ZipFile {
|
|
15
|
-
data: Uint8Array;
|
|
16
|
-
isStream?: boolean;
|
|
17
|
-
}
|
|
18
14
|
declare class ZipWriter extends events.EventEmitter {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
finalized: boolean;
|
|
24
|
-
compressionLevel: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
15
|
+
private stream;
|
|
16
|
+
private zipBuilder;
|
|
17
|
+
private finalized;
|
|
18
|
+
private pendingWrites;
|
|
25
19
|
constructor(options?: ZipWriterOptions);
|
|
26
20
|
append(data: any, options: AppendOptions): void;
|
|
27
21
|
push(chunk: any): boolean;
|
|
@@ -23,8 +23,10 @@ declare class RowXform extends BaseXform {
|
|
|
23
23
|
model: RowModel;
|
|
24
24
|
parser: any;
|
|
25
25
|
private numRowsSeen;
|
|
26
|
+
private lastCellCol;
|
|
26
27
|
constructor(options?: RowXformOptions);
|
|
27
28
|
get tag(): string;
|
|
29
|
+
reset(): void;
|
|
28
30
|
prepare(model: RowModel, options: any): void;
|
|
29
31
|
render(xmlStream: any, model?: RowModel, options?: any): void;
|
|
30
32
|
parseOpen(node: any): boolean;
|