@simplysm/excel 13.0.0-beta.4 → 13.0.0-beta.44
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 +15 -0
- package/dist/excel-cell.d.ts.map +1 -0
- package/dist/excel-cell.js +3 -3
- package/dist/excel-col.d.ts.map +1 -0
- package/dist/excel-col.js +1 -1
- package/dist/excel-row.d.ts.map +1 -0
- package/dist/excel-row.js +1 -1
- package/dist/excel-workbook.d.ts.map +1 -0
- package/dist/excel-workbook.js +7 -7
- package/dist/excel-workbook.js.map +2 -2
- package/dist/excel-worksheet.d.ts.map +1 -0
- package/dist/excel-worksheet.js +4 -4
- package/dist/excel-wrapper.d.ts.map +1 -0
- package/dist/excel-wrapper.js +1 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -8
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/excel-utils.d.ts.map +1 -0
- package/dist/utils/zip-cache.d.ts.map +1 -0
- package/dist/utils/zip-cache.js +8 -8
- package/dist/xml/excel-xml-content-type.d.ts.map +1 -0
- package/dist/xml/excel-xml-drawing.d.ts.map +1 -0
- package/dist/xml/excel-xml-relationship.d.ts.map +1 -0
- package/dist/xml/excel-xml-shared-string.d.ts.map +1 -0
- package/dist/xml/excel-xml-style.d.ts.map +1 -0
- package/dist/xml/excel-xml-unknown.d.ts.map +1 -0
- package/dist/xml/excel-xml-workbook.d.ts.map +1 -0
- package/dist/xml/excel-xml-worksheet.d.ts.map +1 -0
- package/dist/xml/excel-xml-worksheet.js +1 -1
- package/package.json +7 -4
- package/.cache/typecheck-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-node.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-node.tsbuildinfo +0 -1
- package/dist/core-common/src/common.types.d.ts +0 -74
- package/dist/core-common/src/common.types.d.ts.map +0 -1
- package/dist/core-common/src/env.d.ts +0 -6
- package/dist/core-common/src/env.d.ts.map +0 -1
- package/dist/core-common/src/errors/argument-error.d.ts +0 -25
- package/dist/core-common/src/errors/argument-error.d.ts.map +0 -1
- package/dist/core-common/src/errors/not-implemented-error.d.ts +0 -29
- package/dist/core-common/src/errors/not-implemented-error.d.ts.map +0 -1
- package/dist/core-common/src/errors/sd-error.d.ts +0 -27
- package/dist/core-common/src/errors/sd-error.d.ts.map +0 -1
- package/dist/core-common/src/errors/timeout-error.d.ts +0 -31
- package/dist/core-common/src/errors/timeout-error.d.ts.map +0 -1
- package/dist/core-common/src/extensions/arr-ext.d.ts +0 -15
- package/dist/core-common/src/extensions/arr-ext.d.ts.map +0 -1
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +0 -19
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +0 -1
- package/dist/core-common/src/extensions/arr-ext.types.d.ts +0 -215
- package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +0 -1
- package/dist/core-common/src/extensions/map-ext.d.ts +0 -57
- package/dist/core-common/src/extensions/map-ext.d.ts.map +0 -1
- package/dist/core-common/src/extensions/set-ext.d.ts +0 -36
- package/dist/core-common/src/extensions/set-ext.d.ts.map +0 -1
- package/dist/core-common/src/features/debounce-queue.d.ts +0 -53
- package/dist/core-common/src/features/debounce-queue.d.ts.map +0 -1
- package/dist/core-common/src/features/event-emitter.d.ts +0 -66
- package/dist/core-common/src/features/event-emitter.d.ts.map +0 -1
- package/dist/core-common/src/features/serial-queue.d.ts +0 -47
- package/dist/core-common/src/features/serial-queue.d.ts.map +0 -1
- package/dist/core-common/src/index.d.ts +0 -32
- package/dist/core-common/src/index.d.ts.map +0 -1
- package/dist/core-common/src/types/date-only.d.ts +0 -152
- package/dist/core-common/src/types/date-only.d.ts.map +0 -1
- package/dist/core-common/src/types/date-time.d.ts +0 -96
- package/dist/core-common/src/types/date-time.d.ts.map +0 -1
- package/dist/core-common/src/types/lazy-gc-map.d.ts +0 -80
- package/dist/core-common/src/types/lazy-gc-map.d.ts.map +0 -1
- package/dist/core-common/src/types/time.d.ts +0 -68
- package/dist/core-common/src/types/time.d.ts.map +0 -1
- package/dist/core-common/src/types/uuid.d.ts +0 -35
- package/dist/core-common/src/types/uuid.d.ts.map +0 -1
- package/dist/core-common/src/utils/bytes.d.ts +0 -51
- package/dist/core-common/src/utils/bytes.d.ts.map +0 -1
- package/dist/core-common/src/utils/date-format.d.ts +0 -90
- package/dist/core-common/src/utils/date-format.d.ts.map +0 -1
- package/dist/core-common/src/utils/json.d.ts +0 -34
- package/dist/core-common/src/utils/json.d.ts.map +0 -1
- package/dist/core-common/src/utils/num.d.ts +0 -60
- package/dist/core-common/src/utils/num.d.ts.map +0 -1
- package/dist/core-common/src/utils/obj.d.ts +0 -258
- package/dist/core-common/src/utils/obj.d.ts.map +0 -1
- package/dist/core-common/src/utils/path.d.ts +0 -23
- package/dist/core-common/src/utils/path.d.ts.map +0 -1
- package/dist/core-common/src/utils/primitive.d.ts +0 -18
- package/dist/core-common/src/utils/primitive.d.ts.map +0 -1
- package/dist/core-common/src/utils/str.d.ts +0 -103
- package/dist/core-common/src/utils/str.d.ts.map +0 -1
- package/dist/core-common/src/utils/template-strings.d.ts +0 -84
- package/dist/core-common/src/utils/template-strings.d.ts.map +0 -1
- package/dist/core-common/src/utils/transferable.d.ts +0 -47
- package/dist/core-common/src/utils/transferable.d.ts.map +0 -1
- package/dist/core-common/src/utils/wait.d.ts +0 -19
- package/dist/core-common/src/utils/wait.d.ts.map +0 -1
- package/dist/core-common/src/utils/xml.d.ts +0 -36
- package/dist/core-common/src/utils/xml.d.ts.map +0 -1
- package/dist/core-common/src/zip/sd-zip.d.ts +0 -80
- package/dist/core-common/src/zip/sd-zip.d.ts.map +0 -1
- package/dist/excel/src/excel-cell.d.ts.map +0 -1
- package/dist/excel/src/excel-col.d.ts.map +0 -1
- package/dist/excel/src/excel-row.d.ts.map +0 -1
- package/dist/excel/src/excel-workbook.d.ts.map +0 -1
- package/dist/excel/src/excel-worksheet.d.ts.map +0 -1
- package/dist/excel/src/excel-wrapper.d.ts.map +0 -1
- package/dist/excel/src/index.d.ts.map +0 -1
- package/dist/excel/src/types.d.ts.map +0 -1
- package/dist/excel/src/utils/excel-utils.d.ts.map +0 -1
- package/dist/excel/src/utils/zip-cache.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-content-type.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-drawing.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-relationship.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-shared-string.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-style.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-unknown.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-workbook.d.ts.map +0 -1
- package/dist/excel/src/xml/excel-xml-worksheet.d.ts.map +0 -1
- package/src/excel-cell.ts +0 -326
- package/src/excel-col.ts +0 -43
- package/src/excel-row.ts +0 -37
- package/src/excel-workbook.ts +0 -206
- package/src/excel-worksheet.ts +0 -380
- package/src/excel-wrapper.ts +0 -219
- package/src/index.ts +0 -13
- package/src/types.ts +0 -396
- package/src/utils/excel-utils.ts +0 -201
- package/src/utils/zip-cache.ts +0 -103
- package/src/xml/excel-xml-content-type.ts +0 -64
- package/src/xml/excel-xml-drawing.ts +0 -87
- package/src/xml/excel-xml-relationship.ts +0 -86
- package/src/xml/excel-xml-shared-string.ts +0 -80
- package/src/xml/excel-xml-style.ts +0 -393
- package/src/xml/excel-xml-unknown.ts +0 -11
- package/src/xml/excel-xml-workbook.ts +0 -112
- package/src/xml/excel-xml-worksheet.ts +0 -544
- package/tests/excel-cell.spec.ts +0 -407
- package/tests/excel-col.spec.ts +0 -112
- package/tests/excel-row.spec.ts +0 -71
- package/tests/excel-workbook.spec.ts +0 -166
- package/tests/excel-worksheet.spec.ts +0 -389
- package/tests/excel-wrapper.spec.ts +0 -275
- package/tests/fixtures/logo.png +0 -0
- package/tests/image-insert.spec.ts +0 -188
- package/tests/utils/excel-utils.spec.ts +0 -240
- /package/dist/{excel/src/excel-cell.d.ts → excel-cell.d.ts} +0 -0
- /package/dist/{excel/src/excel-col.d.ts → excel-col.d.ts} +0 -0
- /package/dist/{excel/src/excel-row.d.ts → excel-row.d.ts} +0 -0
- /package/dist/{excel/src/excel-workbook.d.ts → excel-workbook.d.ts} +0 -0
- /package/dist/{excel/src/excel-worksheet.d.ts → excel-worksheet.d.ts} +0 -0
- /package/dist/{excel/src/excel-wrapper.d.ts → excel-wrapper.d.ts} +0 -0
- /package/dist/{excel/src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{excel/src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{excel/src/utils → utils}/excel-utils.d.ts +0 -0
- /package/dist/{excel/src/utils → utils}/zip-cache.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-content-type.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-drawing.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-relationship.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-shared-string.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-style.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-unknown.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-workbook.d.ts +0 -0
- /package/dist/{excel/src/xml → xml}/excel-xml-worksheet.d.ts +0 -0
package/src/utils/excel-utils.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { numParseInt } from "@simplysm/core-common";
|
|
2
|
-
import type { ExcelAddressPoint, ExcelAddressRangePoint, ExcelNumberFormat } from "../types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Excel 관련 유틸리티 함수 모음.
|
|
6
|
-
* 셀 주소 변환, 날짜/숫자 변환, 숫자 형식 처리 등의 기능을 제공한다.
|
|
7
|
-
*/
|
|
8
|
-
export class ExcelUtils {
|
|
9
|
-
/** 셀 좌표를 "A1" 형식 문자열로 변환 */
|
|
10
|
-
static stringifyAddr(point: ExcelAddressPoint): string {
|
|
11
|
-
const rowStr = this.stringifyRowAddr(point.r);
|
|
12
|
-
const colStr = this.stringifyColAddr(point.c);
|
|
13
|
-
return `${colStr}${rowStr}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** 행 인덱스(0-based)를 행 주소 문자열로 변환 (예: 0 -> "1") */
|
|
17
|
-
static stringifyRowAddr(r: number): string {
|
|
18
|
-
return (r + 1).toString();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** 열 인덱스(0-based)를 열 주소 문자열로 변환 (예: 0 -> "A", 26 -> "AA") */
|
|
22
|
-
static stringifyColAddr(c: number): string {
|
|
23
|
-
if (c < 0 || c > 16383) {
|
|
24
|
-
throw new Error(`열 인덱스는 0~16383 범위여야 합니다: ${c}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let remained = c;
|
|
28
|
-
let result = String.fromCharCode((remained % 26) + 65);
|
|
29
|
-
remained = Math.floor(remained / 26);
|
|
30
|
-
while (remained !== 0) {
|
|
31
|
-
result = String.fromCharCode((remained % 26) + 64) + result;
|
|
32
|
-
remained = Math.floor(remained / 26);
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 셀 주소에서 행 인덱스 추출 (예: "A3" -> 2) */
|
|
38
|
-
static parseRowAddrCode(addrCode: string): number {
|
|
39
|
-
const rowAddrCode = /\d*$/.exec(addrCode)?.[0] ?? "";
|
|
40
|
-
const parsed = numParseInt(rowAddrCode);
|
|
41
|
-
if (parsed == null) {
|
|
42
|
-
throw new Error(`잘못된 행 주소 코드: ${addrCode}`);
|
|
43
|
-
}
|
|
44
|
-
return parsed - 1;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 셀 주소에서 열 인덱스 추출 (예: "B3" -> 1) */
|
|
48
|
-
static parseColAddrCode(addrCode: string): number {
|
|
49
|
-
const colAddrCode = /^[a-zA-Z]*/.exec(addrCode)?.[0] ?? "";
|
|
50
|
-
|
|
51
|
-
let result = 0;
|
|
52
|
-
const revAddr = Array.from(colAddrCode).reverse().join("");
|
|
53
|
-
for (let i = 0; i < revAddr.length; i++) {
|
|
54
|
-
const col = revAddr.charCodeAt(i) - (i === 0 ? 65 : 64);
|
|
55
|
-
result += col * 26 ** i;
|
|
56
|
-
}
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** 셀 주소를 좌표로 변환 (예: "B3" -> {r: 2, c: 1}) */
|
|
61
|
-
static parseCellAddrCode(addr: string): ExcelAddressPoint {
|
|
62
|
-
return {
|
|
63
|
-
r: ExcelUtils.parseRowAddrCode(addr),
|
|
64
|
-
c: ExcelUtils.parseColAddrCode(addr),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** 범위 주소를 좌표로 변환 (예: "A1:C3" -> {s: {r:0,c:0}, e: {r:2,c:2}}) */
|
|
69
|
-
static parseRangeAddrCode(rangeAddr: string): ExcelAddressRangePoint {
|
|
70
|
-
const parts = rangeAddr.split(":");
|
|
71
|
-
return {
|
|
72
|
-
s: ExcelUtils.parseCellAddrCode(parts[0]),
|
|
73
|
-
e: ExcelUtils.parseCellAddrCode(parts[1] ?? parts[0]),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** 범위 좌표를 주소 문자열로 변환 */
|
|
78
|
-
static stringifyRangeAddr(point: ExcelAddressRangePoint): string {
|
|
79
|
-
const sAddr = this.stringifyAddr(point.s);
|
|
80
|
-
const eAddr = this.stringifyAddr(point.e);
|
|
81
|
-
|
|
82
|
-
if (sAddr === eAddr) {
|
|
83
|
-
return sAddr;
|
|
84
|
-
} else {
|
|
85
|
-
return sAddr + ":" + eAddr;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* JavaScript 타임스탬프(ms)를 Excel 날짜 숫자로 변환.
|
|
91
|
-
* Excel은 1900-01-01을 1로 계산한다 (1899-12-30이 날짜 0).
|
|
92
|
-
*/
|
|
93
|
-
static convertTimeTickToNumber(tick: number): number {
|
|
94
|
-
const currDate = new Date(tick);
|
|
95
|
-
currDate.setMinutes(currDate.getMinutes() - currDate.getTimezoneOffset());
|
|
96
|
-
const excelBaseDateNumberUtc = Date.UTC(1899, 11, 31);
|
|
97
|
-
const inputExcelDateNumberUtc = currDate.getTime() - excelBaseDateNumberUtc;
|
|
98
|
-
return inputExcelDateNumberUtc / (24 * 60 * 60 * 1000) + 1;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Excel 날짜 숫자를 JavaScript 타임스탬프(ms)로 변환.
|
|
103
|
-
* Excel은 1900-01-01을 1로 계산한다 (1899-12-30이 날짜 0).
|
|
104
|
-
*/
|
|
105
|
-
static convertNumberToTimeTick(num: number): number {
|
|
106
|
-
const excelBaseDateNumberUtc = Date.UTC(1899, 11, 31);
|
|
107
|
-
const excelDateNumberUtc = (num - 1) * 24 * 60 * 60 * 1000;
|
|
108
|
-
const dateNumberUtc = excelBaseDateNumberUtc + excelDateNumberUtc;
|
|
109
|
-
const date = new Date(dateNumberUtc);
|
|
110
|
-
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
|
|
111
|
-
return date.getTime();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** 숫자 형식 코드를 형식 이름으로 변환 */
|
|
115
|
-
static convertNumFmtCodeToName(numFmtCode: string): ExcelNumberFormat {
|
|
116
|
-
if (numFmtCode === "General") {
|
|
117
|
-
return "number";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const hasDate = /yy/i.test(numFmtCode) || /dd/i.test(numFmtCode) || /mm/i.test(numFmtCode);
|
|
121
|
-
const hasTime = /hh/i.test(numFmtCode) || /ss/i.test(numFmtCode);
|
|
122
|
-
|
|
123
|
-
if (hasDate && hasTime) {
|
|
124
|
-
return "DateTime"; // 날짜+시간 = DateTime
|
|
125
|
-
} else if (hasDate) {
|
|
126
|
-
return "DateOnly"; // 날짜만 = DateOnly
|
|
127
|
-
} else if (hasTime) {
|
|
128
|
-
return "Time"; // 시간만 = Time
|
|
129
|
-
}
|
|
130
|
-
// 숫자 형식 패턴: 0, #, 소수점, 천단위 구분자, 음수 구분자, 괄호, 통화, 공백, 지수, 백분율 등
|
|
131
|
-
// "[조건부 서식]실제형식" 구조에서 실제형식 부분만 검사 (split("]").at(-1))
|
|
132
|
-
else if (/^[0.#,_;()\-\\$ @*?"E%+]*$/.test(numFmtCode.split("]").at(-1) ?? "")) {
|
|
133
|
-
return "number";
|
|
134
|
-
} else if ((numFmtCode.split("]").at(-1) ?? "").includes("#,0")) {
|
|
135
|
-
return "number";
|
|
136
|
-
} else {
|
|
137
|
-
throw new Error(`[numFmtCode: ${numFmtCode}]에 대한 형식을 알 수 없습니다.`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* 숫자 형식 ID를 형식 이름으로 변환
|
|
143
|
-
*
|
|
144
|
-
* @remarks
|
|
145
|
-
* Excel 내장 형식 ID 범위:
|
|
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
|
-
*/
|
|
152
|
-
static convertNumFmtIdToName(numFmtId: number): ExcelNumberFormat {
|
|
153
|
-
// 숫자/일반/통화/백분율 형식
|
|
154
|
-
if (numFmtId <= 13 || (numFmtId >= 37 && numFmtId <= 40) || numFmtId === 48) {
|
|
155
|
-
return "number";
|
|
156
|
-
}
|
|
157
|
-
// 날짜 형식 (지역화 포함)
|
|
158
|
-
else if (
|
|
159
|
-
(numFmtId >= 14 && numFmtId <= 17) ||
|
|
160
|
-
(numFmtId >= 27 && numFmtId <= 31) ||
|
|
161
|
-
(numFmtId >= 34 && numFmtId <= 36) ||
|
|
162
|
-
(numFmtId >= 50 && numFmtId <= 58)
|
|
163
|
-
) {
|
|
164
|
-
return "DateOnly";
|
|
165
|
-
}
|
|
166
|
-
// 날짜+시간 형식
|
|
167
|
-
else if (numFmtId === 22) {
|
|
168
|
-
return "DateTime";
|
|
169
|
-
}
|
|
170
|
-
// 시간 형식
|
|
171
|
-
else if (
|
|
172
|
-
(numFmtId >= 18 && numFmtId <= 21) ||
|
|
173
|
-
(numFmtId >= 32 && numFmtId <= 33) ||
|
|
174
|
-
(numFmtId >= 45 && numFmtId <= 47)
|
|
175
|
-
) {
|
|
176
|
-
return "Time";
|
|
177
|
-
}
|
|
178
|
-
// 텍스트 형식
|
|
179
|
-
else if (numFmtId === 49) {
|
|
180
|
-
return "string";
|
|
181
|
-
} else {
|
|
182
|
-
throw new Error(`[numFmtId: ${numFmtId}]에 대한 형식을 알 수 없습니다.`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** 숫자 형식 이름을 형식 ID로 변환 */
|
|
187
|
-
static convertNumFmtNameToId(numFmtName: ExcelNumberFormat): number {
|
|
188
|
-
if (numFmtName === "number") {
|
|
189
|
-
return 0;
|
|
190
|
-
} else if (numFmtName === "DateOnly") {
|
|
191
|
-
return 14;
|
|
192
|
-
} else if (numFmtName === "DateTime") {
|
|
193
|
-
return 22;
|
|
194
|
-
} else if (numFmtName === "Time") {
|
|
195
|
-
return 18;
|
|
196
|
-
} else {
|
|
197
|
-
// 마지막 케이스: "string" (TypeScript가 타입 좁히기를 통해 자동으로 확인)
|
|
198
|
-
return 49;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
package/src/utils/zip-cache.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import type { Bytes } from "@simplysm/core-common";
|
|
2
|
-
import { ZipArchive, xmlStringify, xmlParse } from "@simplysm/core-common";
|
|
3
|
-
import type {
|
|
4
|
-
ExcelXml,
|
|
5
|
-
ExcelXmlContentTypeData,
|
|
6
|
-
ExcelXmlDrawingData,
|
|
7
|
-
ExcelXmlRelationshipData,
|
|
8
|
-
ExcelXmlSharedStringData,
|
|
9
|
-
ExcelXmlStyleData,
|
|
10
|
-
ExcelXmlWorkbookData,
|
|
11
|
-
ExcelXmlWorksheetData,
|
|
12
|
-
} from "../types";
|
|
13
|
-
import { ExcelXmlContentType } from "../xml/excel-xml-content-type";
|
|
14
|
-
import { ExcelXmlDrawing } from "../xml/excel-xml-drawing";
|
|
15
|
-
import { ExcelXmlRelationship } from "../xml/excel-xml-relationship";
|
|
16
|
-
import { ExcelXmlSharedString } from "../xml/excel-xml-shared-string";
|
|
17
|
-
import { ExcelXmlStyle } from "../xml/excel-xml-style";
|
|
18
|
-
import { ExcelXmlUnknown } from "../xml/excel-xml-unknown";
|
|
19
|
-
import { ExcelXmlWorkbook } from "../xml/excel-xml-workbook";
|
|
20
|
-
import { ExcelXmlWorksheet } from "../xml/excel-xml-worksheet";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Excel ZIP 아카이브의 파일 캐시를 관리하는 클래스.
|
|
24
|
-
* XML 파일은 파싱하여 ExcelXml 객체로, 그 외 파일은 바이트 배열로 캐싱한다.
|
|
25
|
-
*
|
|
26
|
-
* @remarks
|
|
27
|
-
* ## Lazy Loading 캐시 전략
|
|
28
|
-
*
|
|
29
|
-
* - 파일은 첫 접근 시에만 ZIP에서 읽고 파싱한다
|
|
30
|
-
* - 이후 접근은 캐시된 객체를 반환한다
|
|
31
|
-
* - 대용량 Excel 파일에서 필요한 부분만 로드하여 메모리 효율성을 높인다
|
|
32
|
-
*/
|
|
33
|
-
export class ZipCache {
|
|
34
|
-
private readonly _cache = new Map<string, ExcelXml | Bytes | undefined>();
|
|
35
|
-
private readonly _zip: ZipArchive;
|
|
36
|
-
|
|
37
|
-
constructor(arg?: Blob | Bytes) {
|
|
38
|
-
this._zip = new ZipArchive(arg);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async get(filePath: string): Promise<ExcelXml | Bytes | undefined> {
|
|
42
|
-
if (this._cache.has(filePath)) {
|
|
43
|
-
return this._cache.get(filePath);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const fileData = await this._zip.get(filePath);
|
|
47
|
-
if (fileData == null) {
|
|
48
|
-
this._cache.set(filePath, undefined);
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (filePath.endsWith(".xml") || filePath.endsWith(".rels")) {
|
|
53
|
-
const fileText = new TextDecoder().decode(fileData);
|
|
54
|
-
const xml = xmlParse(fileText, { stripTagPrefix: true });
|
|
55
|
-
if (filePath.endsWith(".rels")) {
|
|
56
|
-
this._cache.set(filePath, new ExcelXmlRelationship(xml as ExcelXmlRelationshipData));
|
|
57
|
-
} else if (filePath === "[Content_Types].xml") {
|
|
58
|
-
this._cache.set(filePath, new ExcelXmlContentType(xml as ExcelXmlContentTypeData));
|
|
59
|
-
} else if (filePath === "xl/workbook.xml") {
|
|
60
|
-
this._cache.set(filePath, new ExcelXmlWorkbook(xml as ExcelXmlWorkbookData));
|
|
61
|
-
} else if (filePath.startsWith("xl/worksheets/sheet") && filePath.endsWith(".xml")) {
|
|
62
|
-
this._cache.set(filePath, new ExcelXmlWorksheet(xml as ExcelXmlWorksheetData));
|
|
63
|
-
} else if (filePath.startsWith("xl/drawings/drawing") && filePath.endsWith(".xml")) {
|
|
64
|
-
this._cache.set(filePath, new ExcelXmlDrawing(xml as ExcelXmlDrawingData));
|
|
65
|
-
} else if (filePath === "xl/sharedStrings.xml") {
|
|
66
|
-
this._cache.set(filePath, new ExcelXmlSharedString(xml as ExcelXmlSharedStringData));
|
|
67
|
-
} else if (filePath === "xl/styles.xml") {
|
|
68
|
-
this._cache.set(filePath, new ExcelXmlStyle(xml as ExcelXmlStyleData));
|
|
69
|
-
} else {
|
|
70
|
-
this._cache.set(filePath, new ExcelXmlUnknown(xml as Record<string, unknown>));
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
this._cache.set(filePath, fileData);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return this._cache.get(filePath);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
set(filePath: string, content: ExcelXml | Bytes): void {
|
|
80
|
-
this._cache.set(filePath, content);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async toBytes(): Promise<Bytes> {
|
|
84
|
-
for (const filePath of this._cache.keys()) {
|
|
85
|
-
const content = this._cache.get(filePath);
|
|
86
|
-
if (content == null) continue;
|
|
87
|
-
|
|
88
|
-
if ("cleanup" in content) {
|
|
89
|
-
content.cleanup();
|
|
90
|
-
this._zip.write(filePath, new TextEncoder().encode(xmlStringify(content.data)));
|
|
91
|
-
} else {
|
|
92
|
-
this._zip.write(filePath, content);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return this._zip.compress();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async close(): Promise<void> {
|
|
100
|
-
await this._zip.close();
|
|
101
|
-
this._cache.clear();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { ExcelXml, ExcelXmlContentTypeData } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* [Content_Types].xml 파일을 관리하는 클래스.
|
|
5
|
-
* 파일별 MIME 타입 정보를 관리한다.
|
|
6
|
-
*/
|
|
7
|
-
export class ExcelXmlContentType implements ExcelXml {
|
|
8
|
-
data: ExcelXmlContentTypeData;
|
|
9
|
-
|
|
10
|
-
constructor(data?: ExcelXmlContentTypeData) {
|
|
11
|
-
if (data == null) {
|
|
12
|
-
this.data = {
|
|
13
|
-
Types: {
|
|
14
|
-
$: {
|
|
15
|
-
xmlns: "http://schemas.openxmlformats.org/package/2006/content-types",
|
|
16
|
-
},
|
|
17
|
-
Default: [
|
|
18
|
-
{
|
|
19
|
-
$: {
|
|
20
|
-
Extension: "rels",
|
|
21
|
-
ContentType: "application/vnd.openxmlformats-package.relationships+xml",
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
$: {
|
|
26
|
-
Extension: "xml",
|
|
27
|
-
ContentType: "application/xml",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
Override: [
|
|
32
|
-
{
|
|
33
|
-
$: {
|
|
34
|
-
PartName: "/xl/workbook.xml",
|
|
35
|
-
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
} else {
|
|
42
|
-
this.data = data;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
add(partName: string, contentType: string): this {
|
|
47
|
-
// 중복 체크
|
|
48
|
-
const exists = this.data.Types.Override.some((item) => item.$.PartName === partName);
|
|
49
|
-
if (exists) {
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
this.data.Types.Override.push({
|
|
54
|
-
$: {
|
|
55
|
-
PartName: partName,
|
|
56
|
-
ContentType: contentType,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
return this;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
cleanup(): void {}
|
|
64
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { ExcelXml, ExcelXmlDrawingData } from "../types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* xl/drawings/drawing*.xml 파일을 관리하는 클래스.
|
|
5
|
-
* 이미지 삽입 시 위치 및 참조 정보를 처리한다.
|
|
6
|
-
*/
|
|
7
|
-
export class ExcelXmlDrawing implements ExcelXml {
|
|
8
|
-
data: ExcelXmlDrawingData;
|
|
9
|
-
|
|
10
|
-
constructor(data?: ExcelXmlDrawingData) {
|
|
11
|
-
if (data == null) {
|
|
12
|
-
this.data = {
|
|
13
|
-
wsDr: {
|
|
14
|
-
$: {
|
|
15
|
-
"xmlns": "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
|
|
16
|
-
"xmlns:a": "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
17
|
-
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
18
|
-
},
|
|
19
|
-
twoCellAnchor: [],
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
} else {
|
|
23
|
-
this.data = data;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
addPicture(opts: {
|
|
28
|
-
from: { r: number; c: number; rOff?: number | string; cOff?: number | string };
|
|
29
|
-
to: { r: number; c: number; rOff?: number | string; cOff?: number | string };
|
|
30
|
-
blipRelId: string;
|
|
31
|
-
}): void {
|
|
32
|
-
this.data.wsDr.twoCellAnchor = this.data.wsDr.twoCellAnchor ?? [];
|
|
33
|
-
|
|
34
|
-
const anchors = this.data.wsDr.twoCellAnchor;
|
|
35
|
-
const picId = anchors.length + 1;
|
|
36
|
-
const name = `Picture ${picId}`;
|
|
37
|
-
|
|
38
|
-
this.data.wsDr.twoCellAnchor.push({
|
|
39
|
-
from: [
|
|
40
|
-
{
|
|
41
|
-
col: [opts.from.c.toString()],
|
|
42
|
-
colOff: [opts.from.cOff != null ? opts.from.cOff.toString() : "0"],
|
|
43
|
-
row: [opts.from.r.toString()],
|
|
44
|
-
rowOff: [opts.from.rOff != null ? opts.from.rOff.toString() : "0"],
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
to: [
|
|
48
|
-
{
|
|
49
|
-
col: [opts.to.c.toString()],
|
|
50
|
-
colOff: [opts.to.cOff != null ? opts.to.cOff.toString() : "0"],
|
|
51
|
-
row: [opts.to.r.toString()],
|
|
52
|
-
rowOff: [opts.to.rOff != null ? opts.to.rOff.toString() : "0"],
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
pic: [
|
|
56
|
-
{
|
|
57
|
-
nvPicPr: [
|
|
58
|
-
{
|
|
59
|
-
cNvPr: [{ $: { id: picId.toString(), name } }],
|
|
60
|
-
cNvPicPr: [{ "a:picLocks": [{ $: { noChangeAspect: "1" } }] }],
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
blipFill: [
|
|
64
|
-
{
|
|
65
|
-
"a:blip": [{ $: { "r:embed": opts.blipRelId } }],
|
|
66
|
-
"a:stretch": [{ "a:fillRect": [] }],
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
spPr: [
|
|
70
|
-
{
|
|
71
|
-
"a:xfrm": [
|
|
72
|
-
{
|
|
73
|
-
"a:off": [{ $: { x: "0", y: "0" } }],
|
|
74
|
-
"a:ext": [{ $: { cx: "0", cy: "0" } }],
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
"a:prstGeom": [{ "$": { prst: "rect" }, "a:avLst": [] }],
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
clientData: [{}],
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
cleanup(): void {}
|
|
87
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import "@simplysm/core-common";
|
|
2
|
-
import { numParseInt } from "@simplysm/core-common";
|
|
3
|
-
import type { ExcelRelationshipData, ExcelXml, ExcelXmlRelationshipData } from "../types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* *.rels 파일을 관리하는 클래스.
|
|
7
|
-
* 파일 간의 참조 관계를 처리한다.
|
|
8
|
-
*/
|
|
9
|
-
export class ExcelXmlRelationship implements ExcelXml {
|
|
10
|
-
data: ExcelXmlRelationshipData;
|
|
11
|
-
|
|
12
|
-
constructor(data?: ExcelXmlRelationshipData) {
|
|
13
|
-
if (data == null) {
|
|
14
|
-
this.data = {
|
|
15
|
-
Relationships: {
|
|
16
|
-
$: {
|
|
17
|
-
xmlns: "http://schemas.openxmlformats.org/package/2006/relationships",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
} else {
|
|
22
|
-
this.data = data;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getTargetByRelId(rId: number): string | undefined {
|
|
27
|
-
return (this.data.Relationships.Relationship ?? []).single((rel) => this._getRelId(rel) === rId)?.$.Target;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
add(target: string, type: string): this {
|
|
31
|
-
this.addAndGetId(target, type);
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
addAndGetId(target: string, type: string): number {
|
|
36
|
-
this.data.Relationships.Relationship = this.data.Relationships.Relationship ?? [];
|
|
37
|
-
|
|
38
|
-
const newId = (this._lastId ?? 0) + 1;
|
|
39
|
-
|
|
40
|
-
this.data.Relationships.Relationship.push({
|
|
41
|
-
$: {
|
|
42
|
-
Id: `rId${newId}`,
|
|
43
|
-
Target: target,
|
|
44
|
-
Type: type,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return newId;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
insert(rId: number, target: string, type: string): this {
|
|
52
|
-
this.data.Relationships.Relationship = this.data.Relationships.Relationship ?? [];
|
|
53
|
-
|
|
54
|
-
const shiftRels = this.data.Relationships.Relationship.filter((rel) => this._getRelId(rel) >= rId);
|
|
55
|
-
for (const shiftRel of shiftRels) {
|
|
56
|
-
shiftRel.$.Id = `rId${this._getRelId(shiftRel) + 1}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
this.data.Relationships.Relationship.push({
|
|
60
|
-
$: {
|
|
61
|
-
Id: `rId${rId}`,
|
|
62
|
-
Target: target,
|
|
63
|
-
Type: type,
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
cleanup(): void {}
|
|
71
|
-
|
|
72
|
-
private get _lastId(): number | undefined {
|
|
73
|
-
const rels = this.data.Relationships.Relationship;
|
|
74
|
-
if (!rels || rels.length === 0) return undefined;
|
|
75
|
-
const maxRel = rels.orderByDesc((rel) => this._getRelId(rel)).first();
|
|
76
|
-
return maxRel ? this._getRelId(maxRel) : undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private _getRelId(rel: ExcelRelationshipData): number {
|
|
80
|
-
const match = /[0-9]+$/.exec(rel.$.Id);
|
|
81
|
-
if (match == null) {
|
|
82
|
-
throw new Error(`잘못된 관계 ID 형식입니다: ${rel.$.Id}`);
|
|
83
|
-
}
|
|
84
|
-
return numParseInt(match[0])!;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExcelXml,
|
|
3
|
-
ExcelXmlSharedStringData,
|
|
4
|
-
ExcelXmlSharedStringDataSi,
|
|
5
|
-
ExcelXmlSharedStringDataText,
|
|
6
|
-
} from "../types";
|
|
7
|
-
import "@simplysm/core-common";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* xl/sharedStrings.xml 파일을 관리하는 클래스.
|
|
11
|
-
* 문자열 중복을 방지하여 파일 크기를 최적화한다.
|
|
12
|
-
*/
|
|
13
|
-
export class ExcelXmlSharedString implements ExcelXml {
|
|
14
|
-
data: ExcelXmlSharedStringData;
|
|
15
|
-
|
|
16
|
-
private readonly _stringIndexesMap: Map<string, number[]>;
|
|
17
|
-
|
|
18
|
-
constructor(data?: ExcelXmlSharedStringData) {
|
|
19
|
-
if (data === undefined) {
|
|
20
|
-
this.data = {
|
|
21
|
-
sst: {
|
|
22
|
-
$: {
|
|
23
|
-
xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
} else {
|
|
28
|
-
this.data = data;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
this._stringIndexesMap = this.data.sst.si
|
|
32
|
-
? this.data.sst.si
|
|
33
|
-
.map((tag, id) => ({ id, tag }))
|
|
34
|
-
.filter((item) => !this._getHasInnerStyleOnSiTag(item.tag))
|
|
35
|
-
.toArrayMap(
|
|
36
|
-
(item) => this._getStringFromSiTag(item.tag),
|
|
37
|
-
(item) => item.id,
|
|
38
|
-
)
|
|
39
|
-
: new Map<string, number[]>();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getIdByString(str: string): number | undefined {
|
|
43
|
-
return this._stringIndexesMap.get(str)?.[0];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
getStringById(id: number): string | undefined {
|
|
47
|
-
const si = this.data.sst.si?.[id];
|
|
48
|
-
return si != null ? this._getStringFromSiTag(si) : undefined;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
add(str: string): number {
|
|
52
|
-
this.data.sst.si = this.data.sst.si ?? [];
|
|
53
|
-
const newLength = this.data.sst.si.push({ t: [str] });
|
|
54
|
-
const arr = this._stringIndexesMap.getOrCreate(str, []);
|
|
55
|
-
arr.push(newLength - 1);
|
|
56
|
-
return newLength - 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
cleanup(): void {}
|
|
60
|
-
|
|
61
|
-
private _getStringFromSiTag(si: ExcelXmlSharedStringDataSi): string {
|
|
62
|
-
if ("t" in si) {
|
|
63
|
-
return this._getStringFromTTag(si.t);
|
|
64
|
-
} else {
|
|
65
|
-
return si.r.map((item) => this._getStringFromTTag(item.t)).join("");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private _getStringFromTTag(t: ExcelXmlSharedStringDataText): string {
|
|
70
|
-
const firstItem = t[0];
|
|
71
|
-
if (typeof firstItem === "string") {
|
|
72
|
-
return firstItem;
|
|
73
|
-
}
|
|
74
|
-
return firstItem._ ?? " ";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private _getHasInnerStyleOnSiTag(si: ExcelXmlSharedStringDataSi): boolean {
|
|
78
|
-
return Object.keys(si).some((item) => item !== "t");
|
|
79
|
-
}
|
|
80
|
-
}
|