@js-ak/excel-toolbox 1.2.7 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/lib/index.js +1 -0
- package/build/cjs/lib/template/index.js +17 -0
- package/build/cjs/lib/template/template-fs.js +465 -0
- package/build/cjs/lib/template/utils/check-row.js +23 -0
- package/build/cjs/lib/template/utils/check-rows.js +19 -0
- package/build/cjs/lib/template/utils/check-start-row.js +18 -0
- package/build/cjs/lib/template/utils/column-index-to-letter.js +17 -0
- package/build/cjs/lib/template/utils/escape-xml.js +24 -0
- package/build/cjs/lib/template/utils/get-max-row-number.js +20 -0
- package/build/cjs/lib/template/utils/get-rows-above.js +23 -0
- package/build/cjs/lib/template/utils/get-rows-below.js +23 -0
- package/build/cjs/lib/template/utils/index.js +27 -0
- package/build/cjs/lib/template/utils/parse-rows.js +30 -0
- package/build/cjs/lib/template/utils/to-excel-column-object.js +29 -0
- package/build/cjs/lib/template/utils/write-rows-to-stream.js +41 -0
- package/build/cjs/lib/xml/build-merged-sheet.js +1 -1
- package/build/cjs/lib/zip/constants.js +16 -1
- package/build/cjs/lib/zip/create-with-stream.js +214 -0
- package/build/cjs/lib/zip/index.js +1 -0
- package/build/cjs/lib/zip/utils/crc-32-stream.js +42 -0
- package/build/cjs/lib/zip/utils/crc-32.js +2 -35
- package/build/cjs/lib/zip/utils/index.js +1 -0
- package/build/esm/lib/index.js +1 -0
- package/build/esm/lib/template/index.js +1 -0
- package/build/esm/lib/template/template-fs.js +428 -0
- package/build/esm/lib/template/utils/check-row.js +20 -0
- package/build/esm/lib/template/utils/check-rows.js +16 -0
- package/build/esm/lib/template/utils/check-start-row.js +15 -0
- package/build/esm/lib/template/utils/column-index-to-letter.js +14 -0
- package/build/esm/lib/template/utils/escape-xml.js +21 -0
- package/build/esm/lib/template/utils/get-max-row-number.js +17 -0
- package/build/esm/lib/template/utils/get-rows-above.js +20 -0
- package/build/esm/lib/template/utils/get-rows-below.js +20 -0
- package/build/esm/lib/template/utils/index.js +11 -0
- package/build/esm/lib/template/utils/parse-rows.js +27 -0
- package/build/esm/lib/template/utils/to-excel-column-object.js +26 -0
- package/build/esm/lib/template/utils/write-rows-to-stream.js +38 -0
- package/build/esm/lib/xml/build-merged-sheet.js +1 -1
- package/build/esm/lib/zip/constants.js +15 -0
- package/build/esm/lib/zip/create-with-stream.js +175 -0
- package/build/esm/lib/zip/index.js +1 -0
- package/build/esm/lib/zip/utils/crc-32-stream.js +39 -0
- package/build/esm/lib/zip/utils/crc-32.js +2 -35
- package/build/esm/lib/zip/utils/index.js +1 -0
- package/build/types/lib/index.d.ts +1 -0
- package/build/types/lib/template/index.d.ts +1 -0
- package/build/types/lib/template/template-fs.d.ts +122 -0
- package/build/types/lib/template/utils/check-row.d.ts +14 -0
- package/build/types/lib/template/utils/check-rows.d.ts +11 -0
- package/build/types/lib/template/utils/check-start-row.d.ts +8 -0
- package/build/types/lib/template/utils/column-index-to-letter.d.ts +7 -0
- package/build/types/lib/template/utils/escape-xml.d.ts +14 -0
- package/build/types/lib/template/utils/get-max-row-number.d.ts +7 -0
- package/build/types/lib/template/utils/get-rows-above.d.ts +12 -0
- package/build/types/lib/template/utils/get-rows-below.d.ts +12 -0
- package/build/types/lib/template/utils/index.d.ts +11 -0
- package/build/types/lib/template/utils/parse-rows.d.ts +1 -0
- package/build/types/lib/template/utils/to-excel-column-object.d.ts +10 -0
- package/build/types/lib/template/utils/write-rows-to-stream.d.ts +25 -0
- package/build/types/lib/zip/constants.d.ts +9 -0
- package/build/types/lib/zip/create-with-stream.d.ts +13 -0
- package/build/types/lib/zip/index.d.ts +1 -0
- package/build/types/lib/zip/utils/crc-32-stream.d.ts +11 -0
- package/build/types/lib/zip/utils/index.d.ts +1 -0
- package/package.json +1 -1
@@ -0,0 +1,38 @@
|
|
1
|
+
import { columnIndexToLetter } from "./column-index-to-letter.js";
|
2
|
+
/**
|
3
|
+
* Writes an async iterable of rows to an Excel XML file.
|
4
|
+
*
|
5
|
+
* Each row is expected to be an array of values, where each value is
|
6
|
+
* converted to a string using the `String()` function. Empty values are
|
7
|
+
* replaced with an empty string.
|
8
|
+
*
|
9
|
+
* The `startRowNumber` parameter is used as the starting row number
|
10
|
+
* for the first row written to the file. Subsequent rows are written
|
11
|
+
* with incrementing row numbers.
|
12
|
+
*
|
13
|
+
* @param output - A file write stream to write the Excel XML to.
|
14
|
+
* @param rows - An async iterable of rows, where each row is an array
|
15
|
+
* of values.
|
16
|
+
* @param startRowNumber - The starting row number to use for the first
|
17
|
+
* row written to the file.
|
18
|
+
*
|
19
|
+
* @returns An object with a single property `rowNumber`, which is the
|
20
|
+
* last row number written to the file (i.e., the `startRowNumber`
|
21
|
+
* plus the number of rows written).
|
22
|
+
*/
|
23
|
+
export async function writeRowsToStream(output, rows, startRowNumber) {
|
24
|
+
let rowNumber = startRowNumber;
|
25
|
+
for await (const row of rows) {
|
26
|
+
// Transform the row into XML
|
27
|
+
const cells = row.map((value, colIndex) => {
|
28
|
+
const colLetter = columnIndexToLetter(colIndex);
|
29
|
+
const cellRef = `${colLetter}${rowNumber}`;
|
30
|
+
const cellValue = String(value ?? "");
|
31
|
+
return `<c r="${cellRef}" t="inlineStr"><is><t>${cellValue}</t></is></c>`;
|
32
|
+
});
|
33
|
+
// Write the row to the file
|
34
|
+
output.write(`<row r="${rowNumber}">${cells.join("")}</row>`);
|
35
|
+
rowNumber++;
|
36
|
+
}
|
37
|
+
return { rowNumber };
|
38
|
+
}
|
@@ -22,7 +22,7 @@ export function buildMergedSheet(originalXml, mergedRows, mergeCells = []) {
|
|
22
22
|
// Construct a new <mergeCells> section with the provided merge references
|
23
23
|
const mergeCellsXml = `<mergeCells count="${mergeCells.length}">${mergeCells.map(mc => `<mergeCell ref="${mc.ref}"/>`).join("")}</mergeCells>`;
|
24
24
|
// Insert <mergeCells> after </sheetData> and before the next XML tag
|
25
|
-
xmlData = xmlData.replace(/(<\/sheetData>)(\s*<)/, `$1
|
25
|
+
xmlData = xmlData.replace(/(<\/sheetData>)(\s*<)/, `$1${mergeCellsXml}$2`);
|
26
26
|
}
|
27
27
|
return Buffer.from(xmlData);
|
28
28
|
}
|
@@ -12,6 +12,21 @@ import { Buffer } from "node:buffer";
|
|
12
12
|
* Found in the central directory that appears at the end of the ZIP file.
|
13
13
|
*/
|
14
14
|
export const CENTRAL_DIR_HEADER_SIG = Buffer.from("504b0102", "hex");
|
15
|
+
/**
|
16
|
+
* Precomputed CRC-32 lookup table for optimized checksum calculation.
|
17
|
+
* The table is generated using the standard IEEE 802.3 (Ethernet) polynomial:
|
18
|
+
* 0xEDB88320 (reversed representation of 0x04C11DB7).
|
19
|
+
*
|
20
|
+
* The table is immediately invoked and cached as a constant for performance,
|
21
|
+
* following the common implementation pattern for CRC algorithms.
|
22
|
+
*/
|
23
|
+
export const CRC32_TABLE = new Uint32Array(256).map((_, n) => {
|
24
|
+
let c = n;
|
25
|
+
for (let k = 0; k < 8; k++) {
|
26
|
+
c = c & 1 ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;
|
27
|
+
}
|
28
|
+
return c >>> 0;
|
29
|
+
});
|
15
30
|
/**
|
16
31
|
* End of Central Directory Record signature (0x504b0506).
|
17
32
|
* Marks the end of the central directory and contains global information
|
@@ -0,0 +1,175 @@
|
|
1
|
+
import * as path from "node:path";
|
2
|
+
import { Transform } from "node:stream";
|
3
|
+
import { createReadStream } from "node:fs";
|
4
|
+
import { pipeline } from "node:stream/promises";
|
5
|
+
import zlib from "node:zlib";
|
6
|
+
import { crc32Stream, dosTime, toBytes } from "./utils/index.js";
|
7
|
+
import { CENTRAL_DIR_HEADER_SIG, END_OF_CENTRAL_DIR_SIG, LOCAL_FILE_HEADER_SIG, } from "./constants.js";
|
8
|
+
/**
|
9
|
+
* Creates a ZIP archive from a collection of files, streaming the output to a provided writable stream.
|
10
|
+
*
|
11
|
+
* @param fileKeys - An array of file paths (relative to the destination) that will be used to create a new workbook.
|
12
|
+
* @param destination - The path where the template files are located.
|
13
|
+
* @param output - A Writable stream that the ZIP archive will be written to.
|
14
|
+
*
|
15
|
+
* @throws {Error} - If a file does not exist in the destination.
|
16
|
+
* @throws {Error} - If a file is not readable.
|
17
|
+
* @throws {Error} - If the writable stream emits an error.
|
18
|
+
*/
|
19
|
+
export async function createWithStream(fileKeys, destination, output) {
|
20
|
+
const centralDirectory = [];
|
21
|
+
let offset = 0;
|
22
|
+
for (const filename of fileKeys.sort((a, b) => a.localeCompare(b))) {
|
23
|
+
if (filename.includes("..")) {
|
24
|
+
throw new Error(`Invalid filename: ${filename}`);
|
25
|
+
}
|
26
|
+
const fullPath = path.join(destination, ...filename.split("/"));
|
27
|
+
const fileNameBuf = Buffer.from(filename, "utf8");
|
28
|
+
const modTime = dosTime(new Date());
|
29
|
+
const source = createReadStream(fullPath);
|
30
|
+
const crc32 = crc32Stream();
|
31
|
+
const deflater = zlib.createDeflateRaw();
|
32
|
+
let uncompSize = 0;
|
33
|
+
let compSize = 0;
|
34
|
+
const compressedChunks = [];
|
35
|
+
const sizeCounter = new Transform({
|
36
|
+
transform(chunk, _enc, cb) {
|
37
|
+
uncompSize += chunk.length;
|
38
|
+
cb(null, chunk);
|
39
|
+
},
|
40
|
+
});
|
41
|
+
const collectCompressed = new Transform({
|
42
|
+
transform(chunk, _enc, cb) {
|
43
|
+
compressedChunks.push(chunk);
|
44
|
+
compSize += chunk.length;
|
45
|
+
cb(null, chunk);
|
46
|
+
},
|
47
|
+
});
|
48
|
+
// deflater.on("data", (chunk) => { console.log("deflater data path:", fullPath, "length:", chunk.length); });
|
49
|
+
// deflater.on("finish", () => { console.log("deflater finished path:", fullPath, "uncompSize:", uncompSize, "compSize:", compSize); });
|
50
|
+
// deflater.on("error", (err) => { console.log("deflater error path:", fullPath, "error:", err); });
|
51
|
+
// deflater.on("close", () => { console.log("deflater closed path:", fullPath); });
|
52
|
+
// deflater.on("pipe", (src) => { console.log("deflater pipe path:", fullPath); });
|
53
|
+
// deflater.on("unpipe", (src) => { console.log("deflater unpipe path:", fullPath); });
|
54
|
+
// deflater.on("drain", () => { console.log("deflater drain path:", fullPath); });
|
55
|
+
// deflater.on("pause", () => { console.log("deflater pause path:", fullPath); });
|
56
|
+
// deflater.on("resume", () => { console.log("deflater resume path:", fullPath); });
|
57
|
+
// deflater.on("end", () => console.log("deflater ended, path:", fullPath));
|
58
|
+
// source.on("data", (chunk) => { console.log("source data path:", fullPath, "length:", chunk.length); });
|
59
|
+
// source.on("finish", () => { console.log("source finished path:", fullPath, "uncompSize:", uncompSize, "compSize:", compSize); });
|
60
|
+
// source.on("error", (err) => { console.log("source error path:", fullPath, "error:", err); });
|
61
|
+
// source.on("close", () => { console.log("source closed path:", fullPath); });
|
62
|
+
// source.on("pipe", (src) => { console.log("source pipe path:", fullPath); });
|
63
|
+
// source.on("unpipe", (src) => { console.log("source unpipe path:", fullPath); });
|
64
|
+
// source.on("drain", () => { console.log("source drain path:", fullPath); });
|
65
|
+
// source.on("pause", () => { console.log("source pause path:", fullPath); });
|
66
|
+
// source.on("resume", () => { console.log("source resume path:", fullPath); });
|
67
|
+
// source.on("end", () => console.log("source ended, path:", fullPath));
|
68
|
+
// sizeCounter.on("data", (chunk) => { console.log("sizeCounter data path:", fullPath, "length:", chunk.length); });
|
69
|
+
// sizeCounter.on("finish", () => { console.log("sizeCounter finished path:", fullPath, "uncompSize:", uncompSize, "compSize:", compSize); });
|
70
|
+
// sizeCounter.on("error", (err) => { console.log("sizeCounter error path:", fullPath, "error:", err); });
|
71
|
+
// sizeCounter.on("close", () => { console.log("sizeCounter closed path:", fullPath); });
|
72
|
+
// sizeCounter.on("pipe", (src) => { console.log("sizeCounter pipe path:", fullPath); });
|
73
|
+
// sizeCounter.on("unpipe", (src) => { console.log("sizeCounter unpipe path:", fullPath); });
|
74
|
+
// sizeCounter.on("drain", () => { console.log("sizeCounter drain path:", fullPath); });
|
75
|
+
// sizeCounter.on("pause", () => { console.log("sizeCounter pause path:", fullPath); });
|
76
|
+
// sizeCounter.on("resume", () => { console.log("sizeCounter resume path:", fullPath); });
|
77
|
+
// sizeCounter.on("end", () => console.log("sizeCounter ended, path:", fullPath));
|
78
|
+
// crc32.on("data", (chunk) => { console.log("crc32 data path:", fullPath, "length:", chunk.length); });
|
79
|
+
// crc32.on("finish", () => { console.log("crc32 finished path:", fullPath, "uncompSize:", uncompSize, "compSize:", compSize); });
|
80
|
+
// crc32.on("error", (err) => { console.log("crc32 error path:", fullPath, "error:", err); });
|
81
|
+
// crc32.on("close", () => { console.log("crc32 closed path:", fullPath); });
|
82
|
+
// crc32.on("pipe", (src) => { console.log("crc32 pipe path:", fullPath); });
|
83
|
+
// crc32.on("unpipe", (src) => { console.log("crc32 unpipe path:", fullPath); });
|
84
|
+
// crc32.on("drain", () => { console.log("crc32 drain path:", fullPath); });
|
85
|
+
// crc32.on("pause", () => { console.log("crc32 pause path:", fullPath); });
|
86
|
+
// crc32.on("resume", () => { console.log("crc32 resume path:", fullPath); });
|
87
|
+
// crc32.on("end", () => console.log("crc32 ended, path:", fullPath));
|
88
|
+
collectCompressed.on("data", ( /* chunk */) => { });
|
89
|
+
// collectCompressed.on("finish", () => { console.log("collectCompressed finished path:", fullPath, "uncompSize:", uncompSize, "compSize:", compSize); });
|
90
|
+
// collectCompressed.on("error", (err) => { console.log("collectCompressed error path:", fullPath, "error:", err); });
|
91
|
+
// collectCompressed.on("close", () => { console.log("collectCompressed closed path:", fullPath); });
|
92
|
+
// collectCompressed.on("pipe", (src) => { console.log("collectCompressed pipe path:", fullPath); });
|
93
|
+
// collectCompressed.on("unpipe", (src) => { console.log("collectCompressed unpipe path:", fullPath); });
|
94
|
+
// collectCompressed.on("drain", () => { console.log("collectCompressed drain path:", fullPath); });
|
95
|
+
// collectCompressed.on("pause", () => { console.log("collectCompressed pause path:", fullPath); });
|
96
|
+
// collectCompressed.on("resume", () => { console.log("collectCompressed resume path:", fullPath); });
|
97
|
+
// collectCompressed.on("end", () => console.log("collectCompressed ended, path:", fullPath));
|
98
|
+
// deflater.on("readable", () => {
|
99
|
+
// console.log("deflater readable path:", fullPath);
|
100
|
+
// });
|
101
|
+
await pipeline(source, sizeCounter, crc32, deflater, collectCompressed);
|
102
|
+
// await new Promise<void>((resolve, reject) => {
|
103
|
+
// source
|
104
|
+
// .pipe(sizeCounter)
|
105
|
+
// .pipe(crc32)
|
106
|
+
// .pipe(deflater)
|
107
|
+
// .pipe(collectCompressed)
|
108
|
+
// .on("finish", resolve)
|
109
|
+
// .on("error", reject);
|
110
|
+
// source.on("error", reject);
|
111
|
+
// deflater.on("error", reject);
|
112
|
+
// });
|
113
|
+
const crc = crc32.digest();
|
114
|
+
const compressed = Buffer.concat(compressedChunks);
|
115
|
+
const localHeader = Buffer.concat([
|
116
|
+
LOCAL_FILE_HEADER_SIG,
|
117
|
+
toBytes(20, 2),
|
118
|
+
toBytes(0, 2),
|
119
|
+
toBytes(8, 2),
|
120
|
+
modTime,
|
121
|
+
toBytes(crc, 4),
|
122
|
+
toBytes(compSize, 4),
|
123
|
+
toBytes(uncompSize, 4),
|
124
|
+
toBytes(fileNameBuf.length, 2),
|
125
|
+
toBytes(0, 2),
|
126
|
+
fileNameBuf,
|
127
|
+
compressed,
|
128
|
+
]);
|
129
|
+
await new Promise((resolve, reject) => {
|
130
|
+
output.write(localHeader, err => err ? reject(err) : resolve());
|
131
|
+
});
|
132
|
+
const centralEntry = Buffer.concat([
|
133
|
+
CENTRAL_DIR_HEADER_SIG,
|
134
|
+
toBytes(20, 2),
|
135
|
+
toBytes(20, 2),
|
136
|
+
toBytes(0, 2),
|
137
|
+
toBytes(8, 2),
|
138
|
+
modTime,
|
139
|
+
toBytes(crc, 4),
|
140
|
+
toBytes(compSize, 4),
|
141
|
+
toBytes(uncompSize, 4),
|
142
|
+
toBytes(fileNameBuf.length, 2),
|
143
|
+
toBytes(0, 2),
|
144
|
+
toBytes(0, 2),
|
145
|
+
toBytes(0, 2),
|
146
|
+
toBytes(0, 2),
|
147
|
+
toBytes(0, 4),
|
148
|
+
toBytes(offset, 4),
|
149
|
+
fileNameBuf,
|
150
|
+
]);
|
151
|
+
centralDirectory.push(centralEntry);
|
152
|
+
offset += localHeader.length;
|
153
|
+
}
|
154
|
+
const centralDirSize = centralDirectory.reduce((sum, entry) => sum + entry.length, 0);
|
155
|
+
const centralDirOffset = offset;
|
156
|
+
for (const entry of centralDirectory) {
|
157
|
+
await new Promise((resolve, reject) => {
|
158
|
+
output.write(entry, err => err ? reject(err) : resolve());
|
159
|
+
});
|
160
|
+
}
|
161
|
+
const endRecord = Buffer.concat([
|
162
|
+
END_OF_CENTRAL_DIR_SIG,
|
163
|
+
toBytes(0, 2),
|
164
|
+
toBytes(0, 2),
|
165
|
+
toBytes(centralDirectory.length, 2),
|
166
|
+
toBytes(centralDirectory.length, 2),
|
167
|
+
toBytes(centralDirSize, 4),
|
168
|
+
toBytes(centralDirOffset, 4),
|
169
|
+
toBytes(0, 2),
|
170
|
+
]);
|
171
|
+
await new Promise((resolve, reject) => {
|
172
|
+
output.write(endRecord, err => err ? reject(err) : resolve());
|
173
|
+
});
|
174
|
+
output.end();
|
175
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { Transform } from "node:stream";
|
2
|
+
import { CRC32_TABLE } from "../constants.js";
|
3
|
+
/**
|
4
|
+
* Computes the CRC-32 checksum for the given byte, using the standard IEEE 802.3 polynomial.
|
5
|
+
* This is a low-level function that is used by the crc32Stream() function.
|
6
|
+
*
|
7
|
+
* @param {number} byte - The byte (0-255) to add to the checksum.
|
8
|
+
* @param {number} crc - The current checksum value to update.
|
9
|
+
* @returns {number} - The new checksum value.
|
10
|
+
*/
|
11
|
+
function crc32(byte, crc = 0xffffffff) {
|
12
|
+
return CRC32_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
13
|
+
}
|
14
|
+
/**
|
15
|
+
* Creates a Transform stream that computes the CRC-32 checksum of the input data.
|
16
|
+
*
|
17
|
+
* The `digest()` method can be called on the returned stream to get the final checksum value.
|
18
|
+
*
|
19
|
+
* @returns {Transform & { digest: () => number }} - The Transform stream.
|
20
|
+
*/
|
21
|
+
export function crc32Stream() {
|
22
|
+
let crc = 0xffffffff;
|
23
|
+
const transform = new Transform({
|
24
|
+
final(callback) {
|
25
|
+
callback();
|
26
|
+
},
|
27
|
+
flush(callback) {
|
28
|
+
callback();
|
29
|
+
},
|
30
|
+
transform(chunk, _encoding, callback) {
|
31
|
+
for (let i = 0; i < chunk.length; i++) {
|
32
|
+
crc = crc32(chunk[i], crc);
|
33
|
+
}
|
34
|
+
callback(null, chunk);
|
35
|
+
},
|
36
|
+
});
|
37
|
+
transform.digest = () => (crc ^ 0xffffffff) >>> 0;
|
38
|
+
return transform;
|
39
|
+
}
|
@@ -1,37 +1,4 @@
|
|
1
|
-
|
2
|
-
* Precomputed CRC-32 lookup table for optimized checksum calculation.
|
3
|
-
* The table is generated using the standard IEEE 802.3 (Ethernet) polynomial:
|
4
|
-
* 0xEDB88320 (reversed representation of 0x04C11DB7).
|
5
|
-
*
|
6
|
-
* The table is immediately invoked and cached as a constant for performance,
|
7
|
-
* following the common implementation pattern for CRC algorithms.
|
8
|
-
*/
|
9
|
-
const crcTable = (() => {
|
10
|
-
// Create a typed array for better performance with 256 32-bit unsigned integers
|
11
|
-
const table = new Uint32Array(256);
|
12
|
-
// Generate table entries for all possible byte values (0-255)
|
13
|
-
for (let i = 0; i < 256; i++) {
|
14
|
-
let crc = i; // Initialize with current byte value
|
15
|
-
// Process each bit (8 times)
|
16
|
-
for (let j = 0; j < 8; j++) {
|
17
|
-
/*
|
18
|
-
* CRC division algorithm:
|
19
|
-
* 1. If LSB is set (crc & 1), XOR with polynomial
|
20
|
-
* 2. Right-shift by 1 (unsigned)
|
21
|
-
*
|
22
|
-
* The polynomial 0xEDB88320 is:
|
23
|
-
* - Bit-reversed version of 0x04C11DB7
|
24
|
-
* - Uses reflected input/output algorithm
|
25
|
-
*/
|
26
|
-
crc = crc & 1
|
27
|
-
? 0xedb88320 ^ (crc >>> 1) // XOR with polynomial if LSB is set
|
28
|
-
: crc >>> 1; // Just shift right if LSB is not set
|
29
|
-
}
|
30
|
-
// Store final 32-bit value (>>> 0 ensures unsigned 32-bit representation)
|
31
|
-
table[i] = crc >>> 0;
|
32
|
-
}
|
33
|
-
return table;
|
34
|
-
})();
|
1
|
+
import { CRC32_TABLE } from "../constants.js";
|
35
2
|
/**
|
36
3
|
* Computes a CRC-32 checksum for the given Buffer using the standard IEEE 802.3 polynomial.
|
37
4
|
* This implementation uses a precomputed lookup table for optimal performance.
|
@@ -62,7 +29,7 @@ export function crc32(buf) {
|
|
62
29
|
* - crc >>> 8 - Shift CRC right by 8 bits (unsigned)
|
63
30
|
* - ^ crcTable[...] - XOR with precomputed table value
|
64
31
|
*/
|
65
|
-
crc = (crc >>> 8) ^
|
32
|
+
crc = (crc >>> 8) ^ CRC32_TABLE[(crc ^ buf[i]) & 0xff];
|
66
33
|
}
|
67
34
|
/*
|
68
35
|
* Final processing:
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./template-fs.js";
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { Writable } from "node:stream";
|
2
|
+
/**
|
3
|
+
* A class for manipulating Excel templates by extracting, modifying, and repacking Excel files.
|
4
|
+
*
|
5
|
+
* @experimental This API is experimental and might change in future versions.
|
6
|
+
*/
|
7
|
+
export declare class TemplateFs {
|
8
|
+
#private;
|
9
|
+
/**
|
10
|
+
* Set of file paths (relative to the template) that will be used to create a new workbook.
|
11
|
+
* @type {Set<string>}
|
12
|
+
*/
|
13
|
+
fileKeys: Set<string>;
|
14
|
+
/**
|
15
|
+
* The path where the template will be expanded.
|
16
|
+
* @type {string}
|
17
|
+
*/
|
18
|
+
destination: string;
|
19
|
+
/**
|
20
|
+
* Flag indicating whether this template instance has been destroyed.
|
21
|
+
* @type {boolean}
|
22
|
+
*/
|
23
|
+
destroyed: boolean;
|
24
|
+
/**
|
25
|
+
* Creates a Template instance.
|
26
|
+
*
|
27
|
+
* @param {Set<string>} fileKeys - Set of file paths (relative to the template) that will be used to create a new workbook.
|
28
|
+
* @param {string} destination - The path where the template will be expanded.
|
29
|
+
* @experimental This API is experimental and might change in future versions.
|
30
|
+
*/
|
31
|
+
constructor(fileKeys: Set<string>, destination: string);
|
32
|
+
/**
|
33
|
+
* Inserts rows into a specific sheet in the template.
|
34
|
+
*
|
35
|
+
* @param {Object} data - The data for row insertion.
|
36
|
+
* @param {string} data.sheetName - The name of the sheet to insert rows into.
|
37
|
+
* @param {number} [data.startRowNumber] - The row number to start inserting from.
|
38
|
+
* @param {unknown[][]} data.rows - The rows to insert.
|
39
|
+
* @returns {Promise<void>}
|
40
|
+
* @throws {Error} If the template instance has been destroyed.
|
41
|
+
* @throws {Error} If the sheet does not exist.
|
42
|
+
* @throws {Error} If the row number is out of range.
|
43
|
+
* @throws {Error} If a column is out of range.
|
44
|
+
* @experimental This API is experimental and might change in future versions.
|
45
|
+
*/
|
46
|
+
insertRows(data: {
|
47
|
+
sheetName: string;
|
48
|
+
startRowNumber?: number;
|
49
|
+
rows: unknown[][];
|
50
|
+
}): Promise<void>;
|
51
|
+
/**
|
52
|
+
* Inserts rows into a specific sheet in the template using an async stream.
|
53
|
+
*
|
54
|
+
* @param {Object} data - The data for row insertion.
|
55
|
+
* @param {string} data.sheetName - The name of the sheet to insert rows into.
|
56
|
+
* @param {number} [data.startRowNumber] - The row number to start inserting from.
|
57
|
+
* @param {AsyncIterable<unknown[]>} data.rows - Async iterable of rows to insert.
|
58
|
+
* @returns {Promise<void>}
|
59
|
+
* @throws {Error} If the template instance has been destroyed.
|
60
|
+
* @throws {Error} If the sheet does not exist.
|
61
|
+
* @throws {Error} If the row number is out of range.
|
62
|
+
* @throws {Error} If a column is out of range.
|
63
|
+
* @experimental This API is experimental and might change in future versions.
|
64
|
+
*/
|
65
|
+
insertRowsStream(data: {
|
66
|
+
sheetName: string;
|
67
|
+
startRowNumber?: number;
|
68
|
+
rows: AsyncIterable<unknown[]>;
|
69
|
+
}): Promise<void>;
|
70
|
+
/**
|
71
|
+
* Saves the modified Excel template to a buffer.
|
72
|
+
*
|
73
|
+
* @returns {Promise<Buffer>} The modified Excel template as a buffer.
|
74
|
+
* @throws {Error} If the template instance has been destroyed.
|
75
|
+
* @experimental This API is experimental and might change in future versions.
|
76
|
+
*/
|
77
|
+
save(): Promise<Buffer>;
|
78
|
+
/**
|
79
|
+
* Writes the modified Excel template to a writable stream.
|
80
|
+
*
|
81
|
+
* @param {Writable} output - The writable stream to write to.
|
82
|
+
* @returns {Promise<void>}
|
83
|
+
* @throws {Error} If the template instance has been destroyed.
|
84
|
+
* @experimental This API is experimental and might change in future versions.
|
85
|
+
*/
|
86
|
+
saveStream(output: Writable): Promise<void>;
|
87
|
+
/**
|
88
|
+
* Replaces the contents of a file in the template.
|
89
|
+
*
|
90
|
+
* @param {string} key - The Excel path of the file to replace.
|
91
|
+
* @param {Buffer|string} content - The new content.
|
92
|
+
* @returns {Promise<void>}
|
93
|
+
* @throws {Error} If the template instance has been destroyed.
|
94
|
+
* @throws {Error} If the file does not exist in the template.
|
95
|
+
* @experimental This API is experimental and might change in future versions.
|
96
|
+
*/
|
97
|
+
set(key: string, content: Buffer | string): Promise<void>;
|
98
|
+
/**
|
99
|
+
* Validates the template by checking all required files exist.
|
100
|
+
*
|
101
|
+
* @returns {Promise<void>}
|
102
|
+
* @throws {Error} If the template instance has been destroyed.
|
103
|
+
* @throws {Error} If any required files are missing.
|
104
|
+
* @experimental This API is experimental and might change in future versions.
|
105
|
+
*/
|
106
|
+
validate(): Promise<void>;
|
107
|
+
/**
|
108
|
+
* Creates a Template instance from an Excel file source.
|
109
|
+
* Removes any existing files in the destination directory.
|
110
|
+
*
|
111
|
+
* @param {Object} data - The data to create the template from.
|
112
|
+
* @param {string} data.source - The path or buffer of the Excel file.
|
113
|
+
* @param {string} data.destination - The path to save the template to.
|
114
|
+
* @returns {Promise<Template>} A new Template instance.
|
115
|
+
* @throws {Error} If reading or writing files fails.
|
116
|
+
* @experimental This API is experimental and might change in future versions.
|
117
|
+
*/
|
118
|
+
static from(data: {
|
119
|
+
destination: string;
|
120
|
+
source: string | Buffer;
|
121
|
+
}): Promise<TemplateFs>;
|
122
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/**
|
2
|
+
* Validates that each key in the given row object is a valid cell reference.
|
3
|
+
*
|
4
|
+
* This function checks that all keys in the provided row object are composed
|
5
|
+
* only of column letters (A-Z, case insensitive). If a key is found that does
|
6
|
+
* not match this pattern, an error is thrown with a message indicating the
|
7
|
+
* invalid cell reference.
|
8
|
+
*
|
9
|
+
* @param row - An object representing a row of data, where keys are cell
|
10
|
+
* references and values are strings.
|
11
|
+
*
|
12
|
+
* @throws {Error} If any key in the row is not a valid column letter.
|
13
|
+
*/
|
14
|
+
export declare function checkRow(row: Record<string, string>): void;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
/**
|
2
|
+
* Validates an array of row objects to ensure each cell reference is valid.
|
3
|
+
* Each row object is checked to ensure that its keys (cell references) are
|
4
|
+
* composed of valid column letters (e.g., "A", "B", "C").
|
5
|
+
*
|
6
|
+
* @param rows An array of row objects, where each object represents a row
|
7
|
+
* of data with cell references as keys and cell values as strings.
|
8
|
+
*
|
9
|
+
* @throws {Error} If any cell reference in the rows is invalid.
|
10
|
+
*/
|
11
|
+
export declare function checkRows(rows: Record<string, string>[]): void;
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* Converts a 0-based column index to an Excel-style letter (A, B, ..., Z, AA, AB, ...).
|
3
|
+
*
|
4
|
+
* @param index - The 0-based column index.
|
5
|
+
* @returns The Excel-style letter for the given column index.
|
6
|
+
*/
|
7
|
+
export declare function columnIndexToLetter(index: number): string;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/**
|
2
|
+
* Escapes special characters in a string for use in an XML document.
|
3
|
+
*
|
4
|
+
* Replaces:
|
5
|
+
* - `&` with `&`
|
6
|
+
* - `<` with `<`
|
7
|
+
* - `>` with `>`
|
8
|
+
* - `"` with `"`
|
9
|
+
* - `'` with `'`
|
10
|
+
*
|
11
|
+
* @param str - The string to escape.
|
12
|
+
* @returns The escaped string.
|
13
|
+
*/
|
14
|
+
export declare function escapeXml(str: string): string;
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* Finds the maximum row number in a list of <row> elements
|
3
|
+
* and returns the maximum row number + 1.
|
4
|
+
* @param {string} line - The line of XML to parse.
|
5
|
+
* @returns {number} - The maximum row number found + 1.
|
6
|
+
*/
|
7
|
+
export declare function getMaxRowNumber(line: string): number;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* Filters out all rows in the given map that have a row number
|
3
|
+
* lower than or equal to the given minRow, and returns the
|
4
|
+
* filtered rows as a single string. Useful for removing rows
|
5
|
+
* from a template that are positioned above a certain row
|
6
|
+
* number.
|
7
|
+
*
|
8
|
+
* @param {Map<number, string>} map - The map of row numbers to row content
|
9
|
+
* @param {number} minRow - The minimum row number to include in the output
|
10
|
+
* @returns {string} The filtered rows, concatenated into a single string
|
11
|
+
*/
|
12
|
+
export declare function getRowsAbove(map: Map<number, string>, minRow: number): string;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
/**
|
2
|
+
* Filters out all rows in the given map that have a row number
|
3
|
+
* greater than or equal to the given maxRow, and returns the
|
4
|
+
* filtered rows as a single string. Useful for removing rows
|
5
|
+
* from a template that are positioned below a certain row
|
6
|
+
* number.
|
7
|
+
*
|
8
|
+
* @param {Map<number, string>} map - The map of row numbers to row content
|
9
|
+
* @param {number} maxRow - The maximum row number to include in the output
|
10
|
+
* @returns {string} The filtered rows, concatenated into a single string
|
11
|
+
*/
|
12
|
+
export declare function getRowsBelow(map: Map<number, string>, maxRow: number): string;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export * from "./check-row.js";
|
2
|
+
export * from "./check-rows.js";
|
3
|
+
export * from "./check-start-row.js";
|
4
|
+
export * from "./column-index-to-letter.js";
|
5
|
+
export * from "./escape-xml.js";
|
6
|
+
export * from "./get-max-row-number.js";
|
7
|
+
export * from "./get-rows-above.js";
|
8
|
+
export * from "./get-rows-below.js";
|
9
|
+
export * from "./parse-rows.js";
|
10
|
+
export * from "./to-excel-column-object.js";
|
11
|
+
export * from "./write-rows-to-stream.js";
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare function parseRows(innerRows: string): Map<number, string>;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/**
|
2
|
+
* Converts an array of values into a Record<string, string> with Excel column names as keys.
|
3
|
+
*
|
4
|
+
* The column names are generated in the standard Excel column naming convention (A, B, ..., Z, AA, AB, ...).
|
5
|
+
* The corresponding values are converted to strings using the String() function.
|
6
|
+
*
|
7
|
+
* @param values - The array of values to convert
|
8
|
+
* @returns The resulting Record<string, string>
|
9
|
+
*/
|
10
|
+
export declare function toExcelColumnObject(values: unknown[]): Record<string, string>;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
/**
|
3
|
+
* Writes an async iterable of rows to an Excel XML file.
|
4
|
+
*
|
5
|
+
* Each row is expected to be an array of values, where each value is
|
6
|
+
* converted to a string using the `String()` function. Empty values are
|
7
|
+
* replaced with an empty string.
|
8
|
+
*
|
9
|
+
* The `startRowNumber` parameter is used as the starting row number
|
10
|
+
* for the first row written to the file. Subsequent rows are written
|
11
|
+
* with incrementing row numbers.
|
12
|
+
*
|
13
|
+
* @param output - A file write stream to write the Excel XML to.
|
14
|
+
* @param rows - An async iterable of rows, where each row is an array
|
15
|
+
* of values.
|
16
|
+
* @param startRowNumber - The starting row number to use for the first
|
17
|
+
* row written to the file.
|
18
|
+
*
|
19
|
+
* @returns An object with a single property `rowNumber`, which is the
|
20
|
+
* last row number written to the file (i.e., the `startRowNumber`
|
21
|
+
* plus the number of rows written).
|
22
|
+
*/
|
23
|
+
export declare function writeRowsToStream(output: fs.WriteStream, rows: AsyncIterable<unknown[]>, startRowNumber: number): Promise<{
|
24
|
+
rowNumber: number;
|
25
|
+
}>;
|
@@ -12,6 +12,15 @@ import { Buffer } from "node:buffer";
|
|
12
12
|
* Found in the central directory that appears at the end of the ZIP file.
|
13
13
|
*/
|
14
14
|
export declare const CENTRAL_DIR_HEADER_SIG: Buffer<ArrayBuffer>;
|
15
|
+
/**
|
16
|
+
* Precomputed CRC-32 lookup table for optimized checksum calculation.
|
17
|
+
* The table is generated using the standard IEEE 802.3 (Ethernet) polynomial:
|
18
|
+
* 0xEDB88320 (reversed representation of 0x04C11DB7).
|
19
|
+
*
|
20
|
+
* The table is immediately invoked and cached as a constant for performance,
|
21
|
+
* following the common implementation pattern for CRC algorithms.
|
22
|
+
*/
|
23
|
+
export declare const CRC32_TABLE: Uint32Array<ArrayBuffer>;
|
15
24
|
/**
|
16
25
|
* End of Central Directory Record signature (0x504b0506).
|
17
26
|
* Marks the end of the central directory and contains global information
|