@js-ak/excel-toolbox 1.7.0 → 1.8.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/template/template-fs.js +89 -10
- package/build/cjs/lib/template/template-memory.js +91 -23
- package/build/cjs/lib/template/utils/compare-columns.js +16 -0
- package/build/cjs/lib/template/utils/found-arrays-in-replacements.js +26 -0
- package/build/cjs/lib/template/utils/index.js +2 -0
- package/build/esm/lib/template/template-fs.js +89 -10
- package/build/esm/lib/template/template-memory.js +91 -23
- package/build/esm/lib/template/utils/compare-columns.js +13 -0
- package/build/esm/lib/template/utils/found-arrays-in-replacements.js +23 -0
- package/build/esm/lib/template/utils/index.js +2 -0
- package/build/types/lib/template/template-fs.d.ts +14 -0
- package/build/types/lib/template/template-memory.d.ts +4 -2
- package/build/types/lib/template/utils/compare-columns.d.ts +8 -0
- package/build/types/lib/template/utils/found-arrays-in-replacements.d.ts +7 -0
- package/build/types/lib/template/utils/index.d.ts +2 -0
- package/package.json +1 -1
@@ -92,6 +92,7 @@ class TemplateFs {
|
|
92
92
|
this.fileKeys = fileKeys;
|
93
93
|
this.destination = destination;
|
94
94
|
}
|
95
|
+
/** Private methods */
|
95
96
|
/**
|
96
97
|
* Removes the temporary directory created by this Template instance.
|
97
98
|
* @private
|
@@ -224,6 +225,20 @@ class TemplateFs {
|
|
224
225
|
const fullPath = path.join(this.destination, ...key.split("/"));
|
225
226
|
await fs.writeFile(fullPath, Buffer.isBuffer(content) ? content : Buffer.from(content));
|
226
227
|
}
|
228
|
+
/**
|
229
|
+
* Replaces placeholders in the given sheet with values from the replacements map.
|
230
|
+
*
|
231
|
+
* The function searches for placeholders in the format `${key}` within the sheet
|
232
|
+
* content, where `key` corresponds to a path in the replacements object.
|
233
|
+
* If a value is found for the key, it replaces the placeholder with the value.
|
234
|
+
* If no value is found, the original placeholder remains unchanged.
|
235
|
+
*
|
236
|
+
* @param sheetName - The name of the sheet to be replaced.
|
237
|
+
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
238
|
+
* @returns A promise that resolves when the substitution is complete.
|
239
|
+
* @throws {Error} If the template instance has been destroyed.
|
240
|
+
* @experimental This API is experimental and might change in future versions.
|
241
|
+
*/
|
227
242
|
async #substitute(sheetName, replacements) {
|
228
243
|
const sharedStringsPath = this.#excelKeys.sharedStrings;
|
229
244
|
const sheetPath = await this.#getSheetPathByName(sheetName);
|
@@ -251,6 +266,54 @@ class TemplateFs {
|
|
251
266
|
await this.#set(sheetPath, sheetContent);
|
252
267
|
}
|
253
268
|
}
|
269
|
+
/**
|
270
|
+
* Removes sheets from the workbook.
|
271
|
+
*
|
272
|
+
* @param {Object} data - The data for sheet removal.
|
273
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
274
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
275
|
+
* @returns {void}
|
276
|
+
*
|
277
|
+
* @throws {Error} If the template instance has been destroyed.
|
278
|
+
* @throws {Error} If the sheet does not exist.
|
279
|
+
* @experimental This API is experimental and might change in future versions.
|
280
|
+
*/
|
281
|
+
async #removeSheets(data) {
|
282
|
+
const { sheetIndexes = [], sheetNames = [] } = data;
|
283
|
+
// first get index of sheets to remove
|
284
|
+
const sheetIndexesToRemove = new Set(sheetIndexes);
|
285
|
+
for (const sheetName of sheetNames) {
|
286
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
287
|
+
const sheetIndexMatch = sheetPath.match(/sheet(\d+)\.xml$/);
|
288
|
+
if (!sheetIndexMatch || !sheetIndexMatch[1]) {
|
289
|
+
throw new Error(`Sheet "${sheetName}" not found`);
|
290
|
+
}
|
291
|
+
const sheetIndex = parseInt(sheetIndexMatch[1], 10);
|
292
|
+
sheetIndexesToRemove.add(sheetIndex);
|
293
|
+
}
|
294
|
+
// Remove sheets by index
|
295
|
+
for (const sheetIndex of sheetIndexesToRemove.values()) {
|
296
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
297
|
+
if (!this.fileKeys.has(sheetPath)) {
|
298
|
+
continue;
|
299
|
+
}
|
300
|
+
// remove sheet file
|
301
|
+
await fs.unlink(path.join(this.destination, ...sheetPath.split("/")));
|
302
|
+
this.fileKeys.delete(sheetPath);
|
303
|
+
// remove sheet from workbook
|
304
|
+
if (this.fileKeys.has(this.#excelKeys.workbook)) {
|
305
|
+
this.#set(this.#excelKeys.workbook, Buffer.from(Utils.Common.removeSheetFromWorkbook(this.#readFile(this.#excelKeys.workbook).toString(), sheetIndex)));
|
306
|
+
}
|
307
|
+
// remove sheet from workbook relations
|
308
|
+
if (this.fileKeys.has(this.#excelKeys.workbookRels)) {
|
309
|
+
this.#set(this.#excelKeys.workbookRels, Buffer.from(Utils.Common.removeSheetFromRels(this.#readFile(this.#excelKeys.workbookRels).toString(), sheetIndex)));
|
310
|
+
}
|
311
|
+
// remove sheet from content types
|
312
|
+
if (this.fileKeys.has(this.#excelKeys.contentTypes)) {
|
313
|
+
this.#set(this.#excelKeys.contentTypes, Buffer.from(Utils.Common.removeSheetFromContentTypes(this.#readFile(this.#excelKeys.contentTypes).toString(), sheetIndex)));
|
314
|
+
}
|
315
|
+
}
|
316
|
+
}
|
254
317
|
/**
|
255
318
|
* Validates the template by checking all required files exist.
|
256
319
|
*
|
@@ -270,6 +333,7 @@ class TemplateFs {
|
|
270
333
|
}
|
271
334
|
}
|
272
335
|
}
|
336
|
+
/** Public methods */
|
273
337
|
/**
|
274
338
|
* Copies a sheet from the template to a new name.
|
275
339
|
*
|
@@ -360,12 +424,12 @@ class TemplateFs {
|
|
360
424
|
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
361
425
|
* @returns A promise that resolves when the substitution is complete.
|
362
426
|
*/
|
363
|
-
substitute(sheetName, replacements) {
|
427
|
+
async substitute(sheetName, replacements) {
|
364
428
|
this.#ensureNotProcessing();
|
365
429
|
this.#ensureNotDestroyed();
|
366
430
|
this.#isProcessing = true;
|
367
431
|
try {
|
368
|
-
|
432
|
+
await this.#substitute(sheetName, replacements);
|
369
433
|
}
|
370
434
|
finally {
|
371
435
|
this.#isProcessing = false;
|
@@ -527,7 +591,7 @@ class TemplateFs {
|
|
527
591
|
}
|
528
592
|
}
|
529
593
|
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
530
|
-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
594
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
531
595
|
dimension.maxColumn = newDimension.maxColumn;
|
532
596
|
}
|
533
597
|
if (newDimension.maxRow > dimension.maxRow) {
|
@@ -574,7 +638,7 @@ class TemplateFs {
|
|
574
638
|
}
|
575
639
|
// new <row>
|
576
640
|
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
577
|
-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
641
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
578
642
|
dimension.maxColumn = newDimension.maxColumn;
|
579
643
|
}
|
580
644
|
if (newDimension.maxRow > dimension.maxRow) {
|
@@ -607,7 +671,7 @@ class TemplateFs {
|
|
607
671
|
output.write("<sheetData>");
|
608
672
|
// Prepare the rows
|
609
673
|
const { dimension: newDimension } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
610
|
-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
674
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
611
675
|
dimension.maxColumn = newDimension.maxColumn;
|
612
676
|
}
|
613
677
|
if (newDimension.maxRow > dimension.maxRow) {
|
@@ -674,6 +738,25 @@ class TemplateFs {
|
|
674
738
|
this.#isProcessing = false;
|
675
739
|
}
|
676
740
|
}
|
741
|
+
/**
|
742
|
+
* Removes sheets from the workbook.
|
743
|
+
*
|
744
|
+
* @param {Object} data
|
745
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
746
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
747
|
+
* @returns {void}
|
748
|
+
*/
|
749
|
+
async removeSheets(data) {
|
750
|
+
this.#ensureNotProcessing();
|
751
|
+
this.#ensureNotDestroyed();
|
752
|
+
this.#isProcessing = true;
|
753
|
+
try {
|
754
|
+
await this.#removeSheets(data);
|
755
|
+
}
|
756
|
+
finally {
|
757
|
+
this.#isProcessing = false;
|
758
|
+
}
|
759
|
+
}
|
677
760
|
/**
|
678
761
|
* Saves the modified Excel template to a buffer.
|
679
762
|
*
|
@@ -762,6 +845,7 @@ class TemplateFs {
|
|
762
845
|
this.#isProcessing = false;
|
763
846
|
}
|
764
847
|
}
|
848
|
+
/** Static methods */
|
765
849
|
/**
|
766
850
|
* Creates a Template instance from an Excel file source.
|
767
851
|
* Removes any existing files in the destination directory.
|
@@ -800,8 +884,3 @@ class TemplateFs {
|
|
800
884
|
}
|
801
885
|
}
|
802
886
|
exports.TemplateFs = TemplateFs;
|
803
|
-
const compareColumns = (a, b) => {
|
804
|
-
if (a === b)
|
805
|
-
return 0;
|
806
|
-
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
|
807
|
-
};
|
@@ -76,6 +76,7 @@ class TemplateMemory {
|
|
76
76
|
constructor(files) {
|
77
77
|
this.files = files;
|
78
78
|
}
|
79
|
+
/** Private methods */
|
79
80
|
/**
|
80
81
|
* Ensures that this Template instance has not been destroyed.
|
81
82
|
* @private
|
@@ -240,7 +241,8 @@ class TemplateMemory {
|
|
240
241
|
sheetContent = await this.#extractXmlFromSheet(sheetPath);
|
241
242
|
const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
|
242
243
|
const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
|
243
|
-
|
244
|
+
const hasArraysInReplacements = Utils.foundArraysInReplacements(replacements);
|
245
|
+
if (hasTablePlaceholders && hasArraysInReplacements) {
|
244
246
|
const result = this.#expandTableRows(sheetContent, sharedStringsContent, replacements);
|
245
247
|
sheetContent = result.sheet;
|
246
248
|
sharedStringsContent = result.shared;
|
@@ -335,28 +337,45 @@ class TemplateMemory {
|
|
335
337
|
* @throws {Error} If the sheet does not exist.
|
336
338
|
* @experimental This API is experimental and might change in future versions.
|
337
339
|
*/
|
338
|
-
#removeSheets(data) {
|
340
|
+
async #removeSheets(data) {
|
339
341
|
const { sheetIndexes = [], sheetNames = [] } = data;
|
340
|
-
|
342
|
+
// first get index of sheets to remove
|
343
|
+
const sheetIndexesToRemove = new Set(sheetIndexes);
|
344
|
+
for (const sheetName of sheetNames) {
|
345
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
346
|
+
const sheetIndexMatch = sheetPath.match(/sheet(\d+)\.xml$/);
|
347
|
+
if (!sheetIndexMatch || !sheetIndexMatch[1]) {
|
348
|
+
throw new Error(`Sheet "${sheetName}" not found`);
|
349
|
+
}
|
350
|
+
const sheetIndex = parseInt(sheetIndexMatch[1], 10);
|
351
|
+
sheetIndexesToRemove.add(sheetIndex);
|
352
|
+
}
|
353
|
+
// Remove sheets by index
|
354
|
+
for (const sheetIndex of sheetIndexesToRemove.values()) {
|
341
355
|
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
342
356
|
if (!this.files[sheetPath]) {
|
343
357
|
continue;
|
344
358
|
}
|
359
|
+
// remove sheet file
|
345
360
|
delete this.files[sheetPath];
|
346
|
-
|
347
|
-
|
361
|
+
// remove sheet from workbook
|
362
|
+
const workbook = this.files[this.#excelKeys.workbook];
|
363
|
+
if (workbook) {
|
364
|
+
this.files[this.#excelKeys.workbook] = Buffer.from(Utils.Common.removeSheetFromWorkbook(workbook.toString(), sheetIndex));
|
348
365
|
}
|
349
|
-
|
350
|
-
|
366
|
+
// remove sheet from workbook relations
|
367
|
+
const workbookRels = this.files[this.#excelKeys.workbookRels];
|
368
|
+
if (workbookRels) {
|
369
|
+
this.files[this.#excelKeys.workbookRels] = Buffer.from(Utils.Common.removeSheetFromRels(workbookRels.toString(), sheetIndex));
|
351
370
|
}
|
352
|
-
|
353
|
-
|
371
|
+
// remove sheet from content types
|
372
|
+
const contentTypes = this.files[this.#excelKeys.contentTypes];
|
373
|
+
if (contentTypes) {
|
374
|
+
this.files[this.#excelKeys.contentTypes] = Buffer.from(Utils.Common.removeSheetFromContentTypes(contentTypes.toString(), sheetIndex));
|
354
375
|
}
|
355
376
|
}
|
356
|
-
for (const sheetName of sheetNames) {
|
357
|
-
Utils.Common.removeSheetByName(this.files, sheetName);
|
358
|
-
}
|
359
377
|
}
|
378
|
+
/** Public methods */
|
360
379
|
/**
|
361
380
|
* Copies a sheet from the template to a new name.
|
362
381
|
*
|
@@ -454,12 +473,12 @@ class TemplateMemory {
|
|
454
473
|
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
455
474
|
* @returns A promise that resolves when the substitution is complete.
|
456
475
|
*/
|
457
|
-
substitute(sheetName, replacements) {
|
476
|
+
async substitute(sheetName, replacements) {
|
458
477
|
this.#ensureNotProcessing();
|
459
478
|
this.#ensureNotDestroyed();
|
460
479
|
this.#isProcessing = true;
|
461
480
|
try {
|
462
|
-
|
481
|
+
await this.#substitute(sheetName, replacements);
|
463
482
|
}
|
464
483
|
finally {
|
465
484
|
this.#isProcessing = false;
|
@@ -525,7 +544,7 @@ class TemplateMemory {
|
|
525
544
|
else {
|
526
545
|
updatedXml = sheetXml.replace(/<worksheet[^>]*>/, (match) => `${match}<sheetData>${rowsXml}</sheetData>`);
|
527
546
|
}
|
528
|
-
await this.#set(sheetPath, Buffer.from(updatedXml));
|
547
|
+
await this.#set(sheetPath, Buffer.from(Utils.updateDimension(updatedXml)));
|
529
548
|
}
|
530
549
|
finally {
|
531
550
|
this.#isProcessing = false;
|
@@ -558,6 +577,26 @@ class TemplateMemory {
|
|
558
577
|
const sheetXml = await this.#extractXmlFromSheet(sheetPath);
|
559
578
|
const output = new memory_write_stream_js_1.MemoryWriteStream();
|
560
579
|
let inserted = false;
|
580
|
+
const initialDimension = sheetXml.match(/<dimension\s+ref="[^"]*"/)?.[0] || "";
|
581
|
+
const dimension = {
|
582
|
+
maxColumn: "A",
|
583
|
+
maxRow: 1,
|
584
|
+
minColumn: "A",
|
585
|
+
minRow: 1,
|
586
|
+
};
|
587
|
+
if (initialDimension) {
|
588
|
+
const dimensionMatch = initialDimension.match(/<dimension\s+ref="([^"]*)"/);
|
589
|
+
if (dimensionMatch) {
|
590
|
+
const dimensionRef = dimensionMatch[1];
|
591
|
+
if (dimensionRef) {
|
592
|
+
const [min, max] = dimensionRef.split(":");
|
593
|
+
dimension.minColumn = min.slice(0, 1);
|
594
|
+
dimension.minRow = parseInt(min.slice(1));
|
595
|
+
dimension.maxColumn = max.slice(0, 1);
|
596
|
+
dimension.maxRow = parseInt(max.slice(1));
|
597
|
+
}
|
598
|
+
}
|
599
|
+
}
|
561
600
|
// --- Case 1: <sheetData>...</sheetData> on one line ---
|
562
601
|
const singleLineMatch = sheetXml.match(/(<sheetData[^>]*>)(.*)(<\/sheetData>)/);
|
563
602
|
if (!inserted && singleLineMatch) {
|
@@ -578,7 +617,13 @@ class TemplateMemory {
|
|
578
617
|
output.write(innerRows);
|
579
618
|
}
|
580
619
|
}
|
581
|
-
const { rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
620
|
+
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
621
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
622
|
+
dimension.maxColumn = newDimension.maxColumn;
|
623
|
+
}
|
624
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
625
|
+
dimension.maxRow = newDimension.maxRow;
|
626
|
+
}
|
582
627
|
if (innerRows) {
|
583
628
|
const filtered = Utils.getRowsAbove(innerRowsMap, actualRowNumber);
|
584
629
|
if (filtered)
|
@@ -595,7 +640,13 @@ class TemplateMemory {
|
|
595
640
|
const matchIndex = match.index;
|
596
641
|
output.write(sheetXml.slice(0, matchIndex));
|
597
642
|
output.write("<sheetData>");
|
598
|
-
await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
643
|
+
const { dimension: newDimension } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
644
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
645
|
+
dimension.maxColumn = newDimension.maxColumn;
|
646
|
+
}
|
647
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
648
|
+
dimension.maxRow = newDimension.maxRow;
|
649
|
+
}
|
599
650
|
output.write("</sheetData>");
|
600
651
|
output.write(sheetXml.slice(matchIndex + match[0].length));
|
601
652
|
inserted = true;
|
@@ -626,7 +677,13 @@ class TemplateMemory {
|
|
626
677
|
output.write(innerRows);
|
627
678
|
}
|
628
679
|
}
|
629
|
-
const { rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, Utils.getMaxRowNumber(innerRows));
|
680
|
+
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, Utils.getMaxRowNumber(innerRows));
|
681
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
682
|
+
dimension.maxColumn = newDimension.maxColumn;
|
683
|
+
}
|
684
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
685
|
+
dimension.maxRow = newDimension.maxRow;
|
686
|
+
}
|
630
687
|
if (innerRows) {
|
631
688
|
const filtered = Utils.getRowsAbove(innerRowsMap, actualRowNumber);
|
632
689
|
if (filtered)
|
@@ -638,8 +695,18 @@ class TemplateMemory {
|
|
638
695
|
}
|
639
696
|
if (!inserted)
|
640
697
|
throw new Error("Failed to locate <sheetData> for insertion");
|
698
|
+
let result = output.toBuffer();
|
699
|
+
// update dimension
|
700
|
+
{
|
701
|
+
const target = initialDimension;
|
702
|
+
const refRange = `${dimension.minColumn}${dimension.minRow}:${dimension.maxColumn}${dimension.maxRow}`;
|
703
|
+
const replacement = `<dimension ref="${refRange}"`;
|
704
|
+
if (target) {
|
705
|
+
result = Buffer.from(result.toString().replace(target, replacement));
|
706
|
+
}
|
707
|
+
}
|
641
708
|
// Save the buffer to the sheet
|
642
|
-
this.files[sheetPath] =
|
709
|
+
this.files[sheetPath] = result;
|
643
710
|
}
|
644
711
|
finally {
|
645
712
|
this.#isProcessing = false;
|
@@ -702,12 +769,12 @@ class TemplateMemory {
|
|
702
769
|
* @param {number} [data.gap=0] - The number of empty rows to insert between each added section.
|
703
770
|
* @returns {void}
|
704
771
|
*/
|
705
|
-
mergeSheets(data) {
|
772
|
+
async mergeSheets(data) {
|
706
773
|
this.#ensureNotProcessing();
|
707
774
|
this.#ensureNotDestroyed();
|
708
775
|
this.#isProcessing = true;
|
709
776
|
try {
|
710
|
-
this.#mergeSheets(data);
|
777
|
+
await this.#mergeSheets(data);
|
711
778
|
}
|
712
779
|
finally {
|
713
780
|
this.#isProcessing = false;
|
@@ -721,17 +788,18 @@ class TemplateMemory {
|
|
721
788
|
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
722
789
|
* @returns {void}
|
723
790
|
*/
|
724
|
-
removeSheets(data) {
|
791
|
+
async removeSheets(data) {
|
725
792
|
this.#ensureNotProcessing();
|
726
793
|
this.#ensureNotDestroyed();
|
727
794
|
this.#isProcessing = true;
|
728
795
|
try {
|
729
|
-
this.#removeSheets(data);
|
796
|
+
await this.#removeSheets(data);
|
730
797
|
}
|
731
798
|
finally {
|
732
799
|
this.#isProcessing = false;
|
733
800
|
}
|
734
801
|
}
|
802
|
+
/** Static methods */
|
735
803
|
/**
|
736
804
|
* Creates a Template instance from an Excel file source.
|
737
805
|
*
|
@@ -0,0 +1,16 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.compareColumns = compareColumns;
|
4
|
+
/**
|
5
|
+
* Compares two column strings and returns a number indicating their relative order.
|
6
|
+
*
|
7
|
+
* @param a - The first column string to compare.
|
8
|
+
* @param b - The second column string to compare.
|
9
|
+
* @returns 0 if the columns are equal, -1 if the first column is less than the second, or 1 if the first column is greater than the second.
|
10
|
+
*/
|
11
|
+
function compareColumns(a, b) {
|
12
|
+
if (a === b) {
|
13
|
+
return 0;
|
14
|
+
}
|
15
|
+
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
|
16
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* Recursively checks if an object contains any arrays in its values.
|
4
|
+
*
|
5
|
+
* @param {Record<string, unknown>} replacements - The object to check for arrays.
|
6
|
+
* @returns {boolean} True if any arrays are found, false otherwise.
|
7
|
+
*/
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
9
|
+
exports.foundArraysInReplacements = foundArraysInReplacements;
|
10
|
+
function foundArraysInReplacements(replacements) {
|
11
|
+
let isFound = false;
|
12
|
+
for (const key in replacements) {
|
13
|
+
const value = replacements[key];
|
14
|
+
if (Array.isArray(value)) {
|
15
|
+
isFound = true;
|
16
|
+
return isFound;
|
17
|
+
}
|
18
|
+
if (typeof value === "object" && value !== null) {
|
19
|
+
isFound = foundArraysInReplacements(value);
|
20
|
+
if (isFound) {
|
21
|
+
return isFound;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
return isFound;
|
26
|
+
}
|
@@ -43,8 +43,10 @@ __exportStar(require("./check-row.js"), exports);
|
|
43
43
|
__exportStar(require("./check-rows.js"), exports);
|
44
44
|
__exportStar(require("./check-start-row.js"), exports);
|
45
45
|
__exportStar(require("./column-index-to-letter.js"), exports);
|
46
|
+
__exportStar(require("./compare-columns.js"), exports);
|
46
47
|
__exportStar(require("./escape-xml.js"), exports);
|
47
48
|
__exportStar(require("./extract-xml-declaration.js"), exports);
|
49
|
+
__exportStar(require("./found-arrays-in-replacements.js"), exports);
|
48
50
|
__exportStar(require("./get-by-path.js"), exports);
|
49
51
|
__exportStar(require("./get-max-row-number.js"), exports);
|
50
52
|
__exportStar(require("./get-rows-above.js"), exports);
|
@@ -53,6 +53,7 @@ export class TemplateFs {
|
|
53
53
|
this.fileKeys = fileKeys;
|
54
54
|
this.destination = destination;
|
55
55
|
}
|
56
|
+
/** Private methods */
|
56
57
|
/**
|
57
58
|
* Removes the temporary directory created by this Template instance.
|
58
59
|
* @private
|
@@ -185,6 +186,20 @@ export class TemplateFs {
|
|
185
186
|
const fullPath = path.join(this.destination, ...key.split("/"));
|
186
187
|
await fs.writeFile(fullPath, Buffer.isBuffer(content) ? content : Buffer.from(content));
|
187
188
|
}
|
189
|
+
/**
|
190
|
+
* Replaces placeholders in the given sheet with values from the replacements map.
|
191
|
+
*
|
192
|
+
* The function searches for placeholders in the format `${key}` within the sheet
|
193
|
+
* content, where `key` corresponds to a path in the replacements object.
|
194
|
+
* If a value is found for the key, it replaces the placeholder with the value.
|
195
|
+
* If no value is found, the original placeholder remains unchanged.
|
196
|
+
*
|
197
|
+
* @param sheetName - The name of the sheet to be replaced.
|
198
|
+
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
199
|
+
* @returns A promise that resolves when the substitution is complete.
|
200
|
+
* @throws {Error} If the template instance has been destroyed.
|
201
|
+
* @experimental This API is experimental and might change in future versions.
|
202
|
+
*/
|
188
203
|
async #substitute(sheetName, replacements) {
|
189
204
|
const sharedStringsPath = this.#excelKeys.sharedStrings;
|
190
205
|
const sheetPath = await this.#getSheetPathByName(sheetName);
|
@@ -212,6 +227,54 @@ export class TemplateFs {
|
|
212
227
|
await this.#set(sheetPath, sheetContent);
|
213
228
|
}
|
214
229
|
}
|
230
|
+
/**
|
231
|
+
* Removes sheets from the workbook.
|
232
|
+
*
|
233
|
+
* @param {Object} data - The data for sheet removal.
|
234
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
235
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
236
|
+
* @returns {void}
|
237
|
+
*
|
238
|
+
* @throws {Error} If the template instance has been destroyed.
|
239
|
+
* @throws {Error} If the sheet does not exist.
|
240
|
+
* @experimental This API is experimental and might change in future versions.
|
241
|
+
*/
|
242
|
+
async #removeSheets(data) {
|
243
|
+
const { sheetIndexes = [], sheetNames = [] } = data;
|
244
|
+
// first get index of sheets to remove
|
245
|
+
const sheetIndexesToRemove = new Set(sheetIndexes);
|
246
|
+
for (const sheetName of sheetNames) {
|
247
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
248
|
+
const sheetIndexMatch = sheetPath.match(/sheet(\d+)\.xml$/);
|
249
|
+
if (!sheetIndexMatch || !sheetIndexMatch[1]) {
|
250
|
+
throw new Error(`Sheet "${sheetName}" not found`);
|
251
|
+
}
|
252
|
+
const sheetIndex = parseInt(sheetIndexMatch[1], 10);
|
253
|
+
sheetIndexesToRemove.add(sheetIndex);
|
254
|
+
}
|
255
|
+
// Remove sheets by index
|
256
|
+
for (const sheetIndex of sheetIndexesToRemove.values()) {
|
257
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
258
|
+
if (!this.fileKeys.has(sheetPath)) {
|
259
|
+
continue;
|
260
|
+
}
|
261
|
+
// remove sheet file
|
262
|
+
await fs.unlink(path.join(this.destination, ...sheetPath.split("/")));
|
263
|
+
this.fileKeys.delete(sheetPath);
|
264
|
+
// remove sheet from workbook
|
265
|
+
if (this.fileKeys.has(this.#excelKeys.workbook)) {
|
266
|
+
this.#set(this.#excelKeys.workbook, Buffer.from(Utils.Common.removeSheetFromWorkbook(this.#readFile(this.#excelKeys.workbook).toString(), sheetIndex)));
|
267
|
+
}
|
268
|
+
// remove sheet from workbook relations
|
269
|
+
if (this.fileKeys.has(this.#excelKeys.workbookRels)) {
|
270
|
+
this.#set(this.#excelKeys.workbookRels, Buffer.from(Utils.Common.removeSheetFromRels(this.#readFile(this.#excelKeys.workbookRels).toString(), sheetIndex)));
|
271
|
+
}
|
272
|
+
// remove sheet from content types
|
273
|
+
if (this.fileKeys.has(this.#excelKeys.contentTypes)) {
|
274
|
+
this.#set(this.#excelKeys.contentTypes, Buffer.from(Utils.Common.removeSheetFromContentTypes(this.#readFile(this.#excelKeys.contentTypes).toString(), sheetIndex)));
|
275
|
+
}
|
276
|
+
}
|
277
|
+
}
|
215
278
|
/**
|
216
279
|
* Validates the template by checking all required files exist.
|
217
280
|
*
|
@@ -231,6 +294,7 @@ export class TemplateFs {
|
|
231
294
|
}
|
232
295
|
}
|
233
296
|
}
|
297
|
+
/** Public methods */
|
234
298
|
/**
|
235
299
|
* Copies a sheet from the template to a new name.
|
236
300
|
*
|
@@ -321,12 +385,12 @@ export class TemplateFs {
|
|
321
385
|
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
322
386
|
* @returns A promise that resolves when the substitution is complete.
|
323
387
|
*/
|
324
|
-
substitute(sheetName, replacements) {
|
388
|
+
async substitute(sheetName, replacements) {
|
325
389
|
this.#ensureNotProcessing();
|
326
390
|
this.#ensureNotDestroyed();
|
327
391
|
this.#isProcessing = true;
|
328
392
|
try {
|
329
|
-
|
393
|
+
await this.#substitute(sheetName, replacements);
|
330
394
|
}
|
331
395
|
finally {
|
332
396
|
this.#isProcessing = false;
|
@@ -488,7 +552,7 @@ export class TemplateFs {
|
|
488
552
|
}
|
489
553
|
}
|
490
554
|
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
491
|
-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
555
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
492
556
|
dimension.maxColumn = newDimension.maxColumn;
|
493
557
|
}
|
494
558
|
if (newDimension.maxRow > dimension.maxRow) {
|
@@ -535,7 +599,7 @@ export class TemplateFs {
|
|
535
599
|
}
|
536
600
|
// new <row>
|
537
601
|
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
538
|
-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
602
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
539
603
|
dimension.maxColumn = newDimension.maxColumn;
|
540
604
|
}
|
541
605
|
if (newDimension.maxRow > dimension.maxRow) {
|
@@ -568,7 +632,7 @@ export class TemplateFs {
|
|
568
632
|
output.write("<sheetData>");
|
569
633
|
// Prepare the rows
|
570
634
|
const { dimension: newDimension } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
571
|
-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
635
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
572
636
|
dimension.maxColumn = newDimension.maxColumn;
|
573
637
|
}
|
574
638
|
if (newDimension.maxRow > dimension.maxRow) {
|
@@ -635,6 +699,25 @@ export class TemplateFs {
|
|
635
699
|
this.#isProcessing = false;
|
636
700
|
}
|
637
701
|
}
|
702
|
+
/**
|
703
|
+
* Removes sheets from the workbook.
|
704
|
+
*
|
705
|
+
* @param {Object} data
|
706
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
707
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
708
|
+
* @returns {void}
|
709
|
+
*/
|
710
|
+
async removeSheets(data) {
|
711
|
+
this.#ensureNotProcessing();
|
712
|
+
this.#ensureNotDestroyed();
|
713
|
+
this.#isProcessing = true;
|
714
|
+
try {
|
715
|
+
await this.#removeSheets(data);
|
716
|
+
}
|
717
|
+
finally {
|
718
|
+
this.#isProcessing = false;
|
719
|
+
}
|
720
|
+
}
|
638
721
|
/**
|
639
722
|
* Saves the modified Excel template to a buffer.
|
640
723
|
*
|
@@ -723,6 +806,7 @@ export class TemplateFs {
|
|
723
806
|
this.#isProcessing = false;
|
724
807
|
}
|
725
808
|
}
|
809
|
+
/** Static methods */
|
726
810
|
/**
|
727
811
|
* Creates a Template instance from an Excel file source.
|
728
812
|
* Removes any existing files in the destination directory.
|
@@ -760,8 +844,3 @@ export class TemplateFs {
|
|
760
844
|
return new TemplateFs(new Set(Object.keys(files)), destinationWithUuid);
|
761
845
|
}
|
762
846
|
}
|
763
|
-
const compareColumns = (a, b) => {
|
764
|
-
if (a === b)
|
765
|
-
return 0;
|
766
|
-
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
|
767
|
-
};
|
@@ -40,6 +40,7 @@ export class TemplateMemory {
|
|
40
40
|
constructor(files) {
|
41
41
|
this.files = files;
|
42
42
|
}
|
43
|
+
/** Private methods */
|
43
44
|
/**
|
44
45
|
* Ensures that this Template instance has not been destroyed.
|
45
46
|
* @private
|
@@ -204,7 +205,8 @@ export class TemplateMemory {
|
|
204
205
|
sheetContent = await this.#extractXmlFromSheet(sheetPath);
|
205
206
|
const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
|
206
207
|
const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
|
207
|
-
|
208
|
+
const hasArraysInReplacements = Utils.foundArraysInReplacements(replacements);
|
209
|
+
if (hasTablePlaceholders && hasArraysInReplacements) {
|
208
210
|
const result = this.#expandTableRows(sheetContent, sharedStringsContent, replacements);
|
209
211
|
sheetContent = result.sheet;
|
210
212
|
sharedStringsContent = result.shared;
|
@@ -299,28 +301,45 @@ export class TemplateMemory {
|
|
299
301
|
* @throws {Error} If the sheet does not exist.
|
300
302
|
* @experimental This API is experimental and might change in future versions.
|
301
303
|
*/
|
302
|
-
#removeSheets(data) {
|
304
|
+
async #removeSheets(data) {
|
303
305
|
const { sheetIndexes = [], sheetNames = [] } = data;
|
304
|
-
|
306
|
+
// first get index of sheets to remove
|
307
|
+
const sheetIndexesToRemove = new Set(sheetIndexes);
|
308
|
+
for (const sheetName of sheetNames) {
|
309
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
310
|
+
const sheetIndexMatch = sheetPath.match(/sheet(\d+)\.xml$/);
|
311
|
+
if (!sheetIndexMatch || !sheetIndexMatch[1]) {
|
312
|
+
throw new Error(`Sheet "${sheetName}" not found`);
|
313
|
+
}
|
314
|
+
const sheetIndex = parseInt(sheetIndexMatch[1], 10);
|
315
|
+
sheetIndexesToRemove.add(sheetIndex);
|
316
|
+
}
|
317
|
+
// Remove sheets by index
|
318
|
+
for (const sheetIndex of sheetIndexesToRemove.values()) {
|
305
319
|
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
306
320
|
if (!this.files[sheetPath]) {
|
307
321
|
continue;
|
308
322
|
}
|
323
|
+
// remove sheet file
|
309
324
|
delete this.files[sheetPath];
|
310
|
-
|
311
|
-
|
325
|
+
// remove sheet from workbook
|
326
|
+
const workbook = this.files[this.#excelKeys.workbook];
|
327
|
+
if (workbook) {
|
328
|
+
this.files[this.#excelKeys.workbook] = Buffer.from(Utils.Common.removeSheetFromWorkbook(workbook.toString(), sheetIndex));
|
312
329
|
}
|
313
|
-
|
314
|
-
|
330
|
+
// remove sheet from workbook relations
|
331
|
+
const workbookRels = this.files[this.#excelKeys.workbookRels];
|
332
|
+
if (workbookRels) {
|
333
|
+
this.files[this.#excelKeys.workbookRels] = Buffer.from(Utils.Common.removeSheetFromRels(workbookRels.toString(), sheetIndex));
|
315
334
|
}
|
316
|
-
|
317
|
-
|
335
|
+
// remove sheet from content types
|
336
|
+
const contentTypes = this.files[this.#excelKeys.contentTypes];
|
337
|
+
if (contentTypes) {
|
338
|
+
this.files[this.#excelKeys.contentTypes] = Buffer.from(Utils.Common.removeSheetFromContentTypes(contentTypes.toString(), sheetIndex));
|
318
339
|
}
|
319
340
|
}
|
320
|
-
for (const sheetName of sheetNames) {
|
321
|
-
Utils.Common.removeSheetByName(this.files, sheetName);
|
322
|
-
}
|
323
341
|
}
|
342
|
+
/** Public methods */
|
324
343
|
/**
|
325
344
|
* Copies a sheet from the template to a new name.
|
326
345
|
*
|
@@ -418,12 +437,12 @@ export class TemplateMemory {
|
|
418
437
|
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
419
438
|
* @returns A promise that resolves when the substitution is complete.
|
420
439
|
*/
|
421
|
-
substitute(sheetName, replacements) {
|
440
|
+
async substitute(sheetName, replacements) {
|
422
441
|
this.#ensureNotProcessing();
|
423
442
|
this.#ensureNotDestroyed();
|
424
443
|
this.#isProcessing = true;
|
425
444
|
try {
|
426
|
-
|
445
|
+
await this.#substitute(sheetName, replacements);
|
427
446
|
}
|
428
447
|
finally {
|
429
448
|
this.#isProcessing = false;
|
@@ -489,7 +508,7 @@ export class TemplateMemory {
|
|
489
508
|
else {
|
490
509
|
updatedXml = sheetXml.replace(/<worksheet[^>]*>/, (match) => `${match}<sheetData>${rowsXml}</sheetData>`);
|
491
510
|
}
|
492
|
-
await this.#set(sheetPath, Buffer.from(updatedXml));
|
511
|
+
await this.#set(sheetPath, Buffer.from(Utils.updateDimension(updatedXml)));
|
493
512
|
}
|
494
513
|
finally {
|
495
514
|
this.#isProcessing = false;
|
@@ -522,6 +541,26 @@ export class TemplateMemory {
|
|
522
541
|
const sheetXml = await this.#extractXmlFromSheet(sheetPath);
|
523
542
|
const output = new MemoryWriteStream();
|
524
543
|
let inserted = false;
|
544
|
+
const initialDimension = sheetXml.match(/<dimension\s+ref="[^"]*"/)?.[0] || "";
|
545
|
+
const dimension = {
|
546
|
+
maxColumn: "A",
|
547
|
+
maxRow: 1,
|
548
|
+
minColumn: "A",
|
549
|
+
minRow: 1,
|
550
|
+
};
|
551
|
+
if (initialDimension) {
|
552
|
+
const dimensionMatch = initialDimension.match(/<dimension\s+ref="([^"]*)"/);
|
553
|
+
if (dimensionMatch) {
|
554
|
+
const dimensionRef = dimensionMatch[1];
|
555
|
+
if (dimensionRef) {
|
556
|
+
const [min, max] = dimensionRef.split(":");
|
557
|
+
dimension.minColumn = min.slice(0, 1);
|
558
|
+
dimension.minRow = parseInt(min.slice(1));
|
559
|
+
dimension.maxColumn = max.slice(0, 1);
|
560
|
+
dimension.maxRow = parseInt(max.slice(1));
|
561
|
+
}
|
562
|
+
}
|
563
|
+
}
|
525
564
|
// --- Case 1: <sheetData>...</sheetData> on one line ---
|
526
565
|
const singleLineMatch = sheetXml.match(/(<sheetData[^>]*>)(.*)(<\/sheetData>)/);
|
527
566
|
if (!inserted && singleLineMatch) {
|
@@ -542,7 +581,13 @@ export class TemplateMemory {
|
|
542
581
|
output.write(innerRows);
|
543
582
|
}
|
544
583
|
}
|
545
|
-
const { rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
584
|
+
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
585
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
586
|
+
dimension.maxColumn = newDimension.maxColumn;
|
587
|
+
}
|
588
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
589
|
+
dimension.maxRow = newDimension.maxRow;
|
590
|
+
}
|
546
591
|
if (innerRows) {
|
547
592
|
const filtered = Utils.getRowsAbove(innerRowsMap, actualRowNumber);
|
548
593
|
if (filtered)
|
@@ -559,7 +604,13 @@ export class TemplateMemory {
|
|
559
604
|
const matchIndex = match.index;
|
560
605
|
output.write(sheetXml.slice(0, matchIndex));
|
561
606
|
output.write("<sheetData>");
|
562
|
-
await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
607
|
+
const { dimension: newDimension } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
608
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
609
|
+
dimension.maxColumn = newDimension.maxColumn;
|
610
|
+
}
|
611
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
612
|
+
dimension.maxRow = newDimension.maxRow;
|
613
|
+
}
|
563
614
|
output.write("</sheetData>");
|
564
615
|
output.write(sheetXml.slice(matchIndex + match[0].length));
|
565
616
|
inserted = true;
|
@@ -590,7 +641,13 @@ export class TemplateMemory {
|
|
590
641
|
output.write(innerRows);
|
591
642
|
}
|
592
643
|
}
|
593
|
-
const { rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, Utils.getMaxRowNumber(innerRows));
|
644
|
+
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, Utils.getMaxRowNumber(innerRows));
|
645
|
+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
646
|
+
dimension.maxColumn = newDimension.maxColumn;
|
647
|
+
}
|
648
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
649
|
+
dimension.maxRow = newDimension.maxRow;
|
650
|
+
}
|
594
651
|
if (innerRows) {
|
595
652
|
const filtered = Utils.getRowsAbove(innerRowsMap, actualRowNumber);
|
596
653
|
if (filtered)
|
@@ -602,8 +659,18 @@ export class TemplateMemory {
|
|
602
659
|
}
|
603
660
|
if (!inserted)
|
604
661
|
throw new Error("Failed to locate <sheetData> for insertion");
|
662
|
+
let result = output.toBuffer();
|
663
|
+
// update dimension
|
664
|
+
{
|
665
|
+
const target = initialDimension;
|
666
|
+
const refRange = `${dimension.minColumn}${dimension.minRow}:${dimension.maxColumn}${dimension.maxRow}`;
|
667
|
+
const replacement = `<dimension ref="${refRange}"`;
|
668
|
+
if (target) {
|
669
|
+
result = Buffer.from(result.toString().replace(target, replacement));
|
670
|
+
}
|
671
|
+
}
|
605
672
|
// Save the buffer to the sheet
|
606
|
-
this.files[sheetPath] =
|
673
|
+
this.files[sheetPath] = result;
|
607
674
|
}
|
608
675
|
finally {
|
609
676
|
this.#isProcessing = false;
|
@@ -666,12 +733,12 @@ export class TemplateMemory {
|
|
666
733
|
* @param {number} [data.gap=0] - The number of empty rows to insert between each added section.
|
667
734
|
* @returns {void}
|
668
735
|
*/
|
669
|
-
mergeSheets(data) {
|
736
|
+
async mergeSheets(data) {
|
670
737
|
this.#ensureNotProcessing();
|
671
738
|
this.#ensureNotDestroyed();
|
672
739
|
this.#isProcessing = true;
|
673
740
|
try {
|
674
|
-
this.#mergeSheets(data);
|
741
|
+
await this.#mergeSheets(data);
|
675
742
|
}
|
676
743
|
finally {
|
677
744
|
this.#isProcessing = false;
|
@@ -685,17 +752,18 @@ export class TemplateMemory {
|
|
685
752
|
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
686
753
|
* @returns {void}
|
687
754
|
*/
|
688
|
-
removeSheets(data) {
|
755
|
+
async removeSheets(data) {
|
689
756
|
this.#ensureNotProcessing();
|
690
757
|
this.#ensureNotDestroyed();
|
691
758
|
this.#isProcessing = true;
|
692
759
|
try {
|
693
|
-
this.#removeSheets(data);
|
760
|
+
await this.#removeSheets(data);
|
694
761
|
}
|
695
762
|
finally {
|
696
763
|
this.#isProcessing = false;
|
697
764
|
}
|
698
765
|
}
|
766
|
+
/** Static methods */
|
699
767
|
/**
|
700
768
|
* Creates a Template instance from an Excel file source.
|
701
769
|
*
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* Compares two column strings and returns a number indicating their relative order.
|
3
|
+
*
|
4
|
+
* @param a - The first column string to compare.
|
5
|
+
* @param b - The second column string to compare.
|
6
|
+
* @returns 0 if the columns are equal, -1 if the first column is less than the second, or 1 if the first column is greater than the second.
|
7
|
+
*/
|
8
|
+
export function compareColumns(a, b) {
|
9
|
+
if (a === b) {
|
10
|
+
return 0;
|
11
|
+
}
|
12
|
+
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
|
13
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
/**
|
2
|
+
* Recursively checks if an object contains any arrays in its values.
|
3
|
+
*
|
4
|
+
* @param {Record<string, unknown>} replacements - The object to check for arrays.
|
5
|
+
* @returns {boolean} True if any arrays are found, false otherwise.
|
6
|
+
*/
|
7
|
+
export function foundArraysInReplacements(replacements) {
|
8
|
+
let isFound = false;
|
9
|
+
for (const key in replacements) {
|
10
|
+
const value = replacements[key];
|
11
|
+
if (Array.isArray(value)) {
|
12
|
+
isFound = true;
|
13
|
+
return isFound;
|
14
|
+
}
|
15
|
+
if (typeof value === "object" && value !== null) {
|
16
|
+
isFound = foundArraysInReplacements(value);
|
17
|
+
if (isFound) {
|
18
|
+
return isFound;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
return isFound;
|
23
|
+
}
|
@@ -4,8 +4,10 @@ export * from "./check-row.js";
|
|
4
4
|
export * from "./check-rows.js";
|
5
5
|
export * from "./check-start-row.js";
|
6
6
|
export * from "./column-index-to-letter.js";
|
7
|
+
export * from "./compare-columns.js";
|
7
8
|
export * from "./escape-xml.js";
|
8
9
|
export * from "./extract-xml-declaration.js";
|
10
|
+
export * from "./found-arrays-in-replacements.js";
|
9
11
|
export * from "./get-by-path.js";
|
10
12
|
export * from "./get-max-row-number.js";
|
11
13
|
export * from "./get-rows-above.js";
|
@@ -29,6 +29,7 @@ export declare class TemplateFs {
|
|
29
29
|
* @experimental This API is experimental and might change in future versions.
|
30
30
|
*/
|
31
31
|
constructor(fileKeys: Set<string>, destination: string);
|
32
|
+
/** Public methods */
|
32
33
|
/**
|
33
34
|
* Copies a sheet from the template to a new name.
|
34
35
|
*
|
@@ -91,6 +92,18 @@ export declare class TemplateFs {
|
|
91
92
|
startRowNumber?: number;
|
92
93
|
rows: AsyncIterable<unknown[]>;
|
93
94
|
}): Promise<void>;
|
95
|
+
/**
|
96
|
+
* Removes sheets from the workbook.
|
97
|
+
*
|
98
|
+
* @param {Object} data
|
99
|
+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
|
100
|
+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
|
101
|
+
* @returns {void}
|
102
|
+
*/
|
103
|
+
removeSheets(data: {
|
104
|
+
sheetNames?: string[];
|
105
|
+
sheetIndexes?: number[];
|
106
|
+
}): Promise<void>;
|
94
107
|
/**
|
95
108
|
* Saves the modified Excel template to a buffer.
|
96
109
|
*
|
@@ -128,6 +141,7 @@ export declare class TemplateFs {
|
|
128
141
|
* @experimental This API is experimental and might change in future versions.
|
129
142
|
*/
|
130
143
|
validate(): Promise<void>;
|
144
|
+
/** Static methods */
|
131
145
|
/**
|
132
146
|
* Creates a Template instance from an Excel file source.
|
133
147
|
* Removes any existing files in the destination directory.
|
@@ -19,6 +19,7 @@ export declare class TemplateMemory {
|
|
19
19
|
* @experimental This API is experimental and might change in future versions.
|
20
20
|
*/
|
21
21
|
constructor(files: Record<string, Buffer>);
|
22
|
+
/** Public methods */
|
22
23
|
/**
|
23
24
|
* Copies a sheet from the template to a new name.
|
24
25
|
*
|
@@ -118,7 +119,7 @@ export declare class TemplateMemory {
|
|
118
119
|
baseSheetIndex?: number;
|
119
120
|
baseSheetName?: string;
|
120
121
|
gap?: number;
|
121
|
-
}): void
|
122
|
+
}): Promise<void>;
|
122
123
|
/**
|
123
124
|
* Removes sheets from the workbook.
|
124
125
|
*
|
@@ -130,7 +131,8 @@ export declare class TemplateMemory {
|
|
130
131
|
removeSheets(data: {
|
131
132
|
sheetNames?: string[];
|
132
133
|
sheetIndexes?: number[];
|
133
|
-
}): void
|
134
|
+
}): Promise<void>;
|
135
|
+
/** Static methods */
|
134
136
|
/**
|
135
137
|
* Creates a Template instance from an Excel file source.
|
136
138
|
*
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/**
|
2
|
+
* Compares two column strings and returns a number indicating their relative order.
|
3
|
+
*
|
4
|
+
* @param a - The first column string to compare.
|
5
|
+
* @param b - The second column string to compare.
|
6
|
+
* @returns 0 if the columns are equal, -1 if the first column is less than the second, or 1 if the first column is greater than the second.
|
7
|
+
*/
|
8
|
+
export declare function compareColumns(a: string, b: string): number;
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* Recursively checks if an object contains any arrays in its values.
|
3
|
+
*
|
4
|
+
* @param {Record<string, unknown>} replacements - The object to check for arrays.
|
5
|
+
* @returns {boolean} True if any arrays are found, false otherwise.
|
6
|
+
*/
|
7
|
+
export declare function foundArraysInReplacements(replacements: Record<string, unknown>): boolean;
|
@@ -4,8 +4,10 @@ export * from "./check-row.js";
|
|
4
4
|
export * from "./check-rows.js";
|
5
5
|
export * from "./check-start-row.js";
|
6
6
|
export * from "./column-index-to-letter.js";
|
7
|
+
export * from "./compare-columns.js";
|
7
8
|
export * from "./escape-xml.js";
|
8
9
|
export * from "./extract-xml-declaration.js";
|
10
|
+
export * from "./found-arrays-in-replacements.js";
|
9
11
|
export * from "./get-by-path.js";
|
10
12
|
export * from "./get-max-row-number.js";
|
11
13
|
export * from "./get-rows-above.js";
|