@js-ak/excel-toolbox 1.5.0 → 1.7.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/README.md +41 -62
- package/build/cjs/lib/merge-sheets-to-base-file-process-sync.js +105 -0
- package/build/cjs/lib/merge-sheets-to-base-file-process.js +3 -3
- package/build/cjs/lib/merge-sheets-to-base-file-sync.js +2 -2
- package/build/cjs/lib/merge-sheets-to-base-file.js +1 -1
- package/build/cjs/lib/template/template-fs.js +143 -63
- package/build/cjs/lib/template/template-memory.js +281 -59
- package/build/cjs/lib/template/utils/index.js +25 -0
- package/build/cjs/lib/template/utils/prepare-row-to-cells.js +5 -1
- package/build/cjs/lib/template/utils/regexp.js +32 -0
- package/build/cjs/lib/template/utils/update-dimension.js +15 -0
- package/build/cjs/lib/template/utils/validate-worksheet-xml.js +74 -74
- package/build/cjs/lib/template/utils/write-rows-to-stream.js +57 -17
- package/build/cjs/lib/xml/extract-rows-from-sheet-sync.js +67 -0
- package/build/cjs/lib/xml/extract-rows-from-sheet.js +4 -2
- package/build/cjs/lib/xml/extract-xml-from-sheet-sync.js +43 -0
- package/build/cjs/lib/xml/extract-xml-from-sheet.js +15 -15
- package/build/cjs/lib/xml/index.js +2 -1
- package/build/esm/lib/merge-sheets-to-base-file-process-sync.js +69 -0
- package/build/esm/lib/merge-sheets-to-base-file-process.js +3 -3
- package/build/esm/lib/merge-sheets-to-base-file-sync.js +2 -2
- package/build/esm/lib/merge-sheets-to-base-file.js +1 -1
- package/build/esm/lib/template/template-fs.js +140 -63
- package/build/esm/lib/template/template-memory.js +281 -59
- package/build/esm/lib/template/utils/index.js +2 -0
- package/build/esm/lib/template/utils/prepare-row-to-cells.js +5 -1
- package/build/esm/lib/template/utils/regexp.js +28 -0
- package/build/esm/lib/template/utils/update-dimension.js +15 -0
- package/build/esm/lib/template/utils/validate-worksheet-xml.js +74 -74
- package/build/esm/lib/template/utils/write-rows-to-stream.js +57 -17
- package/build/esm/lib/xml/extract-rows-from-sheet-sync.js +64 -0
- package/build/esm/lib/xml/extract-rows-from-sheet.js +4 -2
- package/build/esm/lib/xml/extract-xml-from-sheet-sync.js +40 -0
- package/build/esm/lib/xml/extract-xml-from-sheet.js +12 -15
- package/build/esm/lib/xml/index.js +2 -1
- package/build/types/lib/merge-sheets-to-base-file-process-sync.d.ts +27 -0
- package/build/types/lib/merge-sheets-to-base-file-process.d.ts +1 -1
- package/build/types/lib/template/template-fs.d.ts +2 -0
- package/build/types/lib/template/template-memory.d.ts +61 -0
- package/build/types/lib/template/utils/index.d.ts +2 -0
- package/build/types/lib/template/utils/prepare-row-to-cells.d.ts +5 -1
- package/build/types/lib/template/utils/regexp.d.ts +24 -0
- package/build/types/lib/template/utils/update-dimension.d.ts +15 -0
- package/build/types/lib/template/utils/write-rows-to-stream.d.ts +22 -9
- package/build/types/lib/xml/extract-rows-from-sheet-sync.d.ts +28 -0
- package/build/types/lib/xml/extract-rows-from-sheet.d.ts +2 -2
- package/build/types/lib/xml/extract-xml-from-sheet-sync.d.ts +14 -0
- package/build/types/lib/xml/extract-xml-from-sheet.d.ts +2 -2
- package/build/types/lib/xml/index.d.ts +2 -1
- package/package.json +1 -5
- package/build/cjs/lib/xml/extract-xml-from-system-content.js +0 -53
- package/build/esm/lib/xml/extract-xml-from-system-content.js +0 -49
- package/build/types/lib/xml/extract-xml-from-system-content.d.ts +0 -15
@@ -56,6 +56,23 @@ class TemplateMemory {
|
|
56
56
|
* @type {boolean}
|
57
57
|
*/
|
58
58
|
#isProcessing = false;
|
59
|
+
/**
|
60
|
+
* The keys for the Excel files in the template.
|
61
|
+
*/
|
62
|
+
#excelKeys = {
|
63
|
+
contentTypes: "[Content_Types].xml",
|
64
|
+
sharedStrings: "xl/sharedStrings.xml",
|
65
|
+
styles: "xl/styles.xml",
|
66
|
+
workbook: "xl/workbook.xml",
|
67
|
+
workbookRels: "xl/_rels/workbook.xml.rels",
|
68
|
+
};
|
69
|
+
/**
|
70
|
+
* Creates a Template instance from a map of file paths to buffers.
|
71
|
+
*
|
72
|
+
* @param {Object<string, Buffer>} files - The files to create the template from.
|
73
|
+
* @throws {Error} If reading or writing files fails.
|
74
|
+
* @experimental This API is experimental and might change in future versions.
|
75
|
+
*/
|
59
76
|
constructor(files) {
|
60
77
|
this.files = files;
|
61
78
|
}
|
@@ -115,31 +132,75 @@ class TemplateMemory {
|
|
115
132
|
sheetXml: modifiedXml,
|
116
133
|
});
|
117
134
|
}
|
118
|
-
|
135
|
+
/**
|
136
|
+
* Extracts the XML content from an Excel sheet file.
|
137
|
+
*
|
138
|
+
* @param {string} fileKey - The file key of the sheet to extract.
|
139
|
+
* @returns {string} The XML content of the sheet.
|
140
|
+
* @throws {Error} If the file key is not found.
|
141
|
+
* @experimental This API is experimental and might change in future versions.
|
142
|
+
*/
|
143
|
+
async #extractXmlFromSheet(fileKey) {
|
119
144
|
if (!this.files[fileKey]) {
|
120
145
|
throw new Error(`${fileKey} not found`);
|
121
146
|
}
|
122
147
|
return Xml.extractXmlFromSheet(this.files[fileKey]);
|
123
148
|
}
|
124
149
|
/**
|
125
|
-
*
|
126
|
-
*
|
127
|
-
* @
|
128
|
-
* @
|
150
|
+
* Extracts row data from an Excel sheet file.
|
151
|
+
*
|
152
|
+
* @param {string} fileKey - The file key of the sheet to extract.
|
153
|
+
* @returns {Object} An object containing:
|
154
|
+
* - rows: Array of raw XML strings for each <row> element
|
155
|
+
* - lastRowNumber: Highest row number found in the sheet (1-based)
|
156
|
+
* - mergeCells: Array of merged cell ranges (e.g., [{ref: "A1:B2"}])
|
157
|
+
* - xml: The XML content of the sheet
|
158
|
+
* @throws {Error} If the file key is not found
|
159
|
+
* @experimental This API is experimental and might change in future versions.
|
160
|
+
*/
|
161
|
+
async #extractRowsFromSheet(fileKey) {
|
162
|
+
if (!this.files[fileKey]) {
|
163
|
+
throw new Error(`${fileKey} not found`);
|
164
|
+
}
|
165
|
+
return Xml.extractRowsFromSheet(this.files[fileKey]);
|
166
|
+
}
|
167
|
+
/**
|
168
|
+
* Returns the Excel path of the sheet with the given name.
|
169
|
+
*
|
170
|
+
* @param sheetName - The name of the sheet to find.
|
171
|
+
* @returns The Excel path of the sheet.
|
172
|
+
* @throws {Error} If the sheet with the given name does not exist.
|
173
|
+
* @experimental This API is experimental and might change in future versions.
|
129
174
|
*/
|
130
|
-
async #
|
131
|
-
//
|
132
|
-
const workbookXml = this.#
|
133
|
-
const sheetMatch = workbookXml.match(
|
134
|
-
if (!sheetMatch)
|
175
|
+
async #getSheetPathByName(sheetName) {
|
176
|
+
// Find the sheet
|
177
|
+
const workbookXml = await this.#extractXmlFromSheet(this.#excelKeys.workbook);
|
178
|
+
const sheetMatch = workbookXml.match(Utils.sheetMatch(sheetName));
|
179
|
+
if (!sheetMatch || !sheetMatch[1]) {
|
135
180
|
throw new Error(`Sheet "${sheetName}" not found`);
|
181
|
+
}
|
136
182
|
const rId = sheetMatch[1];
|
137
|
-
const relsXml = this.#
|
138
|
-
const relMatch = relsXml.match(
|
139
|
-
if (!relMatch)
|
183
|
+
const relsXml = await this.#extractXmlFromSheet(this.#excelKeys.workbookRels);
|
184
|
+
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
185
|
+
if (!relMatch || !relMatch[1]) {
|
140
186
|
throw new Error(`Relationship "${rId}" not found`);
|
187
|
+
}
|
141
188
|
return "xl/" + relMatch[1].replace(/^\/?xl\//, "");
|
142
189
|
}
|
190
|
+
/**
|
191
|
+
* Returns the Excel path of the sheet with the given ID.
|
192
|
+
*
|
193
|
+
* @param {number} id - The 1-based index of the sheet to find.
|
194
|
+
* @returns {string} The Excel path of the sheet.
|
195
|
+
* @throws {Error} If the sheet index is less than 1.
|
196
|
+
* @experimental This API is experimental and might change in future versions.
|
197
|
+
*/
|
198
|
+
#getSheetPathById(id) {
|
199
|
+
if (id < 1) {
|
200
|
+
throw new Error("Sheet index must be greater than 0");
|
201
|
+
}
|
202
|
+
return `xl/worksheets/sheet${id}.xml`;
|
203
|
+
}
|
143
204
|
/**
|
144
205
|
* Replaces the contents of a file in the template.
|
145
206
|
*
|
@@ -153,16 +214,30 @@ class TemplateMemory {
|
|
153
214
|
async #set(key, content) {
|
154
215
|
this.files[key] = content;
|
155
216
|
}
|
217
|
+
/**
|
218
|
+
* Replaces placeholders in the given sheet with values from the replacements map.
|
219
|
+
*
|
220
|
+
* The function searches for placeholders in the format `${key}` within the sheet
|
221
|
+
* content, where `key` corresponds to a path in the replacements object.
|
222
|
+
* If a value is found for the key, it replaces the placeholder with the value.
|
223
|
+
* If no value is found, the original placeholder remains unchanged.
|
224
|
+
*
|
225
|
+
* @param sheetName - The name of the sheet to be replaced.
|
226
|
+
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
227
|
+
* @returns A promise that resolves when the substitution is complete.
|
228
|
+
* @throws {Error} If the template instance has been destroyed.
|
229
|
+
* @experimental This API is experimental and might change in future versions.
|
230
|
+
*/
|
156
231
|
async #substitute(sheetName, replacements) {
|
157
|
-
const sharedStringsPath =
|
158
|
-
const sheetPath = await this.#getSheetPath(sheetName);
|
232
|
+
const sharedStringsPath = this.#excelKeys.sharedStrings;
|
159
233
|
let sharedStringsContent = "";
|
160
234
|
let sheetContent = "";
|
161
235
|
if (this.files[sharedStringsPath]) {
|
162
|
-
sharedStringsContent = this.#
|
236
|
+
sharedStringsContent = await this.#extractXmlFromSheet(sharedStringsPath);
|
163
237
|
}
|
238
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
164
239
|
if (this.files[sheetPath]) {
|
165
|
-
sheetContent = this.#
|
240
|
+
sheetContent = await this.#extractXmlFromSheet(sheetPath);
|
166
241
|
const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
|
167
242
|
const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
|
168
243
|
if (hasTablePlaceholders) {
|
@@ -180,6 +255,108 @@ class TemplateMemory {
|
|
180
255
|
await this.#set(sheetPath, Buffer.from(sheetContent));
|
181
256
|
}
|
182
257
|
}
|
258
|
+
/**
|
259
|
+
* Merges rows from other sheets into a base sheet.
|
260
|
+
*
|
261
|
+
* @param {Object} data
|
262
|
+
* @param {Object} data.additions
|
263
|
+
* @param {number[]} [data.additions.sheetIndexes] - The 1-based indexes of the sheets to extract rows from.
|
264
|
+
* @param {string[]} [data.additions.sheetNames] - The names of the sheets to extract rows from.
|
265
|
+
* @param {number} [data.baseSheetIndex=1] - The 1-based index of the sheet in the workbook to add rows to.
|
266
|
+
* @param {string} [data.baseSheetName] - The name of the sheet in the workbook to add rows to.
|
267
|
+
* @param {number} [data.gap=0] - The number of empty rows to insert between each added section.
|
268
|
+
* @throws {Error} If the base sheet index is less than 1.
|
269
|
+
* @throws {Error} If the base sheet name is not found.
|
270
|
+
* @throws {Error} If the sheet index is less than 1.
|
271
|
+
* @throws {Error} If the sheet name is not found.
|
272
|
+
* @throws {Error} If no sheets are found to merge.
|
273
|
+
* @experimental This API is experimental and might change in future versions.
|
274
|
+
*/
|
275
|
+
async #mergeSheets(data) {
|
276
|
+
const { additions, baseSheetIndex = 1, baseSheetName, gap = 0, } = data;
|
277
|
+
let fileKey = "";
|
278
|
+
if (baseSheetName) {
|
279
|
+
fileKey = await this.#getSheetPathByName(baseSheetName);
|
280
|
+
}
|
281
|
+
if (baseSheetIndex && !fileKey) {
|
282
|
+
if (baseSheetIndex < 1) {
|
283
|
+
throw new Error("Base sheet index must be greater than 0");
|
284
|
+
}
|
285
|
+
fileKey = `xl/worksheets/sheet${baseSheetIndex}.xml`;
|
286
|
+
}
|
287
|
+
if (!fileKey) {
|
288
|
+
throw new Error("Base sheet not found");
|
289
|
+
}
|
290
|
+
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = await this.#extractRowsFromSheet(fileKey);
|
291
|
+
const allRows = [...baseRows];
|
292
|
+
const allMergeCells = [...baseMergeCells];
|
293
|
+
let currentRowOffset = lastRowNumber + gap;
|
294
|
+
const sheetPaths = [];
|
295
|
+
if (additions.sheetIndexes) {
|
296
|
+
sheetPaths.push(...(await Promise.all(additions.sheetIndexes.map(e => this.#getSheetPathById(e)))));
|
297
|
+
}
|
298
|
+
if (additions.sheetNames) {
|
299
|
+
sheetPaths.push(...(await Promise.all(additions.sheetNames.map(e => this.#getSheetPathByName(e)))));
|
300
|
+
}
|
301
|
+
if (sheetPaths.length === 0) {
|
302
|
+
throw new Error("No sheets found to merge");
|
303
|
+
}
|
304
|
+
for (const sheetPath of sheetPaths) {
|
305
|
+
if (!this.files[sheetPath]) {
|
306
|
+
throw new Error(`Sheet "${sheetPath}" not found`);
|
307
|
+
}
|
308
|
+
const { mergeCells, rows } = await Xml.extractRowsFromSheet(this.files[sheetPath]);
|
309
|
+
const shiftedRows = Xml.shiftRowIndices(rows, currentRowOffset);
|
310
|
+
const shiftedMergeCells = mergeCells.map(cell => {
|
311
|
+
const [start, end] = cell.ref.split(":");
|
312
|
+
if (!start || !end) {
|
313
|
+
return cell;
|
314
|
+
}
|
315
|
+
const shiftedStart = Utils.Common.shiftCellRef(start, currentRowOffset);
|
316
|
+
const shiftedEnd = Utils.Common.shiftCellRef(end, currentRowOffset);
|
317
|
+
return { ...cell, ref: `${shiftedStart}:${shiftedEnd}` };
|
318
|
+
});
|
319
|
+
allRows.push(...shiftedRows);
|
320
|
+
allMergeCells.push(...shiftedMergeCells);
|
321
|
+
currentRowOffset += Utils.Common.getMaxRowNumber(rows) + gap;
|
322
|
+
}
|
323
|
+
const mergedXml = Xml.buildMergedSheet(xml, allRows, allMergeCells);
|
324
|
+
this.#set(fileKey, mergedXml);
|
325
|
+
}
|
326
|
+
/**
|
327
|
+
* Removes sheets from the workbook.
|
328
|
+
*
|
329
|
+
* @param {Object} data - The data for sheet removal.
|
330
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
331
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
332
|
+
* @returns {void}
|
333
|
+
*
|
334
|
+
* @throws {Error} If the template instance has been destroyed.
|
335
|
+
* @throws {Error} If the sheet does not exist.
|
336
|
+
* @experimental This API is experimental and might change in future versions.
|
337
|
+
*/
|
338
|
+
#removeSheets(data) {
|
339
|
+
const { sheetIndexes = [], sheetNames = [] } = data;
|
340
|
+
for (const sheetIndex of sheetIndexes) {
|
341
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
342
|
+
if (!this.files[sheetPath]) {
|
343
|
+
continue;
|
344
|
+
}
|
345
|
+
delete this.files[sheetPath];
|
346
|
+
if (this.files["xl/workbook.xml"]) {
|
347
|
+
this.files["xl/workbook.xml"] = Buffer.from(Utils.Common.removeSheetFromWorkbook(this.files["xl/workbook.xml"].toString(), sheetIndex));
|
348
|
+
}
|
349
|
+
if (this.files["xl/_rels/workbook.xml.rels"]) {
|
350
|
+
this.files["xl/_rels/workbook.xml.rels"] = Buffer.from(Utils.Common.removeSheetFromRels(this.files["xl/_rels/workbook.xml.rels"].toString(), sheetIndex));
|
351
|
+
}
|
352
|
+
if (this.files["[Content_Types].xml"]) {
|
353
|
+
this.files["[Content_Types].xml"] = Buffer.from(Utils.Common.removeSheetFromContentTypes(this.files["[Content_Types].xml"].toString(), sheetIndex));
|
354
|
+
}
|
355
|
+
}
|
356
|
+
for (const sheetName of sheetNames) {
|
357
|
+
Utils.Common.removeSheetByName(this.files, sheetName);
|
358
|
+
}
|
359
|
+
}
|
183
360
|
/**
|
184
361
|
* Copies a sheet from the template to a new name.
|
185
362
|
*
|
@@ -195,30 +372,31 @@ class TemplateMemory {
|
|
195
372
|
this.#ensureNotDestroyed();
|
196
373
|
this.#isProcessing = true;
|
197
374
|
try {
|
375
|
+
if (sourceName === newName) {
|
376
|
+
throw new Error("Source and new sheet names cannot be the same");
|
377
|
+
}
|
198
378
|
// Read workbook.xml and find the source sheet
|
199
|
-
const workbookXmlPath =
|
200
|
-
const workbookXml = this.#
|
379
|
+
const workbookXmlPath = this.#excelKeys.workbook;
|
380
|
+
const workbookXml = await this.#extractXmlFromSheet(this.#excelKeys.workbook);
|
201
381
|
// Find the source sheet
|
202
|
-
const
|
203
|
-
|
204
|
-
if (!sheetMatch)
|
382
|
+
const sheetMatch = workbookXml.match(Utils.sheetMatch(sourceName));
|
383
|
+
if (!sheetMatch || !sheetMatch[1]) {
|
205
384
|
throw new Error(`Sheet "${sourceName}" not found`);
|
206
|
-
|
385
|
+
}
|
207
386
|
// Check if a sheet with the new name already exists
|
208
387
|
if (new RegExp(`<sheet[^>]+name="${newName}"`).test(workbookXml)) {
|
209
388
|
throw new Error(`Sheet "${newName}" already exists`);
|
210
389
|
}
|
211
390
|
// Read workbook.rels
|
212
391
|
// Find the source sheet path by rId
|
213
|
-
const
|
214
|
-
const
|
215
|
-
const
|
216
|
-
const relMatch = relsXml.match(
|
217
|
-
if (!relMatch)
|
218
|
-
throw new Error(`Relationship "${
|
392
|
+
const rId = sheetMatch[1];
|
393
|
+
const relsXmlPath = this.#excelKeys.workbookRels;
|
394
|
+
const relsXml = await this.#extractXmlFromSheet(this.#excelKeys.workbookRels);
|
395
|
+
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
396
|
+
if (!relMatch || !relMatch[1]) {
|
397
|
+
throw new Error(`Relationship "${rId}" not found`);
|
398
|
+
}
|
219
399
|
const sourceTarget = relMatch[1]; // sheetN.xml
|
220
|
-
if (!sourceTarget)
|
221
|
-
throw new Error(`Relationship "${sourceRId}" not found`);
|
222
400
|
const sourceSheetPath = "xl/" + sourceTarget.replace(/^\/?.*xl\//, "");
|
223
401
|
// Get the index of the new sheet
|
224
402
|
const sheetNumbers = Array.from(Object.keys(this.files))
|
@@ -255,7 +433,7 @@ class TemplateMemory {
|
|
255
433
|
// Read [Content_Types].xml
|
256
434
|
// Update [Content_Types].xml
|
257
435
|
const contentTypesPath = "[Content_Types].xml";
|
258
|
-
const contentTypesXml = this.#
|
436
|
+
const contentTypesXml = await this.#extractXmlFromSheet(contentTypesPath);
|
259
437
|
const overrideTag = `<Override PartName="/xl/worksheets/${newSheetFilename}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
260
438
|
const updatedContentTypesXml = contentTypesXml.replace("</Types>", overrideTag + "</Types>");
|
261
439
|
await this.#set(contentTypesPath, Buffer.from(updatedContentTypesXml));
|
@@ -312,19 +490,8 @@ class TemplateMemory {
|
|
312
490
|
Utils.checkStartRow(startRowNumber);
|
313
491
|
Utils.checkRows(preparedRows);
|
314
492
|
// Find the sheet
|
315
|
-
const
|
316
|
-
const
|
317
|
-
if (!sheetMatch || !sheetMatch[1]) {
|
318
|
-
throw new Error(`Sheet "${sheetName}" not found`);
|
319
|
-
}
|
320
|
-
const rId = sheetMatch[1];
|
321
|
-
const relsXml = this.#getXml("xl/_rels/workbook.xml.rels");
|
322
|
-
const relMatch = relsXml.match(new RegExp(`<Relationship[^>]+Id="${rId}"[^>]+Target="([^"]+)"[^>]*/>`));
|
323
|
-
if (!relMatch || !relMatch[1]) {
|
324
|
-
throw new Error(`Relationship "${rId}" not found`);
|
325
|
-
}
|
326
|
-
const sheetPath = "xl/" + relMatch[1].replace(/^\/?xl\//, "");
|
327
|
-
const sheetXml = this.#getXml(sheetPath);
|
493
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
494
|
+
const sheetXml = await this.#extractXmlFromSheet(sheetPath);
|
328
495
|
let nextRow = 0;
|
329
496
|
if (!startRowNumber) {
|
330
497
|
// Find the last row
|
@@ -364,6 +531,20 @@ class TemplateMemory {
|
|
364
531
|
this.#isProcessing = false;
|
365
532
|
}
|
366
533
|
}
|
534
|
+
/**
|
535
|
+
* Inserts rows into a specific sheet in the template using an async stream.
|
536
|
+
*
|
537
|
+
* @param {Object} data - The data for row insertion.
|
538
|
+
* @param {string} data.sheetName - The name of the sheet to insert rows into.
|
539
|
+
* @param {number} [data.startRowNumber] - The row number to start inserting from.
|
540
|
+
* @param {AsyncIterable<unknown[]>} data.rows - Async iterable of rows to insert.
|
541
|
+
* @returns {Promise<void>}
|
542
|
+
* @throws {Error} If the template instance has been destroyed.
|
543
|
+
* @throws {Error} If the sheet does not exist.
|
544
|
+
* @throws {Error} If the row number is out of range.
|
545
|
+
* @throws {Error} If a column is out of range.
|
546
|
+
* @experimental This API is experimental and might change in future versions.
|
547
|
+
*/
|
367
548
|
async insertRowsStream(data) {
|
368
549
|
this.#ensureNotProcessing();
|
369
550
|
this.#ensureNotDestroyed();
|
@@ -372,17 +553,9 @@ class TemplateMemory {
|
|
372
553
|
const { rows, sheetName, startRowNumber } = data;
|
373
554
|
if (!sheetName)
|
374
555
|
throw new Error("Sheet name is required");
|
375
|
-
|
376
|
-
const
|
377
|
-
|
378
|
-
throw new Error(`Sheet "${sheetName}" not found`);
|
379
|
-
const rId = sheetMatch[1];
|
380
|
-
const relsXml = this.#getXml("xl/_rels/workbook.xml.rels");
|
381
|
-
const relMatch = relsXml.match(new RegExp(`<Relationship[^>]+Id="${rId}"[^>]+Target="([^"]+)"[^>]*/>`));
|
382
|
-
if (!relMatch || !relMatch[1])
|
383
|
-
throw new Error(`Relationship "${rId}" not found`);
|
384
|
-
const sheetPath = "xl/" + relMatch[1].replace(/^\/?xl\//, "");
|
385
|
-
const sheetXml = this.#getXml(sheetPath);
|
556
|
+
// Read XML workbook to find sheet name and path
|
557
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
558
|
+
const sheetXml = await this.#extractXmlFromSheet(sheetPath);
|
386
559
|
const output = new memory_write_stream_js_1.MemoryWriteStream();
|
387
560
|
let inserted = false;
|
388
561
|
// --- Case 1: <sheetData>...</sheetData> on one line ---
|
@@ -465,7 +638,7 @@ class TemplateMemory {
|
|
465
638
|
}
|
466
639
|
if (!inserted)
|
467
640
|
throw new Error("Failed to locate <sheetData> for insertion");
|
468
|
-
//
|
641
|
+
// Save the buffer to the sheet
|
469
642
|
this.files[sheetPath] = output.toBuffer();
|
470
643
|
}
|
471
644
|
finally {
|
@@ -486,10 +659,10 @@ class TemplateMemory {
|
|
486
659
|
try {
|
487
660
|
const zipBuffer = await Zip.create(this.files);
|
488
661
|
this.destroyed = true;
|
489
|
-
//
|
662
|
+
// Clear all buffers
|
490
663
|
for (const key in this.files) {
|
491
664
|
if (this.files.hasOwnProperty(key)) {
|
492
|
-
this.files[key] = Buffer.alloc(0); //
|
665
|
+
this.files[key] = Buffer.alloc(0); // Clear the buffer
|
493
666
|
}
|
494
667
|
}
|
495
668
|
return zipBuffer;
|
@@ -519,6 +692,55 @@ class TemplateMemory {
|
|
519
692
|
this.#isProcessing = false;
|
520
693
|
}
|
521
694
|
}
|
695
|
+
/**
|
696
|
+
* Merges sheets into a base sheet.
|
697
|
+
*
|
698
|
+
* @param {Object} data
|
699
|
+
* @param {{ sheetIndexes?: number[]; sheetNames?: string[] }} data.additions - The sheets to merge.
|
700
|
+
* @param {number} [data.baseSheetIndex=1] - The 1-based index of the sheet to merge into.
|
701
|
+
* @param {string} [data.baseSheetName] - The name of the sheet to merge into.
|
702
|
+
* @param {number} [data.gap=0] - The number of empty rows to insert between each added section.
|
703
|
+
* @returns {void}
|
704
|
+
*/
|
705
|
+
mergeSheets(data) {
|
706
|
+
this.#ensureNotProcessing();
|
707
|
+
this.#ensureNotDestroyed();
|
708
|
+
this.#isProcessing = true;
|
709
|
+
try {
|
710
|
+
this.#mergeSheets(data);
|
711
|
+
}
|
712
|
+
finally {
|
713
|
+
this.#isProcessing = false;
|
714
|
+
}
|
715
|
+
}
|
716
|
+
/**
|
717
|
+
* Removes sheets from the workbook.
|
718
|
+
*
|
719
|
+
* @param {Object} data
|
720
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
721
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
722
|
+
* @returns {void}
|
723
|
+
*/
|
724
|
+
removeSheets(data) {
|
725
|
+
this.#ensureNotProcessing();
|
726
|
+
this.#ensureNotDestroyed();
|
727
|
+
this.#isProcessing = true;
|
728
|
+
try {
|
729
|
+
this.#removeSheets(data);
|
730
|
+
}
|
731
|
+
finally {
|
732
|
+
this.#isProcessing = false;
|
733
|
+
}
|
734
|
+
}
|
735
|
+
/**
|
736
|
+
* Creates a Template instance from an Excel file source.
|
737
|
+
*
|
738
|
+
* @param {Object} data - The data to create the template from.
|
739
|
+
* @param {string | Buffer} data.source - The path or buffer of the Excel file.
|
740
|
+
* @returns {Promise<TemplateMemory>} A new Template instance.
|
741
|
+
* @throws {Error} If reading the file fails.
|
742
|
+
* @experimental This API is experimental and might change in future versions.
|
743
|
+
*/
|
522
744
|
static async from(data) {
|
523
745
|
const { source } = data;
|
524
746
|
const buffer = typeof source === "string"
|
@@ -10,10 +10,34 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
11
11
|
o[k2] = m[k];
|
12
12
|
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
19
|
+
var ownKeys = function(o) {
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
21
|
+
var ar = [];
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
23
|
+
return ar;
|
24
|
+
};
|
25
|
+
return ownKeys(o);
|
26
|
+
};
|
27
|
+
return function (mod) {
|
28
|
+
if (mod && mod.__esModule) return mod;
|
29
|
+
var result = {};
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
31
|
+
__setModuleDefault(result, mod);
|
32
|
+
return result;
|
33
|
+
};
|
34
|
+
})();
|
13
35
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
37
|
};
|
16
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
39
|
+
exports.Common = void 0;
|
40
|
+
exports.Common = __importStar(require("../../utils/index.js"));
|
17
41
|
__exportStar(require("./apply-replacements.js"), exports);
|
18
42
|
__exportStar(require("./check-row.js"), exports);
|
19
43
|
__exportStar(require("./check-rows.js"), exports);
|
@@ -30,6 +54,7 @@ __exportStar(require("./process-merge-cells.js"), exports);
|
|
30
54
|
__exportStar(require("./process-merge-finalize.js"), exports);
|
31
55
|
__exportStar(require("./process-rows.js"), exports);
|
32
56
|
__exportStar(require("./process-shared-strings.js"), exports);
|
57
|
+
__exportStar(require("./regexp.js"), exports);
|
33
58
|
__exportStar(require("./to-excel-column-object.js"), exports);
|
34
59
|
__exportStar(require("./update-dimension.js"), exports);
|
35
60
|
__exportStar(require("./validate-worksheet-xml.js"), exports);
|
@@ -8,6 +8,10 @@ function prepareRowToCells(row, rowNumber) {
|
|
8
8
|
const colLetter = (0, column_index_to_letter_js_1.columnIndexToLetter)(colIndex);
|
9
9
|
const cellRef = `${colLetter}${rowNumber}`;
|
10
10
|
const cellValue = (0, escape_xml_js_1.escapeXml)(String(value ?? ""));
|
11
|
-
return
|
11
|
+
return {
|
12
|
+
cellRef,
|
13
|
+
cellValue,
|
14
|
+
cellXml: `<c r="${cellRef}" t="inlineStr"><is><t>${cellValue}</t></is></c>`,
|
15
|
+
};
|
12
16
|
});
|
13
17
|
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.relationshipMatch = relationshipMatch;
|
4
|
+
exports.sheetMatch = sheetMatch;
|
5
|
+
/**
|
6
|
+
* Creates a regular expression to match a relationship element with a specific ID.
|
7
|
+
*
|
8
|
+
* @param {string} id - The relationship ID to match (e.g. "rId1")
|
9
|
+
* @returns {RegExp} A regular expression that matches a Relationship XML element with the given ID and captures the Target attribute value
|
10
|
+
* @example
|
11
|
+
* const regex = relationshipMatch("rId1");
|
12
|
+
* const xml = '<Relationship Id="rId1" Target="worksheets/sheet1.xml"/>';
|
13
|
+
* const match = xml.match(regex);
|
14
|
+
* // match[1] === "worksheets/sheet1.xml"
|
15
|
+
*/
|
16
|
+
function relationshipMatch(id) {
|
17
|
+
return new RegExp(`<Relationship[^>]+Id="${id}"[^>]+Target="([^"]+)"[^>]*/>`);
|
18
|
+
}
|
19
|
+
/**
|
20
|
+
* Creates a regular expression to match a sheet element with a specific name.
|
21
|
+
*
|
22
|
+
* @param {string} sheetName - The name of the sheet to match
|
23
|
+
* @returns {RegExp} A regular expression that matches a sheet XML element with the given name and captures the r:id attribute value
|
24
|
+
* @example
|
25
|
+
* const regex = sheetMatch("Sheet1");
|
26
|
+
* const xml = '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>';
|
27
|
+
* const match = xml.match(regex);
|
28
|
+
* // match[1] === "rId1"
|
29
|
+
*/
|
30
|
+
function sheetMatch(sheetName) {
|
31
|
+
return new RegExp(`<sheet[^>]+name="${sheetName}"[^>]+r:id="([^"]+)"[^>]*/>`);
|
32
|
+
}
|
@@ -1,6 +1,21 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.updateDimension = updateDimension;
|
4
|
+
/**
|
5
|
+
* Updates the dimension element in an Excel worksheet XML string based on the actual cell references.
|
6
|
+
*
|
7
|
+
* This function scans the XML for all cell references and calculates the minimum and maximum
|
8
|
+
* column/row values to determine the actual used range in the worksheet. It then updates
|
9
|
+
* the dimension element to reflect this range.
|
10
|
+
*
|
11
|
+
* @param {string} xml - The worksheet XML string to process
|
12
|
+
* @returns {string} The XML string with updated dimension element
|
13
|
+
* @example
|
14
|
+
* // XML with cells from A1 to C3
|
15
|
+
* const xml = '....<dimension ref="A1:B2"/>.....<c r="C3">...</c>...';
|
16
|
+
* const updated = updateDimension(xml);
|
17
|
+
* // Returns XML with dimension updated to ref="A1:C3"
|
18
|
+
*/
|
4
19
|
function updateDimension(xml) {
|
5
20
|
const cellRefs = [...xml.matchAll(/<c r="([A-Z]+)(\d+)"/g)];
|
6
21
|
if (cellRefs.length === 0)
|