@js-ak/excel-toolbox 1.8.0 → 1.8.2

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.
@@ -137,21 +137,21 @@ class TemplateFs {
137
137
  */
138
138
  #expandTableRows(sheetXml, sharedStringsXml, replacements) {
139
139
  const { initialMergeCells, mergeCellMatches, modifiedXml, } = Utils.processMergeCells(sheetXml);
140
- const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = Utils.processSharedStrings(sharedStringsXml);
141
- const { lastIndex, resultRows, rowShift } = Utils.processRows({
140
+ const { sharedIndexMap, sharedStrings, sharedStringsHeader, } = Utils.processSharedStrings(sharedStringsXml);
141
+ const { isTableReplacementsFound, lastIndex, resultRows, sheetMergeCells, } = Utils.processRows({
142
142
  mergeCellMatches,
143
143
  replacements,
144
144
  sharedIndexMap,
145
145
  sharedStrings,
146
- sheetMergeCells,
147
146
  sheetXml: modifiedXml,
148
147
  });
148
+ if (!isTableReplacementsFound) {
149
+ return null;
150
+ }
149
151
  return Utils.processMergeFinalize({
150
152
  initialMergeCells,
151
153
  lastIndex,
152
- mergeCellMatches,
153
154
  resultRows,
154
- rowShift,
155
155
  sharedStrings,
156
156
  sharedStringsHeader,
157
157
  sheetMergeCells,
@@ -253,8 +253,10 @@ class TemplateFs {
253
253
  const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
254
254
  if (hasTablePlaceholders) {
255
255
  const result = this.#expandTableRows(sheetContent, sharedStringsContent, replacements);
256
- sheetContent = result.sheet;
257
- sharedStringsContent = result.shared;
256
+ if (result) {
257
+ sheetContent = result.sheet;
258
+ sharedStringsContent = result.shared;
259
+ }
258
260
  }
259
261
  }
260
262
  if (this.fileKeys.has(sharedStringsPath)) {
@@ -112,26 +112,27 @@ class TemplateMemory {
112
112
  */
113
113
  #expandTableRows(sheetXml, sharedStringsXml, replacements) {
114
114
  const { initialMergeCells, mergeCellMatches, modifiedXml, } = Utils.processMergeCells(sheetXml);
115
- const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = Utils.processSharedStrings(sharedStringsXml);
116
- const { lastIndex, resultRows, rowShift } = Utils.processRows({
115
+ const { sharedIndexMap, sharedStrings, sharedStringsHeader, } = Utils.processSharedStrings(sharedStringsXml);
116
+ const { isTableReplacementsFound, lastIndex, resultRows, sheetMergeCells, } = Utils.processRows({
117
117
  mergeCellMatches,
118
118
  replacements,
119
119
  sharedIndexMap,
120
120
  sharedStrings,
121
- sheetMergeCells,
122
121
  sheetXml: modifiedXml,
123
122
  });
124
- return Utils.processMergeFinalize({
123
+ if (!isTableReplacementsFound) {
124
+ return null;
125
+ }
126
+ const { shared, sheet } = Utils.processMergeFinalize({
125
127
  initialMergeCells,
126
128
  lastIndex,
127
- mergeCellMatches,
128
129
  resultRows,
129
- rowShift,
130
130
  sharedStrings,
131
131
  sharedStringsHeader,
132
132
  sheetMergeCells,
133
133
  sheetXml: modifiedXml,
134
134
  });
135
+ return { shared, sheet };
135
136
  }
136
137
  /**
137
138
  * Extracts the XML content from an Excel sheet file.
@@ -241,10 +242,13 @@ class TemplateMemory {
241
242
  sheetContent = await this.#extractXmlFromSheet(sheetPath);
242
243
  const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
243
244
  const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
244
- if (hasTablePlaceholders) {
245
+ const hasArraysInReplacements = Utils.foundArraysInReplacements(replacements);
246
+ if (hasTablePlaceholders && hasArraysInReplacements) {
245
247
  const result = this.#expandTableRows(sheetContent, sharedStringsContent, replacements);
246
- sheetContent = result.sheet;
247
- sharedStringsContent = result.shared;
248
+ if (result) {
249
+ sheetContent = result.sheet;
250
+ sharedStringsContent = result.shared;
251
+ }
248
252
  }
249
253
  }
250
254
  if (this.files[sharedStringsPath]) {
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * Recursively checks if an object contains any arrays in its values.
4
+ *
5
+ * @param {Record<string, unknown>} replacements - The object to check for arrays.
6
+ * @returns {boolean} True if any arrays are found, false otherwise.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.foundArraysInReplacements = foundArraysInReplacements;
10
+ function foundArraysInReplacements(replacements) {
11
+ let isFound = false;
12
+ for (const key in replacements) {
13
+ const value = replacements[key];
14
+ if (Array.isArray(value)) {
15
+ isFound = true;
16
+ return isFound;
17
+ }
18
+ if (typeof value === "object" && value !== null) {
19
+ isFound = foundArraysInReplacements(value);
20
+ if (isFound) {
21
+ return isFound;
22
+ }
23
+ }
24
+ }
25
+ return isFound;
26
+ }
@@ -46,6 +46,7 @@ __exportStar(require("./column-index-to-letter.js"), exports);
46
46
  __exportStar(require("./compare-columns.js"), exports);
47
47
  __exportStar(require("./escape-xml.js"), exports);
48
48
  __exportStar(require("./extract-xml-declaration.js"), exports);
49
+ __exportStar(require("./found-arrays-in-replacements.js"), exports);
49
50
  __exportStar(require("./get-by-path.js"), exports);
50
51
  __exportStar(require("./get-max-row-number.js"), exports);
51
52
  __exportStar(require("./get-rows-above.js"), exports);
@@ -31,6 +31,17 @@ function processMergeCells(sheetXml) {
31
31
  }
32
32
  // Remove the <mergeCells> block from the XML
33
33
  const modifiedXml = sheetXml.replace(mergeCellsBlockRegex, "");
34
+ // Sort mergeCellMatches by from by number row
35
+ mergeCellMatches.sort((a, b) => {
36
+ const rowA = parseInt(a.from.match(/\d+/)?.[0] ?? "0");
37
+ const rowB = parseInt(b.from.match(/\d+/)?.[0] ?? "0");
38
+ return rowA - rowB;
39
+ });
40
+ // Create mergeCellsByTags array
41
+ const mergeCellsByTags = [];
42
+ for (const mergeCell of mergeCellMatches) {
43
+ mergeCellsByTags.push(`<mergeCell ref="${mergeCell.from}:${mergeCell.to}" />`);
44
+ }
34
45
  return {
35
46
  initialMergeCells,
36
47
  mergeCellMatches,
@@ -10,10 +10,8 @@ const update_dimension_js_1 = require("./update-dimension.js");
10
10
  * @param {object} data - An object containing the following properties:
11
11
  * - `initialMergeCells`: The initial merge cells from the original sheet.
12
12
  * - `lastIndex`: The last processed position in the sheet XML.
13
- * - `mergeCellMatches`: An array of objects with `from` and `to` properties,
14
13
  * describing the merge cells.
15
14
  * - `resultRows`: An array of processed XML rows.
16
- * - `rowShift`: The total row shift.
17
15
  * - `sharedStrings`: An array of shared strings.
18
16
  * - `sharedStringsHeader`: The XML declaration of the shared strings.
19
17
  * - `sheetMergeCells`: An array of merge cell XML strings.
@@ -24,18 +22,7 @@ const update_dimension_js_1 = require("./update-dimension.js");
24
22
  * - `sheet`: The modified sheet XML string with updated merge cells.
25
23
  */
26
24
  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
- }
25
+ const { initialMergeCells, lastIndex, resultRows, sharedStrings, sharedStringsHeader, sheetMergeCells, sheetXml, } = data;
39
26
  resultRows.push(sheetXml.slice(lastIndex));
40
27
  // Form XML for mergeCells if there are any
41
28
  const mergeXml = sheetMergeCells.length
@@ -18,8 +18,13 @@ exports.processRows = processRows;
18
18
  * - `rowShift`: The total row shift.
19
19
  */
20
20
  function processRows(data) {
21
- const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetMergeCells, sheetXml, } = data;
21
+ const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetXml, } = data;
22
22
  const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
23
+ let isTableReplacementsFound = false;
24
+ // Array for storing merge cells
25
+ const sheetMergeCells = new Set();
26
+ // Set for storing processed rows
27
+ const rowsProcessed = new Set();
23
28
  // Array for storing resulting XML rows
24
29
  const resultRows = [];
25
30
  // Previous position of processed part of XML
@@ -27,7 +32,7 @@ function processRows(data) {
27
32
  // Shift for row numbers
28
33
  let rowShift = 0;
29
34
  // Regular expression for finding <row> elements
30
- const rowRegex = /<row[^>]*?>[\s\S]*?<\/row>/g;
35
+ const rowRegex = /<row\b[^>]*\/>|<row\b[^>]*>[\s\S]*?<\/row>/g;
31
36
  // Process each <row> element
32
37
  for (const match of sheetXml.matchAll(rowRegex)) {
33
38
  // Full XML row
@@ -43,6 +48,8 @@ function processRows(data) {
43
48
  lastIndex = matchEnd;
44
49
  // Get row number from r attribute
45
50
  const originalRowNumber = parseInt(fullRow.match(/<row[^>]* r="(\d+)"/)?.[1] ?? "1", 10);
51
+ // Add the row number to the set of processed rows
52
+ rowsProcessed.add(originalRowNumber);
46
53
  // Update row number based on rowShift
47
54
  const shiftedRowNumber = originalRowNumber + rowShift;
48
55
  // Find shared string indexes in cells of the current row
@@ -73,14 +80,21 @@ function processRows(data) {
73
80
  for (const { from, to } of mergeCellMatches) {
74
81
  const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
75
82
  const [, toCol] = to.match(/^([A-Z]+)(\d+)$/);
83
+ // Consider rows that were skipped but have a mergeCell
84
+ // and don't add those that have already been added
85
+ if (Number(fromRow) < calculatedRowNumber && !rowsProcessed.has(Number(fromRow))) {
86
+ sheetMergeCells.add(`<mergeCell ref="${from}:${to}"/>`);
87
+ rowsProcessed.add(Number(fromRow));
88
+ }
76
89
  if (Number(fromRow) === calculatedRowNumber) {
77
90
  const newFrom = `${fromCol}${shiftedRowNumber}`;
78
91
  const newTo = `${toCol}${shiftedRowNumber}`;
79
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
92
+ sheetMergeCells.add(`<mergeCell ref="${newFrom}:${newTo}"/>`);
80
93
  }
81
94
  }
82
95
  continue;
83
96
  }
97
+ isTableReplacementsFound = true;
84
98
  // Get the table name from the first placeholder
85
99
  const firstMatch = tablePlaceholders[0];
86
100
  const tableName = firstMatch?.[1];
@@ -138,7 +152,7 @@ function processRows(data) {
138
152
  const [, colTo, rowTo] = to.match(/^([A-Z]+)(\d+)$/);
139
153
  const newFrom = `${colFrom}${Number(rowFrom) + i}`;
140
154
  const newTo = `${colTo}${Number(rowTo) + i}`;
141
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
155
+ sheetMergeCells.add(`<mergeCell ref="${newFrom}:${newTo}"/>`);
142
156
  }
143
157
  }
144
158
  // It increases the row shift by the number of added rows minus one replaced
@@ -155,6 +169,11 @@ function processRows(data) {
155
169
  }
156
170
  }
157
171
  }
158
- return { lastIndex, resultRows, rowShift };
172
+ return {
173
+ isTableReplacementsFound,
174
+ lastIndex,
175
+ resultRows,
176
+ rowShift,
177
+ sheetMergeCells: Array.from(sheetMergeCells),
178
+ };
159
179
  }
160
- ;
@@ -17,8 +17,6 @@ const extract_xml_declaration_js_1 = require("./extract-xml-declaration.js");
17
17
  * @returns An object with the four properties above
18
18
  */
19
19
  function processSharedStrings(sharedStringsXml) {
20
- // Final list of merged cells with all changes
21
- const sheetMergeCells = [];
22
20
  // Array for storing shared strings
23
21
  const sharedStrings = [];
24
22
  const sharedStringsHeader = (0, extract_xml_declaration_js_1.extractXmlDeclaration)(sharedStringsXml);
@@ -39,7 +37,6 @@ function processSharedStrings(sharedStringsXml) {
39
37
  sharedIndexMap,
40
38
  sharedStrings,
41
39
  sharedStringsHeader,
42
- sheetMergeCells,
43
40
  };
44
41
  }
45
42
  ;
@@ -98,21 +98,21 @@ export class TemplateFs {
98
98
  */
99
99
  #expandTableRows(sheetXml, sharedStringsXml, replacements) {
100
100
  const { initialMergeCells, mergeCellMatches, modifiedXml, } = Utils.processMergeCells(sheetXml);
101
- const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = Utils.processSharedStrings(sharedStringsXml);
102
- const { lastIndex, resultRows, rowShift } = Utils.processRows({
101
+ const { sharedIndexMap, sharedStrings, sharedStringsHeader, } = Utils.processSharedStrings(sharedStringsXml);
102
+ const { isTableReplacementsFound, lastIndex, resultRows, sheetMergeCells, } = Utils.processRows({
103
103
  mergeCellMatches,
104
104
  replacements,
105
105
  sharedIndexMap,
106
106
  sharedStrings,
107
- sheetMergeCells,
108
107
  sheetXml: modifiedXml,
109
108
  });
109
+ if (!isTableReplacementsFound) {
110
+ return null;
111
+ }
110
112
  return Utils.processMergeFinalize({
111
113
  initialMergeCells,
112
114
  lastIndex,
113
- mergeCellMatches,
114
115
  resultRows,
115
- rowShift,
116
116
  sharedStrings,
117
117
  sharedStringsHeader,
118
118
  sheetMergeCells,
@@ -214,8 +214,10 @@ export class TemplateFs {
214
214
  const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
215
215
  if (hasTablePlaceholders) {
216
216
  const result = this.#expandTableRows(sheetContent, sharedStringsContent, replacements);
217
- sheetContent = result.sheet;
218
- sharedStringsContent = result.shared;
217
+ if (result) {
218
+ sheetContent = result.sheet;
219
+ sharedStringsContent = result.shared;
220
+ }
219
221
  }
220
222
  }
221
223
  if (this.fileKeys.has(sharedStringsPath)) {
@@ -76,26 +76,27 @@ export class TemplateMemory {
76
76
  */
77
77
  #expandTableRows(sheetXml, sharedStringsXml, replacements) {
78
78
  const { initialMergeCells, mergeCellMatches, modifiedXml, } = Utils.processMergeCells(sheetXml);
79
- const { sharedIndexMap, sharedStrings, sharedStringsHeader, sheetMergeCells, } = Utils.processSharedStrings(sharedStringsXml);
80
- const { lastIndex, resultRows, rowShift } = Utils.processRows({
79
+ const { sharedIndexMap, sharedStrings, sharedStringsHeader, } = Utils.processSharedStrings(sharedStringsXml);
80
+ const { isTableReplacementsFound, lastIndex, resultRows, sheetMergeCells, } = Utils.processRows({
81
81
  mergeCellMatches,
82
82
  replacements,
83
83
  sharedIndexMap,
84
84
  sharedStrings,
85
- sheetMergeCells,
86
85
  sheetXml: modifiedXml,
87
86
  });
88
- return Utils.processMergeFinalize({
87
+ if (!isTableReplacementsFound) {
88
+ return null;
89
+ }
90
+ const { shared, sheet } = Utils.processMergeFinalize({
89
91
  initialMergeCells,
90
92
  lastIndex,
91
- mergeCellMatches,
92
93
  resultRows,
93
- rowShift,
94
94
  sharedStrings,
95
95
  sharedStringsHeader,
96
96
  sheetMergeCells,
97
97
  sheetXml: modifiedXml,
98
98
  });
99
+ return { shared, sheet };
99
100
  }
100
101
  /**
101
102
  * Extracts the XML content from an Excel sheet file.
@@ -205,10 +206,13 @@ export class TemplateMemory {
205
206
  sheetContent = await this.#extractXmlFromSheet(sheetPath);
206
207
  const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
207
208
  const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
208
- if (hasTablePlaceholders) {
209
+ const hasArraysInReplacements = Utils.foundArraysInReplacements(replacements);
210
+ if (hasTablePlaceholders && hasArraysInReplacements) {
209
211
  const result = this.#expandTableRows(sheetContent, sharedStringsContent, replacements);
210
- sheetContent = result.sheet;
211
- sharedStringsContent = result.shared;
212
+ if (result) {
213
+ sheetContent = result.sheet;
214
+ sharedStringsContent = result.shared;
215
+ }
212
216
  }
213
217
  }
214
218
  if (this.files[sharedStringsPath]) {
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Recursively checks if an object contains any arrays in its values.
3
+ *
4
+ * @param {Record<string, unknown>} replacements - The object to check for arrays.
5
+ * @returns {boolean} True if any arrays are found, false otherwise.
6
+ */
7
+ export function foundArraysInReplacements(replacements) {
8
+ let isFound = false;
9
+ for (const key in replacements) {
10
+ const value = replacements[key];
11
+ if (Array.isArray(value)) {
12
+ isFound = true;
13
+ return isFound;
14
+ }
15
+ if (typeof value === "object" && value !== null) {
16
+ isFound = foundArraysInReplacements(value);
17
+ if (isFound) {
18
+ return isFound;
19
+ }
20
+ }
21
+ }
22
+ return isFound;
23
+ }
@@ -7,6 +7,7 @@ export * from "./column-index-to-letter.js";
7
7
  export * from "./compare-columns.js";
8
8
  export * from "./escape-xml.js";
9
9
  export * from "./extract-xml-declaration.js";
10
+ export * from "./found-arrays-in-replacements.js";
10
11
  export * from "./get-by-path.js";
11
12
  export * from "./get-max-row-number.js";
12
13
  export * from "./get-rows-above.js";
@@ -28,6 +28,17 @@ export function processMergeCells(sheetXml) {
28
28
  }
29
29
  // Remove the <mergeCells> block from the XML
30
30
  const modifiedXml = sheetXml.replace(mergeCellsBlockRegex, "");
31
+ // Sort mergeCellMatches by from by number row
32
+ mergeCellMatches.sort((a, b) => {
33
+ const rowA = parseInt(a.from.match(/\d+/)?.[0] ?? "0");
34
+ const rowB = parseInt(b.from.match(/\d+/)?.[0] ?? "0");
35
+ return rowA - rowB;
36
+ });
37
+ // Create mergeCellsByTags array
38
+ const mergeCellsByTags = [];
39
+ for (const mergeCell of mergeCellMatches) {
40
+ mergeCellsByTags.push(`<mergeCell ref="${mergeCell.from}:${mergeCell.to}" />`);
41
+ }
31
42
  return {
32
43
  initialMergeCells,
33
44
  mergeCellMatches,
@@ -7,10 +7,8 @@ import { updateDimension } from "./update-dimension.js";
7
7
  * @param {object} data - An object containing the following properties:
8
8
  * - `initialMergeCells`: The initial merge cells from the original sheet.
9
9
  * - `lastIndex`: The last processed position in the sheet XML.
10
- * - `mergeCellMatches`: An array of objects with `from` and `to` properties,
11
10
  * describing the merge cells.
12
11
  * - `resultRows`: An array of processed XML rows.
13
- * - `rowShift`: The total row shift.
14
12
  * - `sharedStrings`: An array of shared strings.
15
13
  * - `sharedStringsHeader`: The XML declaration of the shared strings.
16
14
  * - `sheetMergeCells`: An array of merge cell XML strings.
@@ -21,18 +19,7 @@ import { updateDimension } from "./update-dimension.js";
21
19
  * - `sheet`: The modified sheet XML string with updated merge cells.
22
20
  */
23
21
  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
- }
22
+ const { initialMergeCells, lastIndex, resultRows, sharedStrings, sharedStringsHeader, sheetMergeCells, sheetXml, } = data;
36
23
  resultRows.push(sheetXml.slice(lastIndex));
37
24
  // Form XML for mergeCells if there are any
38
25
  const mergeXml = sheetMergeCells.length
@@ -15,8 +15,13 @@
15
15
  * - `rowShift`: The total row shift.
16
16
  */
17
17
  export function processRows(data) {
18
- const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetMergeCells, sheetXml, } = data;
18
+ const { mergeCellMatches, replacements, sharedIndexMap, sharedStrings, sheetXml, } = data;
19
19
  const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
20
+ let isTableReplacementsFound = false;
21
+ // Array for storing merge cells
22
+ const sheetMergeCells = new Set();
23
+ // Set for storing processed rows
24
+ const rowsProcessed = new Set();
20
25
  // Array for storing resulting XML rows
21
26
  const resultRows = [];
22
27
  // Previous position of processed part of XML
@@ -24,7 +29,7 @@ export function processRows(data) {
24
29
  // Shift for row numbers
25
30
  let rowShift = 0;
26
31
  // Regular expression for finding <row> elements
27
- const rowRegex = /<row[^>]*?>[\s\S]*?<\/row>/g;
32
+ const rowRegex = /<row\b[^>]*\/>|<row\b[^>]*>[\s\S]*?<\/row>/g;
28
33
  // Process each <row> element
29
34
  for (const match of sheetXml.matchAll(rowRegex)) {
30
35
  // Full XML row
@@ -40,6 +45,8 @@ export function processRows(data) {
40
45
  lastIndex = matchEnd;
41
46
  // Get row number from r attribute
42
47
  const originalRowNumber = parseInt(fullRow.match(/<row[^>]* r="(\d+)"/)?.[1] ?? "1", 10);
48
+ // Add the row number to the set of processed rows
49
+ rowsProcessed.add(originalRowNumber);
43
50
  // Update row number based on rowShift
44
51
  const shiftedRowNumber = originalRowNumber + rowShift;
45
52
  // Find shared string indexes in cells of the current row
@@ -70,14 +77,21 @@ export function processRows(data) {
70
77
  for (const { from, to } of mergeCellMatches) {
71
78
  const [, fromCol, fromRow] = from.match(/^([A-Z]+)(\d+)$/);
72
79
  const [, toCol] = to.match(/^([A-Z]+)(\d+)$/);
80
+ // Consider rows that were skipped but have a mergeCell
81
+ // and don't add those that have already been added
82
+ if (Number(fromRow) < calculatedRowNumber && !rowsProcessed.has(Number(fromRow))) {
83
+ sheetMergeCells.add(`<mergeCell ref="${from}:${to}"/>`);
84
+ rowsProcessed.add(Number(fromRow));
85
+ }
73
86
  if (Number(fromRow) === calculatedRowNumber) {
74
87
  const newFrom = `${fromCol}${shiftedRowNumber}`;
75
88
  const newTo = `${toCol}${shiftedRowNumber}`;
76
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
89
+ sheetMergeCells.add(`<mergeCell ref="${newFrom}:${newTo}"/>`);
77
90
  }
78
91
  }
79
92
  continue;
80
93
  }
94
+ isTableReplacementsFound = true;
81
95
  // Get the table name from the first placeholder
82
96
  const firstMatch = tablePlaceholders[0];
83
97
  const tableName = firstMatch?.[1];
@@ -135,7 +149,7 @@ export function processRows(data) {
135
149
  const [, colTo, rowTo] = to.match(/^([A-Z]+)(\d+)$/);
136
150
  const newFrom = `${colFrom}${Number(rowFrom) + i}`;
137
151
  const newTo = `${colTo}${Number(rowTo) + i}`;
138
- sheetMergeCells.push(`<mergeCell ref="${newFrom}:${newTo}"/>`);
152
+ sheetMergeCells.add(`<mergeCell ref="${newFrom}:${newTo}"/>`);
139
153
  }
140
154
  }
141
155
  // It increases the row shift by the number of added rows minus one replaced
@@ -152,6 +166,11 @@ export function processRows(data) {
152
166
  }
153
167
  }
154
168
  }
155
- return { lastIndex, resultRows, rowShift };
169
+ return {
170
+ isTableReplacementsFound,
171
+ lastIndex,
172
+ resultRows,
173
+ rowShift,
174
+ sheetMergeCells: Array.from(sheetMergeCells),
175
+ };
156
176
  }
157
- ;
@@ -14,8 +14,6 @@ import { extractXmlDeclaration } from "./extract-xml-declaration.js";
14
14
  * @returns An object with the four properties above
15
15
  */
16
16
  export function processSharedStrings(sharedStringsXml) {
17
- // Final list of merged cells with all changes
18
- const sheetMergeCells = [];
19
17
  // Array for storing shared strings
20
18
  const sharedStrings = [];
21
19
  const sharedStringsHeader = extractXmlDeclaration(sharedStringsXml);
@@ -36,7 +34,6 @@ export function processSharedStrings(sharedStringsXml) {
36
34
  sharedIndexMap,
37
35
  sharedStrings,
38
36
  sharedStringsHeader,
39
- sheetMergeCells,
40
37
  };
41
38
  }
42
39
  ;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Recursively checks if an object contains any arrays in its values.
3
+ *
4
+ * @param {Record<string, unknown>} replacements - The object to check for arrays.
5
+ * @returns {boolean} True if any arrays are found, false otherwise.
6
+ */
7
+ export declare function foundArraysInReplacements(replacements: Record<string, unknown>): boolean;
@@ -7,6 +7,7 @@ export * from "./column-index-to-letter.js";
7
7
  export * from "./compare-columns.js";
8
8
  export * from "./escape-xml.js";
9
9
  export * from "./extract-xml-declaration.js";
10
+ export * from "./found-arrays-in-replacements.js";
10
11
  export * from "./get-by-path.js";
11
12
  export * from "./get-max-row-number.js";
12
13
  export * from "./get-rows-above.js";
@@ -1,3 +1,16 @@
1
+ export type ProcessMergeFinalizeData = {
2
+ initialMergeCells: string[];
3
+ lastIndex: number;
4
+ resultRows: string[];
5
+ sharedStrings: string[];
6
+ sharedStringsHeader: string | null;
7
+ sheetMergeCells: string[];
8
+ sheetXml: string;
9
+ };
10
+ export type ProcessMergeFinalizeResult = {
11
+ shared: string;
12
+ sheet: string;
13
+ };
1
14
  /**
2
15
  * Finalizes the processing of the merged sheet by updating the merge cells and
3
16
  * inserting them into the sheet XML. It also returns the modified sheet XML and
@@ -6,10 +19,8 @@
6
19
  * @param {object} data - An object containing the following properties:
7
20
  * - `initialMergeCells`: The initial merge cells from the original sheet.
8
21
  * - `lastIndex`: The last processed position in the sheet XML.
9
- * - `mergeCellMatches`: An array of objects with `from` and `to` properties,
10
22
  * describing the merge cells.
11
23
  * - `resultRows`: An array of processed XML rows.
12
- * - `rowShift`: The total row shift.
13
24
  * - `sharedStrings`: An array of shared strings.
14
25
  * - `sharedStringsHeader`: The XML declaration of the shared strings.
15
26
  * - `sheetMergeCells`: An array of merge cell XML strings.
@@ -19,20 +30,4 @@
19
30
  * - `shared`: The modified shared strings XML string.
20
31
  * - `sheet`: The modified sheet XML string with updated merge cells.
21
32
  */
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
- };
33
+ export declare function processMergeFinalize(data: ProcessMergeFinalizeData): ProcessMergeFinalizeResult;
@@ -22,10 +22,11 @@ export declare function processRows(data: {
22
22
  to: string;
23
23
  }[];
24
24
  sharedStrings: string[];
25
- sheetMergeCells: string[];
26
25
  sheetXml: string;
27
26
  }): {
27
+ isTableReplacementsFound: boolean;
28
28
  lastIndex: number;
29
29
  resultRows: string[];
30
30
  rowShift: number;
31
+ sheetMergeCells: string[];
31
32
  };
@@ -16,5 +16,4 @@ export declare function processSharedStrings(sharedStringsXml: string): {
16
16
  sharedIndexMap: Map<string, number>;
17
17
  sharedStrings: string[];
18
18
  sharedStringsHeader: string | null;
19
- sheetMergeCells: string[];
20
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-ak/excel-toolbox",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "excel-toolbox",
5
5
  "publishConfig": {
6
6
  "access": "public",