@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.
- package/README.md +539 -17
- package/dist/excel-cell.d.ts +4 -4
- package/dist/excel-cell.d.ts.map +1 -1
- package/dist/excel-cell.js +9 -10
- package/dist/excel-cell.js.map +1 -1
- package/dist/excel-workbook.d.ts +3 -3
- package/dist/excel-workbook.d.ts.map +1 -1
- package/dist/excel-workbook.js +3 -3
- package/dist/excel-workbook.js.map +1 -1
- package/dist/excel-worksheet.d.ts +1 -1
- package/dist/excel-worksheet.d.ts.map +1 -1
- package/dist/excel-worksheet.js +13 -17
- package/dist/excel-worksheet.js.map +1 -1
- package/dist/excel-wrapper.d.ts +1 -1
- package/dist/excel-wrapper.js +7 -7
- package/dist/excel-wrapper.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/excel-utils.d.ts +5 -5
- package/dist/utils/excel-utils.d.ts.map +1 -1
- package/dist/utils/excel-utils.js +15 -15
- package/dist/utils/excel-utils.js.map +1 -1
- package/dist/utils/zip-cache.js +3 -3
- package/dist/utils/zip-cache.js.map +1 -1
- package/dist/xml/excel-xml-relationship.js +2 -2
- package/dist/xml/excel-xml-relationship.js.map +1 -1
- package/dist/xml/excel-xml-style.js +16 -16
- package/dist/xml/excel-xml-style.js.map +1 -1
- package/dist/xml/excel-xml-workbook.js +6 -6
- package/dist/xml/excel-xml-workbook.js.map +1 -1
- package/dist/xml/excel-xml-worksheet.d.ts +5 -2
- package/dist/xml/excel-xml-worksheet.d.ts.map +1 -1
- package/dist/xml/excel-xml-worksheet.js +38 -20
- package/dist/xml/excel-xml-worksheet.js.map +1 -1
- package/package.json +2 -2
- package/src/excel-cell.ts +10 -11
- package/src/excel-workbook.ts +3 -3
- package/src/excel-worksheet.ts +17 -18
- package/src/excel-wrapper.ts +7 -7
- package/src/types.ts +1 -1
- package/src/utils/excel-utils.ts +15 -15
- package/src/utils/zip-cache.ts +3 -3
- package/src/xml/excel-xml-relationship.ts +2 -2
- package/src/xml/excel-xml-style.ts +16 -16
- package/src/xml/excel-xml-workbook.ts +6 -6
- package/src/xml/excel-xml-worksheet.ts +47 -22
- package/tests/excel-cell.spec.ts +85 -69
- package/tests/excel-col.spec.ts +17 -17
- package/tests/excel-row.spec.ts +13 -13
- package/tests/excel-workbook.spec.ts +26 -26
- package/tests/excel-worksheet.spec.ts +217 -101
- package/tests/excel-wrapper.spec.ts +24 -24
- package/tests/image-insert.spec.ts +5 -5
- package/tests/utils/excel-utils.spec.ts +14 -14
package/src/excel-worksheet.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Bytes } from "@simplysm/core-common";
|
|
2
2
|
import "@simplysm/core-common";
|
|
3
|
-
import {
|
|
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
|
-
//
|
|
113
|
-
|
|
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
|
-
|
|
121
|
+
wsData.copyRow(r, r + 1, { skipMerge: true });
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
|
|
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).
|
|
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).
|
|
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).
|
|
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).
|
|
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) => !
|
|
222
|
+
.filter((item) => !str.isNullOrEmpty(item));
|
|
224
223
|
|
|
225
224
|
for (let c = 0; c < headers.length; c++) {
|
|
226
|
-
await this.cell(0, 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).
|
|
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
|
|
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.
|
|
254
|
+
wsXml.freezeAt(point);
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
//#endregion
|
package/src/excel-wrapper.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Bytes } from "@simplysm/core-common";
|
|
2
|
-
import { DateOnly, DateTime,
|
|
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.
|
|
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.
|
|
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).
|
|
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).
|
|
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.
|
|
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
|
|
191
|
+
return num.parseFloat(String(rawValue));
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
if (innerSchema instanceof ZodBoolean) {
|
package/src/types.ts
CHANGED
package/src/utils/excel-utils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
39
|
-
const rowAddrCode = /\d*$/.exec(
|
|
40
|
-
const parsed =
|
|
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: ${
|
|
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
|
|
49
|
-
const colAddrCode = /^[a-zA-Z]*/.exec(
|
|
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
|
|
61
|
+
static parseCellAddr(addr: string): ExcelAddressPoint {
|
|
62
62
|
return {
|
|
63
|
-
r: ExcelUtils.
|
|
64
|
-
c: ExcelUtils.
|
|
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
|
|
69
|
+
static parseRangeAddr(rangeAddr: string): ExcelAddressRangePoint {
|
|
70
70
|
const parts = rangeAddr.split(":");
|
|
71
71
|
return {
|
|
72
|
-
s: ExcelUtils.
|
|
73
|
-
e: ExcelUtils.
|
|
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(
|
|
105
|
+
static convertNumberToTimeTick(value: number): number {
|
|
106
106
|
const excelBaseDateNumberUtc = Date.UTC(1899, 11, 31);
|
|
107
|
-
const excelDateNumberUtc = (
|
|
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());
|
package/src/utils/zip-cache.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Bytes } from "@simplysm/core-common";
|
|
2
|
-
import { ZipArchive,
|
|
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 =
|
|
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(
|
|
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 {
|
|
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
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
306
|
+
? numFmts.orderByDesc((item) => num.parseInt(item.$.numFmtId) ?? 180).first()
|
|
307
307
|
: undefined;
|
|
308
|
-
const maxId = maxItem ? (
|
|
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
|
-
(
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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) =>
|
|
31
|
-
return maxSheet ?
|
|
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
|
|
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
|
|
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) =>
|
|
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 {
|
|
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.
|
|
49
|
+
(row) => ExcelUtils.parseRowAddr(row.$.r),
|
|
50
50
|
(row) => ({
|
|
51
51
|
data: row,
|
|
52
52
|
cellMap: (row.c ?? []).toMap(
|
|
53
|
-
(cell) => ExcelUtils.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
-
(
|
|
246
|
-
(
|
|
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 =
|
|
261
|
-
const maxNumber =
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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 =
|
|
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
|
|
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 =
|
|
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) => (
|
|
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.
|
|
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.
|
|
534
|
+
(cell) => ExcelUtils.parseColAddr(cell.$.r),
|
|
510
535
|
(cell) => cell,
|
|
511
536
|
),
|
|
512
537
|
};
|