Skip to content

Commit 5757fb5

Browse files
committed
feat: updated TemplateFs TemplateMemory
added TemplateFs removeSheets updated TemplateMemory calc dimension
1 parent e203b2b commit 5757fb5

19 files changed

+800
-42
lines changed

docs/template-fs.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,29 @@ Streams and inserts rows into a worksheet, useful for handling large datasets.
115115

116116
---
117117

118+
### `removeSheets`
119+
120+
Removes specified sheets from the workbook by either their names or 1-based indexes.
121+
122+
- Input:
123+
- `data: object` — configuration object
124+
- `sheetNames?: string[]` — array of sheet names to remove
125+
- `sheetIndexes?: number[]` — array of 1-based sheet indexes to remove
126+
- Output: `Promise<void>`
127+
- Preconditions:
128+
- Instance not destroyed
129+
- Sheets exist
130+
- Postconditions:
131+
- Specified sheets removed from workbook
132+
- Workbook relationships updated
133+
- Content types updated
134+
- Throws if:
135+
- The instance has been destroyed
136+
- Any specified sheet does not exist
137+
- Any specified sheet index is invalid
138+
139+
---
140+
118141
### `save`
119142

120143
Generates a new Excel file and returns it as a `Buffer`.

docs/template-memory.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ Merges multiple worksheets into a single base worksheet.
162162
- `baseSheetIndex?: number` — 1-based index of the base sheet to merge into (optional, default is 1).
163163
- `baseSheetName?: string` — name of the base sheet to merge into (optional).
164164
- `gap?: number` - number of empty rows to insert between merged sections (default: `0`).
165-
- Output: `void`
165+
- Output: `Promise<void>`
166166
- Preconditions:
167167
- Instance not destroyed
168168
- Valid sheet names/indexes
@@ -185,7 +185,7 @@ Removes worksheets from the workbook.
185185
- Input:
186186
- `sheetNames?: string[]` - names of sheets to remove
187187
- `sheetIndexes?: number[]` - 1-based indexes of sheets to remove
188-
- Output: `void`
188+
- Output: `Promise<void>`
189189
- Preconditions:
190190
- Instance not destroyed
191191
- Sheets exist

src/lib/template/template-fs.ts

Lines changed: 122 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export class TemplateFs {
6363
this.destination = destination;
6464
}
6565

66+
/** Private methods */
67+
6668
/**
6769
* Removes the temporary directory created by this Template instance.
6870
* @private
@@ -230,6 +232,20 @@ export class TemplateFs {
230232
await fs.writeFile(fullPath, Buffer.isBuffer(content) ? content : Buffer.from(content));
231233
}
232234

235+
/**
236+
* Replaces placeholders in the given sheet with values from the replacements map.
237+
*
238+
* The function searches for placeholders in the format `${key}` within the sheet
239+
* content, where `key` corresponds to a path in the replacements object.
240+
* If a value is found for the key, it replaces the placeholder with the value.
241+
* If no value is found, the original placeholder remains unchanged.
242+
*
243+
* @param sheetName - The name of the sheet to be replaced.
244+
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
245+
* @returns A promise that resolves when the substitution is complete.
246+
* @throws {Error} If the template instance has been destroyed.
247+
* @experimental This API is experimental and might change in future versions.
248+
*/
233249
async #substitute(sheetName: string, replacements: Record<string, unknown>): Promise<void> {
234250
const sharedStringsPath = this.#excelKeys.sharedStrings;
235251
const sheetPath = await this.#getSheetPathByName(sheetName);
@@ -267,6 +283,79 @@ export class TemplateFs {
267283
}
268284
}
269285

286+
/**
287+
* Removes sheets from the workbook.
288+
*
289+
* @param {Object} data - The data for sheet removal.
290+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
291+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
292+
* @returns {void}
293+
*
294+
* @throws {Error} If the template instance has been destroyed.
295+
* @throws {Error} If the sheet does not exist.
296+
* @experimental This API is experimental and might change in future versions.
297+
*/
298+
async #removeSheets(data: {
299+
sheetNames?: string[];
300+
sheetIndexes?: number[];
301+
}): Promise<void> {
302+
const { sheetIndexes = [], sheetNames = [] } = data;
303+
304+
// first get index of sheets to remove
305+
const sheetIndexesToRemove: Set<number> = new Set(sheetIndexes);
306+
307+
for (const sheetName of sheetNames) {
308+
const sheetPath = await this.#getSheetPathByName(sheetName);
309+
310+
const sheetIndexMatch = sheetPath.match(/sheet(\d+)\.xml$/);
311+
312+
if (!sheetIndexMatch || !sheetIndexMatch[1]) {
313+
throw new Error(`Sheet "${sheetName}" not found`);
314+
}
315+
316+
const sheetIndex = parseInt(sheetIndexMatch[1], 10);
317+
318+
sheetIndexesToRemove.add(sheetIndex);
319+
}
320+
321+
// Remove sheets by index
322+
for (const sheetIndex of sheetIndexesToRemove.values()) {
323+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
324+
325+
if (!this.fileKeys.has(sheetPath)) {
326+
continue;
327+
}
328+
329+
// remove sheet file
330+
await fs.unlink(path.join(this.destination, ...sheetPath.split("/")));
331+
this.fileKeys.delete(sheetPath);
332+
333+
// remove sheet from workbook
334+
if (this.fileKeys.has(this.#excelKeys.workbook)) {
335+
this.#set(this.#excelKeys.workbook, Buffer.from(Utils.Common.removeSheetFromWorkbook(
336+
this.#readFile(this.#excelKeys.workbook).toString(),
337+
sheetIndex,
338+
)));
339+
}
340+
341+
// remove sheet from workbook relations
342+
if (this.fileKeys.has(this.#excelKeys.workbookRels)) {
343+
this.#set(this.#excelKeys.workbookRels, Buffer.from(Utils.Common.removeSheetFromRels(
344+
this.#readFile(this.#excelKeys.workbookRels).toString(),
345+
sheetIndex,
346+
)));
347+
}
348+
349+
// remove sheet from content types
350+
if (this.fileKeys.has(this.#excelKeys.contentTypes)) {
351+
this.#set(this.#excelKeys.contentTypes, Buffer.from(Utils.Common.removeSheetFromContentTypes(
352+
this.#readFile(this.#excelKeys.contentTypes).toString(),
353+
sheetIndex,
354+
)));
355+
}
356+
}
357+
}
358+
270359
/**
271360
* Validates the template by checking all required files exist.
272361
*
@@ -286,6 +375,8 @@ export class TemplateFs {
286375
}
287376
}
288377

378+
/** Public methods */
379+
289380
/**
290381
* Copies a sheet from the template to a new name.
291382
*
@@ -401,14 +492,14 @@ export class TemplateFs {
401492
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
402493
* @returns A promise that resolves when the substitution is complete.
403494
*/
404-
substitute(sheetName: string, replacements: Record<string, unknown>): Promise<void> {
495+
async substitute(sheetName: string, replacements: Record<string, unknown>): Promise<void> {
405496
this.#ensureNotProcessing();
406497
this.#ensureNotDestroyed();
407498

408499
this.#isProcessing = true;
409500

410501
try {
411-
return this.#substitute(sheetName, replacements);
502+
await this.#substitute(sheetName, replacements);
412503
} finally {
413504
this.#isProcessing = false;
414505
}
@@ -617,7 +708,7 @@ export class TemplateFs {
617708

618709
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
619710

620-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
711+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
621712
dimension.maxColumn = newDimension.maxColumn;
622713
}
623714

@@ -680,7 +771,7 @@ export class TemplateFs {
680771
// new <row>
681772
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
682773

683-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
774+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
684775
dimension.maxColumn = newDimension.maxColumn;
685776
}
686777

@@ -727,7 +818,7 @@ export class TemplateFs {
727818
// Prepare the rows
728819
const { dimension: newDimension } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
729820

730-
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
821+
if (Utils.compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
731822
dimension.maxColumn = newDimension.maxColumn;
732823
}
733824

@@ -815,6 +906,30 @@ export class TemplateFs {
815906
}
816907
}
817908

909+
/**
910+
* Removes sheets from the workbook.
911+
*
912+
* @param {Object} data
913+
* @param {number[]} [data.sheetIndexes] - The 1-based indexes of the sheets to remove.
914+
* @param {string[]} [data.sheetNames] - The names of the sheets to remove.
915+
* @returns {void}
916+
*/
917+
async removeSheets(data: {
918+
sheetNames?: string[];
919+
sheetIndexes?: number[];
920+
}): Promise<void> {
921+
this.#ensureNotProcessing();
922+
this.#ensureNotDestroyed();
923+
924+
this.#isProcessing = true;
925+
926+
try {
927+
await this.#removeSheets(data);
928+
} finally {
929+
this.#isProcessing = false;
930+
}
931+
}
932+
818933
/**
819934
* Saves the modified Excel template to a buffer.
820935
*
@@ -920,6 +1035,8 @@ export class TemplateFs {
9201035
}
9211036
}
9221037

1038+
/** Static methods */
1039+
9231040
/**
9241041
* Creates a Template instance from an Excel file source.
9251042
* Removes any existing files in the destination directory.
@@ -970,8 +1087,3 @@ export class TemplateFs {
9701087
return new TemplateFs(new Set(Object.keys(files)), destinationWithUuid);
9711088
}
9721089
}
973-
974-
const compareColumns = (a: string, b: string): number => {
975-
if (a === b) return 0;
976-
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
977-
};

0 commit comments

Comments
 (0)