@simplysm/excel 13.0.76 → 13.0.78

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 (54) hide show
  1. package/README.md +539 -17
  2. package/dist/excel-cell.d.ts +4 -4
  3. package/dist/excel-cell.d.ts.map +1 -1
  4. package/dist/excel-cell.js +9 -10
  5. package/dist/excel-cell.js.map +1 -1
  6. package/dist/excel-workbook.d.ts +3 -3
  7. package/dist/excel-workbook.d.ts.map +1 -1
  8. package/dist/excel-workbook.js +3 -3
  9. package/dist/excel-workbook.js.map +1 -1
  10. package/dist/excel-worksheet.d.ts +1 -1
  11. package/dist/excel-worksheet.d.ts.map +1 -1
  12. package/dist/excel-worksheet.js +13 -17
  13. package/dist/excel-worksheet.js.map +1 -1
  14. package/dist/excel-wrapper.d.ts +1 -1
  15. package/dist/excel-wrapper.js +7 -7
  16. package/dist/excel-wrapper.js.map +1 -1
  17. package/dist/types.d.ts +1 -1
  18. package/dist/types.d.ts.map +1 -1
  19. package/dist/utils/excel-utils.d.ts +5 -5
  20. package/dist/utils/excel-utils.d.ts.map +1 -1
  21. package/dist/utils/excel-utils.js +15 -15
  22. package/dist/utils/excel-utils.js.map +1 -1
  23. package/dist/utils/zip-cache.js +3 -3
  24. package/dist/utils/zip-cache.js.map +1 -1
  25. package/dist/xml/excel-xml-relationship.js +2 -2
  26. package/dist/xml/excel-xml-relationship.js.map +1 -1
  27. package/dist/xml/excel-xml-style.js +16 -16
  28. package/dist/xml/excel-xml-style.js.map +1 -1
  29. package/dist/xml/excel-xml-workbook.js +6 -6
  30. package/dist/xml/excel-xml-workbook.js.map +1 -1
  31. package/dist/xml/excel-xml-worksheet.d.ts +5 -2
  32. package/dist/xml/excel-xml-worksheet.d.ts.map +1 -1
  33. package/dist/xml/excel-xml-worksheet.js +38 -20
  34. package/dist/xml/excel-xml-worksheet.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/excel-cell.ts +10 -11
  37. package/src/excel-workbook.ts +3 -3
  38. package/src/excel-worksheet.ts +17 -18
  39. package/src/excel-wrapper.ts +7 -7
  40. package/src/types.ts +1 -1
  41. package/src/utils/excel-utils.ts +15 -15
  42. package/src/utils/zip-cache.ts +3 -3
  43. package/src/xml/excel-xml-relationship.ts +2 -2
  44. package/src/xml/excel-xml-style.ts +16 -16
  45. package/src/xml/excel-xml-workbook.ts +6 -6
  46. package/src/xml/excel-xml-worksheet.ts +47 -22
  47. package/tests/excel-cell.spec.ts +85 -69
  48. package/tests/excel-col.spec.ts +17 -17
  49. package/tests/excel-row.spec.ts +13 -13
  50. package/tests/excel-workbook.spec.ts +26 -26
  51. package/tests/excel-worksheet.spec.ts +217 -101
  52. package/tests/excel-wrapper.spec.ts +24 -24
  53. package/tests/image-insert.spec.ts +5 -5
  54. package/tests/utils/excel-utils.spec.ts +14 -14
@@ -1,6 +1,6 @@
1
1
  import type { Bytes } from "@simplysm/core-common";
2
2
  import "@simplysm/core-common";
3
- import { strIsNullOrEmpty } from "@simplysm/core-common";
3
+ import { str } from "@simplysm/core-common";
4
4
  import mime from "mime";
5
5
  import type { ExcelCell } from "./excel-cell";
6
6
  import { ExcelCol } from "./excel-col";
@@ -109,21 +109,20 @@ export class ExcelWorksheet {
109
109
  const wsData = await this._getWsData();
110
110
  const range = wsData.range;
111
111
 
112
- // Increment row index by 1 for all merge cells at or below targetR
113
- const mergeCells = wsData.getMergeCells();
114
- for (const mc of mergeCells) {
115
- if (mc.s.r >= targetR) mc.s.r++;
116
- if (mc.e.r >= targetR) mc.e.r++;
117
- }
112
+ // Shift merge cells at or below targetR down by 1
113
+ wsData.shiftMergeCells(targetR, 1);
118
114
 
119
115
  // When srcR >= targetR, adjust for the shifted position of srcR
120
116
  const adjustedSrcR = srcR >= targetR ? srcR + 1 : srcR;
121
117
 
118
+ // Shift existing rows down (from bottom to top to avoid overwriting)
119
+ // Use skipMerge: true because merge cells are already shifted above
122
120
  for (let r = range.e.r; r >= targetR; r--) {
123
- await this.copyRow(r, r + 1);
121
+ wsData.copyRow(r, r + 1, { skipMerge: true });
124
122
  }
125
123
 
126
- await this.copyRow(adjustedSrcR, targetR);
124
+ // Copy source row to target position (includes merge handling)
125
+ wsData.copyRow(adjustedSrcR, targetR);
127
126
  }
128
127
 
129
128
  //#endregion
@@ -172,7 +171,7 @@ export class ExcelWorksheet {
172
171
  const startRow = opt?.headerRowIndex ?? range.s.r;
173
172
 
174
173
  for (let c = range.s.c; c <= range.e.c; c++) {
175
- const headerName = await this.cell(startRow, c).getVal();
174
+ const headerName = await this.cell(startRow, c).getValue();
176
175
  if (typeof headerName === "string") {
177
176
  if (opt?.usableHeaderNameFn == null || opt.usableHeaderNameFn(headerName)) {
178
177
  headerMap.set(headerName, c);
@@ -183,7 +182,7 @@ export class ExcelWorksheet {
183
182
  for (let r = startRow + 1; r <= range.e.r; r++) {
184
183
  if (
185
184
  opt?.checkEndColIndex !== undefined &&
186
- (await this.cell(r, opt.checkEndColIndex).getVal()) === undefined
185
+ (await this.cell(r, opt.checkEndColIndex).getValue()) === undefined
187
186
  ) {
188
187
  break;
189
188
  }
@@ -191,7 +190,7 @@ export class ExcelWorksheet {
191
190
  const record: Record<string, ExcelValueType> = {};
192
191
  for (const header of headerMap.keys()) {
193
192
  const c = headerMap.get(header)!;
194
- record[header] = await this.cell(r, c).getVal();
193
+ record[header] = await this.cell(r, c).getValue();
195
194
  }
196
195
 
197
196
  result.push(record);
@@ -207,7 +206,7 @@ export class ExcelWorksheet {
207
206
  async setDataMatrix(matrix: ExcelValueType[][]): Promise<void> {
208
207
  for (let r = 0; r < matrix.length; r++) {
209
208
  for (let c = 0; c < matrix[r].length; c++) {
210
- await this.cell(r, c).setVal(matrix[r][c]);
209
+ await this.cell(r, c).setValue(matrix[r][c]);
211
210
  }
212
211
  }
213
212
  }
@@ -220,15 +219,15 @@ export class ExcelWorksheet {
220
219
  const headers = records
221
220
  .flatMap((item) => Object.keys(item))
222
221
  .distinct()
223
- .filter((item) => !strIsNullOrEmpty(item));
222
+ .filter((item) => !str.isNullOrEmpty(item));
224
223
 
225
224
  for (let c = 0; c < headers.length; c++) {
226
- await this.cell(0, c).setVal(headers[c]);
225
+ await this.cell(0, c).setValue(headers[c]);
227
226
  }
228
227
 
229
228
  for (let r = 1; r < records.length + 1; r++) {
230
229
  for (let c = 0; c < headers.length; c++) {
231
- await this.cell(r, c).setVal(records[r - 1][headers[c]]);
230
+ await this.cell(r, c).setValue(records[r - 1][headers[c]]);
232
231
  }
233
232
  }
234
233
  }
@@ -247,12 +246,12 @@ export class ExcelWorksheet {
247
246
  }
248
247
 
249
248
  /** Set freeze panes for rows/columns */
250
- async setFix(point: { r?: number; c?: number }): Promise<void> {
249
+ async freezeAt(point: { r?: number; c?: number }): Promise<void> {
251
250
  const wbXml = await this._getWbData();
252
251
  wbXml.initializeView();
253
252
 
254
253
  const wsXml = await this._getWsData();
255
- wsXml.setFix(point);
254
+ wsXml.freezeAt(point);
256
255
  }
257
256
 
258
257
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import type { Bytes } from "@simplysm/core-common";
2
- import { DateOnly, DateTime, numParseFloat, Time } from "@simplysm/core-common";
2
+ import { DateOnly, DateTime, num, Time } from "@simplysm/core-common";
3
3
  import {
4
4
  type z,
5
5
  ZodBoolean,
@@ -98,7 +98,7 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
98
98
  * @example
99
99
  * ```typescript
100
100
  * await using wb = await wrapper.write("Sheet1", records);
101
- * const bytes = await wb.getBytes();
101
+ * const bytes = await wb.toBytes();
102
102
  * ```
103
103
  */
104
104
  async write(
@@ -107,7 +107,7 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
107
107
  options?: { excludes?: (keyof z.infer<TSchema>)[] },
108
108
  ): Promise<ExcelWorkbook> {
109
109
  const wb = new ExcelWorkbook();
110
- const ws = await wb.createWorksheet(wsName);
110
+ const ws = await wb.addWorksheet(wsName);
111
111
 
112
112
  const displayNameMap = this._getDisplayNameMap(options?.excludes as string[] | undefined);
113
113
  const keys = Object.keys(displayNameMap) as (keyof z.infer<TSchema>)[];
@@ -115,7 +115,7 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
115
115
 
116
116
  // Write header row
117
117
  for (let c = 0; c < headers.length; c++) {
118
- await ws.cell(0, c).setVal(headers[c]);
118
+ await ws.cell(0, c).setValue(headers[c]);
119
119
  }
120
120
 
121
121
  // Write data rows
@@ -123,7 +123,7 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
123
123
  for (let c = 0; c < keys.length; c++) {
124
124
  const key = keys[c];
125
125
  const value = records[r][key] as ExcelValueType;
126
- await ws.cell(r + 1, c).setVal(value);
126
+ await ws.cell(r + 1, c).setValue(value);
127
127
  }
128
128
  }
129
129
 
@@ -151,7 +151,7 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
151
151
 
152
152
  // View settings
153
153
  await ws.setZoom(85);
154
- await ws.setFix({ r: 0 });
154
+ await ws.freezeAt({ r: 0 });
155
155
 
156
156
  return wb;
157
157
  }
@@ -188,7 +188,7 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
188
188
 
189
189
  if (innerSchema instanceof ZodNumber) {
190
190
  if (typeof rawValue === "number") return rawValue;
191
- return numParseFloat(String(rawValue));
191
+ return num.parseFloat(String(rawValue));
192
192
  }
193
193
 
194
194
  if (innerSchema instanceof ZodBoolean) {
package/src/types.ts CHANGED
@@ -143,7 +143,7 @@ export interface ExcelCellData {
143
143
  $: {
144
144
  r: string; // address (A~)
145
145
  s?: string; // styleId
146
- t?: string; // type: s(sharedString)
146
+ t?: ExcelCellType; // type: s(sharedString)
147
147
  };
148
148
  v?: [string];
149
149
  f?: [string];
@@ -1,4 +1,4 @@
1
- import { numParseInt } from "@simplysm/core-common";
1
+ import { num } from "@simplysm/core-common";
2
2
  import type { ExcelAddressPoint, ExcelAddressRangePoint, ExcelNumberFormat } from "../types";
3
3
 
4
4
  /**
@@ -35,18 +35,18 @@ export class ExcelUtils {
35
35
  }
36
36
 
37
37
  /** Extract row index from cell address (e.g. "A3" -> 2) */
38
- static parseRowAddrCode(addrCode: string): number {
39
- const rowAddrCode = /\d*$/.exec(addrCode)?.[0] ?? "";
40
- const parsed = numParseInt(rowAddrCode);
38
+ static parseRowAddr(addr: string): number {
39
+ const rowAddrCode = /\d*$/.exec(addr)?.[0] ?? "";
40
+ const parsed = num.parseInt(rowAddrCode);
41
41
  if (parsed == null) {
42
- throw new Error(`Invalid row address code: ${addrCode}`);
42
+ throw new Error(`Invalid row address code: ${rowAddrCode}`);
43
43
  }
44
44
  return parsed - 1;
45
45
  }
46
46
 
47
47
  /** Extract column index from cell address (e.g. "B3" -> 1) */
48
- static parseColAddrCode(addrCode: string): number {
49
- const colAddrCode = /^[a-zA-Z]*/.exec(addrCode)?.[0] ?? "";
48
+ static parseColAddr(addr: string): number {
49
+ const colAddrCode = /^[a-zA-Z]*/.exec(addr)?.[0] ?? "";
50
50
 
51
51
  let result = 0;
52
52
  const revAddr = Array.from(colAddrCode).reverse().join("");
@@ -58,19 +58,19 @@ export class ExcelUtils {
58
58
  }
59
59
 
60
60
  /** Convert cell address to coordinates (e.g. "B3" -> {r: 2, c: 1}) */
61
- static parseCellAddrCode(addr: string): ExcelAddressPoint {
61
+ static parseCellAddr(addr: string): ExcelAddressPoint {
62
62
  return {
63
- r: ExcelUtils.parseRowAddrCode(addr),
64
- c: ExcelUtils.parseColAddrCode(addr),
63
+ r: ExcelUtils.parseRowAddr(addr),
64
+ c: ExcelUtils.parseColAddr(addr),
65
65
  };
66
66
  }
67
67
 
68
68
  /** Convert range address to coordinates (e.g. "A1:C3" -> {s: {r:0,c:0}, e: {r:2,c:2}}) */
69
- static parseRangeAddrCode(rangeAddr: string): ExcelAddressRangePoint {
69
+ static parseRangeAddr(rangeAddr: string): ExcelAddressRangePoint {
70
70
  const parts = rangeAddr.split(":");
71
71
  return {
72
- s: ExcelUtils.parseCellAddrCode(parts[0]),
73
- e: ExcelUtils.parseCellAddrCode(parts[1] ?? parts[0]),
72
+ s: ExcelUtils.parseCellAddr(parts[0]),
73
+ e: ExcelUtils.parseCellAddr(parts[1] ?? parts[0]),
74
74
  };
75
75
  }
76
76
 
@@ -102,9 +102,9 @@ export class ExcelUtils {
102
102
  * Convert Excel date number to JavaScript timestamp (ms).
103
103
  * Excel counts 1900-01-01 as 1 (1899-12-30 is date 0).
104
104
  */
105
- static convertNumberToTimeTick(num: number): number {
105
+ static convertNumberToTimeTick(value: number): number {
106
106
  const excelBaseDateNumberUtc = Date.UTC(1899, 11, 31);
107
- const excelDateNumberUtc = (num - 1) * 24 * 60 * 60 * 1000;
107
+ const excelDateNumberUtc = (value - 1) * 24 * 60 * 60 * 1000;
108
108
  const dateNumberUtc = excelBaseDateNumberUtc + excelDateNumberUtc;
109
109
  const date = new Date(dateNumberUtc);
110
110
  date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
@@ -1,5 +1,5 @@
1
1
  import type { Bytes } from "@simplysm/core-common";
2
- import { ZipArchive, xmlStringify, xmlParse } from "@simplysm/core-common";
2
+ import { ZipArchive, xml as xmlU } from "@simplysm/core-common";
3
3
  import type {
4
4
  ExcelXml,
5
5
  ExcelXmlContentTypeData,
@@ -51,7 +51,7 @@ export class ZipCache {
51
51
 
52
52
  if (filePath.endsWith(".xml") || filePath.endsWith(".rels")) {
53
53
  const fileText = new TextDecoder().decode(fileData);
54
- const xml = xmlParse(fileText, { stripTagPrefix: true });
54
+ const xml = xmlU.parse(fileText, { stripTagPrefix: true });
55
55
  if (filePath.endsWith(".rels")) {
56
56
  this._cache.set(filePath, new ExcelXmlRelationship(xml as ExcelXmlRelationshipData));
57
57
  } else if (filePath === "[Content_Types].xml") {
@@ -87,7 +87,7 @@ export class ZipCache {
87
87
 
88
88
  if ("cleanup" in content) {
89
89
  content.cleanup();
90
- this._zip.write(filePath, new TextEncoder().encode(xmlStringify(content.data)));
90
+ this._zip.write(filePath, new TextEncoder().encode(xmlU.stringify(content.data)));
91
91
  } else {
92
92
  this._zip.write(filePath, content);
93
93
  }
@@ -1,5 +1,5 @@
1
1
  import "@simplysm/core-common";
2
- import { numParseInt } from "@simplysm/core-common";
2
+ import { num } from "@simplysm/core-common";
3
3
  import type { ExcelRelationshipData, ExcelXml, ExcelXmlRelationshipData } from "../types";
4
4
 
5
5
  /**
@@ -84,6 +84,6 @@ export class ExcelXmlRelationship implements ExcelXml {
84
84
  if (match == null) {
85
85
  throw new Error(`Invalid relationship ID format: ${rel.$.Id}`);
86
86
  }
87
- return numParseInt(match[0])!;
87
+ return num.parseInt(match[0])!;
88
88
  }
89
89
  }
@@ -9,7 +9,7 @@ import type {
9
9
  ExcelXmlStyleDataXf,
10
10
  } from "../types";
11
11
  import "@simplysm/core-common";
12
- import { numParseInt, objClone, objEqual } from "@simplysm/core-common";
12
+ import { num, obj } from "@simplysm/core-common";
13
13
 
14
14
  export interface ExcelStyle {
15
15
  numFmtId?: string;
@@ -106,7 +106,7 @@ export class ExcelXmlStyle implements ExcelXml {
106
106
  }
107
107
 
108
108
  addWithClone(id: string, style: ExcelStyle): string {
109
- const idNum = numParseInt(id);
109
+ const idNum = num.parseInt(id);
110
110
  if (idNum == null) {
111
111
  throw new Error(`Invalid style ID: ${id}`);
112
112
  }
@@ -115,7 +115,7 @@ export class ExcelXmlStyle implements ExcelXml {
115
115
  throw new Error(`Non-existent style ID: ${id} (Range: 0-${xfArray.length - 1})`);
116
116
  }
117
117
  const prevXf = xfArray[idNum];
118
- const cloneXf = objClone(prevXf);
118
+ const cloneXf = obj.clone(prevXf);
119
119
 
120
120
  if (style.numFmtId !== undefined) {
121
121
  cloneXf.$.numFmtId = style.numFmtId;
@@ -127,12 +127,12 @@ export class ExcelXmlStyle implements ExcelXml {
127
127
  }
128
128
 
129
129
  if (style.background !== undefined) {
130
- const fillIdNum = cloneXf.$.fillId !== undefined ? numParseInt(cloneXf.$.fillId) : undefined;
130
+ const fillIdNum = cloneXf.$.fillId !== undefined ? num.parseInt(cloneXf.$.fillId) : undefined;
131
131
  const prevFill =
132
132
  fillIdNum !== undefined ? this.data.styleSheet.fills[0].fill[fillIdNum] : undefined;
133
133
 
134
134
  if (prevFill != null) {
135
- const cloneFill = objClone(prevFill);
135
+ const cloneFill = obj.clone(prevFill);
136
136
  cloneFill.patternFill[0].$.patternType = "solid";
137
137
 
138
138
  if (cloneFill.patternFill[0].fgColor == null) {
@@ -159,12 +159,12 @@ export class ExcelXmlStyle implements ExcelXml {
159
159
 
160
160
  if (style.border !== undefined) {
161
161
  const borderIdNum =
162
- cloneXf.$.borderId !== undefined ? numParseInt(cloneXf.$.borderId) : undefined;
162
+ cloneXf.$.borderId !== undefined ? num.parseInt(cloneXf.$.borderId) : undefined;
163
163
  const prevBorder =
164
164
  borderIdNum !== undefined ? this.data.styleSheet.borders[0].border[borderIdNum] : undefined;
165
165
 
166
166
  if (prevBorder != null) {
167
- const cloneBorder = objClone(prevBorder);
167
+ const cloneBorder = obj.clone(prevBorder);
168
168
  this._applyBorderPosition(cloneBorder, "left", style.border.includes("left"));
169
169
  this._applyBorderPosition(cloneBorder, "right", style.border.includes("right"));
170
170
  this._applyBorderPosition(cloneBorder, "top", style.border.includes("top"));
@@ -185,7 +185,7 @@ export class ExcelXmlStyle implements ExcelXml {
185
185
  }
186
186
 
187
187
  get(id: string): ExcelStyle {
188
- const idNum = numParseInt(id);
188
+ const idNum = num.parseInt(id);
189
189
  if (idNum == null) {
190
190
  throw new Error(`Invalid style ID: ${id}`);
191
191
  }
@@ -197,7 +197,7 @@ export class ExcelXmlStyle implements ExcelXml {
197
197
  result.numFmtId = xf.$.numFmtId;
198
198
 
199
199
  if (xf.$.fillId !== undefined) {
200
- const fillIdNum = numParseInt(xf.$.fillId);
200
+ const fillIdNum = num.parseInt(xf.$.fillId);
201
201
  if (fillIdNum != null) {
202
202
  const fill = this.data.styleSheet.fills[0].fill[fillIdNum] as
203
203
  | ExcelXmlStyleDataFill
@@ -212,7 +212,7 @@ export class ExcelXmlStyle implements ExcelXml {
212
212
  }
213
213
 
214
214
  if (xf.$.borderId !== undefined) {
215
- const borderIdNum = numParseInt(xf.$.borderId);
215
+ const borderIdNum = num.parseInt(xf.$.borderId);
216
216
  if (borderIdNum == null) {
217
217
  throw new Error(`Invalid border ID: ${xf.$.borderId}`);
218
218
  }
@@ -303,9 +303,9 @@ export class ExcelXmlStyle implements ExcelXml {
303
303
  const numFmts = this.data.styleSheet.numFmts[0].numFmt;
304
304
  const maxItem =
305
305
  numFmts.length > 0
306
- ? numFmts.orderByDesc((item) => numParseInt(item.$.numFmtId) ?? 180).first()
306
+ ? numFmts.orderByDesc((item) => num.parseInt(item.$.numFmtId) ?? 180).first()
307
307
  : undefined;
308
- const maxId = maxItem ? (numParseInt(maxItem.$.numFmtId) ?? 180) : 180;
308
+ const maxId = maxItem ? (num.parseInt(maxItem.$.numFmtId) ?? 180) : 180;
309
309
  const nextNumFmtId = (maxId + 1).toString();
310
310
  this.data.styleSheet.numFmts[0].numFmt.push({
311
311
  $: {
@@ -314,7 +314,7 @@ export class ExcelXmlStyle implements ExcelXml {
314
314
  },
315
315
  });
316
316
  this.data.styleSheet.numFmts[0].$.count = (
317
- (numParseInt(this.data.styleSheet.numFmts[0].$.count) ?? 0) + 1
317
+ (num.parseInt(this.data.styleSheet.numFmts[0].$.count) ?? 0) + 1
318
318
  ).toString();
319
319
 
320
320
  return nextNumFmtId;
@@ -377,7 +377,7 @@ export class ExcelXmlStyle implements ExcelXml {
377
377
  }
378
378
 
379
379
  private _getSameOrCreateXf(xfItem: ExcelXmlStyleDataXf): string {
380
- const prevSameXf = this.data.styleSheet.cellXfs[0].xf.single((item) => objEqual(item, xfItem));
380
+ const prevSameXf = this.data.styleSheet.cellXfs[0].xf.single((item) => obj.equal(item, xfItem));
381
381
 
382
382
  if (prevSameXf != null) {
383
383
  return this.data.styleSheet.cellXfs[0].xf.indexOf(prevSameXf).toString();
@@ -391,7 +391,7 @@ export class ExcelXmlStyle implements ExcelXml {
391
391
 
392
392
  private _getSameOrCreateFill(fillItem: ExcelXmlStyleDataFill): string {
393
393
  const prevSameFill = this.data.styleSheet.fills[0].fill.single((item) =>
394
- objEqual(item, fillItem),
394
+ obj.equal(item, fillItem),
395
395
  );
396
396
 
397
397
  if (prevSameFill != null) {
@@ -405,7 +405,7 @@ export class ExcelXmlStyle implements ExcelXml {
405
405
 
406
406
  private _getSameOrCreateBorder(borderItem: ExcelXmlStyleDataBorder): string {
407
407
  const prevSameBorder = this.data.styleSheet.borders[0].border.single((item) =>
408
- objEqual(item, borderItem),
408
+ obj.equal(item, borderItem),
409
409
  );
410
410
 
411
411
  if (prevSameBorder != null) {
@@ -1,5 +1,5 @@
1
1
  import "@simplysm/core-common";
2
- import { numParseInt } from "@simplysm/core-common";
2
+ import { num } from "@simplysm/core-common";
3
3
  import type { ExcelXml, ExcelXmlWorkbookData } from "../types";
4
4
 
5
5
  /**
@@ -27,8 +27,8 @@ export class ExcelXmlWorkbook implements ExcelXml {
27
27
  get lastWsRelId(): number | undefined {
28
28
  const sheets = this.data.workbook.sheets?.[0].sheet;
29
29
  if (!sheets || sheets.length === 0) return undefined;
30
- const maxSheet = sheets.orderByDesc((sheet) => numParseInt(sheet.$["r:id"])!).first();
31
- return maxSheet ? numParseInt(maxSheet.$["r:id"]) : undefined;
30
+ const maxSheet = sheets.orderByDesc((sheet) => num.parseInt(sheet.$["r:id"])!).first();
31
+ return maxSheet ? num.parseInt(maxSheet.$["r:id"]) : undefined;
32
32
  }
33
33
 
34
34
  get sheetNames(): string[] {
@@ -81,7 +81,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
81
81
  }
82
82
 
83
83
  getWsRelIdByName(name: string): number | undefined {
84
- return numParseInt(
84
+ return num.parseInt(
85
85
  (this.data.workbook.sheets?.[0].sheet ?? []).single((item) => item.$.name === name)?.$[
86
86
  "r:id"
87
87
  ],
@@ -89,7 +89,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
89
89
  }
90
90
 
91
91
  getWsRelIdByIndex(index: number): number | undefined {
92
- return numParseInt(this.data.workbook.sheets?.[0].sheet[index]?.$["r:id"]);
92
+ return num.parseInt(this.data.workbook.sheets?.[0].sheet[index]?.$["r:id"]);
93
93
  }
94
94
 
95
95
  getWorksheetNameById(id: number): string | undefined {
@@ -107,7 +107,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
107
107
 
108
108
  private _getSheetDataById(id: number) {
109
109
  return (this.data.workbook.sheets?.[0].sheet ?? []).single(
110
- (item) => numParseInt(item.$["r:id"]) === id,
110
+ (item) => num.parseInt(item.$["r:id"]) === id,
111
111
  );
112
112
  }
113
113
 
@@ -7,7 +7,7 @@ import type {
7
7
  ExcelXmlWorksheetData,
8
8
  } from "../types";
9
9
  import { ExcelUtils } from "../utils/excel-utils";
10
- import { numParseInt, objClone } from "@simplysm/core-common";
10
+ import { num, obj } from "@simplysm/core-common";
11
11
  import "@simplysm/core-common";
12
12
 
13
13
  interface RowInfo {
@@ -46,11 +46,11 @@ export class ExcelXmlWorksheet implements ExcelXml {
46
46
  }
47
47
 
48
48
  this._dataMap = (this.data.worksheet.sheetData[0].row ?? []).toMap(
49
- (row) => ExcelUtils.parseRowAddrCode(row.$.r),
49
+ (row) => ExcelUtils.parseRowAddr(row.$.r),
50
50
  (row) => ({
51
51
  data: row,
52
52
  cellMap: (row.c ?? []).toMap(
53
- (cell) => ExcelUtils.parseColAddrCode(cell.$.r),
53
+ (cell) => ExcelUtils.parseColAddr(cell.$.r),
54
54
  (cell) => cell,
55
55
  ),
56
56
  }),
@@ -85,7 +85,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
85
85
  }
86
86
 
87
87
  getCellType(addr: { r: number; c: number }): ExcelCellType | undefined {
88
- return this._getCellData(addr)?.$.t as ExcelCellType | undefined;
88
+ return this._getCellData(addr)?.$.t;
89
89
  }
90
90
 
91
91
  setCellVal(addr: { r: number; c: number }, val: string | undefined): void {
@@ -164,7 +164,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
164
164
  // Check for merge overlap
165
165
  const existingMergeCells = mergeCells[0].mergeCell;
166
166
  for (const mergeCell of existingMergeCells) {
167
- const existingRange = ExcelUtils.parseRangeAddrCode(mergeCell.$.ref);
167
+ const existingRange = ExcelUtils.parseRangeAddr(mergeCell.$.ref);
168
168
 
169
169
  if (
170
170
  newRange.s.r <= existingRange.e.r &&
@@ -195,7 +195,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
195
195
  getMergeCells(): { s: { r: number; c: number }; e: { r: number; c: number } }[] {
196
196
  const mergeCells = this.data.worksheet.mergeCells;
197
197
  if (mergeCells === undefined) return [];
198
- return mergeCells[0].mergeCell.map((item) => ExcelUtils.parseRangeAddrCode(item.$.ref));
198
+ return mergeCells[0].mergeCell.map((item) => ExcelUtils.parseRangeAddr(item.$.ref));
199
199
  }
200
200
 
201
201
  removeMergeCells(fromAddr: { r: number; c: number }, toAddr: { r: number; c: number }): void {
@@ -204,7 +204,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
204
204
  const range = { s: fromAddr, e: toAddr };
205
205
 
206
206
  const filteredMergeCells = this.data.worksheet.mergeCells[0].mergeCell.filter((item) => {
207
- const rangeAddr = ExcelUtils.parseRangeAddrCode(item.$.ref);
207
+ const rangeAddr = ExcelUtils.parseRangeAddr(item.$.ref);
208
208
  return !(
209
209
  rangeAddr.s.r >= range.s.r &&
210
210
  rangeAddr.e.r <= range.e.r &&
@@ -221,6 +221,24 @@ export class ExcelXmlWorksheet implements ExcelXml {
221
221
  }
222
222
  }
223
223
 
224
+ shiftMergeCells(fromRow: number, delta: number): void {
225
+ const mergeCells = this.data.worksheet.mergeCells;
226
+ if (mergeCells === undefined) return;
227
+
228
+ for (const mergeCell of mergeCells[0].mergeCell) {
229
+ const range = ExcelUtils.parseRangeAddr(mergeCell.$.ref);
230
+
231
+ if (range.s.r >= fromRow) {
232
+ range.s.r += delta;
233
+ }
234
+ if (range.e.r >= fromRow) {
235
+ range.e.r += delta;
236
+ }
237
+
238
+ mergeCell.$.ref = ExcelUtils.stringifyRangeAddr(range);
239
+ }
240
+ }
241
+
224
242
  /**
225
243
  * Set width of a specific column.
226
244
  *
@@ -231,7 +249,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
231
249
  * @param width Width to set
232
250
  */
233
251
  setColWidth(colIndex: string, width: string): void {
234
- const colIndexNumber = numParseInt(colIndex);
252
+ const colIndexNumber = num.parseInt(colIndex);
235
253
  if (colIndexNumber == null) {
236
254
  throw new Error(`Invalid column index: ${colIndex}`);
237
255
  }
@@ -242,8 +260,8 @@ export class ExcelXmlWorksheet implements ExcelXml {
242
260
  const col = cols
243
261
  ? cols.col.single(
244
262
  (item) =>
245
- (numParseInt(item.$.min) ?? 0) <= colIndexNumber &&
246
- (numParseInt(item.$.max) ?? 0) >= colIndexNumber,
263
+ (num.parseInt(item.$.min) ?? 0) <= colIndexNumber &&
264
+ (num.parseInt(item.$.max) ?? 0) >= colIndexNumber,
247
265
  )
248
266
  : undefined;
249
267
 
@@ -257,8 +275,8 @@ export class ExcelXmlWorksheet implements ExcelXml {
257
275
  // Multi-column range: split the range and apply new width only to target column
258
276
  // e.g.: existing [1~5, width=10], target=3, new width=20
259
277
  // -> [1~2, width=10], [3, width=20], [4~5, width=10]
260
- const minNumber = numParseInt(col.$.min) ?? 0;
261
- const maxNumber = numParseInt(col.$.max) ?? 0;
278
+ const minNumber = num.parseInt(col.$.min) ?? 0;
279
+ const maxNumber = num.parseInt(col.$.max) ?? 0;
262
280
 
263
281
  let insertIndex = cols.col.indexOf(col);
264
282
 
@@ -324,7 +342,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
324
342
  this.data.worksheet.sheetViews[0].sheetView[0].$.zoomScale = percent.toString();
325
343
  }
326
344
 
327
- setFix(point: { r?: number; c?: number }): void {
345
+ freezeAt(point: { r?: number; c?: number }): void {
328
346
  this.data.worksheet.sheetViews = this.data.worksheet.sheetViews ?? [
329
347
  { sheetView: [{ $: { workbookViewId: "0" } }] },
330
348
  ];
@@ -353,13 +371,13 @@ export class ExcelXmlWorksheet implements ExcelXml {
353
371
  ];
354
372
  }
355
373
 
356
- copyRow(sourceR: number, targetR: number): void {
374
+ copyRow(sourceR: number, targetR: number, options?: { skipMerge?: boolean }): void {
357
375
  // Clone source ROW data
358
376
  const sourceRowInfo = this._dataMap.get(sourceR);
359
377
 
360
378
  if (sourceRowInfo != null) {
361
379
  // Clone rowData
362
- const newRowData: ExcelRowData = objClone(sourceRowInfo.data);
380
+ const newRowData: ExcelRowData = obj.clone(sourceRowInfo.data);
363
381
 
364
382
  // Update ROW address
365
383
  newRowData.$.r = ExcelUtils.stringifyRowAddr(targetR);
@@ -367,7 +385,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
367
385
  // Update each CELL address
368
386
  if (newRowData.c != null) {
369
387
  for (const cellData of newRowData.c) {
370
- const colAddr = ExcelUtils.parseColAddrCode(cellData.$.r);
388
+ const colAddr = ExcelUtils.parseColAddr(cellData.$.r);
371
389
  cellData.$.r = ExcelUtils.stringifyAddr({ r: targetR, c: colAddr });
372
390
  }
373
391
  }
@@ -377,13 +395,20 @@ export class ExcelXmlWorksheet implements ExcelXml {
377
395
  this._deleteRow(targetR);
378
396
  }
379
397
 
398
+ // Skip merge handling if skipMerge option is true
399
+ if (options?.skipMerge === true) {
400
+ return;
401
+ }
402
+
403
+ const allMergeCells = this.getMergeCells();
404
+
380
405
  // Copy and store source row merge cell info first
381
- const sourceMergeCells = this.getMergeCells()
406
+ const sourceMergeCells = allMergeCells
382
407
  .filter((mc) => mc.s.r <= sourceR && mc.e.r >= sourceR)
383
408
  .map((mc) => ({ s: { ...mc.s }, e: { ...mc.e } }));
384
409
 
385
410
  // Remove existing merge cells in target row
386
- for (const mergeCell of this.getMergeCells()) {
411
+ for (const mergeCell of allMergeCells) {
387
412
  if (mergeCell.s.r <= targetR && mergeCell.e.r >= targetR) {
388
413
  this.removeMergeCells(mergeCell.s, mergeCell.e);
389
414
  }
@@ -402,7 +427,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
402
427
  const sourceCellData = this._getCellData(sourceAddr);
403
428
 
404
429
  if (sourceCellData != null) {
405
- const newCellData = objClone(sourceCellData);
430
+ const newCellData = obj.clone(sourceCellData);
406
431
  newCellData.$.r = ExcelUtils.stringifyAddr(targetAddr);
407
432
  this._replaceCellData(targetAddr, newCellData);
408
433
  } else {
@@ -445,14 +470,14 @@ export class ExcelXmlWorksheet implements ExcelXml {
445
470
 
446
471
  // Sort ROWs
447
472
  const rowsData = (result.sheetData[0].row = result.sheetData[0].row ?? []);
448
- rowsData.sort((a, b) => (numParseInt(a.$.r) ?? 0) - (numParseInt(b.$.r) ?? 0));
473
+ rowsData.sort((a, b) => (num.parseInt(a.$.r) ?? 0) - (num.parseInt(b.$.r) ?? 0));
449
474
 
450
475
  // Sort CELLs
451
476
  for (const rowData of rowsData) {
452
477
  const cellsData = rowData.c;
453
478
  if (cellsData == null) continue;
454
479
  cellsData.sort(
455
- (a, b) => ExcelUtils.parseCellAddrCode(a.$.r).c - ExcelUtils.parseCellAddrCode(b.$.r).c,
480
+ (a, b) => ExcelUtils.parseCellAddr(a.$.r).c - ExcelUtils.parseCellAddr(b.$.r).c,
456
481
  );
457
482
  }
458
483
 
@@ -506,7 +531,7 @@ export class ExcelXmlWorksheet implements ExcelXml {
506
531
  const rowInfo = {
507
532
  data: rowData,
508
533
  cellMap: (rowData.c ?? []).toMap(
509
- (cell) => ExcelUtils.parseColAddrCode(cell.$.r),
534
+ (cell) => ExcelUtils.parseColAddr(cell.$.r),
510
535
  (cell) => cell,
511
536
  ),
512
537
  };