@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.
@@ -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
- return this.#substitute(sheetName, replacements);
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
- if (hasTablePlaceholders) {
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
- for (const sheetIndex of sheetIndexes) {
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
- if (this.files["xl/workbook.xml"]) {
347
- this.files["xl/workbook.xml"] = Buffer.from(Utils.Common.removeSheetFromWorkbook(this.files["xl/workbook.xml"].toString(), sheetIndex));
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
- 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));
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
- if (this.files["[Content_Types].xml"]) {
353
- this.files["[Content_Types].xml"] = Buffer.from(Utils.Common.removeSheetFromContentTypes(this.files["[Content_Types].xml"].toString(), sheetIndex));
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
- return this.#substitute(sheetName, replacements);
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] = output.toBuffer();
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
- return this.#substitute(sheetName, replacements);
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
- if (hasTablePlaceholders) {
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
- for (const sheetIndex of sheetIndexes) {
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
- if (this.files["xl/workbook.xml"]) {
311
- this.files["xl/workbook.xml"] = Buffer.from(Utils.Common.removeSheetFromWorkbook(this.files["xl/workbook.xml"].toString(), sheetIndex));
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
- if (this.files["xl/_rels/workbook.xml.rels"]) {
314
- this.files["xl/_rels/workbook.xml.rels"] = Buffer.from(Utils.Common.removeSheetFromRels(this.files["xl/_rels/workbook.xml.rels"].toString(), sheetIndex));
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
- if (this.files["[Content_Types].xml"]) {
317
- this.files["[Content_Types].xml"] = Buffer.from(Utils.Common.removeSheetFromContentTypes(this.files["[Content_Types].xml"].toString(), sheetIndex));
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
- return this.#substitute(sheetName, replacements);
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] = output.toBuffer();
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-ak/excel-toolbox",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "excel-toolbox",
5
5
  "publishConfig": {
6
6
  "access": "public",