@js-ak/excel-toolbox 1.3.2 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/lib/template/template-fs.js +458 -196
- package/build/cjs/lib/template/utils/apply-replacements.js +26 -0
- package/build/cjs/lib/template/utils/check-row.js +6 -11
- package/build/cjs/lib/template/utils/column-index-to-letter.js +14 -3
- package/build/cjs/lib/template/utils/extract-xml-declaration.js +22 -0
- package/build/cjs/lib/template/utils/get-by-path.js +18 -0
- package/build/cjs/lib/template/utils/get-max-row-number.js +1 -1
- package/build/cjs/lib/template/utils/index.js +9 -0
- package/build/cjs/lib/template/utils/process-merge-cells.js +40 -0
- package/build/cjs/lib/template/utils/process-merge-finalize.js +51 -0
- package/build/cjs/lib/template/utils/process-rows.js +160 -0
- package/build/cjs/lib/template/utils/process-shared-strings.js +45 -0
- package/build/cjs/lib/template/utils/to-excel-column-object.js +2 -10
- package/build/cjs/lib/template/utils/update-dimension.js +40 -0
- package/build/cjs/lib/template/utils/validate-worksheet-xml.js +231 -0
- package/build/cjs/lib/template/utils/write-rows-to-stream.js +2 -1
- package/build/cjs/lib/zip/create-with-stream.js +2 -2
- package/build/esm/lib/template/template-fs.js +458 -196
- package/build/esm/lib/template/utils/apply-replacements.js +22 -0
- package/build/esm/lib/template/utils/check-row.js +6 -11
- package/build/esm/lib/template/utils/column-index-to-letter.js +14 -3
- package/build/esm/lib/template/utils/extract-xml-declaration.js +19 -0
- package/build/esm/lib/template/utils/get-by-path.js +15 -0
- package/build/esm/lib/template/utils/get-max-row-number.js +1 -1
- package/build/esm/lib/template/utils/index.js +9 -0
- package/build/esm/lib/template/utils/process-merge-cells.js +37 -0
- package/build/esm/lib/template/utils/process-merge-finalize.js +48 -0
- package/build/esm/lib/template/utils/process-rows.js +157 -0
- package/build/esm/lib/template/utils/process-shared-strings.js +42 -0
- package/build/esm/lib/template/utils/to-excel-column-object.js +2 -10
- package/build/esm/lib/template/utils/update-dimension.js +37 -0
- package/build/esm/lib/template/utils/validate-worksheet-xml.js +228 -0
- package/build/esm/lib/template/utils/write-rows-to-stream.js +2 -1
- package/build/esm/lib/zip/create-with-stream.js +2 -2
- package/build/types/lib/template/template-fs.d.ts +24 -0
- package/build/types/lib/template/utils/apply-replacements.d.ts +13 -0
- package/build/types/lib/template/utils/check-row.d.ts +5 -10
- package/build/types/lib/template/utils/column-index-to-letter.d.ts +11 -3
- package/build/types/lib/template/utils/extract-xml-declaration.d.ts +14 -0
- package/build/types/lib/template/utils/get-by-path.d.ts +8 -0
- package/build/types/lib/template/utils/index.d.ts +9 -0
- package/build/types/lib/template/utils/process-merge-cells.d.ts +20 -0
- package/build/types/lib/template/utils/process-merge-finalize.d.ts +38 -0
- package/build/types/lib/template/utils/process-rows.d.ts +31 -0
- package/build/types/lib/template/utils/process-shared-strings.d.ts +20 -0
- package/build/types/lib/template/utils/update-dimension.d.ts +1 -0
- package/build/types/lib/template/utils/validate-worksheet-xml.d.ts +25 -0
- package/package.json +6 -4
@@ -0,0 +1,228 @@
|
|
1
|
+
/**
|
2
|
+
* Validates an Excel worksheet XML against the expected structure and rules.
|
3
|
+
*
|
4
|
+
* Checks the following:
|
5
|
+
* 1. XML starts with <?xml declaration
|
6
|
+
* 2. Root element is worksheet
|
7
|
+
* 3. Required elements are present
|
8
|
+
* 4. row numbers are in ascending order
|
9
|
+
* 5. No duplicate row numbers
|
10
|
+
* 6. No overlapping merge ranges
|
11
|
+
* 7. All cells are within the specified dimension
|
12
|
+
* 8. All mergeCell tags refer to existing cells
|
13
|
+
*
|
14
|
+
* @param xml The raw XML content of the worksheet
|
15
|
+
* @returns A ValidationResult object indicating if the XML is valid, and an error message if it's not
|
16
|
+
*/
|
17
|
+
export function validateWorksheetXml(xml) {
|
18
|
+
const createError = (message, details) => ({
|
19
|
+
error: { details, message },
|
20
|
+
isValid: false,
|
21
|
+
});
|
22
|
+
// 1. Check for XML declaration
|
23
|
+
if (!xml.startsWith("<?xml")) {
|
24
|
+
return createError("XML must start with <?xml> declaration");
|
25
|
+
}
|
26
|
+
if (!xml.includes("<worksheet") || !xml.includes("</worksheet>")) {
|
27
|
+
return createError("Root element worksheet not found");
|
28
|
+
}
|
29
|
+
// 2. Check for required elements
|
30
|
+
const requiredElements = [
|
31
|
+
{ name: "sheetViews", tag: "<sheetViews>" },
|
32
|
+
{ name: "sheetFormatPr", tag: "<sheetFormatPr" },
|
33
|
+
{ name: "cols", tag: "<cols>" },
|
34
|
+
{ name: "sheetData", tag: "<sheetData>" },
|
35
|
+
{ name: "mergeCells", tag: "<mergeCells" },
|
36
|
+
];
|
37
|
+
for (const { name, tag } of requiredElements) {
|
38
|
+
if (!xml.includes(tag)) {
|
39
|
+
return createError(`Missing required element ${name}`);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
// 3. Extract and validate sheetData
|
43
|
+
const sheetDataStart = xml.indexOf("<sheetData>");
|
44
|
+
const sheetDataEnd = xml.indexOf("</sheetData>");
|
45
|
+
if (sheetDataStart === -1 || sheetDataEnd === -1) {
|
46
|
+
return createError("Invalid sheetData structure");
|
47
|
+
}
|
48
|
+
const sheetDataContent = xml.substring(sheetDataStart + 10, sheetDataEnd);
|
49
|
+
const rows = sheetDataContent.split("</row>");
|
50
|
+
if (rows.length < 2) {
|
51
|
+
return createError("SheetData should contain at least one row");
|
52
|
+
}
|
53
|
+
// Collect information about all rows and cells
|
54
|
+
const allRows = [];
|
55
|
+
const allCells = [];
|
56
|
+
let prevRowNum = 0;
|
57
|
+
for (const row of rows.slice(0, -1)) {
|
58
|
+
if (!row.includes("<row ")) {
|
59
|
+
return createError("Row tag not found", `Fragment: ${row.substring(0, 50)}...`);
|
60
|
+
}
|
61
|
+
if (!row.includes("<c ")) {
|
62
|
+
return createError("Row does not contain any cells", `Row: ${row.substring(0, 50)}...`);
|
63
|
+
}
|
64
|
+
// Extract row number
|
65
|
+
const rowNumMatch = row.match(/<row\s+r="(\d+)"/);
|
66
|
+
if (!rowNumMatch) {
|
67
|
+
return createError("Row number (attribute r) not specified", `Row: ${row.substring(0, 50)}...`);
|
68
|
+
}
|
69
|
+
const rowNum = parseInt(rowNumMatch[1]);
|
70
|
+
// Check for duplicate row numbers
|
71
|
+
if (allRows.includes(rowNum)) {
|
72
|
+
return createError("Duplicate row number found", `Row number: ${rowNum}`);
|
73
|
+
}
|
74
|
+
allRows.push(rowNum);
|
75
|
+
// Check row number order (should be in ascending order)
|
76
|
+
if (rowNum <= prevRowNum) {
|
77
|
+
return createError("Row order is broken", `Current row: ${rowNum}, previous: ${prevRowNum}`);
|
78
|
+
}
|
79
|
+
prevRowNum = rowNum;
|
80
|
+
// Extract all cells in the row
|
81
|
+
const cells = row.match(/<c\s+r="([A-Z]+)(\d+)"/g) || [];
|
82
|
+
for (const cell of cells) {
|
83
|
+
const match = cell.match(/<c\s+r="([A-Z]+)(\d+)"/);
|
84
|
+
if (!match) {
|
85
|
+
return createError("Invalid cell format", `Cell: ${cell}`);
|
86
|
+
}
|
87
|
+
const col = match[1];
|
88
|
+
const cellRowNum = parseInt(match[2]);
|
89
|
+
// Check row number match for each cell
|
90
|
+
if (cellRowNum !== rowNum) {
|
91
|
+
return createError("Row number mismatch in cell", `Expected: ${rowNum}, found: ${cellRowNum} in cell ${col}${cellRowNum}`);
|
92
|
+
}
|
93
|
+
allCells.push({
|
94
|
+
col,
|
95
|
+
row: rowNum,
|
96
|
+
});
|
97
|
+
}
|
98
|
+
}
|
99
|
+
// 4. Check mergeCells
|
100
|
+
const mergeCellsStart = xml.indexOf("<mergeCells");
|
101
|
+
const mergeCellsEnd = xml.indexOf("</mergeCells>");
|
102
|
+
if (mergeCellsStart === -1 || mergeCellsEnd === -1) {
|
103
|
+
return createError("Invalid mergeCells structure");
|
104
|
+
}
|
105
|
+
const mergeCellsContent = xml.substring(mergeCellsStart, mergeCellsEnd);
|
106
|
+
const countMatch = mergeCellsContent.match(/count="(\d+)"/);
|
107
|
+
if (!countMatch) {
|
108
|
+
return createError("Count attribute not specified for mergeCells");
|
109
|
+
}
|
110
|
+
const mergeCellTags = mergeCellsContent.match(/<mergeCell\s+ref="([A-Z]+\d+:[A-Z]+\d+)"\s*\/>/g);
|
111
|
+
if (!mergeCellTags) {
|
112
|
+
return createError("No merged cells found");
|
113
|
+
}
|
114
|
+
// Check if the number of mergeCells matches the count attribute
|
115
|
+
if (mergeCellTags.length !== parseInt(countMatch[1])) {
|
116
|
+
return createError("Mismatch in the number of merged cells", `Expected: ${countMatch[1]}, found: ${mergeCellTags.length}`);
|
117
|
+
}
|
118
|
+
// Check for duplicates of mergeCell
|
119
|
+
const mergeRefs = new Set();
|
120
|
+
const duplicates = new Set();
|
121
|
+
for (const mergeTag of mergeCellTags) {
|
122
|
+
const refMatch = mergeTag.match(/ref="([A-Z]+\d+:[A-Z]+\d+)"/);
|
123
|
+
if (!refMatch) {
|
124
|
+
return createError("Invalid merge cell format", `Tag: ${mergeTag}`);
|
125
|
+
}
|
126
|
+
const ref = refMatch[1];
|
127
|
+
if (mergeRefs.has(ref)) {
|
128
|
+
duplicates.add(ref);
|
129
|
+
}
|
130
|
+
else {
|
131
|
+
mergeRefs.add(ref);
|
132
|
+
}
|
133
|
+
}
|
134
|
+
if (duplicates.size > 0) {
|
135
|
+
return createError("Duplicates of merged cells found", `Duplicates: ${Array.from(duplicates).join(", ")}`);
|
136
|
+
}
|
137
|
+
// Check for overlapping merge ranges
|
138
|
+
const mergedRanges = Array.from(mergeRefs).map(ref => {
|
139
|
+
const [start, end] = ref.split(":");
|
140
|
+
return {
|
141
|
+
endCol: end.match(/[A-Z]+/)?.[0] || "",
|
142
|
+
endRow: parseInt(end.match(/\d+/)?.[0] || "0"),
|
143
|
+
startCol: start.match(/[A-Z]+/)?.[0] || "",
|
144
|
+
startRow: parseInt(start.match(/\d+/)?.[0] || "0"),
|
145
|
+
};
|
146
|
+
});
|
147
|
+
for (let i = 0; i < mergedRanges.length; i++) {
|
148
|
+
for (let j = i + 1; j < mergedRanges.length; j++) {
|
149
|
+
const a = mergedRanges[i];
|
150
|
+
const b = mergedRanges[j];
|
151
|
+
if (rangesIntersect(a, b)) {
|
152
|
+
return createError("Found intersecting merged cells", `Intersecting: ${getRangeString(a)} and ${getRangeString(b)}`);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
// 5. Check dimension and match with real data
|
157
|
+
const dimensionMatch = xml.match(/<dimension\s+ref="([A-Z]+\d+:[A-Z]+\d+)"\s*\/>/);
|
158
|
+
if (!dimensionMatch) {
|
159
|
+
return createError("Data range (dimension) is not specified");
|
160
|
+
}
|
161
|
+
const [startCell, endCell] = dimensionMatch[1].split(":");
|
162
|
+
const startCol = startCell.match(/[A-Z]+/)?.[0];
|
163
|
+
const startRow = parseInt(startCell.match(/\d+/)?.[0] || "0");
|
164
|
+
const endCol = endCell.match(/[A-Z]+/)?.[0];
|
165
|
+
const endRow = parseInt(endCell.match(/\d+/)?.[0] || "0");
|
166
|
+
if (!startCol || !endCol || isNaN(startRow) || isNaN(endRow)) {
|
167
|
+
return createError("Invalid dimension format", `Dimension: ${dimensionMatch[1]}`);
|
168
|
+
}
|
169
|
+
const startColNum = colToNumber(startCol);
|
170
|
+
const endColNum = colToNumber(endCol);
|
171
|
+
// Check if all cells are within the dimension
|
172
|
+
for (const cell of allCells) {
|
173
|
+
const colNum = colToNumber(cell.col);
|
174
|
+
if (cell.row < startRow || cell.row > endRow) {
|
175
|
+
return createError("Cell is outside the specified area (by row)", `Cell: ${cell.col}${cell.row}, dimension: ${dimensionMatch[1]}`);
|
176
|
+
}
|
177
|
+
if (colNum < startColNum || colNum > endColNum) {
|
178
|
+
return createError("Cell is outside the specified area (by column)", `Cell: ${cell.col}${cell.row}, dimension: ${dimensionMatch[1]}`);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
// 6. Additional check: all mergeCell tags refer to existing cells
|
182
|
+
for (const mergeTag of mergeCellTags) {
|
183
|
+
const refMatch = mergeTag.match(/ref="([A-Z]+\d+:[A-Z]+\d+)"/);
|
184
|
+
if (!refMatch) {
|
185
|
+
return createError("Invalid merge cell format", `Tag: ${mergeTag}`);
|
186
|
+
}
|
187
|
+
const [cell1, cell2] = refMatch[1].split(":");
|
188
|
+
const cell1Col = cell1.match(/[A-Z]+/)?.[0];
|
189
|
+
const cell1Row = parseInt(cell1.match(/\d+/)?.[0] || "0");
|
190
|
+
const cell2Col = cell2.match(/[A-Z]+/)?.[0];
|
191
|
+
const cell2Row = parseInt(cell2.match(/\d+/)?.[0] || "0");
|
192
|
+
if (!cell1Col || !cell2Col || isNaN(cell1Row) || isNaN(cell2Row)) {
|
193
|
+
return createError("Invalid merged cell coordinates", `Merged cells: ${refMatch[1]}`);
|
194
|
+
}
|
195
|
+
// Check if the merged cells exist
|
196
|
+
const cell1Exists = allCells.some(c => c.row === cell1Row && c.col === cell1Col);
|
197
|
+
const cell2Exists = allCells.some(c => c.row === cell2Row && c.col === cell2Col);
|
198
|
+
if (!cell1Exists || !cell2Exists) {
|
199
|
+
return createError("Merged cell reference points to non-existent cells", `Merged cells: ${refMatch[1]}, missing: ${!cell1Exists ? `${cell1Col}${cell1Row}` : `${cell2Col}${cell2Row}`}`);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
return { isValid: true };
|
203
|
+
}
|
204
|
+
// A function to check if two ranges intersect
|
205
|
+
function rangesIntersect(a, b) {
|
206
|
+
const aStartColNum = colToNumber(a.startCol);
|
207
|
+
const aEndColNum = colToNumber(a.endCol);
|
208
|
+
const bStartColNum = colToNumber(b.startCol);
|
209
|
+
const bEndColNum = colToNumber(b.endCol);
|
210
|
+
// Check if the rows intersect
|
211
|
+
const rowsIntersect = !(a.endRow < b.startRow || a.startRow > b.endRow);
|
212
|
+
// Check if the columns intersect
|
213
|
+
const colsIntersect = !(aEndColNum < bStartColNum || aStartColNum > bEndColNum);
|
214
|
+
return rowsIntersect && colsIntersect;
|
215
|
+
}
|
216
|
+
// Function to get the range string1
|
217
|
+
function getRangeString(range) {
|
218
|
+
return `${range.startCol}${range.startRow}:${range.endCol}${range.endRow}`;
|
219
|
+
}
|
220
|
+
// Function to convert column letters to numbers
|
221
|
+
function colToNumber(col) {
|
222
|
+
let num = 0;
|
223
|
+
for (let i = 0; i < col.length; i++) {
|
224
|
+
num = num * 26 + (col.charCodeAt(i) - 64);
|
225
|
+
}
|
226
|
+
return num;
|
227
|
+
}
|
228
|
+
;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { columnIndexToLetter } from "./column-index-to-letter.js";
|
2
|
+
import { escapeXml } from "./escape-xml.js";
|
2
3
|
/**
|
3
4
|
* Writes an async iterable of rows to an Excel XML file.
|
4
5
|
*
|
@@ -27,7 +28,7 @@ export async function writeRowsToStream(output, rows, startRowNumber) {
|
|
27
28
|
const cells = row.map((value, colIndex) => {
|
28
29
|
const colLetter = columnIndexToLetter(colIndex);
|
29
30
|
const cellRef = `${colLetter}${rowNumber}`;
|
30
|
-
const cellValue = String(value ?? "");
|
31
|
+
const cellValue = escapeXml(String(value ?? ""));
|
31
32
|
return `<c r="${cellRef}" t="inlineStr"><is><t>${cellValue}</t></is></c>`;
|
32
33
|
});
|
33
34
|
// Write the row to the file
|
@@ -52,9 +52,9 @@ export async function createWithStream(fileKeys, destination, output) {
|
|
52
52
|
});
|
53
53
|
const collectCompressed = new PassThrough();
|
54
54
|
collectCompressed.on("data", chunk => {
|
55
|
-
//
|
55
|
+
// Count compressed bytes
|
56
56
|
compSize += chunk.length;
|
57
|
-
//
|
57
|
+
// Save compressed chunk
|
58
58
|
compressedChunks.push(chunk);
|
59
59
|
});
|
60
60
|
// Run all transforms in pipeline: read -> count size -> CRC -> deflate -> collect compressed
|
@@ -29,6 +29,30 @@ 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
|
+
/**
|
33
|
+
* Copies a sheet from the template to a new name.
|
34
|
+
*
|
35
|
+
* @param {string} sourceName - The name of the sheet to copy.
|
36
|
+
* @param {string} newName - The new name for the sheet.
|
37
|
+
* @returns {Promise<void>}
|
38
|
+
* @throws {Error} If the sheet with the source name does not exist.
|
39
|
+
* @throws {Error} If a sheet with the new name already exists.
|
40
|
+
* @experimental This API is experimental and might change in future versions.
|
41
|
+
*/
|
42
|
+
copySheet(sourceName: string, newName: string): Promise<void>;
|
43
|
+
/**
|
44
|
+
* Replaces placeholders in the given sheet with values from the replacements map.
|
45
|
+
*
|
46
|
+
* The function searches for placeholders in the format `${key}` within the sheet
|
47
|
+
* content, where `key` corresponds to a path in the replacements object.
|
48
|
+
* If a value is found for the key, it replaces the placeholder with the value.
|
49
|
+
* If no value is found, the original placeholder remains unchanged.
|
50
|
+
*
|
51
|
+
* @param sheetName - The name of the sheet to be replaced.
|
52
|
+
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
53
|
+
* @returns A promise that resolves when the substitution is complete.
|
54
|
+
*/
|
55
|
+
substitute(sheetName: string, replacements: Record<string, unknown>): Promise<void>;
|
32
56
|
/**
|
33
57
|
* Inserts rows into a specific sheet in the template.
|
34
58
|
*
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* Replaces placeholders in the given content string with values from the replacements map.
|
3
|
+
*
|
4
|
+
* The function searches for placeholders in the format `${key}` within the content
|
5
|
+
* string, where `key` corresponds to a path in the replacements object.
|
6
|
+
* If a value is found for the key, it replaces the placeholder with the value.
|
7
|
+
* If no value is found, the original placeholder remains unchanged.
|
8
|
+
*
|
9
|
+
* @param content - The string containing placeholders to be replaced.
|
10
|
+
* @param replacements - An object where keys represent placeholder paths and values are the replacements.
|
11
|
+
* @returns A new string with placeholders replaced by corresponding values from the replacements object.
|
12
|
+
*/
|
13
|
+
export declare const applyReplacements: (content: string, replacements: Record<string, unknown>) => string;
|
@@ -1,14 +1,9 @@
|
|
1
1
|
/**
|
2
|
-
* Validates
|
2
|
+
* Validates an object representing a single row of data to ensure that its keys
|
3
|
+
* are valid Excel column references. Throws an error if any of the keys are
|
4
|
+
* invalid.
|
3
5
|
*
|
4
|
-
*
|
5
|
-
*
|
6
|
-
* not match this pattern, an error is thrown with a message indicating the
|
7
|
-
* invalid cell reference.
|
8
|
-
*
|
9
|
-
* @param row - An object representing a row of data, where keys are cell
|
10
|
-
* references and values are strings.
|
11
|
-
*
|
12
|
-
* @throws {Error} If any key in the row is not a valid column letter.
|
6
|
+
* @param row An object with string keys that represent the cell references and
|
7
|
+
* string values that represent the values of those cells.
|
13
8
|
*/
|
14
9
|
export declare function checkRow(row: Record<string, string>): void;
|
@@ -1,7 +1,15 @@
|
|
1
1
|
/**
|
2
|
-
* Converts a
|
2
|
+
* Converts a zero-based column index to its corresponding Excel column letter.
|
3
3
|
*
|
4
|
-
* @
|
5
|
-
* @
|
4
|
+
* @throws Will throw an error if the input is not a positive integer.
|
5
|
+
* @param {number} index - The zero-based index of the column to convert.
|
6
|
+
* @returns {string} The corresponding Excel column letter.
|
7
|
+
*
|
8
|
+
* @example
|
9
|
+
* columnIndexToLetter(0); // returns "A"
|
10
|
+
* columnIndexToLetter(25); // returns "Z"
|
11
|
+
* columnIndexToLetter(26); // returns "AA"
|
12
|
+
* columnIndexToLetter(51); // returns "AZ"
|
13
|
+
* columnIndexToLetter(52); // returns "BA"
|
6
14
|
*/
|
7
15
|
export declare function columnIndexToLetter(index: number): string;
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/**
|
2
|
+
* Extracts the XML declaration from a given XML string.
|
3
|
+
*
|
4
|
+
* The XML declaration is a string that looks like `<?xml ...?>` and is usually
|
5
|
+
* present at the beginning of an XML file. It contains information about the
|
6
|
+
* XML version, encoding, and standalone status.
|
7
|
+
*
|
8
|
+
* This function returns `null` if the input string does not have a valid XML
|
9
|
+
* declaration.
|
10
|
+
*
|
11
|
+
* @param xmlString - The XML string to extract the declaration from.
|
12
|
+
* @returns The extracted XML declaration string, or `null`.
|
13
|
+
*/
|
14
|
+
export declare function extractXmlDeclaration(xmlString: string): string | null;
|
@@ -0,0 +1,8 @@
|
|
1
|
+
/**
|
2
|
+
* Gets a value from an object by a given path.
|
3
|
+
*
|
4
|
+
* @param obj - The object to search.
|
5
|
+
* @param path - The path to the value, separated by dots.
|
6
|
+
* @returns The value at the given path, or undefined if not found.
|
7
|
+
*/
|
8
|
+
export declare function getByPath(obj: unknown, path: string): unknown;
|
@@ -1,11 +1,20 @@
|
|
1
|
+
export * from "./apply-replacements.js";
|
1
2
|
export * from "./check-row.js";
|
2
3
|
export * from "./check-rows.js";
|
3
4
|
export * from "./check-start-row.js";
|
4
5
|
export * from "./column-index-to-letter.js";
|
5
6
|
export * from "./escape-xml.js";
|
7
|
+
export * from "./extract-xml-declaration.js";
|
8
|
+
export * from "./get-by-path.js";
|
6
9
|
export * from "./get-max-row-number.js";
|
7
10
|
export * from "./get-rows-above.js";
|
8
11
|
export * from "./get-rows-below.js";
|
9
12
|
export * from "./parse-rows.js";
|
13
|
+
export * from "./process-merge-cells.js";
|
14
|
+
export * from "./process-merge-finalize.js";
|
15
|
+
export * from "./process-rows.js";
|
16
|
+
export * from "./process-shared-strings.js";
|
10
17
|
export * from "./to-excel-column-object.js";
|
18
|
+
export * from "./update-dimension.js";
|
19
|
+
export * from "./validate-worksheet-xml.js";
|
11
20
|
export * from "./write-rows-to-stream.js";
|
@@ -0,0 +1,20 @@
|
|
1
|
+
/**
|
2
|
+
* Processes the sheet XML by extracting the initial <mergeCells> block and
|
3
|
+
* extracting all merge cell references. The function returns an object with
|
4
|
+
* three properties:
|
5
|
+
* - `initialMergeCells`: The initial <mergeCells> block as a string array.
|
6
|
+
* - `mergeCellMatches`: An array of objects with `from` and `to` properties,
|
7
|
+
* representing the merge cell references.
|
8
|
+
* - `modifiedXml`: The modified sheet XML with the <mergeCells> block removed.
|
9
|
+
*
|
10
|
+
* @param sheetXml - The sheet XML string.
|
11
|
+
* @returns An object with the above three properties.
|
12
|
+
*/
|
13
|
+
export declare function processMergeCells(sheetXml: string): {
|
14
|
+
initialMergeCells: string[];
|
15
|
+
mergeCellMatches: {
|
16
|
+
from: string;
|
17
|
+
to: string;
|
18
|
+
}[];
|
19
|
+
modifiedXml: string;
|
20
|
+
};
|
@@ -0,0 +1,38 @@
|
|
1
|
+
/**
|
2
|
+
* Finalizes the processing of the merged sheet by updating the merge cells and
|
3
|
+
* inserting them into the sheet XML. It also returns the modified sheet XML and
|
4
|
+
* shared strings.
|
5
|
+
*
|
6
|
+
* @param {object} data - An object containing the following properties:
|
7
|
+
* - `initialMergeCells`: The initial merge cells from the original sheet.
|
8
|
+
* - `lastIndex`: The last processed position in the sheet XML.
|
9
|
+
* - `mergeCellMatches`: An array of objects with `from` and `to` properties,
|
10
|
+
* describing the merge cells.
|
11
|
+
* - `resultRows`: An array of processed XML rows.
|
12
|
+
* - `rowShift`: The total row shift.
|
13
|
+
* - `sharedStrings`: An array of shared strings.
|
14
|
+
* - `sharedStringsHeader`: The XML declaration of the shared strings.
|
15
|
+
* - `sheetMergeCells`: An array of merge cell XML strings.
|
16
|
+
* - `sheetXml`: The original sheet XML string.
|
17
|
+
*
|
18
|
+
* @returns An object with two properties:
|
19
|
+
* - `shared`: The modified shared strings XML string.
|
20
|
+
* - `sheet`: The modified sheet XML string with updated merge cells.
|
21
|
+
*/
|
22
|
+
export declare function processMergeFinalize(data: {
|
23
|
+
initialMergeCells: string[];
|
24
|
+
lastIndex: number;
|
25
|
+
mergeCellMatches: {
|
26
|
+
from: string;
|
27
|
+
to: string;
|
28
|
+
}[];
|
29
|
+
resultRows: string[];
|
30
|
+
rowShift: number;
|
31
|
+
sharedStrings: string[];
|
32
|
+
sharedStringsHeader: string | null;
|
33
|
+
sheetMergeCells: string[];
|
34
|
+
sheetXml: string;
|
35
|
+
}): {
|
36
|
+
shared: string;
|
37
|
+
sheet: string;
|
38
|
+
};
|
@@ -0,0 +1,31 @@
|
|
1
|
+
/**
|
2
|
+
* Processes a sheet XML by replacing table placeholders with real data and adjusting row numbers accordingly.
|
3
|
+
*
|
4
|
+
* @param data - An object containing the following properties:
|
5
|
+
* - `replacements`: An object where keys are table names and values are arrays of objects with table data.
|
6
|
+
* - `sharedIndexMap`: A Map of shared string indexes by their text content.
|
7
|
+
* - `mergeCellMatches`: An array of objects with `from` and `to` properties, describing the merge cells.
|
8
|
+
* - `sharedStrings`: An array of shared strings.
|
9
|
+
* - `sheetMergeCells`: An array of merge cell XML strings.
|
10
|
+
* - `sheetXml`: The sheet XML string.
|
11
|
+
*
|
12
|
+
* @returns An object with the following properties:
|
13
|
+
* - `lastIndex`: The last processed position in the sheet XML.
|
14
|
+
* - `resultRows`: An array of processed XML rows.
|
15
|
+
* - `rowShift`: The total row shift.
|
16
|
+
*/
|
17
|
+
export declare function processRows(data: {
|
18
|
+
replacements: Record<string, unknown>;
|
19
|
+
sharedIndexMap: Map<string, number>;
|
20
|
+
mergeCellMatches: {
|
21
|
+
from: string;
|
22
|
+
to: string;
|
23
|
+
}[];
|
24
|
+
sharedStrings: string[];
|
25
|
+
sheetMergeCells: string[];
|
26
|
+
sheetXml: string;
|
27
|
+
}): {
|
28
|
+
lastIndex: number;
|
29
|
+
resultRows: string[];
|
30
|
+
rowShift: number;
|
31
|
+
};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
/**
|
2
|
+
* Processes the shared strings XML by extracting the XML declaration,
|
3
|
+
* extracting individual <si> elements, and storing them in an array.
|
4
|
+
*
|
5
|
+
* The function returns an object with four properties:
|
6
|
+
* - sharedIndexMap: A map of shared string content to their corresponding index
|
7
|
+
* - sharedStrings: An array of shared strings
|
8
|
+
* - sharedStringsHeader: The XML declaration of the shared strings
|
9
|
+
* - sheetMergeCells: An empty array, which is only used for type compatibility
|
10
|
+
* with the return type of processBuild.
|
11
|
+
*
|
12
|
+
* @param sharedStringsXml - The XML string of the shared strings
|
13
|
+
* @returns An object with the four properties above
|
14
|
+
*/
|
15
|
+
export declare function processSharedStrings(sharedStringsXml: string): {
|
16
|
+
sharedIndexMap: Map<string, number>;
|
17
|
+
sharedStrings: string[];
|
18
|
+
sharedStringsHeader: string | null;
|
19
|
+
sheetMergeCells: string[];
|
20
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare function updateDimension(xml: string): string;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
interface ValidationResult {
|
2
|
+
isValid: boolean;
|
3
|
+
error?: {
|
4
|
+
message: string;
|
5
|
+
details?: string;
|
6
|
+
};
|
7
|
+
}
|
8
|
+
/**
|
9
|
+
* Validates an Excel worksheet XML against the expected structure and rules.
|
10
|
+
*
|
11
|
+
* Checks the following:
|
12
|
+
* 1. XML starts with <?xml declaration
|
13
|
+
* 2. Root element is worksheet
|
14
|
+
* 3. Required elements are present
|
15
|
+
* 4. row numbers are in ascending order
|
16
|
+
* 5. No duplicate row numbers
|
17
|
+
* 6. No overlapping merge ranges
|
18
|
+
* 7. All cells are within the specified dimension
|
19
|
+
* 8. All mergeCell tags refer to existing cells
|
20
|
+
*
|
21
|
+
* @param xml The raw XML content of the worksheet
|
22
|
+
* @returns A ValidationResult object indicating if the XML is valid, and an error message if it's not
|
23
|
+
*/
|
24
|
+
export declare function validateWorksheetXml(xml: string): ValidationResult;
|
25
|
+
export {};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@js-ak/excel-toolbox",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.4.1",
|
4
4
|
"description": "excel-toolbox",
|
5
5
|
"publishConfig": {
|
6
6
|
"access": "public",
|
@@ -38,13 +38,14 @@
|
|
38
38
|
"package.json"
|
39
39
|
],
|
40
40
|
"scripts": {
|
41
|
-
"build": "npm run build:cjs && npm run build:esm && npm run postbuild:
|
41
|
+
"build": "npm run build:cjs && npm run build:esm && npm run postbuild:cjs && npm run postbuild:esm",
|
42
42
|
"build:esm": "tsc -p tsconfig.esm.json",
|
43
43
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
44
44
|
"lint": "eslint . --ext .ts",
|
45
45
|
"postbuild:esm": "node scripts/write-esm-package.js",
|
46
46
|
"postbuild:cjs": "node scripts/write-cjs-package.js",
|
47
|
-
"test": "
|
47
|
+
"test": "vitest run",
|
48
|
+
"test:watch": "vitest"
|
48
49
|
},
|
49
50
|
"repository": {
|
50
51
|
"type": "git",
|
@@ -75,7 +76,8 @@
|
|
75
76
|
"globals": "16.0.0",
|
76
77
|
"semantic-release": "24.0.0",
|
77
78
|
"typescript": "5.8.3",
|
78
|
-
"typescript-eslint": "8.29.0"
|
79
|
+
"typescript-eslint": "8.29.0",
|
80
|
+
"vitest": "3.1.1"
|
79
81
|
},
|
80
82
|
"dependencies": {
|
81
83
|
"pako": "2.1.0"
|