@js-ak/excel-toolbox 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/lib/merge-sheets-to-base-file-process-sync.js +105 -0
- package/build/cjs/lib/merge-sheets-to-base-file-process.js +3 -3
- package/build/cjs/lib/merge-sheets-to-base-file-sync.js +2 -2
- package/build/cjs/lib/merge-sheets-to-base-file.js +1 -1
- package/build/cjs/lib/template/template-fs.js +8 -8
- package/build/cjs/lib/template/template-memory.js +21 -21
- package/build/cjs/lib/xml/extract-rows-from-sheet-sync.js +67 -0
- package/build/cjs/lib/xml/extract-rows-from-sheet.js +4 -2
- package/build/cjs/lib/xml/extract-xml-from-sheet-sync.js +43 -0
- package/build/cjs/lib/xml/extract-xml-from-sheet.js +15 -15
- package/build/cjs/lib/xml/index.js +2 -1
- package/build/esm/lib/merge-sheets-to-base-file-process-sync.js +69 -0
- package/build/esm/lib/merge-sheets-to-base-file-process.js +3 -3
- package/build/esm/lib/merge-sheets-to-base-file-sync.js +2 -2
- package/build/esm/lib/merge-sheets-to-base-file.js +1 -1
- package/build/esm/lib/template/template-fs.js +8 -8
- package/build/esm/lib/template/template-memory.js +21 -21
- package/build/esm/lib/xml/extract-rows-from-sheet-sync.js +64 -0
- package/build/esm/lib/xml/extract-rows-from-sheet.js +4 -2
- package/build/esm/lib/xml/extract-xml-from-sheet-sync.js +40 -0
- package/build/esm/lib/xml/extract-xml-from-sheet.js +12 -15
- package/build/esm/lib/xml/index.js +2 -1
- package/build/types/lib/merge-sheets-to-base-file-process-sync.d.ts +27 -0
- package/build/types/lib/merge-sheets-to-base-file-process.d.ts +1 -1
- package/build/types/lib/xml/extract-rows-from-sheet-sync.d.ts +28 -0
- package/build/types/lib/xml/extract-rows-from-sheet.d.ts +2 -2
- package/build/types/lib/xml/extract-xml-from-sheet-sync.d.ts +14 -0
- package/build/types/lib/xml/extract-xml-from-sheet.d.ts +2 -2
- package/build/types/lib/xml/index.d.ts +2 -1
- package/package.json +1 -5
- package/build/cjs/lib/xml/extract-xml-from-system-content.js +0 -53
- package/build/esm/lib/xml/extract-xml-from-system-content.js +0 -49
- package/build/types/lib/xml/extract-xml-from-system-content.d.ts +0 -15
@@ -0,0 +1,105 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
19
|
+
var ownKeys = function(o) {
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
21
|
+
var ar = [];
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
23
|
+
return ar;
|
24
|
+
};
|
25
|
+
return ownKeys(o);
|
26
|
+
};
|
27
|
+
return function (mod) {
|
28
|
+
if (mod && mod.__esModule) return mod;
|
29
|
+
var result = {};
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
31
|
+
__setModuleDefault(result, mod);
|
32
|
+
return result;
|
33
|
+
};
|
34
|
+
})();
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
36
|
+
exports.mergeSheetsToBaseFileProcessSync = mergeSheetsToBaseFileProcessSync;
|
37
|
+
const Utils = __importStar(require("./utils/index.js"));
|
38
|
+
const Xml = __importStar(require("./xml/index.js"));
|
39
|
+
/**
|
40
|
+
* Merges rows from other Excel files into a base Excel file.
|
41
|
+
*
|
42
|
+
* This function is a process-friendly version of mergeSheetsToBaseFile.
|
43
|
+
* It takes a single object with the following properties:
|
44
|
+
* - additions: An array of objects with two properties:
|
45
|
+
* - files: A dictionary of file paths to their corresponding XML content
|
46
|
+
* - sheetIndexes: The 1-based indexes of the sheet to extract rows from
|
47
|
+
* - baseFiles: A dictionary of file paths to their corresponding XML content
|
48
|
+
* - baseSheetIndex: The 1-based index of the sheet in the base file to add rows to
|
49
|
+
* - gap: The number of empty rows to insert between each added section
|
50
|
+
* - sheetNamesToRemove: The names of sheets to remove from the output file
|
51
|
+
* - sheetsToRemove: The 1-based indices of sheets to remove from the output file
|
52
|
+
*
|
53
|
+
* The function returns a dictionary of file paths to their corresponding XML content.
|
54
|
+
*/
|
55
|
+
function mergeSheetsToBaseFileProcessSync(data) {
|
56
|
+
const { additions, baseFiles, baseSheetIndex, gap, sheetNamesToRemove, sheetsToRemove, } = data;
|
57
|
+
const basePath = `xl/worksheets/sheet${baseSheetIndex}.xml`;
|
58
|
+
if (!baseFiles[basePath]) {
|
59
|
+
throw new Error(`Base file does not contain ${basePath}`);
|
60
|
+
}
|
61
|
+
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = Xml.extractRowsFromSheetSync(baseFiles[basePath]);
|
62
|
+
const allRows = [...baseRows];
|
63
|
+
const allMergeCells = [...baseMergeCells];
|
64
|
+
let currentRowOffset = lastRowNumber + gap;
|
65
|
+
for (const { files, sheetIndexes } of additions) {
|
66
|
+
for (const sheetIndex of sheetIndexes) {
|
67
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
68
|
+
if (!files[sheetPath]) {
|
69
|
+
throw new Error(`File does not contain ${sheetPath}`);
|
70
|
+
}
|
71
|
+
const { mergeCells, rows } = Xml.extractRowsFromSheetSync(files[sheetPath]);
|
72
|
+
const shiftedRows = Xml.shiftRowIndices(rows, currentRowOffset);
|
73
|
+
const shiftedMergeCells = mergeCells.map(cell => {
|
74
|
+
const [start, end] = cell.ref.split(":");
|
75
|
+
if (!start || !end) {
|
76
|
+
return cell;
|
77
|
+
}
|
78
|
+
const shiftedStart = Utils.shiftCellRef(start, currentRowOffset);
|
79
|
+
const shiftedEnd = Utils.shiftCellRef(end, currentRowOffset);
|
80
|
+
return { ...cell, ref: `${shiftedStart}:${shiftedEnd}` };
|
81
|
+
});
|
82
|
+
allRows.push(...shiftedRows);
|
83
|
+
allMergeCells.push(...shiftedMergeCells);
|
84
|
+
currentRowOffset += Utils.getMaxRowNumber(rows) + gap;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
const mergedXml = Xml.buildMergedSheet(xml, allRows, allMergeCells);
|
88
|
+
baseFiles[basePath] = mergedXml;
|
89
|
+
for (const sheetIndex of sheetsToRemove) {
|
90
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
91
|
+
delete baseFiles[sheetPath];
|
92
|
+
if (baseFiles["xl/workbook.xml"]) {
|
93
|
+
baseFiles["xl/workbook.xml"] = Buffer.from(Utils.removeSheetFromWorkbook(baseFiles["xl/workbook.xml"].toString(), sheetIndex));
|
94
|
+
}
|
95
|
+
if (baseFiles["xl/_rels/workbook.xml.rels"]) {
|
96
|
+
baseFiles["xl/_rels/workbook.xml.rels"] = Buffer.from(Utils.removeSheetFromRels(baseFiles["xl/_rels/workbook.xml.rels"].toString(), sheetIndex));
|
97
|
+
}
|
98
|
+
if (baseFiles["[Content_Types].xml"]) {
|
99
|
+
baseFiles["[Content_Types].xml"] = Buffer.from(Utils.removeSheetFromContentTypes(baseFiles["[Content_Types].xml"].toString(), sheetIndex));
|
100
|
+
}
|
101
|
+
}
|
102
|
+
for (const sheetName of sheetNamesToRemove) {
|
103
|
+
Utils.removeSheetByName(baseFiles, sheetName);
|
104
|
+
}
|
105
|
+
}
|
@@ -52,13 +52,13 @@ const Xml = __importStar(require("./xml/index.js"));
|
|
52
52
|
*
|
53
53
|
* The function returns a dictionary of file paths to their corresponding XML content.
|
54
54
|
*/
|
55
|
-
function mergeSheetsToBaseFileProcess(data) {
|
55
|
+
async function mergeSheetsToBaseFileProcess(data) {
|
56
56
|
const { additions, baseFiles, baseSheetIndex, gap, sheetNamesToRemove, sheetsToRemove, } = data;
|
57
57
|
const basePath = `xl/worksheets/sheet${baseSheetIndex}.xml`;
|
58
58
|
if (!baseFiles[basePath]) {
|
59
59
|
throw new Error(`Base file does not contain ${basePath}`);
|
60
60
|
}
|
61
|
-
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = Xml.extractRowsFromSheet(baseFiles[basePath]);
|
61
|
+
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = await Xml.extractRowsFromSheet(baseFiles[basePath]);
|
62
62
|
const allRows = [...baseRows];
|
63
63
|
const allMergeCells = [...baseMergeCells];
|
64
64
|
let currentRowOffset = lastRowNumber + gap;
|
@@ -68,7 +68,7 @@ function mergeSheetsToBaseFileProcess(data) {
|
|
68
68
|
if (!files[sheetPath]) {
|
69
69
|
throw new Error(`File does not contain ${sheetPath}`);
|
70
70
|
}
|
71
|
-
const { mergeCells, rows } = Xml.extractRowsFromSheet(files[sheetPath]);
|
71
|
+
const { mergeCells, rows } = await Xml.extractRowsFromSheet(files[sheetPath]);
|
72
72
|
const shiftedRows = Xml.shiftRowIndices(rows, currentRowOffset);
|
73
73
|
const shiftedMergeCells = mergeCells.map(cell => {
|
74
74
|
const [start, end] = cell.ref.split(":");
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.mergeSheetsToBaseFileSync = mergeSheetsToBaseFileSync;
|
37
37
|
const Utils = __importStar(require("./utils/index.js"));
|
38
38
|
const Zip = __importStar(require("./zip/index.js"));
|
39
|
-
const
|
39
|
+
const merge_sheets_to_base_file_process_sync_js_1 = require("./merge-sheets-to-base-file-process-sync.js");
|
40
40
|
/**
|
41
41
|
* Merge rows from other Excel files into a base Excel file.
|
42
42
|
* The output is a new Excel file with the merged content.
|
@@ -65,7 +65,7 @@ function mergeSheetsToBaseFileSync(data) {
|
|
65
65
|
sheetIndexes,
|
66
66
|
});
|
67
67
|
}
|
68
|
-
(0,
|
68
|
+
(0, merge_sheets_to_base_file_process_sync_js_1.mergeSheetsToBaseFileProcessSync)({
|
69
69
|
additions: additionsUpdated,
|
70
70
|
baseFiles,
|
71
71
|
baseSheetIndex,
|
@@ -65,7 +65,7 @@ async function mergeSheetsToBaseFile(data) {
|
|
65
65
|
sheetIndexes,
|
66
66
|
});
|
67
67
|
}
|
68
|
-
(0, merge_sheets_to_base_file_process_js_1.mergeSheetsToBaseFileProcess)({
|
68
|
+
await (0, merge_sheets_to_base_file_process_js_1.mergeSheetsToBaseFileProcess)({
|
69
69
|
additions: additionsUpdated,
|
70
70
|
baseFiles,
|
71
71
|
baseSheetIndex,
|
@@ -165,13 +165,13 @@ class TemplateFs {
|
|
165
165
|
*/
|
166
166
|
async #getSheetPathByName(sheetName) {
|
167
167
|
// Read XML workbook to find sheet name and path
|
168
|
-
const workbookXml = Xml.extractXmlFromSheet(await this.#readFile(this.#excelKeys.workbook));
|
168
|
+
const workbookXml = await Xml.extractXmlFromSheet(await this.#readFile(this.#excelKeys.workbook));
|
169
169
|
const sheetMatch = workbookXml.match(Utils.sheetMatch(sheetName));
|
170
170
|
if (!sheetMatch || !sheetMatch[1]) {
|
171
171
|
throw new Error(`Sheet "${sheetName}" not found`);
|
172
172
|
}
|
173
173
|
const rId = sheetMatch[1];
|
174
|
-
const relsXml = Xml.extractXmlFromSheet(await this.#readFile(this.#excelKeys.workbookRels));
|
174
|
+
const relsXml = await Xml.extractXmlFromSheet(await this.#readFile(this.#excelKeys.workbookRels));
|
175
175
|
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
176
176
|
if (!relMatch || !relMatch[1]) {
|
177
177
|
throw new Error(`Relationship "${rId}" not found`);
|
@@ -230,10 +230,10 @@ class TemplateFs {
|
|
230
230
|
let sharedStringsContent = "";
|
231
231
|
let sheetContent = "";
|
232
232
|
if (this.fileKeys.has(sharedStringsPath)) {
|
233
|
-
sharedStringsContent = Xml.extractXmlFromSheet(await this.#readFile(sharedStringsPath));
|
233
|
+
sharedStringsContent = await Xml.extractXmlFromSheet(await this.#readFile(sharedStringsPath));
|
234
234
|
}
|
235
235
|
if (this.fileKeys.has(sheetPath)) {
|
236
|
-
sheetContent = Xml.extractXmlFromSheet(await this.#readFile(sheetPath));
|
236
|
+
sheetContent = await Xml.extractXmlFromSheet(await this.#readFile(sheetPath));
|
237
237
|
const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
|
238
238
|
const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
|
239
239
|
if (hasTablePlaceholders) {
|
@@ -290,7 +290,7 @@ class TemplateFs {
|
|
290
290
|
}
|
291
291
|
// Read workbook.xml and find the source sheet
|
292
292
|
const workbookXmlPath = this.#excelKeys.workbook;
|
293
|
-
const workbookXml = Xml.extractXmlFromSheet(await this.#readFile(workbookXmlPath));
|
293
|
+
const workbookXml = await Xml.extractXmlFromSheet(await this.#readFile(workbookXmlPath));
|
294
294
|
// Find the source sheet
|
295
295
|
const sheetMatch = workbookXml.match(Utils.sheetMatch(sourceName));
|
296
296
|
if (!sheetMatch || !sheetMatch[1]) {
|
@@ -304,7 +304,7 @@ class TemplateFs {
|
|
304
304
|
// Find the source sheet path by rId
|
305
305
|
const rId = sheetMatch[1];
|
306
306
|
const relsXmlPath = this.#excelKeys.workbookRels;
|
307
|
-
const relsXml = Xml.extractXmlFromSheet(await this.#readFile(relsXmlPath));
|
307
|
+
const relsXml = await Xml.extractXmlFromSheet(await this.#readFile(relsXmlPath));
|
308
308
|
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
309
309
|
if (!relMatch || !relMatch[1]) {
|
310
310
|
throw new Error(`Relationship "${rId}" not found`);
|
@@ -339,7 +339,7 @@ class TemplateFs {
|
|
339
339
|
// Read [Content_Types].xml
|
340
340
|
// Update [Content_Types].xml
|
341
341
|
const contentTypesPath = this.#excelKeys.contentTypes;
|
342
|
-
const contentTypesXml = Xml.extractXmlFromSheet(await this.#readFile(contentTypesPath));
|
342
|
+
const contentTypesXml = await Xml.extractXmlFromSheet(await this.#readFile(contentTypesPath));
|
343
343
|
const overrideTag = `<Override PartName="/xl/worksheets/${newSheetFilename}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
344
344
|
const updatedContentTypesXml = contentTypesXml.replace("</Types>", overrideTag + "</Types>");
|
345
345
|
await this.#set(contentTypesPath, updatedContentTypesXml);
|
@@ -398,7 +398,7 @@ class TemplateFs {
|
|
398
398
|
// Find the sheet
|
399
399
|
const sheetPath = await this.#getSheetPathByName(sheetName);
|
400
400
|
const sheetXmlRaw = await this.#readFile(sheetPath);
|
401
|
-
const sheetXml = Xml.extractXmlFromSheet(sheetXmlRaw);
|
401
|
+
const sheetXml = await Xml.extractXmlFromSheet(sheetXmlRaw);
|
402
402
|
let nextRow = 0;
|
403
403
|
if (!startRowNumber) {
|
404
404
|
// Find the last row
|
@@ -140,7 +140,7 @@ class TemplateMemory {
|
|
140
140
|
* @throws {Error} If the file key is not found.
|
141
141
|
* @experimental This API is experimental and might change in future versions.
|
142
142
|
*/
|
143
|
-
#extractXmlFromSheet(fileKey) {
|
143
|
+
async #extractXmlFromSheet(fileKey) {
|
144
144
|
if (!this.files[fileKey]) {
|
145
145
|
throw new Error(`${fileKey} not found`);
|
146
146
|
}
|
@@ -158,7 +158,7 @@ class TemplateMemory {
|
|
158
158
|
* @throws {Error} If the file key is not found
|
159
159
|
* @experimental This API is experimental and might change in future versions.
|
160
160
|
*/
|
161
|
-
#extractRowsFromSheet(fileKey) {
|
161
|
+
async #extractRowsFromSheet(fileKey) {
|
162
162
|
if (!this.files[fileKey]) {
|
163
163
|
throw new Error(`${fileKey} not found`);
|
164
164
|
}
|
@@ -172,15 +172,15 @@ class TemplateMemory {
|
|
172
172
|
* @throws {Error} If the sheet with the given name does not exist.
|
173
173
|
* @experimental This API is experimental and might change in future versions.
|
174
174
|
*/
|
175
|
-
#getSheetPathByName(sheetName) {
|
175
|
+
async #getSheetPathByName(sheetName) {
|
176
176
|
// Find the sheet
|
177
|
-
const workbookXml = this.#extractXmlFromSheet(this.#excelKeys.workbook);
|
177
|
+
const workbookXml = await this.#extractXmlFromSheet(this.#excelKeys.workbook);
|
178
178
|
const sheetMatch = workbookXml.match(Utils.sheetMatch(sheetName));
|
179
179
|
if (!sheetMatch || !sheetMatch[1]) {
|
180
180
|
throw new Error(`Sheet "${sheetName}" not found`);
|
181
181
|
}
|
182
182
|
const rId = sheetMatch[1];
|
183
|
-
const relsXml = this.#extractXmlFromSheet(this.#excelKeys.workbookRels);
|
183
|
+
const relsXml = await this.#extractXmlFromSheet(this.#excelKeys.workbookRels);
|
184
184
|
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
185
185
|
if (!relMatch || !relMatch[1]) {
|
186
186
|
throw new Error(`Relationship "${rId}" not found`);
|
@@ -233,11 +233,11 @@ class TemplateMemory {
|
|
233
233
|
let sharedStringsContent = "";
|
234
234
|
let sheetContent = "";
|
235
235
|
if (this.files[sharedStringsPath]) {
|
236
|
-
sharedStringsContent = this.#extractXmlFromSheet(sharedStringsPath);
|
236
|
+
sharedStringsContent = await this.#extractXmlFromSheet(sharedStringsPath);
|
237
237
|
}
|
238
|
-
const sheetPath = this.#getSheetPathByName(sheetName);
|
238
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
239
239
|
if (this.files[sheetPath]) {
|
240
|
-
sheetContent = this.#extractXmlFromSheet(sheetPath);
|
240
|
+
sheetContent = await this.#extractXmlFromSheet(sheetPath);
|
241
241
|
const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
|
242
242
|
const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
|
243
243
|
if (hasTablePlaceholders) {
|
@@ -272,11 +272,11 @@ class TemplateMemory {
|
|
272
272
|
* @throws {Error} If no sheets are found to merge.
|
273
273
|
* @experimental This API is experimental and might change in future versions.
|
274
274
|
*/
|
275
|
-
#mergeSheets(data) {
|
275
|
+
async #mergeSheets(data) {
|
276
276
|
const { additions, baseSheetIndex = 1, baseSheetName, gap = 0, } = data;
|
277
277
|
let fileKey = "";
|
278
278
|
if (baseSheetName) {
|
279
|
-
fileKey = this.#getSheetPathByName(baseSheetName);
|
279
|
+
fileKey = await this.#getSheetPathByName(baseSheetName);
|
280
280
|
}
|
281
281
|
if (baseSheetIndex && !fileKey) {
|
282
282
|
if (baseSheetIndex < 1) {
|
@@ -287,16 +287,16 @@ class TemplateMemory {
|
|
287
287
|
if (!fileKey) {
|
288
288
|
throw new Error("Base sheet not found");
|
289
289
|
}
|
290
|
-
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = this.#extractRowsFromSheet(fileKey);
|
290
|
+
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = await this.#extractRowsFromSheet(fileKey);
|
291
291
|
const allRows = [...baseRows];
|
292
292
|
const allMergeCells = [...baseMergeCells];
|
293
293
|
let currentRowOffset = lastRowNumber + gap;
|
294
294
|
const sheetPaths = [];
|
295
295
|
if (additions.sheetIndexes) {
|
296
|
-
sheetPaths.push(...(additions.sheetIndexes
|
296
|
+
sheetPaths.push(...(await Promise.all(additions.sheetIndexes.map(e => this.#getSheetPathById(e)))));
|
297
297
|
}
|
298
298
|
if (additions.sheetNames) {
|
299
|
-
sheetPaths.push(...(additions.sheetNames
|
299
|
+
sheetPaths.push(...(await Promise.all(additions.sheetNames.map(e => this.#getSheetPathByName(e)))));
|
300
300
|
}
|
301
301
|
if (sheetPaths.length === 0) {
|
302
302
|
throw new Error("No sheets found to merge");
|
@@ -305,7 +305,7 @@ class TemplateMemory {
|
|
305
305
|
if (!this.files[sheetPath]) {
|
306
306
|
throw new Error(`Sheet "${sheetPath}" not found`);
|
307
307
|
}
|
308
|
-
const { mergeCells, rows } = Xml.extractRowsFromSheet(this.files[sheetPath]);
|
308
|
+
const { mergeCells, rows } = await Xml.extractRowsFromSheet(this.files[sheetPath]);
|
309
309
|
const shiftedRows = Xml.shiftRowIndices(rows, currentRowOffset);
|
310
310
|
const shiftedMergeCells = mergeCells.map(cell => {
|
311
311
|
const [start, end] = cell.ref.split(":");
|
@@ -377,7 +377,7 @@ class TemplateMemory {
|
|
377
377
|
}
|
378
378
|
// Read workbook.xml and find the source sheet
|
379
379
|
const workbookXmlPath = this.#excelKeys.workbook;
|
380
|
-
const workbookXml = this.#extractXmlFromSheet(this.#excelKeys.workbook);
|
380
|
+
const workbookXml = await this.#extractXmlFromSheet(this.#excelKeys.workbook);
|
381
381
|
// Find the source sheet
|
382
382
|
const sheetMatch = workbookXml.match(Utils.sheetMatch(sourceName));
|
383
383
|
if (!sheetMatch || !sheetMatch[1]) {
|
@@ -391,7 +391,7 @@ class TemplateMemory {
|
|
391
391
|
// Find the source sheet path by rId
|
392
392
|
const rId = sheetMatch[1];
|
393
393
|
const relsXmlPath = this.#excelKeys.workbookRels;
|
394
|
-
const relsXml = this.#extractXmlFromSheet(this.#excelKeys.workbookRels);
|
394
|
+
const relsXml = await this.#extractXmlFromSheet(this.#excelKeys.workbookRels);
|
395
395
|
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
396
396
|
if (!relMatch || !relMatch[1]) {
|
397
397
|
throw new Error(`Relationship "${rId}" not found`);
|
@@ -433,7 +433,7 @@ class TemplateMemory {
|
|
433
433
|
// Read [Content_Types].xml
|
434
434
|
// Update [Content_Types].xml
|
435
435
|
const contentTypesPath = "[Content_Types].xml";
|
436
|
-
const contentTypesXml = this.#extractXmlFromSheet(contentTypesPath);
|
436
|
+
const contentTypesXml = await this.#extractXmlFromSheet(contentTypesPath);
|
437
437
|
const overrideTag = `<Override PartName="/xl/worksheets/${newSheetFilename}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
438
438
|
const updatedContentTypesXml = contentTypesXml.replace("</Types>", overrideTag + "</Types>");
|
439
439
|
await this.#set(contentTypesPath, Buffer.from(updatedContentTypesXml));
|
@@ -490,8 +490,8 @@ class TemplateMemory {
|
|
490
490
|
Utils.checkStartRow(startRowNumber);
|
491
491
|
Utils.checkRows(preparedRows);
|
492
492
|
// Find the sheet
|
493
|
-
const sheetPath = this.#getSheetPathByName(sheetName);
|
494
|
-
const sheetXml = this.#extractXmlFromSheet(sheetPath);
|
493
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
494
|
+
const sheetXml = await this.#extractXmlFromSheet(sheetPath);
|
495
495
|
let nextRow = 0;
|
496
496
|
if (!startRowNumber) {
|
497
497
|
// Find the last row
|
@@ -554,8 +554,8 @@ class TemplateMemory {
|
|
554
554
|
if (!sheetName)
|
555
555
|
throw new Error("Sheet name is required");
|
556
556
|
// Read XML workbook to find sheet name and path
|
557
|
-
const sheetPath = this.#getSheetPathByName(sheetName);
|
558
|
-
const sheetXml = this.#extractXmlFromSheet(sheetPath);
|
557
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
558
|
+
const sheetXml = await this.#extractXmlFromSheet(sheetPath);
|
559
559
|
const output = new memory_write_stream_js_1.MemoryWriteStream();
|
560
560
|
let inserted = false;
|
561
561
|
// --- Case 1: <sheetData>...</sheetData> on one line ---
|
@@ -0,0 +1,67 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.extractRowsFromSheetSync = extractRowsFromSheetSync;
|
4
|
+
const extract_xml_from_sheet_sync_js_1 = require("./extract-xml-from-sheet-sync.js");
|
5
|
+
/**
|
6
|
+
* Parses a worksheet (either as Buffer or string) to extract row data,
|
7
|
+
* last row number, and merge cell information from Excel XML format.
|
8
|
+
*
|
9
|
+
* This function is particularly useful for processing Excel files in
|
10
|
+
* Open XML Spreadsheet format (.xlsx).
|
11
|
+
*
|
12
|
+
* @param {Buffer|string} sheet - The worksheet content to parse, either as:
|
13
|
+
* - Buffer (binary Excel sheet)
|
14
|
+
* - string (raw XML content)
|
15
|
+
* @returns {{
|
16
|
+
* rows: string[],
|
17
|
+
* lastRowNumber: number,
|
18
|
+
* mergeCells: {ref: string}[]
|
19
|
+
* }} An object containing:
|
20
|
+
* - rows: Array of raw XML strings for each <row> element
|
21
|
+
* - lastRowNumber: Highest row number found in the sheet (1-based)
|
22
|
+
* - mergeCells: Array of merged cell ranges (e.g., [{ref: "A1:B2"}])
|
23
|
+
* @throws {Error} If the sheetData section is not found in the XML
|
24
|
+
*/
|
25
|
+
function extractRowsFromSheetSync(sheet) {
|
26
|
+
// Convert Buffer input to XML string if needed
|
27
|
+
const xml = typeof sheet === "string"
|
28
|
+
? sheet
|
29
|
+
: (0, extract_xml_from_sheet_sync_js_1.extractXmlFromSheetSync)(sheet);
|
30
|
+
// Extract the sheetData section containing all rows
|
31
|
+
const sheetDataMatch = xml.match(/<sheetData[^>]*>([\s\S]*?)<\/sheetData>/);
|
32
|
+
if (!sheetDataMatch) {
|
33
|
+
throw new Error("sheetData not found in worksheet XML");
|
34
|
+
}
|
35
|
+
const sheetDataContent = sheetDataMatch[1] || "";
|
36
|
+
// Extract all <row> elements using regex
|
37
|
+
const rowMatches = [...sheetDataContent.matchAll(/<row\b[^>]*\/>|<row\b[^>]*>[\s\S]*?<\/row>/g)];
|
38
|
+
const rows = rowMatches.map(match => match[0]);
|
39
|
+
// Calculate the highest row number present in the sheet
|
40
|
+
const lastRowNumber = rowMatches
|
41
|
+
.map(match => {
|
42
|
+
// Extract row number from r="..." attribute (1-based)
|
43
|
+
const rowNumMatch = match[0].match(/r="(\d+)"/);
|
44
|
+
return rowNumMatch?.[1] ? parseInt(rowNumMatch[1], 10) : null;
|
45
|
+
})
|
46
|
+
.filter((row) => row !== null) // Type guard to filter out nulls
|
47
|
+
.reduce((max, current) => Math.max(max, current), 0); // Find maximum row number
|
48
|
+
// Extract all merged cell ranges from the worksheet
|
49
|
+
const mergeCells = [];
|
50
|
+
const mergeCellsMatch = xml.match(/<mergeCells[^>]*>([\s\S]*?)<\/mergeCells>/);
|
51
|
+
if (mergeCellsMatch) {
|
52
|
+
// Find all mergeCell entries with ref attributes
|
53
|
+
const mergeCellMatches = mergeCellsMatch[1]?.match(/<mergeCell[^>]+ref="([^"]+)"[^>]*>/g) || [];
|
54
|
+
mergeCellMatches.forEach(match => {
|
55
|
+
const refMatch = match.match(/ref="([^"]+)"/);
|
56
|
+
if (refMatch?.[1]) {
|
57
|
+
mergeCells.push({ ref: refMatch[1] }); // Store the cell range (e.g., "A1:B2")
|
58
|
+
}
|
59
|
+
});
|
60
|
+
}
|
61
|
+
return {
|
62
|
+
lastRowNumber,
|
63
|
+
mergeCells,
|
64
|
+
rows,
|
65
|
+
xml,
|
66
|
+
};
|
67
|
+
}
|
@@ -22,9 +22,11 @@ const extract_xml_from_sheet_js_1 = require("./extract-xml-from-sheet.js");
|
|
22
22
|
* - mergeCells: Array of merged cell ranges (e.g., [{ref: "A1:B2"}])
|
23
23
|
* @throws {Error} If the sheetData section is not found in the XML
|
24
24
|
*/
|
25
|
-
function extractRowsFromSheet(sheet) {
|
25
|
+
async function extractRowsFromSheet(sheet) {
|
26
26
|
// Convert Buffer input to XML string if needed
|
27
|
-
const xml = typeof sheet === "string"
|
27
|
+
const xml = typeof sheet === "string"
|
28
|
+
? sheet
|
29
|
+
: await (0, extract_xml_from_sheet_js_1.extractXmlFromSheet)(sheet);
|
28
30
|
// Extract the sheetData section containing all rows
|
29
31
|
const sheetDataMatch = xml.match(/<sheetData[^>]*>([\s\S]*?)<\/sheetData>/);
|
30
32
|
if (!sheetDataMatch) {
|
@@ -0,0 +1,43 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.extractXmlFromSheetSync = extractXmlFromSheetSync;
|
4
|
+
const node_zlib_1 = require("node:zlib");
|
5
|
+
/**
|
6
|
+
* Extracts and parses XML content from an Excel worksheet file (e.g., xl/worksheets/sheet1.xml).
|
7
|
+
* Handles both compressed (raw deflate) and uncompressed (plain XML) formats.
|
8
|
+
*
|
9
|
+
* This function is designed to work with Excel Open XML (.xlsx) worksheet files,
|
10
|
+
* which may be stored in either compressed or uncompressed format within the ZIP container.
|
11
|
+
*
|
12
|
+
* @param {Buffer} buffer - The file content to process, which may be:
|
13
|
+
* - Raw XML text
|
14
|
+
* - Deflate-compressed XML data (without zlib headers)
|
15
|
+
* @returns {string} - The extracted XML content as a UTF-8 string
|
16
|
+
* @throws {Error} - If the buffer is empty or cannot be processed
|
17
|
+
*/
|
18
|
+
function extractXmlFromSheetSync(buffer) {
|
19
|
+
if (!buffer || buffer.length === 0) {
|
20
|
+
throw new Error("Empty buffer provided");
|
21
|
+
}
|
22
|
+
let xml;
|
23
|
+
// Check if the buffer starts with an XML declaration (<?xml)
|
24
|
+
const head = buffer.subarray(0, 1024).toString("utf8").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").trim();
|
25
|
+
const isXml = /^<\?xml[\s\S]+<\w+[\s>]/.test(head);
|
26
|
+
if (isXml) {
|
27
|
+
// Case 1: Already uncompressed XML - convert directly to string
|
28
|
+
xml = buffer.toString("utf8");
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
// Case 2: Attempt to decompress as raw deflate data
|
32
|
+
try {
|
33
|
+
xml = (0, node_zlib_1.inflateRawSync)(buffer).toString("utf8");
|
34
|
+
}
|
35
|
+
catch (err) {
|
36
|
+
throw new Error("Failed to decompress sheet XML: " + (err instanceof Error ? err.message : String(err)));
|
37
|
+
}
|
38
|
+
}
|
39
|
+
// Sanitize XML by removing control characters (except tab, newline, carriage return)
|
40
|
+
// This handles potential corruption from binary data or encoding issues
|
41
|
+
xml = xml.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "");
|
42
|
+
return xml;
|
43
|
+
}
|
@@ -1,7 +1,12 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
6
|
exports.extractXmlFromSheet = extractXmlFromSheet;
|
4
|
-
const
|
7
|
+
const node_util_1 = __importDefault(require("node:util"));
|
8
|
+
const node_zlib_1 = __importDefault(require("node:zlib"));
|
9
|
+
const inflateRaw = node_util_1.default.promisify(node_zlib_1.default.inflateRaw);
|
5
10
|
/**
|
6
11
|
* Extracts and parses XML content from an Excel worksheet file (e.g., xl/worksheets/sheet1.xml).
|
7
12
|
* Handles both compressed (raw deflate) and uncompressed (plain XML) formats.
|
@@ -12,35 +17,30 @@ const pako_1 = require("pako");
|
|
12
17
|
* @param {Buffer} buffer - The file content to process, which may be:
|
13
18
|
* - Raw XML text
|
14
19
|
* - Deflate-compressed XML data (without zlib headers)
|
15
|
-
* @returns {string} - The extracted XML content as a UTF-8 string
|
20
|
+
* @returns {Promise<string>} - The extracted XML content as a UTF-8 string
|
16
21
|
* @throws {Error} - If the buffer is empty or cannot be processed
|
17
22
|
*/
|
18
|
-
function extractXmlFromSheet(buffer) {
|
23
|
+
async function extractXmlFromSheet(buffer) {
|
19
24
|
if (!buffer || buffer.length === 0) {
|
20
25
|
throw new Error("Empty buffer provided");
|
21
26
|
}
|
22
27
|
let xml;
|
23
28
|
// Check if the buffer starts with an XML declaration (<?xml)
|
24
|
-
const
|
25
|
-
|
29
|
+
const head = buffer.subarray(0, 1024).toString("utf8").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").trim();
|
30
|
+
const isXml = /^<\?xml[\s\S]+<\w+[\s>]/.test(head);
|
31
|
+
if (isXml) {
|
26
32
|
// Case 1: Already uncompressed XML - convert directly to string
|
27
33
|
xml = buffer.toString("utf8");
|
28
34
|
}
|
29
35
|
else {
|
30
36
|
// Case 2: Attempt to decompress as raw deflate data
|
31
|
-
|
32
|
-
|
33
|
-
if (inflated && inflated.includes("<sheetData")) {
|
34
|
-
xml = inflated;
|
37
|
+
try {
|
38
|
+
xml = (await inflateRaw(buffer)).toString("utf8");
|
35
39
|
}
|
36
|
-
|
37
|
-
throw new Error("
|
40
|
+
catch (err) {
|
41
|
+
throw new Error("Failed to decompress sheet XML: " + (err instanceof Error ? err.message : String(err)));
|
38
42
|
}
|
39
43
|
}
|
40
|
-
// Fallback: If no XML obtained yet, try direct UTF-8 conversion
|
41
|
-
if (!xml) {
|
42
|
-
xml = buffer.toString("utf8");
|
43
|
-
}
|
44
44
|
// Sanitize XML by removing control characters (except tab, newline, carriage return)
|
45
45
|
// This handles potential corruption from binary data or encoding issues
|
46
46
|
xml = xml.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "");
|
@@ -15,7 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
15
15
|
};
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
17
17
|
__exportStar(require("./build-merged-sheet.js"), exports);
|
18
|
+
__exportStar(require("./extract-rows-from-sheet-sync.js"), exports);
|
18
19
|
__exportStar(require("./extract-rows-from-sheet.js"), exports);
|
20
|
+
__exportStar(require("./extract-xml-from-sheet-sync.js"), exports);
|
19
21
|
__exportStar(require("./extract-xml-from-sheet.js"), exports);
|
20
|
-
__exportStar(require("./extract-xml-from-system-content.js"), exports);
|
21
22
|
__exportStar(require("./shift-row-indices.js"), exports);
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import * as Utils from "./utils/index.js";
|
2
|
+
import * as Xml from "./xml/index.js";
|
3
|
+
/**
|
4
|
+
* Merges rows from other Excel files into a base Excel file.
|
5
|
+
*
|
6
|
+
* This function is a process-friendly version of mergeSheetsToBaseFile.
|
7
|
+
* It takes a single object with the following properties:
|
8
|
+
* - additions: An array of objects with two properties:
|
9
|
+
* - files: A dictionary of file paths to their corresponding XML content
|
10
|
+
* - sheetIndexes: The 1-based indexes of the sheet to extract rows from
|
11
|
+
* - baseFiles: A dictionary of file paths to their corresponding XML content
|
12
|
+
* - baseSheetIndex: The 1-based index of the sheet in the base file to add rows to
|
13
|
+
* - gap: The number of empty rows to insert between each added section
|
14
|
+
* - sheetNamesToRemove: The names of sheets to remove from the output file
|
15
|
+
* - sheetsToRemove: The 1-based indices of sheets to remove from the output file
|
16
|
+
*
|
17
|
+
* The function returns a dictionary of file paths to their corresponding XML content.
|
18
|
+
*/
|
19
|
+
export function mergeSheetsToBaseFileProcessSync(data) {
|
20
|
+
const { additions, baseFiles, baseSheetIndex, gap, sheetNamesToRemove, sheetsToRemove, } = data;
|
21
|
+
const basePath = `xl/worksheets/sheet${baseSheetIndex}.xml`;
|
22
|
+
if (!baseFiles[basePath]) {
|
23
|
+
throw new Error(`Base file does not contain ${basePath}`);
|
24
|
+
}
|
25
|
+
const { lastRowNumber, mergeCells: baseMergeCells, rows: baseRows, xml, } = Xml.extractRowsFromSheetSync(baseFiles[basePath]);
|
26
|
+
const allRows = [...baseRows];
|
27
|
+
const allMergeCells = [...baseMergeCells];
|
28
|
+
let currentRowOffset = lastRowNumber + gap;
|
29
|
+
for (const { files, sheetIndexes } of additions) {
|
30
|
+
for (const sheetIndex of sheetIndexes) {
|
31
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
32
|
+
if (!files[sheetPath]) {
|
33
|
+
throw new Error(`File does not contain ${sheetPath}`);
|
34
|
+
}
|
35
|
+
const { mergeCells, rows } = Xml.extractRowsFromSheetSync(files[sheetPath]);
|
36
|
+
const shiftedRows = Xml.shiftRowIndices(rows, currentRowOffset);
|
37
|
+
const shiftedMergeCells = mergeCells.map(cell => {
|
38
|
+
const [start, end] = cell.ref.split(":");
|
39
|
+
if (!start || !end) {
|
40
|
+
return cell;
|
41
|
+
}
|
42
|
+
const shiftedStart = Utils.shiftCellRef(start, currentRowOffset);
|
43
|
+
const shiftedEnd = Utils.shiftCellRef(end, currentRowOffset);
|
44
|
+
return { ...cell, ref: `${shiftedStart}:${shiftedEnd}` };
|
45
|
+
});
|
46
|
+
allRows.push(...shiftedRows);
|
47
|
+
allMergeCells.push(...shiftedMergeCells);
|
48
|
+
currentRowOffset += Utils.getMaxRowNumber(rows) + gap;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
const mergedXml = Xml.buildMergedSheet(xml, allRows, allMergeCells);
|
52
|
+
baseFiles[basePath] = mergedXml;
|
53
|
+
for (const sheetIndex of sheetsToRemove) {
|
54
|
+
const sheetPath = `xl/worksheets/sheet${sheetIndex}.xml`;
|
55
|
+
delete baseFiles[sheetPath];
|
56
|
+
if (baseFiles["xl/workbook.xml"]) {
|
57
|
+
baseFiles["xl/workbook.xml"] = Buffer.from(Utils.removeSheetFromWorkbook(baseFiles["xl/workbook.xml"].toString(), sheetIndex));
|
58
|
+
}
|
59
|
+
if (baseFiles["xl/_rels/workbook.xml.rels"]) {
|
60
|
+
baseFiles["xl/_rels/workbook.xml.rels"] = Buffer.from(Utils.removeSheetFromRels(baseFiles["xl/_rels/workbook.xml.rels"].toString(), sheetIndex));
|
61
|
+
}
|
62
|
+
if (baseFiles["[Content_Types].xml"]) {
|
63
|
+
baseFiles["[Content_Types].xml"] = Buffer.from(Utils.removeSheetFromContentTypes(baseFiles["[Content_Types].xml"].toString(), sheetIndex));
|
64
|
+
}
|
65
|
+
}
|
66
|
+
for (const sheetName of sheetNamesToRemove) {
|
67
|
+
Utils.removeSheetByName(baseFiles, sheetName);
|
68
|
+
}
|
69
|
+
}
|