@js-ak/excel-toolbox 1.4.0 → 1.5.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.
Files changed (42) hide show
  1. package/build/cjs/lib/template/index.js +1 -0
  2. package/build/cjs/lib/template/memory-write-stream.js +17 -0
  3. package/build/cjs/lib/template/template-fs.js +4 -223
  4. package/build/cjs/lib/template/template-memory.js +531 -0
  5. package/build/cjs/lib/template/utils/extract-xml-declaration.js +1 -1
  6. package/build/cjs/lib/template/utils/get-max-row-number.js +1 -1
  7. package/build/cjs/lib/template/utils/index.js +4 -0
  8. package/build/cjs/lib/template/utils/prepare-row-to-cells.js +13 -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/validate-worksheet-xml.js +65 -51
  15. package/build/cjs/lib/template/utils/write-rows-to-stream.js +15 -10
  16. package/build/esm/lib/template/index.js +1 -0
  17. package/build/esm/lib/template/memory-write-stream.js +13 -0
  18. package/build/esm/lib/template/template-fs.js +4 -223
  19. package/build/esm/lib/template/template-memory.js +494 -0
  20. package/build/esm/lib/template/utils/extract-xml-declaration.js +1 -1
  21. package/build/esm/lib/template/utils/get-max-row-number.js +1 -1
  22. package/build/esm/lib/template/utils/index.js +4 -0
  23. package/build/esm/lib/template/utils/prepare-row-to-cells.js +10 -0
  24. package/build/esm/lib/template/utils/process-merge-cells.js +37 -0
  25. package/build/esm/lib/template/utils/process-merge-finalize.js +48 -0
  26. package/build/esm/lib/template/utils/process-rows.js +157 -0
  27. package/build/esm/lib/template/utils/process-shared-strings.js +42 -0
  28. package/build/esm/lib/template/utils/to-excel-column-object.js +2 -10
  29. package/build/esm/lib/template/utils/validate-worksheet-xml.js +65 -51
  30. package/build/esm/lib/template/utils/write-rows-to-stream.js +15 -10
  31. package/build/types/lib/template/index.d.ts +1 -0
  32. package/build/types/lib/template/memory-write-stream.d.ts +6 -0
  33. package/build/types/lib/template/template-memory.d.ts +85 -0
  34. package/build/types/lib/template/utils/index.d.ts +4 -0
  35. package/build/types/lib/template/utils/prepare-row-to-cells.d.ts +1 -0
  36. package/build/types/lib/template/utils/process-merge-cells.d.ts +20 -0
  37. package/build/types/lib/template/utils/process-merge-finalize.d.ts +38 -0
  38. package/build/types/lib/template/utils/process-rows.d.ts +31 -0
  39. package/build/types/lib/template/utils/process-shared-strings.d.ts +20 -0
  40. package/build/types/lib/template/utils/validate-worksheet-xml.d.ts +16 -0
  41. package/build/types/lib/template/utils/write-rows-to-stream.d.ts +6 -2
  42. package/package.json +5 -3
@@ -85,9 +85,9 @@ export class TemplateFs {
85
85
  * @experimental This API is experimental and might change in future versions.
86
86
  */
87
87
  #expandTableRows(sheetXml, sharedStringsXml, replacements) {
88
- const { initialMergeCells, mergeCellMatches, modifiedXml, } = processMergeCells(sheetXml);
89
- const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = processSharedStrings(sharedStringsXml);
90
- const { lastIndex, resultRows, rowShift } = processRows({
88
+ const { initialMergeCells, mergeCellMatches, modifiedXml, } = Utils.processMergeCells(sheetXml);
89
+ const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = Utils.processSharedStrings(sharedStringsXml);
90
+ const { lastIndex, resultRows, rowShift } = Utils.processRows({
91
91
  mergeCellMatches,
92
92
  replacements,
93
93
  sharedIndexMap,
@@ -95,7 +95,7 @@ export class TemplateFs {
95
95
  sheetMergeCells,
96
96
  sheetXml: modifiedXml,
97
97
  });
98
- return processBuild({
98
+ return Utils.processMergeFinalize({
99
99
  initialMergeCells,
100
100
  lastIndex,
101
101
  mergeCellMatches,
@@ -688,222 +688,3 @@ export class TemplateFs {
688
688
  return new TemplateFs(new Set(Object.keys(files)), destination);
689
689
  }
690
690
  }
691
- function processMergeCells(sheetXml) {
692
- // Regular expression for finding <mergeCells> block
693
- const mergeCellsBlockRegex = /<mergeCells[^>]*>[\s\S]*?<\/mergeCells>/;
694
- // Find the first <mergeCells> block (if there are multiple, in xlsx usually there is only one)
695
- const mergeCellsBlockMatch = sheetXml.match(mergeCellsBlockRegex);
696
- const initialMergeCells = [];
697
- const mergeCellMatches = [];
698
- if (mergeCellsBlockMatch) {
699
- const mergeCellsBlock = mergeCellsBlockMatch[0];
700
- initialMergeCells.push(mergeCellsBlock);
701
- // Extract <mergeCell ref="A1:B2"/> from this block
702
- const mergeCellRegex = /<mergeCell ref="([A-Z]+\d+):([A-Z]+\d+)"\/>/g;
703
- for (const match of mergeCellsBlock.matchAll(mergeCellRegex)) {
704
- mergeCellMatches.push({ from: match[1], to: match[2] });
705
- }
706
- }
707
- // Remove the <mergeCells> block from the XML
708
- const modifiedXml = sheetXml.replace(mergeCellsBlockRegex, "");
709
- return {
710
- initialMergeCells,
711
- mergeCellMatches,
712
- modifiedXml,
713
- };
714
- }
715
- ;
716
- function processSharedStrings(sharedStringsXml) {
717
- // Final list of merged cells with all changes
718
- const sheetMergeCells = [];
719
- // Array for storing shared strings
720
- const sharedStrings = [];
721
- const sharedStringsHeader = Utils.extractXmlDeclaration(sharedStringsXml);
722
- // Map for fast lookup of shared string index by content
723
- const sharedIndexMap = new Map();
724
- // Regular expression for finding <si> elements (shared string items)
725
- const siRegex = /<si>([\s\S]*?)<\/si>/g;
726
- // Parse sharedStringsXml and fill sharedStrings and sharedIndexMap
727
- for (const match of sharedStringsXml.matchAll(siRegex)) {
728
- const content = match[1];
729
- if (!content)
730
- throw new Error("Shared index not found");
731
- const fullSi = `<si>${content}</si>`;
732
- sharedIndexMap.set(content, sharedStrings.length);
733
- sharedStrings.push(fullSi);
734
- }
735
- return {
736
- sharedIndexMap,
737
- sharedStrings,
738
- sharedStringsHeader,
739
- sheetMergeCells,
740
- };
741
- }
742
- ;
743
- function processRows(data) {
744
- const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetMergeCells, sheetXml, } = data;
745
- const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
746
- // Array for storing resulting XML rows
747
- const resultRows = [];
748
- // Previous position of processed part of XML
749
- let lastIndex = 0;
750
- // Shift for row numbers
751
- let rowShift = 0;
752
- // Regular expression for finding <row> elements
753
- const rowRegex = /<row[^>]*?>[\s\S]*?<\/row>/g;
754
- // Process each <row> element
755
- for (const match of sheetXml.matchAll(rowRegex)) {
756
- // Full XML row
757
- const fullRow = match[0];
758
- // Start position of the row in XML
759
- const matchStart = match.index;
760
- // End position of the row in XML
761
- const matchEnd = matchStart + fullRow.length;
762
- // Add the intermediate XML chunk (if any) between the previous and the current row
763
- if (lastIndex !== matchStart) {
764
- resultRows.push(sheetXml.slice(lastIndex, matchStart));
765
- }
766
- lastIndex = matchEnd;
767
- // Get row number from r attribute
768
- const originalRowNumber = parseInt(fullRow.match(/<row[^>]* r="(\d+)"/)?.[1] ?? "1", 10);
769
- // Update row number based on rowShift
770
- const shiftedRowNumber = originalRowNumber + rowShift;
771
- // Find shared string indexes in cells of the current row
772
- const sharedValueIndexes = [];
773
- // Regular expression for finding a cell
774
- const cellRegex = /<c[^>]*?r="([A-Z]+\d+)"[^>]*?>([\s\S]*?)<\/c>/g;
775
- for (const cell of fullRow.matchAll(cellRegex)) {
776
- const cellTag = cell[0];
777
- // Check if the cell is a shared string
778
- const isShared = /t="s"/.test(cellTag);
779
- const valueMatch = cellTag.match(/<v>(\d+)<\/v>/);
780
- if (isShared && valueMatch) {
781
- sharedValueIndexes.push(parseInt(valueMatch[1], 10));
782
- }
783
- }
784
- // Get the text content of shared strings by their indexes
785
- const sharedTexts = sharedValueIndexes.map(i => sharedStrings[i]?.replace(/<\/?si>/g, "") ?? "");
786
- // Find table placeholders in shared strings
787
- const tablePlaceholders = sharedTexts.flatMap(e => [...e.matchAll(TABLE_REGEX)]);
788
- // If there are no table placeholders, just shift the row
789
- if (tablePlaceholders.length === 0) {
790
- const updatedRow = fullRow
791
- .replace(/(<row[^>]* r=")(\d+)(")/, `$1${shiftedRowNumber}$3`)
792
- .replace(/<c r="([A-Z]+)(\d+)"/g, (_, col) => `<c r="${col}${shiftedRowNumber}"`);
793
- resultRows.push(updatedRow);
794
- // Update mergeCells for regular row with rowShift
795
- const calculatedRowNumber = originalRowNumber + rowShift;
796
- for (const { from, to } of mergeCellMatches) {
797
- const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
798
- const [, toCol] = to.match(/^([A-Z]+)(\d+)$/);
799
- if (Number(fromRow) === calculatedRowNumber) {
800
- const newFrom = `${fromCol}${shiftedRowNumber}`;
801
- const newTo = `${toCol}${shiftedRowNumber}`;
802
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
803
- }
804
- }
805
- continue;
806
- }
807
- // Get the table name from the first placeholder
808
- const firstMatch = tablePlaceholders[0];
809
- const tableName = firstMatch?.[1];
810
- if (!tableName)
811
- throw new Error("Table name not found");
812
- // Get data for replacement from replacements
813
- const array = replacements[tableName];
814
- if (!array)
815
- continue;
816
- if (!Array.isArray(array))
817
- throw new Error("Table data is not an array");
818
- const tableRowStart = shiftedRowNumber;
819
- // Find mergeCells to duplicate (mergeCells that start with the current row)
820
- const mergeCellsToDuplicate = mergeCellMatches.filter(({ from }) => {
821
- const match = from.match(/^([A-Z]+)(\d+)$/);
822
- if (!match)
823
- return false;
824
- // Row number of the merge cell start position is in the second group
825
- const rowNumber = match[2];
826
- return Number(rowNumber) === tableRowStart;
827
- });
828
- // Change the current row to multiple rows from the data array
829
- for (let i = 0; i < array.length; i++) {
830
- const rowData = array[i];
831
- let newRow = fullRow;
832
- // Replace placeholders in shared strings with real data
833
- sharedValueIndexes.forEach((originalIdx, idx) => {
834
- const originalText = sharedTexts[idx];
835
- if (!originalText)
836
- throw new Error("Shared value not found");
837
- // Replace placeholders ${tableName.field} with real data from array data
838
- const replacedText = originalText.replace(TABLE_REGEX, (_, tbl, field) => tbl === tableName ? String(rowData?.[field] ?? "") : "");
839
- // Add new text to shared strings if it doesn't exist
840
- let newIndex;
841
- if (sharedIndexMap.has(replacedText)) {
842
- newIndex = sharedIndexMap.get(replacedText);
843
- }
844
- else {
845
- newIndex = sharedStrings.length;
846
- sharedIndexMap.set(replacedText, newIndex);
847
- sharedStrings.push(`<si>${replacedText}</si>`);
848
- }
849
- // Replace the shared string index in the cell
850
- newRow = newRow.replace(`<v>${originalIdx}</v>`, `<v>${newIndex}</v>`);
851
- });
852
- // Update row number and cell references
853
- const newRowNum = shiftedRowNumber + i;
854
- newRow = newRow
855
- .replace(/<row[^>]* r="\d+"/, rowTag => rowTag.replace(/r="\d+"/, `r="${newRowNum}"`))
856
- .replace(/<c r="([A-Z]+)\d+"/g, (_, col) => `<c r="${col}${newRowNum}"`);
857
- resultRows.push(newRow);
858
- // Add duplicate mergeCells for new rows
859
- for (const { from, to } of mergeCellsToDuplicate) {
860
- const [, colFrom, rowFrom] = from.match(/^([A-Z]+)(\d+)$/);
861
- const [, colTo, rowTo] = to.match(/^([A-Z]+)(\d+)$/);
862
- const newFrom = `${colFrom}${Number(rowFrom) + i}`;
863
- const newTo = `${colTo}${Number(rowTo) + i}`;
864
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
865
- }
866
- }
867
- // It increases the row shift by the number of added rows minus one replaced
868
- rowShift += array.length - 1;
869
- const delta = array.length - 1;
870
- const calculatedRowNumber = originalRowNumber + rowShift - array.length + 1;
871
- if (delta > 0) {
872
- for (const merge of mergeCellMatches) {
873
- const fromRow = parseInt(merge.from.match(/\d+$/)[0], 10);
874
- if (fromRow > calculatedRowNumber) {
875
- merge.from = merge.from.replace(/\d+$/, r => `${parseInt(r) + delta}`);
876
- merge.to = merge.to.replace(/\d+$/, r => `${parseInt(r) + delta}`);
877
- }
878
- }
879
- }
880
- }
881
- return { lastIndex, resultRows, rowShift };
882
- }
883
- ;
884
- function processBuild(data) {
885
- const { initialMergeCells, lastIndex, mergeCellMatches, resultRows, rowShift, sharedStrings, sharedStringsHeader, sheetMergeCells, sheetXml, } = data;
886
- for (const { from, to } of mergeCellMatches) {
887
- const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
888
- const [, toCol, toRow] = to.match(/^([A-Z]+)(\d+)$/);
889
- const fromRowNum = Number(fromRow);
890
- // These rows have already been processed, don't add duplicates
891
- if (fromRowNum <= lastIndex)
892
- continue;
893
- const newFrom = `${fromCol}${fromRowNum + rowShift}`;
894
- const newTo = `${toCol}${Number(toRow) + rowShift}`;
895
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
896
- }
897
- resultRows.push(sheetXml.slice(lastIndex));
898
- // Form XML for mergeCells if there are any
899
- const mergeXml = sheetMergeCells.length
900
- ? `<mergeCells count="${sheetMergeCells.length}">${sheetMergeCells.join("")}</mergeCells>`
901
- : initialMergeCells;
902
- // Insert mergeCells before the closing sheetData tag
903
- const sheetWithMerge = resultRows.join("").replace(/<\/sheetData>/, `</sheetData>${mergeXml}`);
904
- // Return modified sheet XML and shared strings
905
- return {
906
- shared: `${sharedStringsHeader}\n<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="${sharedStrings.length}" uniqueCount="${sharedStrings.length}">${sharedStrings.join("")}</sst>`,
907
- sheet: Utils.updateDimension(sheetWithMerge),
908
- };
909
- }