@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.
Files changed (48) hide show
  1. package/build/cjs/lib/template/template-fs.js +458 -196
  2. package/build/cjs/lib/template/utils/apply-replacements.js +26 -0
  3. package/build/cjs/lib/template/utils/check-row.js +6 -11
  4. package/build/cjs/lib/template/utils/column-index-to-letter.js +14 -3
  5. package/build/cjs/lib/template/utils/extract-xml-declaration.js +22 -0
  6. package/build/cjs/lib/template/utils/get-by-path.js +18 -0
  7. package/build/cjs/lib/template/utils/get-max-row-number.js +1 -1
  8. package/build/cjs/lib/template/utils/index.js +9 -0
  9. package/build/cjs/lib/template/utils/process-merge-cells.js +40 -0
  10. package/build/cjs/lib/template/utils/process-merge-finalize.js +51 -0
  11. package/build/cjs/lib/template/utils/process-rows.js +160 -0
  12. package/build/cjs/lib/template/utils/process-shared-strings.js +45 -0
  13. package/build/cjs/lib/template/utils/to-excel-column-object.js +2 -10
  14. package/build/cjs/lib/template/utils/update-dimension.js +40 -0
  15. package/build/cjs/lib/template/utils/validate-worksheet-xml.js +231 -0
  16. package/build/cjs/lib/template/utils/write-rows-to-stream.js +2 -1
  17. package/build/cjs/lib/zip/create-with-stream.js +2 -2
  18. package/build/esm/lib/template/template-fs.js +458 -196
  19. package/build/esm/lib/template/utils/apply-replacements.js +22 -0
  20. package/build/esm/lib/template/utils/check-row.js +6 -11
  21. package/build/esm/lib/template/utils/column-index-to-letter.js +14 -3
  22. package/build/esm/lib/template/utils/extract-xml-declaration.js +19 -0
  23. package/build/esm/lib/template/utils/get-by-path.js +15 -0
  24. package/build/esm/lib/template/utils/get-max-row-number.js +1 -1
  25. package/build/esm/lib/template/utils/index.js +9 -0
  26. package/build/esm/lib/template/utils/process-merge-cells.js +37 -0
  27. package/build/esm/lib/template/utils/process-merge-finalize.js +48 -0
  28. package/build/esm/lib/template/utils/process-rows.js +157 -0
  29. package/build/esm/lib/template/utils/process-shared-strings.js +42 -0
  30. package/build/esm/lib/template/utils/to-excel-column-object.js +2 -10
  31. package/build/esm/lib/template/utils/update-dimension.js +37 -0
  32. package/build/esm/lib/template/utils/validate-worksheet-xml.js +228 -0
  33. package/build/esm/lib/template/utils/write-rows-to-stream.js +2 -1
  34. package/build/esm/lib/zip/create-with-stream.js +2 -2
  35. package/build/types/lib/template/template-fs.d.ts +24 -0
  36. package/build/types/lib/template/utils/apply-replacements.d.ts +13 -0
  37. package/build/types/lib/template/utils/check-row.d.ts +5 -10
  38. package/build/types/lib/template/utils/column-index-to-letter.d.ts +11 -3
  39. package/build/types/lib/template/utils/extract-xml-declaration.d.ts +14 -0
  40. package/build/types/lib/template/utils/get-by-path.d.ts +8 -0
  41. package/build/types/lib/template/utils/index.d.ts +9 -0
  42. package/build/types/lib/template/utils/process-merge-cells.d.ts +20 -0
  43. package/build/types/lib/template/utils/process-merge-finalize.d.ts +38 -0
  44. package/build/types/lib/template/utils/process-rows.d.ts +31 -0
  45. package/build/types/lib/template/utils/process-shared-strings.d.ts +20 -0
  46. package/build/types/lib/template/utils/update-dimension.d.ts +1 -0
  47. package/build/types/lib/template/utils/validate-worksheet-xml.d.ts +25 -0
  48. package/package.json +6 -4
@@ -0,0 +1,22 @@
1
+ import { getByPath } from "./get-by-path.js";
2
+ /**
3
+ * Replaces placeholders in the given content string with values from the replacements map.
4
+ *
5
+ * The function searches for placeholders in the format `${key}` within the content
6
+ * string, where `key` corresponds to a path in the replacements object.
7
+ * If a value is found for the key, it replaces the placeholder with the value.
8
+ * If no value is found, the original placeholder remains unchanged.
9
+ *
10
+ * @param content - The string containing placeholders to be replaced.
11
+ * @param replacements - An object where keys represent placeholder paths and values are the replacements.
12
+ * @returns A new string with placeholders replaced by corresponding values from the replacements object.
13
+ */
14
+ export const applyReplacements = (content, replacements) => {
15
+ if (!content) {
16
+ return "";
17
+ }
18
+ return content.replace(/\$\{([^}]+)\}/g, (match, path) => {
19
+ const value = getByPath(replacements, path);
20
+ return value !== undefined ? String(value) : match;
21
+ });
22
+ };
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Validates that each key in the given row object is a valid cell reference.
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
- * This function checks that all keys in the provided row object are composed
5
- * only of column letters (A-Z, case insensitive). If a key is found that does
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 function checkRow(row) {
15
10
  for (const key of Object.keys(row)) {
16
- if (!/^[A-Z]+$/i.test(key)) {
11
+ if (!/^[A-Z]+$/i.test(key) || !/^[A-Z]$|^[A-Z][A-Z]$|^[A-Z][A-Z][A-Z]$/i.test(key)) {
17
12
  throw new Error(`Invalid cell reference "${key}" in row. Only column letters (like "A", "B", "C") are allowed.`);
18
13
  }
19
14
  }
@@ -1,10 +1,21 @@
1
1
  /**
2
- * Converts a 0-based column index to an Excel-style letter (A, B, ..., Z, AA, AB, ...).
2
+ * Converts a zero-based column index to its corresponding Excel column letter.
3
3
  *
4
- * @param index - The 0-based column index.
5
- * @returns The Excel-style letter for the given column index.
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 function columnIndexToLetter(index) {
16
+ if (!Number.isInteger(index) || index < 0) {
17
+ throw new Error(`Invalid column index: ${index}. Must be a positive integer.`);
18
+ }
8
19
  let letters = "";
9
20
  while (index >= 0) {
10
21
  letters = String.fromCharCode((index % 26) + 65) + letters;
@@ -0,0 +1,19 @@
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 function extractXmlDeclaration(xmlString) {
15
+ // const declarationRegex = /^<\?xml\s+[^?]+\?>/;
16
+ const declarationRegex = /^<\?xml\s+version\s*=\s*["'][^"']+["'](\s+(encoding|standalone)\s*=\s*["'][^"']+["'])*\s*\?>/;
17
+ const match = xmlString.trim().match(declarationRegex);
18
+ return match ? match[0] : null;
19
+ }
@@ -0,0 +1,15 @@
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 function getByPath(obj, path) {
9
+ return path.split(".").reduce((acc, key) => {
10
+ if (acc && typeof acc === "object" && key in acc) {
11
+ return acc[key];
12
+ }
13
+ return undefined;
14
+ }, obj);
15
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export function getMaxRowNumber(line) {
8
8
  let result = 1;
9
- const rowMatches = [...line.matchAll(/<row[^>]+r="(\d+)"[^>]*>/g)];
9
+ const rowMatches = [...line.matchAll(/<row[^>]+r="(\d+)"[^>]*>/gi)];
10
10
  for (const match of rowMatches) {
11
11
  const rowNum = parseInt(match[1], 10);
12
12
  if (rowNum >= result) {
@@ -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,37 @@
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 function processMergeCells(sheetXml) {
14
+ // Regular expression for finding <mergeCells> block
15
+ const mergeCellsBlockRegex = /<mergeCells[^>]*>[\s\S]*?<\/mergeCells>/;
16
+ // Find the first <mergeCells> block (if there are multiple, in xlsx usually there is only one)
17
+ const mergeCellsBlockMatch = sheetXml.match(mergeCellsBlockRegex);
18
+ const initialMergeCells = [];
19
+ const mergeCellMatches = [];
20
+ if (mergeCellsBlockMatch) {
21
+ const mergeCellsBlock = mergeCellsBlockMatch[0];
22
+ initialMergeCells.push(mergeCellsBlock);
23
+ // Extract <mergeCell ref="A1:B2"/> from this block
24
+ const mergeCellRegex = /<mergeCell ref="([A-Z]+\d+):([A-Z]+\d+)"\/>/g;
25
+ for (const match of mergeCellsBlock.matchAll(mergeCellRegex)) {
26
+ mergeCellMatches.push({ from: match[1], to: match[2] });
27
+ }
28
+ }
29
+ // Remove the <mergeCells> block from the XML
30
+ const modifiedXml = sheetXml.replace(mergeCellsBlockRegex, "");
31
+ return {
32
+ initialMergeCells,
33
+ mergeCellMatches,
34
+ modifiedXml,
35
+ };
36
+ }
37
+ ;
@@ -0,0 +1,48 @@
1
+ import { updateDimension } from "./update-dimension.js";
2
+ /**
3
+ * Finalizes the processing of the merged sheet by updating the merge cells and
4
+ * inserting them into the sheet XML. It also returns the modified sheet XML and
5
+ * shared strings.
6
+ *
7
+ * @param {object} data - An object containing the following properties:
8
+ * - `initialMergeCells`: The initial merge cells from the original sheet.
9
+ * - `lastIndex`: The last processed position in the sheet XML.
10
+ * - `mergeCellMatches`: An array of objects with `from` and `to` properties,
11
+ * describing the merge cells.
12
+ * - `resultRows`: An array of processed XML rows.
13
+ * - `rowShift`: The total row shift.
14
+ * - `sharedStrings`: An array of shared strings.
15
+ * - `sharedStringsHeader`: The XML declaration of the shared strings.
16
+ * - `sheetMergeCells`: An array of merge cell XML strings.
17
+ * - `sheetXml`: The original sheet XML string.
18
+ *
19
+ * @returns An object with two properties:
20
+ * - `shared`: The modified shared strings XML string.
21
+ * - `sheet`: The modified sheet XML string with updated merge cells.
22
+ */
23
+ export function processMergeFinalize(data) {
24
+ const { initialMergeCells, lastIndex, mergeCellMatches, resultRows, rowShift, sharedStrings, sharedStringsHeader, sheetMergeCells, sheetXml, } = data;
25
+ for (const { from, to } of mergeCellMatches) {
26
+ const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
27
+ const [, toCol, toRow] = to.match(/^([A-Z]+)(\d+)$/);
28
+ const fromRowNum = Number(fromRow);
29
+ // These rows have already been processed, don't add duplicates
30
+ if (fromRowNum <= lastIndex)
31
+ continue;
32
+ const newFrom = `${fromCol}${fromRowNum + rowShift}`;
33
+ const newTo = `${toCol}${Number(toRow) + rowShift}`;
34
+ sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
35
+ }
36
+ resultRows.push(sheetXml.slice(lastIndex));
37
+ // Form XML for mergeCells if there are any
38
+ const mergeXml = sheetMergeCells.length
39
+ ? `<mergeCells count="${sheetMergeCells.length}">${sheetMergeCells.join("")}</mergeCells>`
40
+ : initialMergeCells;
41
+ // Insert mergeCells before the closing sheetData tag
42
+ const sheetWithMerge = resultRows.join("").replace(/<\/sheetData>/, `</sheetData>${mergeXml}`);
43
+ // Return modified sheet XML and shared strings
44
+ return {
45
+ shared: `${sharedStringsHeader}\n<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${sharedStrings.length}" uniqueCount="${sharedStrings.length}">${sharedStrings.join("")}</sst>`,
46
+ sheet: updateDimension(sheetWithMerge),
47
+ };
48
+ }
@@ -0,0 +1,157 @@
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 function processRows(data) {
18
+ const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetMergeCells, sheetXml, } = data;
19
+ const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
20
+ // Array for storing resulting XML rows
21
+ const resultRows = [];
22
+ // Previous position of processed part of XML
23
+ let lastIndex = 0;
24
+ // Shift for row numbers
25
+ let rowShift = 0;
26
+ // Regular expression for finding <row> elements
27
+ const rowRegex = /<row[^>]*?>[\s\S]*?<\/row>/g;
28
+ // Process each <row> element
29
+ for (const match of sheetXml.matchAll(rowRegex)) {
30
+ // Full XML row
31
+ const fullRow = match[0];
32
+ // Start position of the row in XML
33
+ const matchStart = match.index;
34
+ // End position of the row in XML
35
+ const matchEnd = matchStart + fullRow.length;
36
+ // Add the intermediate XML chunk (if any) between the previous and the current row
37
+ if (lastIndex !== matchStart) {
38
+ resultRows.push(sheetXml.slice(lastIndex, matchStart));
39
+ }
40
+ lastIndex = matchEnd;
41
+ // Get row number from r attribute
42
+ const originalRowNumber = parseInt(fullRow.match(/<row[^>]* r="(\d+)"/)?.[1] ?? "1", 10);
43
+ // Update row number based on rowShift
44
+ const shiftedRowNumber = originalRowNumber + rowShift;
45
+ // Find shared string indexes in cells of the current row
46
+ const sharedValueIndexes = [];
47
+ // Regular expression for finding a cell
48
+ const cellRegex = /<c[^>]*?r="([A-Z]+\d+)"[^>]*?>([\s\S]*?)<\/c>/g;
49
+ for (const cell of fullRow.matchAll(cellRegex)) {
50
+ const cellTag = cell[0];
51
+ // Check if the cell is a shared string
52
+ const isShared = /t="s"/.test(cellTag);
53
+ const valueMatch = cellTag.match(/<v>(\d+)<\/v>/);
54
+ if (isShared && valueMatch) {
55
+ sharedValueIndexes.push(parseInt(valueMatch[1], 10));
56
+ }
57
+ }
58
+ // Get the text content of shared strings by their indexes
59
+ const sharedTexts = sharedValueIndexes.map(i => sharedStrings[i]?.replace(/<\/?si>/g, "") ?? "");
60
+ // Find table placeholders in shared strings
61
+ const tablePlaceholders = sharedTexts.flatMap(e => [...e.matchAll(TABLE_REGEX)]);
62
+ // If there are no table placeholders, just shift the row
63
+ if (tablePlaceholders.length === 0) {
64
+ const updatedRow = fullRow
65
+ .replace(/(<row[^>]* r=")(\d+)(")/, `$1${shiftedRowNumber}$3`)
66
+ .replace(/<c r="([A-Z]+)(\d+)"/g, (_, col) => `<c r="${col}${shiftedRowNumber}"`);
67
+ resultRows.push(updatedRow);
68
+ // Update mergeCells for regular row with rowShift
69
+ const calculatedRowNumber = originalRowNumber + rowShift;
70
+ for (const { from, to } of mergeCellMatches) {
71
+ const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
72
+ const [, toCol] = to.match(/^([A-Z]+)(\d+)$/);
73
+ if (Number(fromRow) === calculatedRowNumber) {
74
+ const newFrom = `${fromCol}${shiftedRowNumber}`;
75
+ const newTo = `${toCol}${shiftedRowNumber}`;
76
+ sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
77
+ }
78
+ }
79
+ continue;
80
+ }
81
+ // Get the table name from the first placeholder
82
+ const firstMatch = tablePlaceholders[0];
83
+ const tableName = firstMatch?.[1];
84
+ if (!tableName)
85
+ throw new Error("Table name not found");
86
+ // Get data for replacement from replacements
87
+ const array = replacements[tableName];
88
+ if (!array)
89
+ continue;
90
+ if (!Array.isArray(array))
91
+ throw new Error("Table data is not an array");
92
+ const tableRowStart = shiftedRowNumber;
93
+ // Find mergeCells to duplicate (mergeCells that start with the current row)
94
+ const mergeCellsToDuplicate = mergeCellMatches.filter(({ from }) => {
95
+ const match = from.match(/^([A-Z]+)(\d+)$/);
96
+ if (!match)
97
+ return false;
98
+ // Row number of the merge cell start position is in the second group
99
+ const rowNumber = match[2];
100
+ return Number(rowNumber) === tableRowStart;
101
+ });
102
+ // Change the current row to multiple rows from the data array
103
+ for (let i = 0; i < array.length; i++) {
104
+ const rowData = array[i];
105
+ let newRow = fullRow;
106
+ // Replace placeholders in shared strings with real data
107
+ sharedValueIndexes.forEach((originalIdx, idx) => {
108
+ const originalText = sharedTexts[idx];
109
+ if (!originalText)
110
+ throw new Error("Shared value not found");
111
+ // Replace placeholders ${tableName.field} with real data from array data
112
+ const replacedText = originalText.replace(TABLE_REGEX, (_, tbl, field) => tbl === tableName ? String(rowData?.[field] ?? "") : "");
113
+ // Add new text to shared strings if it doesn't exist
114
+ let newIndex;
115
+ if (sharedIndexMap.has(replacedText)) {
116
+ newIndex = sharedIndexMap.get(replacedText);
117
+ }
118
+ else {
119
+ newIndex = sharedStrings.length;
120
+ sharedIndexMap.set(replacedText, newIndex);
121
+ sharedStrings.push(`<si>${replacedText}</si>`);
122
+ }
123
+ // Replace the shared string index in the cell
124
+ newRow = newRow.replace(`<v>${originalIdx}</v>`, `<v>${newIndex}</v>`);
125
+ });
126
+ // Update row number and cell references
127
+ const newRowNum = shiftedRowNumber + i;
128
+ newRow = newRow
129
+ .replace(/<row[^>]* r="\d+"/, rowTag => rowTag.replace(/r="\d+"/, `r="${newRowNum}"`))
130
+ .replace(/<c r="([A-Z]+)\d+"/g, (_, col) => `<c r="${col}${newRowNum}"`);
131
+ resultRows.push(newRow);
132
+ // Add duplicate mergeCells for new rows
133
+ for (const { from, to } of mergeCellsToDuplicate) {
134
+ const [, colFrom, rowFrom] = from.match(/^([A-Z]+)(\d+)$/);
135
+ const [, colTo, rowTo] = to.match(/^([A-Z]+)(\d+)$/);
136
+ const newFrom = `${colFrom}${Number(rowFrom) + i}`;
137
+ const newTo = `${colTo}${Number(rowTo) + i}`;
138
+ sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
139
+ }
140
+ }
141
+ // It increases the row shift by the number of added rows minus one replaced
142
+ rowShift += array.length - 1;
143
+ const delta = array.length - 1;
144
+ const calculatedRowNumber = originalRowNumber + rowShift - array.length + 1;
145
+ if (delta > 0) {
146
+ for (const merge of mergeCellMatches) {
147
+ const fromRow = parseInt(merge.from.match(/\d+$/)[0], 10);
148
+ if (fromRow > calculatedRowNumber) {
149
+ merge.from = merge.from.replace(/\d+$/, r => `${parseInt(r) + delta}`);
150
+ merge.to = merge.to.replace(/\d+$/, r => `${parseInt(r) + delta}`);
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return { lastIndex, resultRows, rowShift };
156
+ }
157
+ ;
@@ -0,0 +1,42 @@
1
+ import { extractXmlDeclaration } from "./extract-xml-declaration.js";
2
+ /**
3
+ * Processes the shared strings XML by extracting the XML declaration,
4
+ * extracting individual <si> elements, and storing them in an array.
5
+ *
6
+ * The function returns an object with four properties:
7
+ * - sharedIndexMap: A map of shared string content to their corresponding index
8
+ * - sharedStrings: An array of shared strings
9
+ * - sharedStringsHeader: The XML declaration of the shared strings
10
+ * - sheetMergeCells: An empty array, which is only used for type compatibility
11
+ * with the return type of processBuild.
12
+ *
13
+ * @param sharedStringsXml - The XML string of the shared strings
14
+ * @returns An object with the four properties above
15
+ */
16
+ export function processSharedStrings(sharedStringsXml) {
17
+ // Final list of merged cells with all changes
18
+ const sheetMergeCells = [];
19
+ // Array for storing shared strings
20
+ const sharedStrings = [];
21
+ const sharedStringsHeader = extractXmlDeclaration(sharedStringsXml);
22
+ // Map for fast lookup of shared string index by content
23
+ const sharedIndexMap = new Map();
24
+ // Regular expression for finding <si> elements (shared string items)
25
+ const siRegex = /<si>([\s\S]*?)<\/si>/g;
26
+ // Parse sharedStringsXml and fill sharedStrings and sharedIndexMap
27
+ for (const match of sharedStringsXml.matchAll(siRegex)) {
28
+ const content = match[1];
29
+ if (!content)
30
+ continue;
31
+ const fullSi = `<si>${content}</si>`;
32
+ sharedIndexMap.set(content, sharedStrings.length);
33
+ sharedStrings.push(fullSi);
34
+ }
35
+ return {
36
+ sharedIndexMap,
37
+ sharedStrings,
38
+ sharedStringsHeader,
39
+ sheetMergeCells,
40
+ };
41
+ }
42
+ ;
@@ -1,3 +1,4 @@
1
+ import { columnIndexToLetter } from "./column-index-to-letter.js";
1
2
  /**
2
3
  * Converts an array of values into a Record<string, string> with Excel column names as keys.
3
4
  *
@@ -8,18 +9,9 @@
8
9
  * @returns The resulting Record<string, string>
9
10
  */
10
11
  export function toExcelColumnObject(values) {
11
- const toExcelColumn = (index) => {
12
- let column = "";
13
- let i = index;
14
- while (i >= 0) {
15
- column = String.fromCharCode((i % 26) + 65) + column;
16
- i = Math.floor(i / 26) - 1;
17
- }
18
- return column;
19
- };
20
12
  const result = {};
21
13
  for (let i = 0; i < values.length; i++) {
22
- const key = toExcelColumn(i);
14
+ const key = columnIndexToLetter(i);
23
15
  result[key] = String(values[i]);
24
16
  }
25
17
  return result;
@@ -0,0 +1,37 @@
1
+ export function updateDimension(xml) {
2
+ const cellRefs = [...xml.matchAll(/<c r="([A-Z]+)(\d+)"/g)];
3
+ if (cellRefs.length === 0)
4
+ return xml;
5
+ let minCol = Infinity, maxCol = -Infinity;
6
+ let minRow = Infinity, maxRow = -Infinity;
7
+ for (const [, colStr, rowStr] of cellRefs) {
8
+ const col = columnLetterToNumber(colStr);
9
+ const row = parseInt(rowStr, 10);
10
+ if (col < minCol)
11
+ minCol = col;
12
+ if (col > maxCol)
13
+ maxCol = col;
14
+ if (row < minRow)
15
+ minRow = row;
16
+ if (row > maxRow)
17
+ maxRow = row;
18
+ }
19
+ const newRef = `${columnNumberToLetter(minCol)}${minRow}:${columnNumberToLetter(maxCol)}${maxRow}`;
20
+ return xml.replace(/<dimension ref="[^"]*"/, `<dimension ref="${newRef}"`);
21
+ }
22
+ function columnLetterToNumber(letters) {
23
+ let num = 0;
24
+ for (let i = 0; i < letters.length; i++) {
25
+ num = num * 26 + (letters.charCodeAt(i) - 64);
26
+ }
27
+ return num;
28
+ }
29
+ function columnNumberToLetter(num) {
30
+ let letters = "";
31
+ while (num > 0) {
32
+ const rem = (num - 1) % 26;
33
+ letters = String.fromCharCode(65 + rem) + letters;
34
+ num = Math.floor((num - 1) / 26);
35
+ }
36
+ return letters;
37
+ }