@simplysm/excel 13.0.100 → 14.0.4
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 +66 -78
- package/dist/excel-cell.d.ts +28 -28
- package/dist/excel-cell.d.ts.map +1 -1
- package/dist/excel-cell.js +273 -264
- package/dist/excel-cell.js.map +1 -6
- package/dist/excel-col.d.ts +4 -4
- package/dist/excel-col.d.ts.map +1 -1
- package/dist/excel-col.js +33 -35
- package/dist/excel-col.js.map +1 -6
- package/dist/excel-row.d.ts +3 -3
- package/dist/excel-row.d.ts.map +1 -1
- package/dist/excel-row.js +28 -30
- package/dist/excel-row.js.map +1 -6
- package/dist/excel-workbook.d.ts +23 -23
- package/dist/excel-workbook.d.ts.map +1 -1
- package/dist/excel-workbook.js +151 -125
- package/dist/excel-workbook.js.map +1 -6
- package/dist/excel-worksheet.d.ts +32 -32
- package/dist/excel-worksheet.d.ts.map +1 -1
- package/dist/excel-worksheet.js +281 -253
- package/dist/excel-worksheet.js.map +1 -6
- package/dist/excel-wrapper.d.ts +7 -7
- package/dist/excel-wrapper.d.ts.map +1 -1
- package/dist/excel-wrapper.js +190 -226
- package/dist/excel-wrapper.js.map +1 -6
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -6
- package/dist/types.d.ts +13 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -6
- package/dist/utils/excel-utils.d.ts +23 -23
- package/dist/utils/excel-utils.d.ts.map +1 -1
- package/dist/utils/excel-utils.js +183 -151
- package/dist/utils/excel-utils.js.map +1 -6
- package/dist/utils/zip-cache.d.ts +6 -6
- package/dist/utils/zip-cache.js +78 -60
- package/dist/utils/zip-cache.js.map +1 -6
- package/dist/xml/excel-xml-content-type.d.ts +2 -2
- package/dist/xml/excel-xml-content-type.js +55 -53
- package/dist/xml/excel-xml-content-type.js.map +1 -6
- package/dist/xml/excel-xml-drawing.d.ts +2 -2
- package/dist/xml/excel-xml-drawing.js +74 -73
- package/dist/xml/excel-xml-drawing.js.map +1 -6
- package/dist/xml/excel-xml-relationship.d.ts +2 -2
- package/dist/xml/excel-xml-relationship.js +67 -67
- package/dist/xml/excel-xml-relationship.js.map +1 -6
- package/dist/xml/excel-xml-shared-string.d.ts +2 -2
- package/dist/xml/excel-xml-shared-string.js +57 -55
- package/dist/xml/excel-xml-shared-string.js.map +1 -6
- package/dist/xml/excel-xml-style.d.ts +2 -2
- package/dist/xml/excel-xml-style.js +311 -295
- package/dist/xml/excel-xml-style.js.map +1 -6
- package/dist/xml/excel-xml-unknown.d.ts +2 -2
- package/dist/xml/excel-xml-unknown.js +11 -10
- package/dist/xml/excel-xml-unknown.js.map +1 -6
- package/dist/xml/excel-xml-workbook.d.ts +3 -2
- package/dist/xml/excel-xml-workbook.d.ts.map +1 -1
- package/dist/xml/excel-xml-workbook.js +95 -90
- package/dist/xml/excel-xml-workbook.js.map +1 -6
- package/dist/xml/excel-xml-worksheet.d.ts +6 -6
- package/dist/xml/excel-xml-worksheet.js +450 -393
- package/dist/xml/excel-xml-worksheet.js.map +1 -6
- package/docs/core.md +147 -0
- package/docs/types.md +201 -212
- package/docs/wrapper.md +32 -62
- package/package.json +8 -6
- package/src/excel-cell.ts +36 -36
- package/src/excel-col.ts +4 -4
- package/src/excel-row.ts +3 -3
- package/src/excel-workbook.ts +38 -38
- package/src/excel-worksheet.ts +69 -51
- package/src/excel-wrapper.ts +55 -50
- package/src/index.ts +3 -3
- package/src/types.ts +17 -17
- package/src/utils/excel-utils.ts +47 -41
- package/src/utils/zip-cache.ts +6 -6
- package/src/xml/excel-xml-content-type.ts +3 -3
- package/src/xml/excel-xml-drawing.ts +2 -2
- package/src/xml/excel-xml-relationship.ts +3 -3
- package/src/xml/excel-xml-shared-string.ts +2 -2
- package/src/xml/excel-xml-style.ts +13 -13
- package/src/xml/excel-xml-unknown.ts +2 -2
- package/src/xml/excel-xml-workbook.ts +14 -6
- package/src/xml/excel-xml-worksheet.ts +43 -43
- package/docs/core-classes.md +0 -541
- package/docs/utilities.md +0 -128
- package/tests/excel-cell.spec.ts +0 -393
- package/tests/excel-col.spec.ts +0 -81
- package/tests/excel-row.spec.ts +0 -61
- package/tests/excel-workbook.spec.ts +0 -205
- package/tests/excel-worksheet.spec.ts +0 -469
- package/tests/excel-wrapper.spec.ts +0 -273
- package/tests/fixtures/logo.png +0 -0
- package/tests/fixtures//354/264/210/352/270/260/355/231/224.xlsx +0 -0
- package/tests/image-insert.spec.ts +0 -190
- package/tests/utils/excel-utils.spec.ts +0 -198
package/src/utils/excel-utils.ts
CHANGED
|
@@ -2,49 +2,50 @@ import { num } from "@simplysm/core-common";
|
|
|
2
2
|
import type { ExcelAddressPoint, ExcelAddressRangePoint, ExcelNumberFormat } from "../types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Excel 유틸리티 함수 모음.
|
|
6
|
+
* 셀 주소 변환, 날짜/숫자 변환, 숫자 형식 처리 기능을 제공한다.
|
|
7
7
|
*/
|
|
8
8
|
export class ExcelUtils {
|
|
9
|
-
/**
|
|
9
|
+
/** 셀 좌표를 "A1" 형식 문자열로 변환 */
|
|
10
10
|
static stringifyAddr(point: ExcelAddressPoint): string {
|
|
11
11
|
const rowStr = this.stringifyRowAddr(point.r);
|
|
12
12
|
const colStr = this.stringifyColAddr(point.c);
|
|
13
13
|
return `${colStr}${rowStr}`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/** 행 인덱스(0 기반)를 행 주소 문자열로 변환 (예: 0 -> "1") */
|
|
17
17
|
static stringifyRowAddr(r: number): string {
|
|
18
18
|
return (r + 1).toString();
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
21
|
+
/** 열 인덱스(0 기반)를 열 주소 문자열로 변환 (예: 0 -> "A", 26 -> "AA") */
|
|
22
22
|
static stringifyColAddr(c: number): string {
|
|
23
23
|
if (c < 0 || c > 16383) {
|
|
24
|
-
throw new Error(
|
|
24
|
+
throw new Error(`열 인덱스는 0~16383 범위여야 합니다: ${c}`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
let remained = c;
|
|
28
28
|
let result = String.fromCharCode((remained % 26) + 65);
|
|
29
29
|
remained = Math.floor(remained / 26);
|
|
30
30
|
while (remained !== 0) {
|
|
31
|
-
|
|
31
|
+
remained -= 1;
|
|
32
|
+
result = String.fromCharCode((remained % 26) + 65) + result;
|
|
32
33
|
remained = Math.floor(remained / 26);
|
|
33
34
|
}
|
|
34
35
|
return result;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
/**
|
|
38
|
+
/** 셀 주소에서 행 인덱스 추출 (예: "A3" -> 2) */
|
|
38
39
|
static parseRowAddr(addr: string): number {
|
|
39
40
|
const rowAddrCode = /\d*$/.exec(addr)?.[0] ?? "";
|
|
40
41
|
const parsed = num.parseInt(rowAddrCode);
|
|
41
42
|
if (parsed == null) {
|
|
42
|
-
throw new Error(
|
|
43
|
+
throw new Error(`잘못된 행 주소 코드: ${rowAddrCode}`);
|
|
43
44
|
}
|
|
44
45
|
return parsed - 1;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
+
/** 셀 주소에서 열 인덱스 추출 (예: "B3" -> 1) */
|
|
48
49
|
static parseColAddr(addr: string): number {
|
|
49
50
|
const colAddrCode = /^[a-zA-Z]*/.exec(addr)?.[0] ?? "";
|
|
50
51
|
|
|
@@ -57,7 +58,7 @@ export class ExcelUtils {
|
|
|
57
58
|
return result;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
/**
|
|
61
|
+
/** 셀 주소를 좌표로 변환 (예: "B3" -> {r: 2, c: 1}) */
|
|
61
62
|
static parseCellAddr(addr: string): ExcelAddressPoint {
|
|
62
63
|
return {
|
|
63
64
|
r: ExcelUtils.parseRowAddr(addr),
|
|
@@ -65,7 +66,7 @@ export class ExcelUtils {
|
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
/**
|
|
69
|
+
/** 범위 주소를 좌표로 변환 (예: "A1:C3" -> {s: {r:0,c:0}, e: {r:2,c:2}}) */
|
|
69
70
|
static parseRangeAddr(rangeAddr: string): ExcelAddressRangePoint {
|
|
70
71
|
const parts = rangeAddr.split(":");
|
|
71
72
|
return {
|
|
@@ -74,7 +75,7 @@ export class ExcelUtils {
|
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/**
|
|
78
|
+
/** 범위 좌표를 주소 문자열로 변환 */
|
|
78
79
|
static stringifyRangeAddr(point: ExcelAddressRangePoint): string {
|
|
79
80
|
const sAddr = this.stringifyAddr(point.s);
|
|
80
81
|
const eAddr = this.stringifyAddr(point.e);
|
|
@@ -87,8 +88,8 @@ export class ExcelUtils {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
/**
|
|
90
|
-
*
|
|
91
|
-
* Excel
|
|
91
|
+
* JavaScript 타임스탬프(ms)를 Excel 날짜 숫자로 변환한다.
|
|
92
|
+
* Excel은 1900-01-01을 1로 계산한다 (1899-12-30이 날짜 0).
|
|
92
93
|
*/
|
|
93
94
|
static convertTimeTickToNumber(tick: number): number {
|
|
94
95
|
const currDate = new Date(tick);
|
|
@@ -99,8 +100,8 @@ export class ExcelUtils {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
/**
|
|
102
|
-
*
|
|
103
|
-
* Excel
|
|
103
|
+
* Excel 날짜 숫자를 JavaScript 타임스탬프(ms)로 변환한다.
|
|
104
|
+
* Excel은 1900-01-01을 1로 계산한다 (1899-12-30이 날짜 0).
|
|
104
105
|
*/
|
|
105
106
|
static convertNumberToTimeTick(value: number): number {
|
|
106
107
|
const excelBaseDateNumberUtc = Date.UTC(1899, 11, 31);
|
|
@@ -111,50 +112,55 @@ export class ExcelUtils {
|
|
|
111
112
|
return date.getTime();
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
/**
|
|
115
|
+
/** 숫자 형식 코드를 형식 이름으로 변환 */
|
|
115
116
|
static convertNumFmtCodeToName(numFmtCode: string): ExcelNumberFormat {
|
|
116
117
|
if (numFmtCode === "General") {
|
|
117
118
|
return "number";
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
const
|
|
121
|
+
// "mm"이 시간 문맥(h/hh 뒤 또는 ss 앞)에서 쓰이면 분(minute)이므로 날짜 판별에서 제외
|
|
122
|
+
const codeForDateCheck = numFmtCode
|
|
123
|
+
.replace(/h{1,2}\s*:?\s*mm/gi, "")
|
|
124
|
+
.replace(/mm\s*:?\s*ss/gi, "");
|
|
125
|
+
const hasDate =
|
|
126
|
+
/yy/i.test(numFmtCode) || /dd/i.test(numFmtCode) || /mm/i.test(codeForDateCheck);
|
|
127
|
+
const hasTime = /h{1,2}/i.test(numFmtCode) || /ss/i.test(numFmtCode);
|
|
122
128
|
|
|
123
129
|
if (hasDate && hasTime) {
|
|
124
|
-
return "DateTime"; //
|
|
130
|
+
return "DateTime"; // 날짜+시간 = DateTime
|
|
125
131
|
} else if (hasDate) {
|
|
126
|
-
return "DateOnly"; //
|
|
132
|
+
return "DateOnly"; // 날짜만 = DateOnly
|
|
127
133
|
} else if (hasTime) {
|
|
128
|
-
return "Time"; //
|
|
134
|
+
return "Time"; // 시간만 = Time
|
|
129
135
|
}
|
|
130
|
-
//
|
|
131
|
-
//
|
|
136
|
+
// 숫자 형식 패턴: 0, #, 소수점, 천단위 구분, 음수 구분, 괄호, 통화, 공백, 지수, 퍼센트 등
|
|
137
|
+
// "[조건부 형식]실제 형식" 구조에서 실제 형식 부분만 검사 (split("]").at(-1))
|
|
132
138
|
else if (/^[0.#,_;()\-\\$ @*?"E%+]*$/.test(numFmtCode.split("]").at(-1) ?? "")) {
|
|
133
139
|
return "number";
|
|
134
140
|
} else if ((numFmtCode.split("]").at(-1) ?? "").includes("#,0")) {
|
|
135
141
|
return "number";
|
|
136
142
|
} else {
|
|
137
|
-
throw new Error(
|
|
143
|
+
throw new Error(`알 수 없는 형식 [numFmtCode: ${numFmtCode}]`);
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
/**
|
|
142
|
-
*
|
|
148
|
+
* 숫자 형식 ID를 형식 이름으로 변환
|
|
143
149
|
*
|
|
144
150
|
* @remarks
|
|
145
|
-
* Excel
|
|
146
|
-
* - 0~13, 37~40, 48:
|
|
147
|
-
* - 14~17, 27~31, 34~36, 50~58:
|
|
148
|
-
* - 22:
|
|
149
|
-
* - 18~21, 32~33, 45~47:
|
|
150
|
-
* - 49:
|
|
151
|
+
* Excel 내장 형식 ID 범위:
|
|
152
|
+
* - 0~13, 37~40, 48: 숫자/일반/통화/퍼센트 형식
|
|
153
|
+
* - 14~17, 27~31, 34~36, 50~58: 날짜 형식 (로컬라이즈 포함)
|
|
154
|
+
* - 22: 날짜+시간 형식
|
|
155
|
+
* - 18~21, 32~33, 45~47: 시간 형식
|
|
156
|
+
* - 49: 텍스트 형식
|
|
151
157
|
*/
|
|
152
158
|
static convertNumFmtIdToName(numFmtId: number): ExcelNumberFormat {
|
|
153
|
-
//
|
|
159
|
+
// 숫자/일반/통화/퍼센트 형식
|
|
154
160
|
if (numFmtId <= 13 || (numFmtId >= 37 && numFmtId <= 40) || numFmtId === 48) {
|
|
155
161
|
return "number";
|
|
156
162
|
}
|
|
157
|
-
//
|
|
163
|
+
// 날짜 형식 (로컬라이즈 포함)
|
|
158
164
|
else if (
|
|
159
165
|
(numFmtId >= 14 && numFmtId <= 17) ||
|
|
160
166
|
(numFmtId >= 27 && numFmtId <= 31) ||
|
|
@@ -163,11 +169,11 @@ export class ExcelUtils {
|
|
|
163
169
|
) {
|
|
164
170
|
return "DateOnly";
|
|
165
171
|
}
|
|
166
|
-
//
|
|
172
|
+
// 날짜+시간 형식
|
|
167
173
|
else if (numFmtId === 22) {
|
|
168
174
|
return "DateTime";
|
|
169
175
|
}
|
|
170
|
-
//
|
|
176
|
+
// 시간 형식
|
|
171
177
|
else if (
|
|
172
178
|
(numFmtId >= 18 && numFmtId <= 21) ||
|
|
173
179
|
(numFmtId >= 32 && numFmtId <= 33) ||
|
|
@@ -175,15 +181,15 @@ export class ExcelUtils {
|
|
|
175
181
|
) {
|
|
176
182
|
return "Time";
|
|
177
183
|
}
|
|
178
|
-
//
|
|
184
|
+
// 텍스트 형식
|
|
179
185
|
else if (numFmtId === 49) {
|
|
180
186
|
return "string";
|
|
181
187
|
} else {
|
|
182
|
-
throw new Error(
|
|
188
|
+
throw new Error(`알 수 없는 형식 [numFmtId: ${numFmtId}]`);
|
|
183
189
|
}
|
|
184
190
|
}
|
|
185
191
|
|
|
186
|
-
/**
|
|
192
|
+
/** 숫자 형식 이름을 형식 ID로 변환 */
|
|
187
193
|
static convertNumFmtNameToId(numFmtName: ExcelNumberFormat): number {
|
|
188
194
|
if (numFmtName === "number") {
|
|
189
195
|
return 0;
|
|
@@ -194,7 +200,7 @@ export class ExcelUtils {
|
|
|
194
200
|
} else if (numFmtName === "Time") {
|
|
195
201
|
return 18;
|
|
196
202
|
} else {
|
|
197
|
-
//
|
|
203
|
+
// 마지막 케이스: "string" (TypeScript가 타입 좁히기를 통해 자동 검증)
|
|
198
204
|
return 49;
|
|
199
205
|
}
|
|
200
206
|
}
|
package/src/utils/zip-cache.ts
CHANGED
|
@@ -20,15 +20,15 @@ import { ExcelXmlWorkbook } from "../xml/excel-xml-workbook";
|
|
|
20
20
|
import { ExcelXmlWorksheet } from "../xml/excel-xml-worksheet";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
* XML
|
|
23
|
+
* Excel ZIP 아카이브의 파일 캐시를 관리하는 클래스.
|
|
24
|
+
* XML 파일은 ExcelXml 객체로 파싱하고, 기타 파일은 바이트 배열로 캐싱한다.
|
|
25
25
|
*
|
|
26
26
|
* @remarks
|
|
27
|
-
* ## Lazy Loading
|
|
27
|
+
* ## Lazy Loading 캐시 전략
|
|
28
28
|
*
|
|
29
|
-
* -
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
29
|
+
* - 파일은 최초 접근 시에만 ZIP에서 읽고 파싱함
|
|
30
|
+
* - 이후 접근은 캐싱된 객체를 반환함
|
|
31
|
+
* - 대용량 Excel 파일에서 필요한 부분만 로드하여 메모리 효율적
|
|
32
32
|
*/
|
|
33
33
|
export class ZipCache {
|
|
34
34
|
private readonly _cache = new Map<string, ExcelXml | Bytes | undefined>();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExcelXml, ExcelXmlContentTypeData } from "../types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* [Content_Types].xml을 관리하는 클래스.
|
|
5
|
+
* 파일별 MIME 타입 정보를 관리한다.
|
|
6
6
|
*/
|
|
7
7
|
export class ExcelXmlContentType implements ExcelXml {
|
|
8
8
|
data: ExcelXmlContentTypeData;
|
|
@@ -45,7 +45,7 @@ export class ExcelXmlContentType implements ExcelXml {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
add(partName: string, contentType: string): this {
|
|
48
|
-
//
|
|
48
|
+
// 중복 검사
|
|
49
49
|
const exists = this.data.Types.Override.some((item) => item.$.PartName === partName);
|
|
50
50
|
if (exists) {
|
|
51
51
|
return this;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExcelXml, ExcelXmlDrawingData } from "../types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* xl/drawings/drawing*.xml 파일을 관리하는 클래스.
|
|
5
|
+
* 이미지 삽입을 위한 위치 및 참조 정보를 처리한다.
|
|
6
6
|
*/
|
|
7
7
|
export class ExcelXmlDrawing implements ExcelXml {
|
|
8
8
|
data: ExcelXmlDrawingData;
|
|
@@ -3,8 +3,8 @@ import { num } from "@simplysm/core-common";
|
|
|
3
3
|
import type { ExcelRelationshipData, ExcelXml, ExcelXmlRelationshipData } from "../types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* *.rels 파일을 관리하는 클래스.
|
|
7
|
+
* 파일 간의 참조 관계를 처리한다.
|
|
8
8
|
*/
|
|
9
9
|
export class ExcelXmlRelationship implements ExcelXml {
|
|
10
10
|
data: ExcelXmlRelationshipData;
|
|
@@ -82,7 +82,7 @@ export class ExcelXmlRelationship implements ExcelXml {
|
|
|
82
82
|
private _getRelId(rel: ExcelRelationshipData): number {
|
|
83
83
|
const match = /[0-9]+$/.exec(rel.$.Id);
|
|
84
84
|
if (match == null) {
|
|
85
|
-
throw new Error(
|
|
85
|
+
throw new Error(`잘못된 관계 ID 형식: ${rel.$.Id}`);
|
|
86
86
|
}
|
|
87
87
|
return num.parseInt(match[0])!;
|
|
88
88
|
}
|
|
@@ -7,8 +7,8 @@ import type {
|
|
|
7
7
|
import "@simplysm/core-common";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* xl/sharedStrings.xml을 관리하는 클래스.
|
|
11
|
+
* 문자열 중복을 방지하여 파일 크기를 최적화한다.
|
|
12
12
|
*/
|
|
13
13
|
export class ExcelXmlSharedString implements ExcelXml {
|
|
14
14
|
data: ExcelXmlSharedStringData;
|
|
@@ -21,8 +21,8 @@ export interface ExcelStyle {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* xl/styles.xml을 관리하는 클래스.
|
|
25
|
+
* 숫자 형식, 배경색, 테두리, 정렬 등의 스타일을 처리한다.
|
|
26
26
|
*/
|
|
27
27
|
export class ExcelXmlStyle implements ExcelXml {
|
|
28
28
|
data: ExcelXmlStyleData;
|
|
@@ -108,11 +108,11 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
108
108
|
addWithClone(id: string, style: ExcelStyle): string {
|
|
109
109
|
const idNum = num.parseInt(id);
|
|
110
110
|
if (idNum == null) {
|
|
111
|
-
throw new Error(
|
|
111
|
+
throw new Error(`잘못된 스타일 ID: ${id}`);
|
|
112
112
|
}
|
|
113
113
|
const xfArray = this.data.styleSheet.cellXfs[0].xf;
|
|
114
114
|
if (idNum < 0 || idNum >= xfArray.length) {
|
|
115
|
-
throw new Error(
|
|
115
|
+
throw new Error(`존재하지 않는 스타일 ID: ${id} (범위: 0-${xfArray.length - 1})`);
|
|
116
116
|
}
|
|
117
117
|
const prevXf = xfArray[idNum];
|
|
118
118
|
const cloneXf = obj.clone(prevXf);
|
|
@@ -136,9 +136,9 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
136
136
|
cloneFill.patternFill[0].$.patternType = "solid";
|
|
137
137
|
|
|
138
138
|
if (cloneFill.patternFill[0].fgColor == null) {
|
|
139
|
-
cloneFill.patternFill[0].fgColor = [{ $: { rgb: style.background } }];
|
|
139
|
+
cloneFill.patternFill[0].fgColor = [{ $: { rgb: style.background.toUpperCase() } }];
|
|
140
140
|
} else {
|
|
141
|
-
cloneFill.patternFill[0].fgColor[0].$.rgb = style.background;
|
|
141
|
+
cloneFill.patternFill[0].fgColor[0].$.rgb = style.background.toUpperCase();
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
cloneXf.$.applyFill = "1";
|
|
@@ -187,7 +187,7 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
187
187
|
get(id: string): ExcelStyle {
|
|
188
188
|
const idNum = num.parseInt(id);
|
|
189
189
|
if (idNum == null) {
|
|
190
|
-
throw new Error(
|
|
190
|
+
throw new Error(`잘못된 스타일 ID: ${id}`);
|
|
191
191
|
}
|
|
192
192
|
const xf = this.data.styleSheet.cellXfs[0].xf[idNum] as ExcelXmlStyleDataXf | undefined;
|
|
193
193
|
|
|
@@ -204,7 +204,7 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
204
204
|
| undefined;
|
|
205
205
|
if (fill == null) {
|
|
206
206
|
throw new Error(
|
|
207
|
-
|
|
207
|
+
`존재하지 않는 fill ID: ${xf.$.fillId} (범위: 0-${this.data.styleSheet.fills[0].fill.length - 1})`,
|
|
208
208
|
);
|
|
209
209
|
}
|
|
210
210
|
result.background = fill.patternFill[0].fgColor?.[0].$.rgb;
|
|
@@ -214,14 +214,14 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
214
214
|
if (xf.$.borderId !== undefined) {
|
|
215
215
|
const borderIdNum = num.parseInt(xf.$.borderId);
|
|
216
216
|
if (borderIdNum == null) {
|
|
217
|
-
throw new Error(
|
|
217
|
+
throw new Error(`잘못된 border ID: ${xf.$.borderId}`);
|
|
218
218
|
}
|
|
219
219
|
const border = this.data.styleSheet.borders[0].border[borderIdNum] as
|
|
220
220
|
| ExcelXmlStyleDataBorder
|
|
221
221
|
| undefined;
|
|
222
222
|
if (border == null) {
|
|
223
223
|
throw new Error(
|
|
224
|
-
|
|
224
|
+
`존재하지 않는 border ID: ${xf.$.borderId} (범위: 0-${this.data.styleSheet.borders[0].border.length - 1})`,
|
|
225
225
|
);
|
|
226
226
|
}
|
|
227
227
|
if (
|
|
@@ -262,7 +262,7 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
262
262
|
cleanup(): void {
|
|
263
263
|
const result = {} as ExcelXmlStyleData["styleSheet"];
|
|
264
264
|
|
|
265
|
-
//
|
|
265
|
+
// 정렬 순서 (numFmts를 먼저)
|
|
266
266
|
|
|
267
267
|
if (this.data.styleSheet.numFmts != null) {
|
|
268
268
|
result.numFmts = this.data.styleSheet.numFmts;
|
|
@@ -282,7 +282,7 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
282
282
|
//#region Private Methods
|
|
283
283
|
|
|
284
284
|
private _setNumFmtCode(numFmtCode: string): string {
|
|
285
|
-
//
|
|
285
|
+
// 코드가 이미 존재하면 건너뛰기
|
|
286
286
|
const existsNumFmtId = (this.data.styleSheet.numFmts?.[0].numFmt ?? []).single(
|
|
287
287
|
(item) => item.$.formatCode === numFmtCode,
|
|
288
288
|
)?.$.numFmtId;
|
|
@@ -299,7 +299,7 @@ export class ExcelXmlStyle implements ExcelXml {
|
|
|
299
299
|
|
|
300
300
|
this.data.styleSheet.numFmts[0].numFmt = this.data.styleSheet.numFmts[0].numFmt ?? [];
|
|
301
301
|
|
|
302
|
-
// Excel
|
|
302
|
+
// Excel 사용자 정의 숫자 형식은 ID 180+부터 시작 (0-163: 내장, 164-179: 예약)
|
|
303
303
|
const numFmts = this.data.styleSheet.numFmts[0].numFmt;
|
|
304
304
|
const maxItem =
|
|
305
305
|
numFmts.length > 0
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExcelXml } from "../types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 알 수 없는 형식의 Excel XML 데이터를 보존하는 클래스.
|
|
5
|
+
* 원본 데이터를 손실 없이 유지한다.
|
|
6
6
|
*/
|
|
7
7
|
export class ExcelXmlUnknown implements ExcelXml {
|
|
8
8
|
constructor(public readonly data: Record<string, unknown>) {}
|
|
@@ -3,8 +3,8 @@ import { num } from "@simplysm/core-common";
|
|
|
3
3
|
import type { ExcelXml, ExcelXmlWorkbookData } from "../types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* xl/workbook.xml을 관리하는 클래스.
|
|
7
|
+
* 워크시트 목록 및 관계 ID를 처리한다.
|
|
8
8
|
*/
|
|
9
9
|
export class ExcelXmlWorkbook implements ExcelXml {
|
|
10
10
|
data: ExcelXmlWorkbookData;
|
|
@@ -31,6 +31,13 @@ export class ExcelXmlWorkbook implements ExcelXml {
|
|
|
31
31
|
return maxSheet ? num.parseInt(maxSheet.$["r:id"]) : undefined;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
get lastSheetId(): number | undefined {
|
|
35
|
+
const sheets = this.data.workbook.sheets?.[0].sheet;
|
|
36
|
+
if (!sheets || sheets.length === 0) return undefined;
|
|
37
|
+
const maxSheet = sheets.orderByDesc((sheet) => num.parseInt(sheet.$.sheetId)!).first();
|
|
38
|
+
return maxSheet ? num.parseInt(maxSheet.$.sheetId) : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
get sheetNames(): string[] {
|
|
35
42
|
return this.data.workbook.sheets?.[0].sheet.map((item) => item.$.name) ?? [];
|
|
36
43
|
}
|
|
@@ -39,12 +46,13 @@ export class ExcelXmlWorkbook implements ExcelXml {
|
|
|
39
46
|
const replacedName = this._getReplacedName(name);
|
|
40
47
|
|
|
41
48
|
const newWsRelId = (this.lastWsRelId ?? 0) + 1;
|
|
49
|
+
const newSheetId = (this.lastSheetId ?? 0) + 1;
|
|
42
50
|
|
|
43
51
|
this.data.workbook.sheets = this.data.workbook.sheets ?? [{ sheet: [] }];
|
|
44
52
|
this.data.workbook.sheets[0].sheet.push({
|
|
45
53
|
$: {
|
|
46
54
|
"name": replacedName,
|
|
47
|
-
"sheetId":
|
|
55
|
+
"sheetId": newSheetId.toString(),
|
|
48
56
|
"r:id": `rId${newWsRelId}`,
|
|
49
57
|
},
|
|
50
58
|
});
|
|
@@ -55,7 +63,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
|
|
|
55
63
|
cleanup(): void {
|
|
56
64
|
const result = {} as ExcelXmlWorkbookData["workbook"];
|
|
57
65
|
|
|
58
|
-
//
|
|
66
|
+
// 정렬 순서 ("sheets" 기준, 나머지는 원래 위치 유지)
|
|
59
67
|
|
|
60
68
|
const workbookRec = this.data.workbook as Record<string, unknown>;
|
|
61
69
|
const resultRec = result as Record<string, unknown>;
|
|
@@ -99,7 +107,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
|
|
|
99
107
|
setWorksheetNameById(id: number, newName: string): void {
|
|
100
108
|
const sheetData = this._getSheetDataById(id);
|
|
101
109
|
if (sheetData == null) {
|
|
102
|
-
throw new Error(
|
|
110
|
+
throw new Error(`워크시트 ID ${id}를 찾을 수 없습니다`);
|
|
103
111
|
}
|
|
104
112
|
const replacedName = this._getReplacedName(newName);
|
|
105
113
|
sheetData.$.name = replacedName;
|
|
@@ -112,7 +120,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
|
|
|
112
120
|
}
|
|
113
121
|
|
|
114
122
|
private _getReplacedName(name: string): string {
|
|
115
|
-
//--
|
|
123
|
+
//-- 잘못된 시트 이름 문자를 "_"로 대체
|
|
116
124
|
return name.replace(/[:\\/?*\[\]']/g, "_");
|
|
117
125
|
}
|
|
118
126
|
}
|