pivotkit-xlsx 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +42 -2
- package/dist/THIRD_PARTY_LICENSES.txt +85 -0
- package/dist/chunks/chunk-KDJGTIXW.js +1 -0
- package/dist/chunks/chunk-TEWBHPQ4.js +1 -0
- package/dist/chunks/import-csv-TRZA6M7W.js +1 -0
- package/dist/chunks/import-json-YR5ADN4H.js +1 -0
- package/dist/chunks/reader-S5MTRZAR.js +9 -0
- package/dist/index.js +1 -0
- package/package.json +66 -4
- package/src/index.js +21 -0
- package/src/reader.js +231 -0
- package/src/workbook.js +79 -0
- package/src/xml.js +593 -0
- package/src/zip.js +292 -0
- package/types/index.d.ts +3 -0
- package/types/reader.d.ts +15 -0
- package/types/workbook.d.ts +21 -0
- package/types/xml.d.ts +84 -0
- package/types/zip.d.ts +55 -0
package/src/zip.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The ZIP container of an .xlsx file, read and checked by pivotkit-xlsx itself before any XML reaches
|
|
3
|
+
* read-excel-file:
|
|
4
|
+
*
|
|
5
|
+
* - the directory at the end of the file is read first: the number of parts (`limits.archiveEntries`)
|
|
6
|
+
* and the size they unpack to (`limits.unpackedBytes`) are checked before anything is unpacked, so a
|
|
7
|
+
* ZIP bomb is refused before it costs memory;
|
|
8
|
+
* - a part is unpacked into a buffer of the size the directory states, never more: a part that
|
|
9
|
+
* unpacks to more than it claims stops the read;
|
|
10
|
+
* - encrypted parts and unknown compression methods are refused.
|
|
11
|
+
*
|
|
12
|
+
* Not part of the public API.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Inflate } from "fflate";
|
|
16
|
+
import { fileError, limitError } from "pivotkit-core/import";
|
|
17
|
+
|
|
18
|
+
const EOCD = 0x06054b50;
|
|
19
|
+
const EOCD64_LOCATOR = 0x07064b50;
|
|
20
|
+
const EOCD64 = 0x06064b50;
|
|
21
|
+
const CENTRAL = 0x02014b50;
|
|
22
|
+
const LOCAL = 0x04034b50;
|
|
23
|
+
/** Compressed bytes pushed to the inflater at a time: one push unpacks to at most about 1,000 times this. */
|
|
24
|
+
const PUSH_BYTES = 16_384;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} ZipEntry
|
|
28
|
+
* @property {string} name
|
|
29
|
+
* @property {number} method 0 stored, 8 deflated
|
|
30
|
+
* @property {number} compressedSize
|
|
31
|
+
* @property {number} size unpacked, as the directory states
|
|
32
|
+
* @property {number} localOffset
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const u16 = (b, i) => b[i] | (b[i + 1] << 8);
|
|
36
|
+
const u32 = (b, i) => (b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24)) >>> 0;
|
|
37
|
+
const u64 = (b, i) => u32(b, i) + u32(b, i + 4) * 2 ** 32;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The entries of a ZIP file's central directory, checked against the limits.
|
|
41
|
+
* @param {Uint8Array} bytes
|
|
42
|
+
* @param {string} file for messages
|
|
43
|
+
* @param {{ archiveEntries: number, unpackedBytes: number }} limits
|
|
44
|
+
* @returns {ZipEntry[]}
|
|
45
|
+
*/
|
|
46
|
+
export function readDirectory(bytes, file, limits) {
|
|
47
|
+
const broken = (what) => fileError("DAMAGED", file, `the workbook is damaged or cut off (${what}); open it in Excel and save it again.`);
|
|
48
|
+
let end = -1;
|
|
49
|
+
for (let i = bytes.length - 22; i >= 0 && i >= bytes.length - 65_557; i--) {
|
|
50
|
+
if (u32(bytes, i) === EOCD) {
|
|
51
|
+
end = i;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (end < 0) throw broken("no ZIP directory");
|
|
56
|
+
let count = u16(bytes, end + 10);
|
|
57
|
+
let size = u32(bytes, end + 12);
|
|
58
|
+
let offset = u32(bytes, end + 16);
|
|
59
|
+
if ((count === 0xffff || offset === 0xffffffff || size === 0xffffffff) && end >= 20 && u32(bytes, end - 20) === EOCD64_LOCATOR) {
|
|
60
|
+
const at = u64(bytes, end - 12);
|
|
61
|
+
if (at + 56 > bytes.length || u32(bytes, at) !== EOCD64) throw broken("ZIP64 directory");
|
|
62
|
+
count = u64(bytes, at + 32);
|
|
63
|
+
size = u64(bytes, at + 40);
|
|
64
|
+
offset = u64(bytes, at + 48);
|
|
65
|
+
}
|
|
66
|
+
if (count > limits.archiveEntries) {
|
|
67
|
+
throw limitError("archiveEntries", count, limits.archiveEntries, file, "parts in its ZIP directory");
|
|
68
|
+
}
|
|
69
|
+
if (offset + size > bytes.length) throw broken("directory outside the file");
|
|
70
|
+
/** @type {ZipEntry[]} */
|
|
71
|
+
const entries = [];
|
|
72
|
+
let total = 0;
|
|
73
|
+
let p = offset;
|
|
74
|
+
const text = new TextDecoder();
|
|
75
|
+
for (let k = 0; k < count; k++) {
|
|
76
|
+
if (p + 46 > bytes.length || u32(bytes, p) !== CENTRAL) throw broken("directory entry");
|
|
77
|
+
const flags = u16(bytes, p + 8);
|
|
78
|
+
const method = u16(bytes, p + 10);
|
|
79
|
+
let compressedSize = u32(bytes, p + 20);
|
|
80
|
+
let unpacked = u32(bytes, p + 24);
|
|
81
|
+
const nameLength = u16(bytes, p + 28);
|
|
82
|
+
const extraLength = u16(bytes, p + 30);
|
|
83
|
+
const commentLength = u16(bytes, p + 32);
|
|
84
|
+
let localOffset = u32(bytes, p + 42);
|
|
85
|
+
const name = text.decode(bytes.subarray(p + 46, p + 46 + nameLength));
|
|
86
|
+
// ZIP64: the sizes and the offset that do not fit 32 bits are in the extra field 0x0001
|
|
87
|
+
let e = p + 46 + nameLength;
|
|
88
|
+
const extraEnd = e + extraLength;
|
|
89
|
+
while (e + 4 <= extraEnd) {
|
|
90
|
+
const id = u16(bytes, e);
|
|
91
|
+
const len = u16(bytes, e + 2);
|
|
92
|
+
if (id === 0x0001) {
|
|
93
|
+
let q = e + 4;
|
|
94
|
+
if (unpacked === 0xffffffff) (unpacked = u64(bytes, q)), (q += 8);
|
|
95
|
+
if (compressedSize === 0xffffffff) (compressedSize = u64(bytes, q)), (q += 8);
|
|
96
|
+
if (localOffset === 0xffffffff) localOffset = u64(bytes, q);
|
|
97
|
+
}
|
|
98
|
+
e += 4 + len;
|
|
99
|
+
}
|
|
100
|
+
if (flags & 1) {
|
|
101
|
+
throw fileError("ENCRYPTED", file, "the workbook is encrypted; save it without a password, then load it.");
|
|
102
|
+
}
|
|
103
|
+
if (method !== 0 && method !== 8) {
|
|
104
|
+
throw fileError("DAMAGED", file, `a part of the workbook uses compression method ${method}, which is not read; save it again in Excel.`);
|
|
105
|
+
}
|
|
106
|
+
total += unpacked;
|
|
107
|
+
if (total > limits.unpackedBytes) {
|
|
108
|
+
throw limitError("unpackedBytes", total, limits.unpackedBytes, file, "bytes unpacked, by its ZIP directory; a ZIP bomb or a workbook too large to load");
|
|
109
|
+
}
|
|
110
|
+
entries.push({ name, method, compressedSize, size: unpacked, localOffset });
|
|
111
|
+
p = extraEnd + commentLength;
|
|
112
|
+
}
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Where an entry's data starts and ends, from its local header. */
|
|
117
|
+
function dataRange(bytes, entry, file) {
|
|
118
|
+
const at = entry.localOffset;
|
|
119
|
+
if (at + 30 > bytes.length || u32(bytes, at) !== LOCAL) {
|
|
120
|
+
throw fileError("DAMAGED", file, `the workbook is damaged (part "${entry.name}"); open it in Excel and save it again.`);
|
|
121
|
+
}
|
|
122
|
+
const start = at + 30 + u16(bytes, at + 26) + u16(bytes, at + 28);
|
|
123
|
+
const end = start + entry.compressedSize;
|
|
124
|
+
if (end > bytes.length) {
|
|
125
|
+
throw fileError("DAMAGED", file, `the workbook is cut off (part "${entry.name}"); load the whole file.`);
|
|
126
|
+
}
|
|
127
|
+
return [start, end];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Unpacks an entry, streaming: `onChunk` gets each piece of the unpacked bytes. More bytes than the
|
|
132
|
+
* directory states stop it with an error.
|
|
133
|
+
* @param {Uint8Array} bytes
|
|
134
|
+
* @param {ZipEntry} entry
|
|
135
|
+
* @param {string} file
|
|
136
|
+
* @param {(chunk: Uint8Array) => void} onChunk
|
|
137
|
+
* @param {() => Promise<void> | void} [between] called between pushes (abort checks, progress)
|
|
138
|
+
*/
|
|
139
|
+
export async function unpackStream(bytes, entry, file, onChunk, between) {
|
|
140
|
+
const [start, end] = dataRange(bytes, entry, file);
|
|
141
|
+
let produced = 0;
|
|
142
|
+
const take = (chunk) => {
|
|
143
|
+
produced += chunk.length;
|
|
144
|
+
if (produced > entry.size) {
|
|
145
|
+
throw fileError("DAMAGED", file, `the part "${entry.name}" unpacks to more than the ${entry.size.toLocaleString("en-US")} bytes its directory states: a damaged or forged workbook.`);
|
|
146
|
+
}
|
|
147
|
+
if (chunk.length > 0) onChunk(chunk);
|
|
148
|
+
};
|
|
149
|
+
if (entry.method === 0) {
|
|
150
|
+
for (let at = start; at < end; at += 1 << 20) {
|
|
151
|
+
take(bytes.subarray(at, Math.min(end, at + (1 << 20))));
|
|
152
|
+
await between?.();
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
const inflater = new Inflate((chunk) => take(chunk));
|
|
156
|
+
for (let at = start; at < end; at += PUSH_BYTES) {
|
|
157
|
+
const stop = Math.min(end, at + PUSH_BYTES);
|
|
158
|
+
try {
|
|
159
|
+
inflater.push(bytes.subarray(at, stop), stop === end);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (/** @type {any} */ (error)?.code === undefined || typeof (/** @type {any} */ (error).code) !== "number") throw error;
|
|
162
|
+
throw fileError("DAMAGED", file, `the part "${entry.name}" cannot be unpacked; the workbook is damaged or cut off.`);
|
|
163
|
+
}
|
|
164
|
+
await between?.();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (produced !== entry.size) {
|
|
168
|
+
throw fileError("DAMAGED", file, `the part "${entry.name}" unpacks to ${produced.toLocaleString("en-US")} bytes, not the ${entry.size.toLocaleString("en-US")} its directory states; the workbook is damaged.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Unpacks an entry into one buffer of the size the directory states.
|
|
174
|
+
* @returns {Promise<Uint8Array>}
|
|
175
|
+
*/
|
|
176
|
+
export async function unpack(bytes, entry, file, between) {
|
|
177
|
+
const out = new Uint8Array(entry.size);
|
|
178
|
+
let at = 0;
|
|
179
|
+
await unpackStream(bytes, entry, file, (chunk) => {
|
|
180
|
+
out.set(chunk, at);
|
|
181
|
+
at += chunk.length;
|
|
182
|
+
}, between);
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const CRC_TABLE = (() => {
|
|
187
|
+
const t = new Int32Array(256);
|
|
188
|
+
for (let n = 0; n < 256; n++) {
|
|
189
|
+
let c = n;
|
|
190
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
191
|
+
t[n] = c;
|
|
192
|
+
}
|
|
193
|
+
return t;
|
|
194
|
+
})();
|
|
195
|
+
|
|
196
|
+
/** CRC-32 of bytes, continued from `crc` (start with 0). */
|
|
197
|
+
export function crc32(bytes, crc = 0, from = 0, to = bytes.length) {
|
|
198
|
+
let c = ~crc;
|
|
199
|
+
for (let i = from; i < to; i++) c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
|
|
200
|
+
return ~c >>> 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Writes a ZIP without compression into one growing buffer: what read-excel-file gets, so it
|
|
205
|
+
* unpacks nothing (no worker) and the sheet's XML is copied once, while it streams in.
|
|
206
|
+
*/
|
|
207
|
+
export function createStoredZip(initial = 1 << 20) {
|
|
208
|
+
let buf = new Uint8Array(initial);
|
|
209
|
+
let length = 0;
|
|
210
|
+
const central = [];
|
|
211
|
+
const reserve = (n) => {
|
|
212
|
+
if (length + n <= buf.length) return;
|
|
213
|
+
let size = Math.ceil(buf.length * 1.5);
|
|
214
|
+
while (size < length + n) size = Math.ceil(size * 1.5);
|
|
215
|
+
const grown = new Uint8Array(size);
|
|
216
|
+
grown.set(buf.subarray(0, length));
|
|
217
|
+
buf = grown;
|
|
218
|
+
};
|
|
219
|
+
const put16 = (at, v) => {
|
|
220
|
+
buf[at] = v & 0xff;
|
|
221
|
+
buf[at + 1] = (v >>> 8) & 0xff;
|
|
222
|
+
};
|
|
223
|
+
const put32 = (at, v) => {
|
|
224
|
+
put16(at, v & 0xffff);
|
|
225
|
+
put16(at + 2, v >>> 16);
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
/** Starts a part; `write` its bytes, then `end()`. */
|
|
229
|
+
begin(path) {
|
|
230
|
+
const name = new TextEncoder().encode(path);
|
|
231
|
+
reserve(30 + name.length);
|
|
232
|
+
const header = length;
|
|
233
|
+
put32(header, LOCAL);
|
|
234
|
+
put16(header + 4, 20);
|
|
235
|
+
buf.fill(0, header + 6, header + 26);
|
|
236
|
+
put16(header + 26, name.length);
|
|
237
|
+
put16(header + 28, 0);
|
|
238
|
+
buf.set(name, header + 30);
|
|
239
|
+
length += 30 + name.length;
|
|
240
|
+
const start = length;
|
|
241
|
+
let crc = 0;
|
|
242
|
+
return {
|
|
243
|
+
write(bytes, from = 0, to = bytes.length) {
|
|
244
|
+
reserve(to - from);
|
|
245
|
+
buf.set(bytes.subarray(from, to), length);
|
|
246
|
+
crc = crc32(bytes, crc, from, to);
|
|
247
|
+
length += to - from;
|
|
248
|
+
},
|
|
249
|
+
end() {
|
|
250
|
+
const size = length - start;
|
|
251
|
+
if (size > 0xfffffffe) throw new RangeError("a stored part over 4 GiB");
|
|
252
|
+
put32(header + 14, crc);
|
|
253
|
+
put32(header + 18, size);
|
|
254
|
+
put32(header + 22, size);
|
|
255
|
+
central.push({ name, crc, size, offset: header });
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
add(path, bytes) {
|
|
260
|
+
const part = this.begin(path);
|
|
261
|
+
part.write(bytes);
|
|
262
|
+
part.end();
|
|
263
|
+
},
|
|
264
|
+
/** The finished ZIP (a view of the buffer). */
|
|
265
|
+
finish() {
|
|
266
|
+
const at = length;
|
|
267
|
+
for (const e of central) {
|
|
268
|
+
reserve(46 + e.name.length);
|
|
269
|
+
put32(length, CENTRAL);
|
|
270
|
+
put16(length + 4, 20);
|
|
271
|
+
put16(length + 6, 20);
|
|
272
|
+
buf.fill(0, length + 8, length + 46);
|
|
273
|
+
put32(length + 16, e.crc);
|
|
274
|
+
put32(length + 20, e.size);
|
|
275
|
+
put32(length + 24, e.size);
|
|
276
|
+
put16(length + 28, e.name.length);
|
|
277
|
+
put32(length + 42, e.offset);
|
|
278
|
+
buf.set(e.name, length + 46);
|
|
279
|
+
length += 46 + e.name.length;
|
|
280
|
+
}
|
|
281
|
+
reserve(22);
|
|
282
|
+
put32(length, EOCD);
|
|
283
|
+
buf.fill(0, length + 4, length + 22);
|
|
284
|
+
put16(length + 8, central.length);
|
|
285
|
+
put16(length + 10, central.length);
|
|
286
|
+
put32(length + 12, length - at);
|
|
287
|
+
put32(length + 16, at);
|
|
288
|
+
length += 22;
|
|
289
|
+
return buf.subarray(0, length);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worksheets of a workbook: name, index (0-based) and whether it is visible.
|
|
3
|
+
* @param {Blob | ArrayBuffer | Uint8Array} file
|
|
4
|
+
* @param {import("pivotkit-core/import").ReadOptions} [options]
|
|
5
|
+
* @returns {Promise<import("pivotkit-core/import").SheetInfo[]>}
|
|
6
|
+
*/
|
|
7
|
+
export declare function sheets(file: Blob | ArrayBuffer | Uint8Array, options?: import("pivotkit-core/import").ReadOptions): Promise<import("pivotkit-core/import").SheetInfo[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Reads one sheet of a workbook into a table: `options.sheet` (a name, or an index from 0), else the
|
|
10
|
+
* first visible one.
|
|
11
|
+
* @param {Blob | ArrayBuffer | Uint8Array} file
|
|
12
|
+
* @param {import("pivotkit-core/import").ReadOptions} [options]
|
|
13
|
+
* @returns {Promise<import("pivotkit-core/import").ImportTable>}
|
|
14
|
+
*/
|
|
15
|
+
export declare function read(file: Blob | ArrayBuffer | Uint8Array, options?: import("pivotkit-core/import").ReadOptions): Promise<import("pivotkit-core/import").ImportTable>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** XML text with its five named entities and character references decoded. */
|
|
2
|
+
export declare function decodeXml(text: any): any;
|
|
3
|
+
/** "worksheets/sheet1.xml" relative to xl/ (or "/xl/..." absolute) -> "xl/worksheets/sheet1.xml" */
|
|
4
|
+
export declare function partPath(target: any): string;
|
|
5
|
+
/**
|
|
6
|
+
* The worksheets of a workbook in their order (chart sheets and dialog sheets are not tables).
|
|
7
|
+
* @param {string} workbookXml
|
|
8
|
+
* @param {string} relsXml
|
|
9
|
+
* @returns {Array<{ name: string, index: number, visible: boolean, path: string }>}
|
|
10
|
+
*/
|
|
11
|
+
export declare function worksheets(workbookXml: string, relsXml: string): Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
index: number;
|
|
14
|
+
visible: boolean;
|
|
15
|
+
path: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** The paths of the shared strings and the styles, from the workbook's relationships. */
|
|
18
|
+
export declare function sharedParts(relsXml: any): {
|
|
19
|
+
sharedStrings: string;
|
|
20
|
+
styles: string;
|
|
21
|
+
};
|
package/types/xml.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Index of `pattern` in `buf[from, to)`, or -1. */
|
|
2
|
+
export declare function indexOf(buf: any, pattern: any, from?: number, to?: any): any;
|
|
3
|
+
/**
|
|
4
|
+
* A check of an XML part's bytes, fed piece by piece: throws at a `<!DOCTYPE` or `<!ENTITY`.
|
|
5
|
+
* @param {string} file
|
|
6
|
+
* @param {string} part
|
|
7
|
+
*/
|
|
8
|
+
export declare function createXmlCheck(file: string, part: string): (chunk: any) => void;
|
|
9
|
+
/** Checks a whole XML part. */
|
|
10
|
+
export declare function checkXml(bytes: any, file: any, part: any): void;
|
|
11
|
+
/**
|
|
12
|
+
* A run of XML whitespace longer than this, in text or between a tag's attributes, is cut to this
|
|
13
|
+
* length: four times the most characters an Excel cell holds (32,767), so no value Excel writes changes.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SPACE_RUN: number;
|
|
16
|
+
/**
|
|
17
|
+
* The longest text, tag, comment, CDATA section or processing instruction of an XML part handed to
|
|
18
|
+
* read-excel-file, counted after whitespace runs are cut. An Excel cell holds at most 32,767
|
|
19
|
+
* characters (at most about 300 KB as XML); a longer token is refused.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MAX_TOKEN: number;
|
|
22
|
+
/**
|
|
23
|
+
* The longest tag: the parser reads a tag byte by byte in JavaScript, again from its start with every
|
|
24
|
+
* piece, where it finds a text's end with a native search. A sheet's tags are a few dozen bytes.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_TAG: number;
|
|
27
|
+
/**
|
|
28
|
+
* Passes an XML part on to `out`, piece by piece, with every run of whitespace in its text and
|
|
29
|
+
* between a tag's attributes cut to SPACE_RUN bytes, and refuses (DAMAGED) a tag longer than MAX_TAG
|
|
30
|
+
* and a text, comment, CDATA section or processing instruction longer than MAX_TOKEN.
|
|
31
|
+
*
|
|
32
|
+
* read-excel-file parses a sheet in pieces; a token that is not finished at the end of a piece is
|
|
33
|
+
* read again from its start with the next one, and it calls itself once per piece. A token of tens
|
|
34
|
+
* of megabytes (a workbook a few dozen kilobytes small whose XML is padded with spaces) costs it
|
|
35
|
+
* quadratic time, a gigabyte of memory and a stack overflow. Tokens it gets from here are short. The
|
|
36
|
+
* scan follows what that parser takes as one token: a quoted attribute value is part of its tag
|
|
37
|
+
* even with a ">" in it, a comment ends at "-->", a CDATA section at "]]>", a processing
|
|
38
|
+
* instruction at "?>".
|
|
39
|
+
* @param {{ write: (bytes: Uint8Array, from?: number, to?: number) => void }} out
|
|
40
|
+
* @param {string} file for messages
|
|
41
|
+
* @param {string} part for messages
|
|
42
|
+
*/
|
|
43
|
+
export declare function createXmlGuard(out: {
|
|
44
|
+
write: (bytes: Uint8Array, from?: number, to?: number) => void;
|
|
45
|
+
}, file: string, part: string): {
|
|
46
|
+
/**
|
|
47
|
+
* @param {Uint8Array} bytes
|
|
48
|
+
* @param {number} [from]
|
|
49
|
+
* @param {number} [to]
|
|
50
|
+
*/
|
|
51
|
+
write(bytes: Uint8Array, from?: number, to?: number): void;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Streams a sheet's XML and leaves out its rows without a value and everything after `</sheetData>`
|
|
55
|
+
* but the last end tag (the root's): read-excel-file reads nothing there. Feed pieces with `push`; the
|
|
56
|
+
* XML kept goes to `out.write`; `end()` gives the row numbers seen.
|
|
57
|
+
*
|
|
58
|
+
* It also checks where the values are against the limits, before read-excel-file sees them: the
|
|
59
|
+
* parser makes a dense array of the sheet's extent (every row up to the last row with a value, each
|
|
60
|
+
* as wide as the widest), so a value in row 1,000,000 or column XFD costs that much memory however
|
|
61
|
+
* small the file is. A value beyond `maxRow` or `maxColumn`, or an extent of more than `maxCells`,
|
|
62
|
+
* stops the read with LIMIT_EXCEEDED.
|
|
63
|
+
* @param {{ write: (bytes: Uint8Array, from?: number, to?: number) => void }} out
|
|
64
|
+
* @param {{ file?: string, part?: string, maxRow?: number, maxColumn?: number, maxCells?: number }} [limits] `file` and `part` name them in messages
|
|
65
|
+
*/
|
|
66
|
+
export declare function createSheetFilter(out: {
|
|
67
|
+
write: (bytes: Uint8Array, from?: number, to?: number) => void;
|
|
68
|
+
}, limits?: {
|
|
69
|
+
file?: string;
|
|
70
|
+
part?: string;
|
|
71
|
+
maxRow?: number;
|
|
72
|
+
maxColumn?: number;
|
|
73
|
+
maxCells?: number;
|
|
74
|
+
}): {
|
|
75
|
+
/** @param {Uint8Array} chunk */
|
|
76
|
+
push(chunk: Uint8Array): void;
|
|
77
|
+
/** @returns {{ lastRow: number, lastValueRow: number, dropped: number, errors: number }} */
|
|
78
|
+
end(): {
|
|
79
|
+
lastRow: number;
|
|
80
|
+
lastValueRow: number;
|
|
81
|
+
dropped: number;
|
|
82
|
+
errors: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
package/types/zip.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type ZipEntry = {
|
|
2
|
+
name: string;
|
|
3
|
+
/**
|
|
4
|
+
* 0 stored, 8 deflated
|
|
5
|
+
*/
|
|
6
|
+
method: number;
|
|
7
|
+
compressedSize: number;
|
|
8
|
+
/**
|
|
9
|
+
* unpacked, as the directory states
|
|
10
|
+
*/
|
|
11
|
+
size: number;
|
|
12
|
+
localOffset: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The entries of a ZIP file's central directory, checked against the limits.
|
|
16
|
+
* @param {Uint8Array} bytes
|
|
17
|
+
* @param {string} file for messages
|
|
18
|
+
* @param {{ archiveEntries: number, unpackedBytes: number }} limits
|
|
19
|
+
* @returns {ZipEntry[]}
|
|
20
|
+
*/
|
|
21
|
+
export declare function readDirectory(bytes: Uint8Array, file: string, limits: {
|
|
22
|
+
archiveEntries: number;
|
|
23
|
+
unpackedBytes: number;
|
|
24
|
+
}): ZipEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* Unpacks an entry, streaming: `onChunk` gets each piece of the unpacked bytes. More bytes than the
|
|
27
|
+
* directory states stop it with an error.
|
|
28
|
+
* @param {Uint8Array} bytes
|
|
29
|
+
* @param {ZipEntry} entry
|
|
30
|
+
* @param {string} file
|
|
31
|
+
* @param {(chunk: Uint8Array) => void} onChunk
|
|
32
|
+
* @param {() => Promise<void> | void} [between] called between pushes (abort checks, progress)
|
|
33
|
+
*/
|
|
34
|
+
export declare function unpackStream(bytes: Uint8Array, entry: ZipEntry, file: string, onChunk: (chunk: Uint8Array) => void, between?: () => Promise<void> | void): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Unpacks an entry into one buffer of the size the directory states.
|
|
37
|
+
* @returns {Promise<Uint8Array>}
|
|
38
|
+
*/
|
|
39
|
+
export declare function unpack(bytes: any, entry: any, file: any, between: any): Promise<Uint8Array>;
|
|
40
|
+
/** CRC-32 of bytes, continued from `crc` (start with 0). */
|
|
41
|
+
export declare function crc32(bytes: any, crc?: number, from?: number, to?: any): number;
|
|
42
|
+
/**
|
|
43
|
+
* Writes a ZIP without compression into one growing buffer: what read-excel-file gets, so it
|
|
44
|
+
* unpacks nothing (no worker) and the sheet's XML is copied once, while it streams in.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createStoredZip(initial?: number): {
|
|
47
|
+
/** Starts a part; `write` its bytes, then `end()`. */
|
|
48
|
+
begin(path: any): {
|
|
49
|
+
write(bytes: any, from?: number, to?: any): void;
|
|
50
|
+
end(): void;
|
|
51
|
+
};
|
|
52
|
+
add(path: any, bytes: any): void;
|
|
53
|
+
/** The finished ZIP (a view of the buffer). */
|
|
54
|
+
finish(): Uint8Array<ArrayBuffer>;
|
|
55
|
+
};
|