@js-ak/excel-toolbox 1.4.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -62
- package/build/cjs/lib/template/index.js +1 -0
- package/build/cjs/lib/template/memory-write-stream.js +17 -0
- package/build/cjs/lib/template/template-fs.js +137 -57
- package/build/cjs/lib/template/template-memory.js +753 -0
- package/build/cjs/lib/template/utils/index.js +25 -0
- package/build/cjs/lib/template/utils/prepare-row-to-cells.js +17 -0
- package/build/cjs/lib/template/utils/regexp.js +32 -0
- package/build/cjs/lib/template/utils/update-dimension.js +15 -0
- package/build/cjs/lib/template/utils/validate-worksheet-xml.js +74 -74
- package/build/cjs/lib/template/utils/write-rows-to-stream.js +66 -22
- package/build/esm/lib/template/index.js +1 -0
- package/build/esm/lib/template/memory-write-stream.js +13 -0
- package/build/esm/lib/template/template-fs.js +134 -57
- package/build/esm/lib/template/template-memory.js +716 -0
- package/build/esm/lib/template/utils/index.js +2 -0
- package/build/esm/lib/template/utils/prepare-row-to-cells.js +14 -0
- package/build/esm/lib/template/utils/regexp.js +28 -0
- package/build/esm/lib/template/utils/update-dimension.js +15 -0
- package/build/esm/lib/template/utils/validate-worksheet-xml.js +74 -74
- package/build/esm/lib/template/utils/write-rows-to-stream.js +66 -22
- package/build/types/lib/template/index.d.ts +1 -0
- package/build/types/lib/template/memory-write-stream.d.ts +6 -0
- package/build/types/lib/template/template-fs.d.ts +2 -0
- package/build/types/lib/template/template-memory.d.ts +146 -0
- package/build/types/lib/template/utils/index.d.ts +2 -0
- package/build/types/lib/template/utils/prepare-row-to-cells.d.ts +5 -0
- package/build/types/lib/template/utils/regexp.d.ts +24 -0
- package/build/types/lib/template/utils/update-dimension.d.ts +15 -0
- package/build/types/lib/template/utils/write-rows-to-stream.d.ts +28 -11
- package/package.json +5 -3
@@ -10,10 +10,34 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
11
11
|
o[k2] = m[k];
|
12
12
|
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
19
|
+
var ownKeys = function(o) {
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
21
|
+
var ar = [];
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
23
|
+
return ar;
|
24
|
+
};
|
25
|
+
return ownKeys(o);
|
26
|
+
};
|
27
|
+
return function (mod) {
|
28
|
+
if (mod && mod.__esModule) return mod;
|
29
|
+
var result = {};
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
31
|
+
__setModuleDefault(result, mod);
|
32
|
+
return result;
|
33
|
+
};
|
34
|
+
})();
|
13
35
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
14
36
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
15
37
|
};
|
16
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
39
|
+
exports.Common = void 0;
|
40
|
+
exports.Common = __importStar(require("../../utils/index.js"));
|
17
41
|
__exportStar(require("./apply-replacements.js"), exports);
|
18
42
|
__exportStar(require("./check-row.js"), exports);
|
19
43
|
__exportStar(require("./check-rows.js"), exports);
|
@@ -30,6 +54,7 @@ __exportStar(require("./process-merge-cells.js"), exports);
|
|
30
54
|
__exportStar(require("./process-merge-finalize.js"), exports);
|
31
55
|
__exportStar(require("./process-rows.js"), exports);
|
32
56
|
__exportStar(require("./process-shared-strings.js"), exports);
|
57
|
+
__exportStar(require("./regexp.js"), exports);
|
33
58
|
__exportStar(require("./to-excel-column-object.js"), exports);
|
34
59
|
__exportStar(require("./update-dimension.js"), exports);
|
35
60
|
__exportStar(require("./validate-worksheet-xml.js"), exports);
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.prepareRowToCells = prepareRowToCells;
|
4
|
+
const column_index_to_letter_js_1 = require("./column-index-to-letter.js");
|
5
|
+
const escape_xml_js_1 = require("./escape-xml.js");
|
6
|
+
function prepareRowToCells(row, rowNumber) {
|
7
|
+
return row.map((value, colIndex) => {
|
8
|
+
const colLetter = (0, column_index_to_letter_js_1.columnIndexToLetter)(colIndex);
|
9
|
+
const cellRef = `${colLetter}${rowNumber}`;
|
10
|
+
const cellValue = (0, escape_xml_js_1.escapeXml)(String(value ?? ""));
|
11
|
+
return {
|
12
|
+
cellRef,
|
13
|
+
cellValue,
|
14
|
+
cellXml: `<c r="${cellRef}" t="inlineStr"><is><t>${cellValue}</t></is></c>`,
|
15
|
+
};
|
16
|
+
});
|
17
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.relationshipMatch = relationshipMatch;
|
4
|
+
exports.sheetMatch = sheetMatch;
|
5
|
+
/**
|
6
|
+
* Creates a regular expression to match a relationship element with a specific ID.
|
7
|
+
*
|
8
|
+
* @param {string} id - The relationship ID to match (e.g. "rId1")
|
9
|
+
* @returns {RegExp} A regular expression that matches a Relationship XML element with the given ID and captures the Target attribute value
|
10
|
+
* @example
|
11
|
+
* const regex = relationshipMatch("rId1");
|
12
|
+
* const xml = '<Relationship Id="rId1" Target="worksheets/sheet1.xml"/>';
|
13
|
+
* const match = xml.match(regex);
|
14
|
+
* // match[1] === "worksheets/sheet1.xml"
|
15
|
+
*/
|
16
|
+
function relationshipMatch(id) {
|
17
|
+
return new RegExp(`<Relationship[^>]+Id="${id}"[^>]+Target="([^"]+)"[^>]*/>`);
|
18
|
+
}
|
19
|
+
/**
|
20
|
+
* Creates a regular expression to match a sheet element with a specific name.
|
21
|
+
*
|
22
|
+
* @param {string} sheetName - The name of the sheet to match
|
23
|
+
* @returns {RegExp} A regular expression that matches a sheet XML element with the given name and captures the r:id attribute value
|
24
|
+
* @example
|
25
|
+
* const regex = sheetMatch("Sheet1");
|
26
|
+
* const xml = '<sheet name="Sheet1" sheetId="1" r:id="rId1"/>';
|
27
|
+
* const match = xml.match(regex);
|
28
|
+
* // match[1] === "rId1"
|
29
|
+
*/
|
30
|
+
function sheetMatch(sheetName) {
|
31
|
+
return new RegExp(`<sheet[^>]+name="${sheetName}"[^>]+r:id="([^"]+)"[^>]*/>`);
|
32
|
+
}
|
@@ -1,6 +1,21 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.updateDimension = updateDimension;
|
4
|
+
/**
|
5
|
+
* Updates the dimension element in an Excel worksheet XML string based on the actual cell references.
|
6
|
+
*
|
7
|
+
* This function scans the XML for all cell references and calculates the minimum and maximum
|
8
|
+
* column/row values to determine the actual used range in the worksheet. It then updates
|
9
|
+
* the dimension element to reflect this range.
|
10
|
+
*
|
11
|
+
* @param {string} xml - The worksheet XML string to process
|
12
|
+
* @returns {string} The XML string with updated dimension element
|
13
|
+
* @example
|
14
|
+
* // XML with cells from A1 to C3
|
15
|
+
* const xml = '....<dimension ref="A1:B2"/>.....<c r="C3">...</c>...';
|
16
|
+
* const updated = updateDimension(xml);
|
17
|
+
* // Returns XML with dimension updated to ref="A1:C3"
|
18
|
+
*/
|
4
19
|
function updateDimension(xml) {
|
5
20
|
const cellRefs = [...xml.matchAll(/<c r="([A-Z]+)(\d+)"/g)];
|
6
21
|
if (cellRefs.length === 0)
|
@@ -33,9 +33,7 @@ function validateWorksheetXml(xml) {
|
|
33
33
|
const requiredElements = [
|
34
34
|
{ name: "sheetViews", tag: "<sheetViews>" },
|
35
35
|
{ name: "sheetFormatPr", tag: "<sheetFormatPr" },
|
36
|
-
{ name: "cols", tag: "<cols>" },
|
37
36
|
{ name: "sheetData", tag: "<sheetData>" },
|
38
|
-
{ name: "mergeCells", tag: "<mergeCells" },
|
39
37
|
];
|
40
38
|
for (const { name, tag } of requiredElements) {
|
41
39
|
if (!xml.includes(tag)) {
|
@@ -100,59 +98,82 @@ function validateWorksheetXml(xml) {
|
|
100
98
|
}
|
101
99
|
}
|
102
100
|
// 4. Check mergeCells
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
const
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
101
|
+
if (xml.includes("<mergeCells")) {
|
102
|
+
const mergeCellsStart = xml.indexOf("<mergeCells");
|
103
|
+
const mergeCellsEnd = xml.indexOf("</mergeCells>");
|
104
|
+
if (mergeCellsStart === -1 || mergeCellsEnd === -1) {
|
105
|
+
return createError("Invalid mergeCells structure");
|
106
|
+
}
|
107
|
+
const mergeCellsContent = xml.substring(mergeCellsStart, mergeCellsEnd);
|
108
|
+
const countMatch = mergeCellsContent.match(/count="(\d+)"/);
|
109
|
+
if (!countMatch) {
|
110
|
+
return createError("Count attribute not specified for mergeCells");
|
111
|
+
}
|
112
|
+
const mergeCellTags = mergeCellsContent.match(/<mergeCell\s+ref="([A-Z]+\d+:[A-Z]+\d+)"\s*\/>/g);
|
113
|
+
if (!mergeCellTags) {
|
114
|
+
return createError("No merged cells found");
|
115
|
+
}
|
116
|
+
// Check if the number of mergeCells matches the count attribute
|
117
|
+
if (mergeCellTags.length !== parseInt(countMatch[1])) {
|
118
|
+
return createError("Mismatch in the number of merged cells", `Expected: ${countMatch[1]}, found: ${mergeCellTags.length}`);
|
119
|
+
}
|
120
|
+
// Check for duplicates of mergeCell
|
121
|
+
const mergeRefs = new Set();
|
122
|
+
const duplicates = new Set();
|
123
|
+
for (const mergeTag of mergeCellTags) {
|
124
|
+
const refMatch = mergeTag.match(/ref="([A-Z]+\d+:[A-Z]+\d+)"/);
|
125
|
+
if (!refMatch) {
|
126
|
+
return createError("Invalid merge cell format", `Tag: ${mergeTag}`);
|
127
|
+
}
|
128
|
+
const ref = refMatch[1];
|
129
|
+
if (mergeRefs.has(ref)) {
|
130
|
+
duplicates.add(ref);
|
131
|
+
}
|
132
|
+
else {
|
133
|
+
mergeRefs.add(ref);
|
134
|
+
}
|
132
135
|
}
|
133
|
-
|
134
|
-
|
136
|
+
if (duplicates.size > 0) {
|
137
|
+
return createError("Duplicates of merged cells found", `Duplicates: ${Array.from(duplicates).join(", ")}`);
|
138
|
+
}
|
139
|
+
// Check for overlapping merge ranges
|
140
|
+
const mergedRanges = Array.from(mergeRefs).map(ref => {
|
141
|
+
const [start, end] = ref.split(":");
|
142
|
+
return {
|
143
|
+
endCol: end.match(/[A-Z]+/)?.[0] || "",
|
144
|
+
endRow: parseInt(end.match(/\d+/)?.[0] || "0"),
|
145
|
+
startCol: start.match(/[A-Z]+/)?.[0] || "",
|
146
|
+
startRow: parseInt(start.match(/\d+/)?.[0] || "0"),
|
147
|
+
};
|
148
|
+
});
|
149
|
+
for (let i = 0; i < mergedRanges.length; i++) {
|
150
|
+
for (let j = i + 1; j < mergedRanges.length; j++) {
|
151
|
+
const a = mergedRanges[i];
|
152
|
+
const b = mergedRanges[j];
|
153
|
+
if (rangesIntersect(a, b)) {
|
154
|
+
return createError("Found intersecting merged cells", `Intersecting: ${getRangeString(a)} and ${getRangeString(b)}`);
|
155
|
+
}
|
156
|
+
}
|
135
157
|
}
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
const
|
153
|
-
|
154
|
-
|
155
|
-
return createError("Found intersecting merged cells", `Intersecting: ${getRangeString(a)} and ${getRangeString(b)}`);
|
158
|
+
// 6. Additional check: all mergeCell tags refer to existing cells
|
159
|
+
for (const mergeTag of mergeCellTags) {
|
160
|
+
const refMatch = mergeTag.match(/ref="([A-Z]+\d+:[A-Z]+\d+)"/);
|
161
|
+
if (!refMatch) {
|
162
|
+
return createError("Invalid merge cell format", `Tag: ${mergeTag}`);
|
163
|
+
}
|
164
|
+
const [cell1, cell2] = refMatch[1].split(":");
|
165
|
+
const cell1Col = cell1.match(/[A-Z]+/)?.[0];
|
166
|
+
const cell1Row = parseInt(cell1.match(/\d+/)?.[0] || "0");
|
167
|
+
const cell2Col = cell2.match(/[A-Z]+/)?.[0];
|
168
|
+
const cell2Row = parseInt(cell2.match(/\d+/)?.[0] || "0");
|
169
|
+
if (!cell1Col || !cell2Col || isNaN(cell1Row) || isNaN(cell2Row)) {
|
170
|
+
return createError("Invalid merged cell coordinates", `Merged cells: ${refMatch[1]}`);
|
171
|
+
}
|
172
|
+
// Check if the merged cells exist
|
173
|
+
const cell1Exists = allCells.some(c => c.row === cell1Row && c.col === cell1Col);
|
174
|
+
const cell2Exists = allCells.some(c => c.row === cell2Row && c.col === cell2Col);
|
175
|
+
if (!cell1Exists || !cell2Exists) {
|
176
|
+
return createError("Merged cell reference points to non-existent cells", `Merged cells: ${refMatch[1]}, missing: ${!cell1Exists ? `${cell1Col}${cell1Row}` : `${cell2Col}${cell2Row}`}`);
|
156
177
|
}
|
157
178
|
}
|
158
179
|
}
|
@@ -181,27 +202,6 @@ function validateWorksheetXml(xml) {
|
|
181
202
|
return createError("Cell is outside the specified area (by column)", `Cell: ${cell.col}${cell.row}, dimension: ${dimensionMatch[1]}`);
|
182
203
|
}
|
183
204
|
}
|
184
|
-
// 6. Additional check: all mergeCell tags refer to existing cells
|
185
|
-
for (const mergeTag of mergeCellTags) {
|
186
|
-
const refMatch = mergeTag.match(/ref="([A-Z]+\d+:[A-Z]+\d+)"/);
|
187
|
-
if (!refMatch) {
|
188
|
-
return createError("Invalid merge cell format", `Tag: ${mergeTag}`);
|
189
|
-
}
|
190
|
-
const [cell1, cell2] = refMatch[1].split(":");
|
191
|
-
const cell1Col = cell1.match(/[A-Z]+/)?.[0];
|
192
|
-
const cell1Row = parseInt(cell1.match(/\d+/)?.[0] || "0");
|
193
|
-
const cell2Col = cell2.match(/[A-Z]+/)?.[0];
|
194
|
-
const cell2Row = parseInt(cell2.match(/\d+/)?.[0] || "0");
|
195
|
-
if (!cell1Col || !cell2Col || isNaN(cell1Row) || isNaN(cell2Row)) {
|
196
|
-
return createError("Invalid merged cell coordinates", `Merged cells: ${refMatch[1]}`);
|
197
|
-
}
|
198
|
-
// Check if the merged cells exist
|
199
|
-
const cell1Exists = allCells.some(c => c.row === cell1Row && c.col === cell1Col);
|
200
|
-
const cell2Exists = allCells.some(c => c.row === cell2Row && c.col === cell2Col);
|
201
|
-
if (!cell1Exists || !cell2Exists) {
|
202
|
-
return createError("Merged cell reference points to non-existent cells", `Merged cells: ${refMatch[1]}, missing: ${!cell1Exists ? `${cell1Col}${cell1Row}` : `${cell2Col}${cell2Row}`}`);
|
203
|
-
}
|
204
|
-
}
|
205
205
|
return { isValid: true };
|
206
206
|
}
|
207
207
|
// A function to check if two ranges intersect
|
@@ -1,8 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.writeRowsToStream = writeRowsToStream;
|
4
|
-
const
|
5
|
-
const escape_xml_js_1 = require("./escape-xml.js");
|
4
|
+
const prepare_row_to_cells_js_1 = require("./prepare-row-to-cells.js");
|
6
5
|
/**
|
7
6
|
* Writes an async iterable of rows to an Excel XML file.
|
8
7
|
*
|
@@ -14,29 +13,74 @@ const escape_xml_js_1 = require("./escape-xml.js");
|
|
14
13
|
* for the first row written to the file. Subsequent rows are written
|
15
14
|
* with incrementing row numbers.
|
16
15
|
*
|
17
|
-
* @param output - A file write stream to write the Excel XML to.
|
18
|
-
* @param rows - An async iterable of rows, where each row is an array
|
19
|
-
*
|
20
|
-
* @param startRowNumber - The starting row number to use for the first
|
21
|
-
*
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
25
|
-
*
|
16
|
+
* @param {WritableLike} output - A file write stream to write the Excel XML to.
|
17
|
+
* @param {AsyncIterable<unknown[] | unknown[][]>} rows - An async iterable of rows, where each row is an array
|
18
|
+
* of values or an array of arrays of values.
|
19
|
+
* @param {number} startRowNumber - The starting row number to use for the first
|
20
|
+
* row written to the file.
|
21
|
+
* @returns {Promise<{
|
22
|
+
* dimension: {
|
23
|
+
* maxColumn: string;
|
24
|
+
* maxRow: number;
|
25
|
+
* minColumn: string;
|
26
|
+
* minRow: number;
|
27
|
+
* };
|
28
|
+
* rowNumber: number;
|
29
|
+
* }>} An object containing:
|
30
|
+
* - dimension: The boundaries of the written data (min/max columns and rows)
|
31
|
+
* - rowNumber: The last row number written to the file
|
26
32
|
*/
|
27
33
|
async function writeRowsToStream(output, rows, startRowNumber) {
|
28
34
|
let rowNumber = startRowNumber;
|
35
|
+
const dimension = {
|
36
|
+
maxColumn: "A",
|
37
|
+
maxRow: startRowNumber,
|
38
|
+
minColumn: "A",
|
39
|
+
minRow: startRowNumber,
|
40
|
+
};
|
41
|
+
// Функция для сравнения колонок (A < B, AA > Z и т.д.)
|
42
|
+
const compareColumns = (a, b) => {
|
43
|
+
if (a === b)
|
44
|
+
return 0;
|
45
|
+
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
|
46
|
+
};
|
47
|
+
const processRow = (row, currentRowNumber) => {
|
48
|
+
const cells = (0, prepare_row_to_cells_js_1.prepareRowToCells)(row, currentRowNumber);
|
49
|
+
if (cells.length === 0)
|
50
|
+
return;
|
51
|
+
output.write(`<row r="${currentRowNumber}">${cells.map(cell => cell.cellXml).join("")}</row>`);
|
52
|
+
// Обновление границ
|
53
|
+
const firstCellRef = cells[0]?.cellRef;
|
54
|
+
const lastCellRef = cells[cells.length - 1]?.cellRef;
|
55
|
+
if (firstCellRef) {
|
56
|
+
const colLetters = firstCellRef.match(/[A-Z]+/)?.[0] || "";
|
57
|
+
if (compareColumns(colLetters, dimension.minColumn) < 0) {
|
58
|
+
dimension.minColumn = colLetters;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
if (lastCellRef) {
|
62
|
+
const colLetters = lastCellRef.match(/[A-Z]+/)?.[0] || "";
|
63
|
+
if (compareColumns(colLetters, dimension.maxColumn) > 0) {
|
64
|
+
dimension.maxColumn = colLetters;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
dimension.maxRow = currentRowNumber;
|
68
|
+
};
|
29
69
|
for await (const row of rows) {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
const
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
70
|
+
if (!row.length)
|
71
|
+
continue;
|
72
|
+
if (Array.isArray(row[0])) {
|
73
|
+
for (const subRow of row) {
|
74
|
+
if (!subRow.length)
|
75
|
+
continue;
|
76
|
+
processRow(subRow, rowNumber);
|
77
|
+
rowNumber++;
|
78
|
+
}
|
79
|
+
}
|
80
|
+
else {
|
81
|
+
processRow(row, rowNumber);
|
82
|
+
rowNumber++;
|
83
|
+
}
|
40
84
|
}
|
41
|
-
return { rowNumber };
|
85
|
+
return { dimension, rowNumber };
|
42
86
|
}
|