@js-ak/excel-toolbox 1.4.0 → 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 (29) hide show
  1. package/build/cjs/lib/template/template-fs.js +4 -223
  2. package/build/cjs/lib/template/utils/extract-xml-declaration.js +1 -1
  3. package/build/cjs/lib/template/utils/get-max-row-number.js +1 -1
  4. package/build/cjs/lib/template/utils/index.js +4 -0
  5. package/build/cjs/lib/template/utils/process-merge-cells.js +40 -0
  6. package/build/cjs/lib/template/utils/process-merge-finalize.js +51 -0
  7. package/build/cjs/lib/template/utils/process-rows.js +160 -0
  8. package/build/cjs/lib/template/utils/process-shared-strings.js +45 -0
  9. package/build/cjs/lib/template/utils/to-excel-column-object.js +2 -10
  10. package/build/cjs/lib/template/utils/validate-worksheet-xml.js +65 -51
  11. package/build/cjs/lib/template/utils/write-rows-to-stream.js +2 -1
  12. package/build/esm/lib/template/template-fs.js +4 -223
  13. package/build/esm/lib/template/utils/extract-xml-declaration.js +1 -1
  14. package/build/esm/lib/template/utils/get-max-row-number.js +1 -1
  15. package/build/esm/lib/template/utils/index.js +4 -0
  16. package/build/esm/lib/template/utils/process-merge-cells.js +37 -0
  17. package/build/esm/lib/template/utils/process-merge-finalize.js +48 -0
  18. package/build/esm/lib/template/utils/process-rows.js +157 -0
  19. package/build/esm/lib/template/utils/process-shared-strings.js +42 -0
  20. package/build/esm/lib/template/utils/to-excel-column-object.js +2 -10
  21. package/build/esm/lib/template/utils/validate-worksheet-xml.js +65 -51
  22. package/build/esm/lib/template/utils/write-rows-to-stream.js +2 -1
  23. package/build/types/lib/template/utils/index.d.ts +4 -0
  24. package/build/types/lib/template/utils/process-merge-cells.d.ts +20 -0
  25. package/build/types/lib/template/utils/process-merge-finalize.d.ts +38 -0
  26. package/build/types/lib/template/utils/process-rows.d.ts +31 -0
  27. package/build/types/lib/template/utils/process-shared-strings.d.ts +20 -0
  28. package/build/types/lib/template/utils/validate-worksheet-xml.d.ts +16 -0
  29. package/package.json +1 -1
@@ -121,9 +121,9 @@ class TemplateFs {
121
121
  * @experimental This API is experimental and might change in future versions.
122
122
  */
123
123
  #expandTableRows(sheetXml, sharedStringsXml, replacements) {
124
- const { initialMergeCells, mergeCellMatches, modifiedXml, } = processMergeCells(sheetXml);
125
- const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = processSharedStrings(sharedStringsXml);
126
- const { lastIndex, resultRows, rowShift } = processRows({
124
+ const { initialMergeCells, mergeCellMatches, modifiedXml, } = Utils.processMergeCells(sheetXml);
125
+ const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = Utils.processSharedStrings(sharedStringsXml);
126
+ const { lastIndex, resultRows, rowShift } = Utils.processRows({
127
127
  mergeCellMatches,
128
128
  replacements,
129
129
  sharedIndexMap,
@@ -131,7 +131,7 @@ class TemplateFs {
131
131
  sheetMergeCells,
132
132
  sheetXml: modifiedXml,
133
133
  });
134
- return processBuild({
134
+ return Utils.processMergeFinalize({
135
135
  initialMergeCells,
136
136
  lastIndex,
137
137
  mergeCellMatches,
@@ -725,222 +725,3 @@ class TemplateFs {
725
725
  }
726
726
  }
727
727
  exports.TemplateFs = TemplateFs;
728
- function processMergeCells(sheetXml) {
729
- // Regular expression for finding <mergeCells> block
730
- const mergeCellsBlockRegex = /<mergeCells[^>]*>[\s\S]*?<\/mergeCells>/;
731
- // Find the first <mergeCells> block (if there are multiple, in xlsx usually there is only one)
732
- const mergeCellsBlockMatch = sheetXml.match(mergeCellsBlockRegex);
733
- const initialMergeCells = [];
734
- const mergeCellMatches = [];
735
- if (mergeCellsBlockMatch) {
736
- const mergeCellsBlock = mergeCellsBlockMatch[0];
737
- initialMergeCells.push(mergeCellsBlock);
738
- // Extract <mergeCell ref="A1:B2"/> from this block
739
- const mergeCellRegex = /<mergeCell ref="([A-Z]+\d+):([A-Z]+\d+)"\/>/g;
740
- for (const match of mergeCellsBlock.matchAll(mergeCellRegex)) {
741
- mergeCellMatches.push({ from: match[1], to: match[2] });
742
- }
743
- }
744
- // Remove the <mergeCells> block from the XML
745
- const modifiedXml = sheetXml.replace(mergeCellsBlockRegex, "");
746
- return {
747
- initialMergeCells,
748
- mergeCellMatches,
749
- modifiedXml,
750
- };
751
- }
752
- ;
753
- function processSharedStrings(sharedStringsXml) {
754
- // Final list of merged cells with all changes
755
- const sheetMergeCells = [];
756
- // Array for storing shared strings
757
- const sharedStrings = [];
758
- const sharedStringsHeader = Utils.extractXmlDeclaration(sharedStringsXml);
759
- // Map for fast lookup of shared string index by content
760
- const sharedIndexMap = new Map();
761
- // Regular expression for finding <si> elements (shared string items)
762
- const siRegex = /<si>([\s\S]*?)<\/si>/g;
763
- // Parse sharedStringsXml and fill sharedStrings and sharedIndexMap
764
- for (const match of sharedStringsXml.matchAll(siRegex)) {
765
- const content = match[1];
766
- if (!content)
767
- throw new Error("Shared index not found");
768
- const fullSi = `<si>${content}</si>`;
769
- sharedIndexMap.set(content, sharedStrings.length);
770
- sharedStrings.push(fullSi);
771
- }
772
- return {
773
- sharedIndexMap,
774
- sharedStrings,
775
- sharedStringsHeader,
776
- sheetMergeCells,
777
- };
778
- }
779
- ;
780
- function processRows(data) {
781
- const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetMergeCells, sheetXml, } = data;
782
- const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
783
- // Array for storing resulting XML rows
784
- const resultRows = [];
785
- // Previous position of processed part of XML
786
- let lastIndex = 0;
787
- // Shift for row numbers
788
- let rowShift = 0;
789
- // Regular expression for finding <row> elements
790
- const rowRegex = /<row[^>]*?>[\s\S]*?<\/row>/g;
791
- // Process each <row> element
792
- for (const match of sheetXml.matchAll(rowRegex)) {
793
- // Full XML row
794
- const fullRow = match[0];
795
- // Start position of the row in XML
796
- const matchStart = match.index;
797
- // End position of the row in XML
798
- const matchEnd = matchStart + fullRow.length;
799
- // Add the intermediate XML chunk (if any) between the previous and the current row
800
- if (lastIndex !== matchStart) {
801
- resultRows.push(sheetXml.slice(lastIndex, matchStart));
802
- }
803
- lastIndex = matchEnd;
804
- // Get row number from r attribute
805
- const originalRowNumber = parseInt(fullRow.match(/<row[^>]* r="(\d+)"/)?.[1] ?? "1", 10);
806
- // Update row number based on rowShift
807
- const shiftedRowNumber = originalRowNumber + rowShift;
808
- // Find shared string indexes in cells of the current row
809
- const sharedValueIndexes = [];
810
- // Regular expression for finding a cell
811
- const cellRegex = /<c[^>]*?r="([A-Z]+\d+)"[^>]*?>([\s\S]*?)<\/c>/g;
812
- for (const cell of fullRow.matchAll(cellRegex)) {
813
- const cellTag = cell[0];
814
- // Check if the cell is a shared string
815
- const isShared = /t="s"/.test(cellTag);
816
- const valueMatch = cellTag.match(/<v>(\d+)<\/v>/);
817
- if (isShared && valueMatch) {
818
- sharedValueIndexes.push(parseInt(valueMatch[1], 10));
819
- }
820
- }
821
- // Get the text content of shared strings by their indexes
822
- const sharedTexts = sharedValueIndexes.map(i => sharedStrings[i]?.replace(/<\/?si>/g, "") ?? "");
823
- // Find table placeholders in shared strings
824
- const tablePlaceholders = sharedTexts.flatMap(e => [...e.matchAll(TABLE_REGEX)]);
825
- // If there are no table placeholders, just shift the row
826
- if (tablePlaceholders.length === 0) {
827
- const updatedRow = fullRow
828
- .replace(/(<row[^>]* r=")(\d+)(")/, `$1${shiftedRowNumber}$3`)
829
- .replace(/<c r="([A-Z]+)(\d+)"/g, (_, col) => `<c r="${col}${shiftedRowNumber}"`);
830
- resultRows.push(updatedRow);
831
- // Update mergeCells for regular row with rowShift
832
- const calculatedRowNumber = originalRowNumber + rowShift;
833
- for (const { from, to } of mergeCellMatches) {
834
- const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
835
- const [, toCol] = to.match(/^([A-Z]+)(\d+)$/);
836
- if (Number(fromRow) === calculatedRowNumber) {
837
- const newFrom = `${fromCol}${shiftedRowNumber}`;
838
- const newTo = `${toCol}${shiftedRowNumber}`;
839
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
840
- }
841
- }
842
- continue;
843
- }
844
- // Get the table name from the first placeholder
845
- const firstMatch = tablePlaceholders[0];
846
- const tableName = firstMatch?.[1];
847
- if (!tableName)
848
- throw new Error("Table name not found");
849
- // Get data for replacement from replacements
850
- const array = replacements[tableName];
851
- if (!array)
852
- continue;
853
- if (!Array.isArray(array))
854
- throw new Error("Table data is not an array");
855
- const tableRowStart = shiftedRowNumber;
856
- // Find mergeCells to duplicate (mergeCells that start with the current row)
857
- const mergeCellsToDuplicate = mergeCellMatches.filter(({ from }) => {
858
- const match = from.match(/^([A-Z]+)(\d+)$/);
859
- if (!match)
860
- return false;
861
- // Row number of the merge cell start position is in the second group
862
- const rowNumber = match[2];
863
- return Number(rowNumber) === tableRowStart;
864
- });
865
- // Change the current row to multiple rows from the data array
866
- for (let i = 0; i < array.length; i++) {
867
- const rowData = array[i];
868
- let newRow = fullRow;
869
- // Replace placeholders in shared strings with real data
870
- sharedValueIndexes.forEach((originalIdx, idx) => {
871
- const originalText = sharedTexts[idx];
872
- if (!originalText)
873
- throw new Error("Shared value not found");
874
- // Replace placeholders ${tableName.field} with real data from array data
875
- const replacedText = originalText.replace(TABLE_REGEX, (_, tbl, field) => tbl === tableName ? String(rowData?.[field] ?? "") : "");
876
- // Add new text to shared strings if it doesn't exist
877
- let newIndex;
878
- if (sharedIndexMap.has(replacedText)) {
879
- newIndex = sharedIndexMap.get(replacedText);
880
- }
881
- else {
882
- newIndex = sharedStrings.length;
883
- sharedIndexMap.set(replacedText, newIndex);
884
- sharedStrings.push(`<si>${replacedText}</si>`);
885
- }
886
- // Replace the shared string index in the cell
887
- newRow = newRow.replace(`<v>${originalIdx}</v>`, `<v>${newIndex}</v>`);
888
- });
889
- // Update row number and cell references
890
- const newRowNum = shiftedRowNumber + i;
891
- newRow = newRow
892
- .replace(/<row[^>]* r="\d+"/, rowTag => rowTag.replace(/r="\d+"/, `r="${newRowNum}"`))
893
- .replace(/<c r="([A-Z]+)\d+"/g, (_, col) => `<c r="${col}${newRowNum}"`);
894
- resultRows.push(newRow);
895
- // Add duplicate mergeCells for new rows
896
- for (const { from, to } of mergeCellsToDuplicate) {
897
- const [, colFrom, rowFrom] = from.match(/^([A-Z]+)(\d+)$/);
898
- const [, colTo, rowTo] = to.match(/^([A-Z]+)(\d+)$/);
899
- const newFrom = `${colFrom}${Number(rowFrom) + i}`;
900
- const newTo = `${colTo}${Number(rowTo) + i}`;
901
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
902
- }
903
- }
904
- // It increases the row shift by the number of added rows minus one replaced
905
- rowShift += array.length - 1;
906
- const delta = array.length - 1;
907
- const calculatedRowNumber = originalRowNumber + rowShift - array.length + 1;
908
- if (delta > 0) {
909
- for (const merge of mergeCellMatches) {
910
- const fromRow = parseInt(merge.from.match(/\d+$/)[0], 10);
911
- if (fromRow > calculatedRowNumber) {
912
- merge.from = merge.from.replace(/\d+$/, r => `${parseInt(r) + delta}`);
913
- merge.to = merge.to.replace(/\d+$/, r => `${parseInt(r) + delta}`);
914
- }
915
- }
916
- }
917
- }
918
- return { lastIndex, resultRows, rowShift };
919
- }
920
- ;
921
- function processBuild(data) {
922
- const { initialMergeCells, lastIndex, mergeCellMatches, resultRows, rowShift, sharedStrings, sharedStringsHeader, sheetMergeCells, sheetXml, } = data;
923
- for (const { from, to } of mergeCellMatches) {
924
- const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
925
- const [, toCol, toRow] = to.match(/^([A-Z]+)(\d+)$/);
926
- const fromRowNum = Number(fromRow);
927
- // These rows have already been processed, don't add duplicates
928
- if (fromRowNum <= lastIndex)
929
- continue;
930
- const newFrom = `${fromCol}${fromRowNum + rowShift}`;
931
- const newTo = `${toCol}${Number(toRow) + rowShift}`;
932
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
933
- }
934
- resultRows.push(sheetXml.slice(lastIndex));
935
- // Form XML for mergeCells if there are any
936
- const mergeXml = sheetMergeCells.length
937
- ? `<mergeCells count="${sheetMergeCells.length}">${sheetMergeCells.join("")}</mergeCells>`
938
- : initialMergeCells;
939
- // Insert mergeCells before the closing sheetData tag
940
- const sheetWithMerge = resultRows.join("").replace(/<\/sheetData>/, `</sheetData>${mergeXml}`);
941
- // Return modified sheet XML and shared strings
942
- return {
943
- shared: `${sharedStringsHeader}\n<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${sharedStrings.length}" uniqueCount="${sharedStrings.length}">${sharedStrings.join("")}</sst>`,
944
- sheet: Utils.updateDimension(sheetWithMerge),
945
- };
946
- }
@@ -17,6 +17,6 @@ exports.extractXmlDeclaration = extractXmlDeclaration;
17
17
  function extractXmlDeclaration(xmlString) {
18
18
  // const declarationRegex = /^<\?xml\s+[^?]+\?>/;
19
19
  const declarationRegex = /^<\?xml\s+version\s*=\s*["'][^"']+["'](\s+(encoding|standalone)\s*=\s*["'][^"']+["'])*\s*\?>/;
20
- const match = xmlString.match(declarationRegex);
20
+ const match = xmlString.trim().match(declarationRegex);
21
21
  return match ? match[0] : null;
22
22
  }
@@ -9,7 +9,7 @@ exports.getMaxRowNumber = getMaxRowNumber;
9
9
  */
10
10
  function getMaxRowNumber(line) {
11
11
  let result = 1;
12
- const rowMatches = [...line.matchAll(/<row[^>]+r="(\d+)"[^>]*>/g)];
12
+ const rowMatches = [...line.matchAll(/<row[^>]+r="(\d+)"[^>]*>/gi)];
13
13
  for (const match of rowMatches) {
14
14
  const rowNum = parseInt(match[1], 10);
15
15
  if (rowNum >= result) {
@@ -26,6 +26,10 @@ __exportStar(require("./get-max-row-number.js"), exports);
26
26
  __exportStar(require("./get-rows-above.js"), exports);
27
27
  __exportStar(require("./get-rows-below.js"), exports);
28
28
  __exportStar(require("./parse-rows.js"), exports);
29
+ __exportStar(require("./process-merge-cells.js"), exports);
30
+ __exportStar(require("./process-merge-finalize.js"), exports);
31
+ __exportStar(require("./process-rows.js"), exports);
32
+ __exportStar(require("./process-shared-strings.js"), exports);
29
33
  __exportStar(require("./to-excel-column-object.js"), exports);
30
34
  __exportStar(require("./update-dimension.js"), exports);
31
35
  __exportStar(require("./validate-worksheet-xml.js"), exports);
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processMergeCells = processMergeCells;
4
+ /**
5
+ * Processes the sheet XML by extracting the initial <mergeCells> block and
6
+ * extracting all merge cell references. The function returns an object with
7
+ * three properties:
8
+ * - `initialMergeCells`: The initial <mergeCells> block as a string array.
9
+ * - `mergeCellMatches`: An array of objects with `from` and `to` properties,
10
+ * representing the merge cell references.
11
+ * - `modifiedXml`: The modified sheet XML with the <mergeCells> block removed.
12
+ *
13
+ * @param sheetXml - The sheet XML string.
14
+ * @returns An object with the above three properties.
15
+ */
16
+ function processMergeCells(sheetXml) {
17
+ // Regular expression for finding <mergeCells> block
18
+ const mergeCellsBlockRegex = /<mergeCells[^>]*>[\s\S]*?<\/mergeCells>/;
19
+ // Find the first <mergeCells> block (if there are multiple, in xlsx usually there is only one)
20
+ const mergeCellsBlockMatch = sheetXml.match(mergeCellsBlockRegex);
21
+ const initialMergeCells = [];
22
+ const mergeCellMatches = [];
23
+ if (mergeCellsBlockMatch) {
24
+ const mergeCellsBlock = mergeCellsBlockMatch[0];
25
+ initialMergeCells.push(mergeCellsBlock);
26
+ // Extract <mergeCell ref="A1:B2"/> from this block
27
+ const mergeCellRegex = /<mergeCell ref="([A-Z]+\d+):([A-Z]+\d+)"\/>/g;
28
+ for (const match of mergeCellsBlock.matchAll(mergeCellRegex)) {
29
+ mergeCellMatches.push({ from: match[1], to: match[2] });
30
+ }
31
+ }
32
+ // Remove the <mergeCells> block from the XML
33
+ const modifiedXml = sheetXml.replace(mergeCellsBlockRegex, "");
34
+ return {
35
+ initialMergeCells,
36
+ mergeCellMatches,
37
+ modifiedXml,
38
+ };
39
+ }
40
+ ;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processMergeFinalize = processMergeFinalize;
4
+ const update_dimension_js_1 = require("./update-dimension.js");
5
+ /**
6
+ * Finalizes the processing of the merged sheet by updating the merge cells and
7
+ * inserting them into the sheet XML. It also returns the modified sheet XML and
8
+ * shared strings.
9
+ *
10
+ * @param {object} data - An object containing the following properties:
11
+ * - `initialMergeCells`: The initial merge cells from the original sheet.
12
+ * - `lastIndex`: The last processed position in the sheet XML.
13
+ * - `mergeCellMatches`: An array of objects with `from` and `to` properties,
14
+ * describing the merge cells.
15
+ * - `resultRows`: An array of processed XML rows.
16
+ * - `rowShift`: The total row shift.
17
+ * - `sharedStrings`: An array of shared strings.
18
+ * - `sharedStringsHeader`: The XML declaration of the shared strings.
19
+ * - `sheetMergeCells`: An array of merge cell XML strings.
20
+ * - `sheetXml`: The original sheet XML string.
21
+ *
22
+ * @returns An object with two properties:
23
+ * - `shared`: The modified shared strings XML string.
24
+ * - `sheet`: The modified sheet XML string with updated merge cells.
25
+ */
26
+ function processMergeFinalize(data) {
27
+ const { initialMergeCells, lastIndex, mergeCellMatches, resultRows, rowShift, sharedStrings, sharedStringsHeader, sheetMergeCells, sheetXml, } = data;
28
+ for (const { from, to } of mergeCellMatches) {
29
+ const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
30
+ const [, toCol, toRow] = to.match(/^([A-Z]+)(\d+)$/);
31
+ const fromRowNum = Number(fromRow);
32
+ // These rows have already been processed, don't add duplicates
33
+ if (fromRowNum <= lastIndex)
34
+ continue;
35
+ const newFrom = `${fromCol}${fromRowNum + rowShift}`;
36
+ const newTo = `${toCol}${Number(toRow) + rowShift}`;
37
+ sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
38
+ }
39
+ resultRows.push(sheetXml.slice(lastIndex));
40
+ // Form XML for mergeCells if there are any
41
+ const mergeXml = sheetMergeCells.length
42
+ ? `<mergeCells count="${sheetMergeCells.length}">${sheetMergeCells.join("")}</mergeCells>`
43
+ : initialMergeCells;
44
+ // Insert mergeCells before the closing sheetData tag
45
+ const sheetWithMerge = resultRows.join("").replace(/<\/sheetData>/, `</sheetData>${mergeXml}`);
46
+ // Return modified sheet XML and shared strings
47
+ return {
48
+ shared: `${sharedStringsHeader}\n<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${sharedStrings.length}" uniqueCount="${sharedStrings.length}">${sharedStrings.join("")}</sst>`,
49
+ sheet: (0, update_dimension_js_1.updateDimension)(sheetWithMerge),
50
+ };
51
+ }
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processRows = processRows;
4
+ /**
5
+ * Processes a sheet XML by replacing table placeholders with real data and adjusting row numbers accordingly.
6
+ *
7
+ * @param data - An object containing the following properties:
8
+ * - `replacements`: An object where keys are table names and values are arrays of objects with table data.
9
+ * - `sharedIndexMap`: A Map of shared string indexes by their text content.
10
+ * - `mergeCellMatches`: An array of objects with `from` and `to` properties, describing the merge cells.
11
+ * - `sharedStrings`: An array of shared strings.
12
+ * - `sheetMergeCells`: An array of merge cell XML strings.
13
+ * - `sheetXml`: The sheet XML string.
14
+ *
15
+ * @returns An object with the following properties:
16
+ * - `lastIndex`: The last processed position in the sheet XML.
17
+ * - `resultRows`: An array of processed XML rows.
18
+ * - `rowShift`: The total row shift.
19
+ */
20
+ function processRows(data) {
21
+ const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetMergeCells, sheetXml, } = data;
22
+ const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
23
+ // Array for storing resulting XML rows
24
+ const resultRows = [];
25
+ // Previous position of processed part of XML
26
+ let lastIndex = 0;
27
+ // Shift for row numbers
28
+ let rowShift = 0;
29
+ // Regular expression for finding <row> elements
30
+ const rowRegex = /<row[^>]*?>[\s\S]*?<\/row>/g;
31
+ // Process each <row> element
32
+ for (const match of sheetXml.matchAll(rowRegex)) {
33
+ // Full XML row
34
+ const fullRow = match[0];
35
+ // Start position of the row in XML
36
+ const matchStart = match.index;
37
+ // End position of the row in XML
38
+ const matchEnd = matchStart + fullRow.length;
39
+ // Add the intermediate XML chunk (if any) between the previous and the current row
40
+ if (lastIndex !== matchStart) {
41
+ resultRows.push(sheetXml.slice(lastIndex, matchStart));
42
+ }
43
+ lastIndex = matchEnd;
44
+ // Get row number from r attribute
45
+ const originalRowNumber = parseInt(fullRow.match(/<row[^>]* r="(\d+)"/)?.[1] ?? "1", 10);
46
+ // Update row number based on rowShift
47
+ const shiftedRowNumber = originalRowNumber + rowShift;
48
+ // Find shared string indexes in cells of the current row
49
+ const sharedValueIndexes = [];
50
+ // Regular expression for finding a cell
51
+ const cellRegex = /<c[^>]*?r="([A-Z]+\d+)"[^>]*?>([\s\S]*?)<\/c>/g;
52
+ for (const cell of fullRow.matchAll(cellRegex)) {
53
+ const cellTag = cell[0];
54
+ // Check if the cell is a shared string
55
+ const isShared = /t="s"/.test(cellTag);
56
+ const valueMatch = cellTag.match(/<v>(\d+)<\/v>/);
57
+ if (isShared && valueMatch) {
58
+ sharedValueIndexes.push(parseInt(valueMatch[1], 10));
59
+ }
60
+ }
61
+ // Get the text content of shared strings by their indexes
62
+ const sharedTexts = sharedValueIndexes.map(i => sharedStrings[i]?.replace(/<\/?si>/g, "") ?? "");
63
+ // Find table placeholders in shared strings
64
+ const tablePlaceholders = sharedTexts.flatMap(e => [...e.matchAll(TABLE_REGEX)]);
65
+ // If there are no table placeholders, just shift the row
66
+ if (tablePlaceholders.length === 0) {
67
+ const updatedRow = fullRow
68
+ .replace(/(<row[^>]* r=")(\d+)(")/, `$1${shiftedRowNumber}$3`)
69
+ .replace(/<c r="([A-Z]+)(\d+)"/g, (_, col) => `<c r="${col}${shiftedRowNumber}"`);
70
+ resultRows.push(updatedRow);
71
+ // Update mergeCells for regular row with rowShift
72
+ const calculatedRowNumber = originalRowNumber + rowShift;
73
+ for (const { from, to } of mergeCellMatches) {
74
+ const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
75
+ const [, toCol] = to.match(/^([A-Z]+)(\d+)$/);
76
+ if (Number(fromRow) === calculatedRowNumber) {
77
+ const newFrom = `${fromCol}${shiftedRowNumber}`;
78
+ const newTo = `${toCol}${shiftedRowNumber}`;
79
+ sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
80
+ }
81
+ }
82
+ continue;
83
+ }
84
+ // Get the table name from the first placeholder
85
+ const firstMatch = tablePlaceholders[0];
86
+ const tableName = firstMatch?.[1];
87
+ if (!tableName)
88
+ throw new Error("Table name not found");
89
+ // Get data for replacement from replacements
90
+ const array = replacements[tableName];
91
+ if (!array)
92
+ continue;
93
+ if (!Array.isArray(array))
94
+ throw new Error("Table data is not an array");
95
+ const tableRowStart = shiftedRowNumber;
96
+ // Find mergeCells to duplicate (mergeCells that start with the current row)
97
+ const mergeCellsToDuplicate = mergeCellMatches.filter(({ from }) => {
98
+ const match = from.match(/^([A-Z]+)(\d+)$/);
99
+ if (!match)
100
+ return false;
101
+ // Row number of the merge cell start position is in the second group
102
+ const rowNumber = match[2];
103
+ return Number(rowNumber) === tableRowStart;
104
+ });
105
+ // Change the current row to multiple rows from the data array
106
+ for (let i = 0; i < array.length; i++) {
107
+ const rowData = array[i];
108
+ let newRow = fullRow;
109
+ // Replace placeholders in shared strings with real data
110
+ sharedValueIndexes.forEach((originalIdx, idx) => {
111
+ const originalText = sharedTexts[idx];
112
+ if (!originalText)
113
+ throw new Error("Shared value not found");
114
+ // Replace placeholders ${tableName.field} with real data from array data
115
+ const replacedText = originalText.replace(TABLE_REGEX, (_, tbl, field) => tbl === tableName ? String(rowData?.[field] ?? "") : "");
116
+ // Add new text to shared strings if it doesn't exist
117
+ let newIndex;
118
+ if (sharedIndexMap.has(replacedText)) {
119
+ newIndex = sharedIndexMap.get(replacedText);
120
+ }
121
+ else {
122
+ newIndex = sharedStrings.length;
123
+ sharedIndexMap.set(replacedText, newIndex);
124
+ sharedStrings.push(`<si>${replacedText}</si>`);
125
+ }
126
+ // Replace the shared string index in the cell
127
+ newRow = newRow.replace(`<v>${originalIdx}</v>`, `<v>${newIndex}</v>`);
128
+ });
129
+ // Update row number and cell references
130
+ const newRowNum = shiftedRowNumber + i;
131
+ newRow = newRow
132
+ .replace(/<row[^>]* r="\d+"/, rowTag => rowTag.replace(/r="\d+"/, `r="${newRowNum}"`))
133
+ .replace(/<c r="([A-Z]+)\d+"/g, (_, col) => `<c r="${col}${newRowNum}"`);
134
+ resultRows.push(newRow);
135
+ // Add duplicate mergeCells for new rows
136
+ for (const { from, to } of mergeCellsToDuplicate) {
137
+ const [, colFrom, rowFrom] = from.match(/^([A-Z]+)(\d+)$/);
138
+ const [, colTo, rowTo] = to.match(/^([A-Z]+)(\d+)$/);
139
+ const newFrom = `${colFrom}${Number(rowFrom) + i}`;
140
+ const newTo = `${colTo}${Number(rowTo) + i}`;
141
+ sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
142
+ }
143
+ }
144
+ // It increases the row shift by the number of added rows minus one replaced
145
+ rowShift += array.length - 1;
146
+ const delta = array.length - 1;
147
+ const calculatedRowNumber = originalRowNumber + rowShift - array.length + 1;
148
+ if (delta > 0) {
149
+ for (const merge of mergeCellMatches) {
150
+ const fromRow = parseInt(merge.from.match(/\d+$/)[0], 10);
151
+ if (fromRow > calculatedRowNumber) {
152
+ merge.from = merge.from.replace(/\d+$/, r => `${parseInt(r) + delta}`);
153
+ merge.to = merge.to.replace(/\d+$/, r => `${parseInt(r) + delta}`);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ return { lastIndex, resultRows, rowShift };
159
+ }
160
+ ;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.processSharedStrings = processSharedStrings;
4
+ const extract_xml_declaration_js_1 = require("./extract-xml-declaration.js");
5
+ /**
6
+ * Processes the shared strings XML by extracting the XML declaration,
7
+ * extracting individual <si> elements, and storing them in an array.
8
+ *
9
+ * The function returns an object with four properties:
10
+ * - sharedIndexMap: A map of shared string content to their corresponding index
11
+ * - sharedStrings: An array of shared strings
12
+ * - sharedStringsHeader: The XML declaration of the shared strings
13
+ * - sheetMergeCells: An empty array, which is only used for type compatibility
14
+ * with the return type of processBuild.
15
+ *
16
+ * @param sharedStringsXml - The XML string of the shared strings
17
+ * @returns An object with the four properties above
18
+ */
19
+ function processSharedStrings(sharedStringsXml) {
20
+ // Final list of merged cells with all changes
21
+ const sheetMergeCells = [];
22
+ // Array for storing shared strings
23
+ const sharedStrings = [];
24
+ const sharedStringsHeader = (0, extract_xml_declaration_js_1.extractXmlDeclaration)(sharedStringsXml);
25
+ // Map for fast lookup of shared string index by content
26
+ const sharedIndexMap = new Map();
27
+ // Regular expression for finding <si> elements (shared string items)
28
+ const siRegex = /<si>([\s\S]*?)<\/si>/g;
29
+ // Parse sharedStringsXml and fill sharedStrings and sharedIndexMap
30
+ for (const match of sharedStringsXml.matchAll(siRegex)) {
31
+ const content = match[1];
32
+ if (!content)
33
+ continue;
34
+ const fullSi = `<si>${content}</si>`;
35
+ sharedIndexMap.set(content, sharedStrings.length);
36
+ sharedStrings.push(fullSi);
37
+ }
38
+ return {
39
+ sharedIndexMap,
40
+ sharedStrings,
41
+ sharedStringsHeader,
42
+ sheetMergeCells,
43
+ };
44
+ }
45
+ ;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toExcelColumnObject = toExcelColumnObject;
4
+ const column_index_to_letter_js_1 = require("./column-index-to-letter.js");
4
5
  /**
5
6
  * Converts an array of values into a Record<string, string> with Excel column names as keys.
6
7
  *
@@ -11,18 +12,9 @@ exports.toExcelColumnObject = toExcelColumnObject;
11
12
  * @returns The resulting Record<string, string>
12
13
  */
13
14
  function toExcelColumnObject(values) {
14
- const toExcelColumn = (index) => {
15
- let column = "";
16
- let i = index;
17
- while (i >= 0) {
18
- column = String.fromCharCode((i % 26) + 65) + column;
19
- i = Math.floor(i / 26) - 1;
20
- }
21
- return column;
22
- };
23
15
  const result = {};
24
16
  for (let i = 0; i < values.length; i++) {
25
- const key = toExcelColumn(i);
17
+ const key = (0, column_index_to_letter_js_1.columnIndexToLetter)(i);
26
18
  result[key] = String(values[i]);
27
19
  }
28
20
  return result;