@simplysm/excel 13.0.95 → 13.0.97

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 (2) hide show
  1. package/package.json +2 -2
  2. package/README.md +0 -400
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/excel",
3
- "version": "13.0.95",
3
+ "version": "13.0.97",
4
4
  "description": "Excel file processing library",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -21,6 +21,6 @@
21
21
  "dependencies": {
22
22
  "mime": "^4.1.0",
23
23
  "zod": "^4.3.6",
24
- "@simplysm/core-common": "13.0.95"
24
+ "@simplysm/core-common": "13.0.97"
25
25
  }
26
26
  }
package/README.md DELETED
@@ -1,400 +0,0 @@
1
- # @simplysm/excel
2
-
3
- Excel 파일(.xlsx) 처리 라이브러리. 지연 로딩 아키텍처로 대용량 파일도 메모리 효율적으로 처리한다.
4
-
5
- ## 설치
6
-
7
- ```bash
8
- npm install @simplysm/excel
9
- ```
10
-
11
- **의존성:** `@simplysm/core-common`, `mime`, `zod`
12
-
13
- ## 아키텍처
14
-
15
- ZIP 내부의 XML을 지연 로딩(Lazy Loading)하여 필요한 시점에만 파싱한다. 대용량 SharedStrings나 Styles XML이 있어도 해당 셀에 접근할 때만 로드되므로 메모리 효율적이다.
16
-
17
- ```
18
- ExcelWorkbook (워크북)
19
- ├── ExcelWorksheet (워크시트)
20
- │ ├── ExcelRow (행) → ExcelCell (셀)
21
- │ └── ExcelCol (열) → ExcelCell (셀)
22
- ├── ZipCache (ZIP 파일 캐시, 지연 로딩)
23
- └── ExcelWrapper (Zod 스키마 기반 래퍼)
24
- ```
25
-
26
- **모든 행/열/셀 인덱스는 0-based이다.**
27
-
28
- ## 리소스 관리
29
-
30
- ExcelWorkbook은 내부적으로 ZIP 리소스를 관리하므로, 사용 후 반드시 리소스를 해제해야 한다.
31
-
32
- ```typescript
33
- // 방법 1: await using (권장)
34
- await using wb = new ExcelWorkbook(bytes);
35
- const ws = await wb.getWorksheet(0);
36
- // ... 작업 수행
37
- // 스코프 종료 시 자동 해제
38
-
39
- // 방법 2: try-finally
40
- const wb = new ExcelWorkbook(bytes);
41
- try {
42
- const ws = await wb.getWorksheet(0);
43
- // ... 작업 수행
44
- } finally {
45
- await wb.close();
46
- }
47
- ```
48
-
49
- ## 워크북 (ExcelWorkbook)
50
-
51
- ### 생성/열기
52
-
53
- ```typescript
54
- import { ExcelWorkbook } from "@simplysm/excel";
55
-
56
- // 새 워크북 생성
57
- await using wb = new ExcelWorkbook();
58
-
59
- // 기존 파일 열기 (Uint8Array 또는 Blob)
60
- await using wb = new ExcelWorkbook(fileBytes);
61
- await using wb = new ExcelWorkbook(blob);
62
- ```
63
-
64
- ### 워크시트 관리
65
-
66
- ```typescript
67
- // 워크시트 이름 목록
68
- const names = await wb.getWorksheetNames(); // string[]
69
-
70
- // 워크시트 조회 (인덱스: 0-based)
71
- const ws = await wb.getWorksheet(0);
72
- const ws = await wb.getWorksheet("Sheet1");
73
-
74
- // 워크시트 추가
75
- const ws = await wb.addWorksheet("새시트");
76
-
77
- // 워크시트 이름 변경
78
- await ws.setName("새이름");
79
- const name = await ws.getName();
80
- ```
81
-
82
- ### 파일 내보내기
83
-
84
- ```typescript
85
- const bytes = await wb.toBytes(); // Uint8Array (Bytes)
86
- const blob = await wb.toBlob(); // Blob (MIME: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)
87
- ```
88
-
89
- ## 워크시트 (ExcelWorksheet)
90
-
91
- ### 셀/행/열 접근
92
-
93
- 모든 인덱스는 0-based이다.
94
-
95
- ```typescript
96
- // 셀 접근
97
- const cell = ws.cell(0, 0); // A1 (r=0, c=0)
98
-
99
- // 행 접근
100
- const row = ws.row(0); // 첫 번째 행
101
- const cells = await row.getCells(); // 행의 모든 셀
102
-
103
- // 열 접근
104
- const col = ws.col(0); // 첫 번째 열
105
- const cells = await col.getCells(); // 열의 모든 셀
106
-
107
- // 열에서 특정 행의 셀
108
- const cell = ws.col(2).cell(5); // C6
109
- // 행에서 특정 열의 셀
110
- const cell = ws.row(5).cell(2); // C6
111
- ```
112
-
113
- ### 데이터 범위
114
-
115
- ```typescript
116
- const range = await ws.getRange();
117
- // { s: { r: 0, c: 0 }, e: { r: 9, c: 4 } }
118
-
119
- // 모든 셀을 2차원 배열로
120
- const cells = await ws.getCells(); // ExcelCell[][]
121
- ```
122
-
123
- ### 행 복사
124
-
125
- ```typescript
126
- // 행 복사 (덮어쓰기)
127
- await ws.copyRow(0, 5); // 0번 행을 5번 행으로 복사
128
-
129
- // 삽입 복사 (기존 행은 아래로 밀림)
130
- await ws.insertCopyRow(0, 5); // 0번 행을 5번 위치에 삽입 복사
131
-
132
- // 행 스타일만 복사
133
- await ws.copyRowStyle(0, 5);
134
- ```
135
-
136
- ### 셀 복사
137
-
138
- ```typescript
139
- // 셀 복사 (값 + 스타일)
140
- await ws.copyCell({ r: 0, c: 0 }, { r: 1, c: 1 });
141
-
142
- // 셀 스타일만 복사
143
- await ws.copyCellStyle({ r: 0, c: 0 }, { r: 1, c: 1 });
144
- ```
145
-
146
- ### 뷰 설정
147
-
148
- ```typescript
149
- // 줌 설정 (퍼센트)
150
- await ws.setZoom(150);
151
-
152
- // 틀고정 (r, c 모두 선택적)
153
- await ws.freezeAt({ r: 1 }); // 1번 행 이후 고정 (첫 행 고정)
154
- await ws.freezeAt({ c: 2 }); // 2번 열 이후 고정
155
- await ws.freezeAt({ r: 1, c: 1 }); // 행+열 모두 고정
156
- ```
157
-
158
- ### 데이터 테이블
159
-
160
- ```typescript
161
- // 시트를 레코드 배열로 읽기
162
- const data = await ws.getDataTable();
163
- // Record<string, ExcelValueType>[]
164
-
165
- // 옵션 지정
166
- const data = await ws.getDataTable({
167
- headerRowIndex: 0, // 헤더 행 인덱스 (기본: 범위의 첫 행)
168
- checkEndColIndex: 0, // 이 열이 비어있으면 데이터 종료
169
- usableHeaderNameFn: (name) => name !== "", // 사용할 헤더 필터
170
- });
171
-
172
- // 2차원 배열 쓰기
173
- await ws.setDataMatrix([
174
- ["이름", "나이", "이메일"],
175
- ["Alice", 30, "alice@example.com"],
176
- ["Bob", 25, "bob@example.com"],
177
- ]);
178
-
179
- // 레코드 배열 쓰기 (헤더 자동 생성)
180
- await ws.setRecords([
181
- { name: "Alice", age: 30 },
182
- { name: "Bob", age: 25 },
183
- ]);
184
- ```
185
-
186
- ### 이미지 삽입
187
-
188
- ```typescript
189
- await ws.addImage({
190
- bytes: imageBytes, // Uint8Array (이미지 바이너리)
191
- ext: "png", // 확장자 (png, jpg 등)
192
- from: { // 시작 위치 (0-based)
193
- r: 0, c: 0,
194
- rOff: 0, // 행 오프셋 (EMU 단위, 선택적)
195
- cOff: 0, // 열 오프셋 (EMU 단위, 선택적)
196
- },
197
- to: { // 종료 위치 (생략 시 from + 1행 1열)
198
- r: 5, c: 3,
199
- rOff: 0,
200
- cOff: 0,
201
- },
202
- });
203
- ```
204
-
205
- ## 셀 (ExcelCell)
206
-
207
- ### 값 읽기/쓰기
208
-
209
- ```typescript
210
- // 값 설정
211
- await ws.cell(0, 0).setValue("텍스트");
212
- await ws.cell(0, 1).setValue(1234);
213
- await ws.cell(0, 2).setValue(true);
214
- await ws.cell(0, 3).setValue(new DateOnly(2024, 1, 15));
215
- await ws.cell(0, 4).setValue(new DateTime(2024, 1, 15, 10, 30));
216
- await ws.cell(0, 5).setValue(new Time(10, 30, 0));
217
- await ws.cell(0, 6).setValue(undefined); // 셀 삭제
218
-
219
- // 값 읽기
220
- const val = await ws.cell(0, 0).getValue();
221
- // ExcelValueType = number | string | DateOnly | DateTime | Time | boolean | undefined
222
- ```
223
-
224
- ### 수식
225
-
226
- ```typescript
227
- await ws.cell(1, 0).setFormula("SUM(B1:B10)");
228
- await ws.cell(1, 0).setFormula(undefined); // 수식 제거
229
-
230
- const formula = await ws.cell(1, 0).getFormula(); // string | undefined
231
- ```
232
-
233
- ### 셀 병합
234
-
235
- ```typescript
236
- // cell(startR, startC).merge(endR, endC)
237
- // 종료 좌표는 0-based 절대 좌표
238
- await ws.cell(0, 0).merge(2, 2); // A1:C3 범위 병합 (3행 x 3열)
239
- await ws.cell(0, 0).merge(0, 3); // A1:D1 범위 병합 (1행 x 4열)
240
- ```
241
-
242
- ### 스타일
243
-
244
- ```typescript
245
- await ws.cell(0, 0).setStyle({
246
- background: "00FF0000", // 배경색 (ARGB 8자리 hex, alpha 반전)
247
- border: ["left", "right", "top", "bottom"], // 테두리 위치
248
- horizontalAlign: "center", // 수평 정렬: "left" | "center" | "right"
249
- verticalAlign: "center", // 수직 정렬: "top" | "center" | "bottom"
250
- numberFormat: "number", // 숫자 서식: "number" | "string" | "DateOnly" | "DateTime" | "Time"
251
- });
252
-
253
- // 스타일 ID 직접 접근
254
- const styleId = await ws.cell(0, 0).getStyleId();
255
- await ws.cell(0, 0).setStyleId(styleId);
256
- ```
257
-
258
- **ExcelStyleOptions:**
259
-
260
- | 필드 | 타입 | 설명 |
261
- |------|------|------|
262
- | `background` | `string` | ARGB 8자리 hex (예: `"00FF0000"` = 빨강) |
263
- | `border` | `ExcelBorderPosition[]` | `"left"`, `"right"`, `"top"`, `"bottom"` |
264
- | `horizontalAlign` | `ExcelHorizontalAlign` | `"left"`, `"center"`, `"right"` |
265
- | `verticalAlign` | `ExcelVerticalAlign` | `"top"`, `"center"`, `"bottom"` |
266
- | `numberFormat` | `ExcelNumberFormat` | `"number"`, `"string"`, `"DateOnly"`, `"DateTime"`, `"Time"` |
267
-
268
- ## 열 너비
269
-
270
- ```typescript
271
- await ws.col(0).setWidth(20); // A열 너비 설정
272
- ```
273
-
274
- ## ExcelWrapper (Zod 스키마 기반)
275
-
276
- Zod 스키마를 기반으로 타입 안전한 Excel 읽기/쓰기를 제공한다.
277
-
278
- ### 스키마 정의
279
-
280
- ```typescript
281
- import { ExcelWrapper } from "@simplysm/excel";
282
- import { z } from "zod";
283
-
284
- const schema = z.object({
285
- name: z.string().describe("이름"), // .describe()로 Excel 헤더 이름 지정
286
- age: z.number().describe("나이"),
287
- email: z.string().optional().describe("이메일"), // optional: 필수 아닌 필드
288
- active: z.boolean().describe("활성"), // boolean: 기본값 false
289
- });
290
-
291
- const wrapper = new ExcelWrapper(schema);
292
- ```
293
-
294
- - `.describe("헤더이름")`: Excel 헤더에 표시될 이름 지정 (미지정 시 필드 키 사용)
295
- - `z.optional()` / `z.nullable()` / `z.default()`: 선택 필드 (헤더 강조 없음)
296
- - 필수 필드(boolean 제외)는 `write()` 시 헤더가 노란색으로 강조됨
297
-
298
- ### 읽기
299
-
300
- ```typescript
301
- const records = await wrapper.read(fileBytes, "Sheet1");
302
- // z.infer<typeof schema>[] 타입으로 반환
303
-
304
- // 인덱스로 시트 지정 (기본: 0)
305
- const records = await wrapper.read(fileBytes, 0);
306
-
307
- // 특정 필드 제외
308
- const records = await wrapper.read(fileBytes, "Sheet1", {
309
- excludes: ["email"],
310
- });
311
- ```
312
-
313
- **값 변환 규칙:**
314
- - `ZodString`: 문자열로 변환
315
- - `ZodNumber`: `parseFloat`로 변환
316
- - `ZodBoolean`: `"1"`, `"true"` = true / `"0"`, `"false"` = false
317
- - `DateOnly`, `DateTime`, `Time`: 그대로 전달
318
- - 빈 값: `ZodDefault` → 기본값, `ZodOptional`/`ZodNullable` → `undefined`, `ZodBoolean` → `false`
319
-
320
- ### 쓰기
321
-
322
- ```typescript
323
- // ExcelWorkbook을 반환하므로 리소스 관리 필요
324
- await using wb = await wrapper.write("Sheet1", [
325
- { name: "Alice", age: 30, email: "alice@example.com", active: true },
326
- { name: "Bob", age: 25, active: false },
327
- ]);
328
- const bytes = await wb.toBytes();
329
-
330
- // 특정 필드 제외
331
- await using wb = await wrapper.write("Sheet1", records, {
332
- excludes: ["email"],
333
- });
334
- ```
335
-
336
- `write()` 자동 적용 사항:
337
- - 모든 셀에 테두리(상하좌우) 적용
338
- - 필수 필드(boolean 제외) 헤더에 노란색 배경
339
- - 줌 85%, 첫 행 틀고정
340
-
341
- ## 유틸리티 (ExcelUtils)
342
-
343
- ```typescript
344
- import { ExcelUtils } from "@simplysm/excel";
345
-
346
- // 주소 변환 (0-based 좌표 <-> "A1" 형식)
347
- ExcelUtils.stringifyAddr({ r: 0, c: 0 }); // "A1"
348
- ExcelUtils.stringifyAddr({ r: 2, c: 1 }); // "B3"
349
- ExcelUtils.stringifyColAddr(0); // "A"
350
- ExcelUtils.stringifyColAddr(26); // "AA"
351
- ExcelUtils.stringifyRowAddr(0); // "1"
352
-
353
- ExcelUtils.parseCellAddr("B3"); // { r: 2, c: 1 }
354
- ExcelUtils.parseColAddr("B3"); // 1
355
- ExcelUtils.parseRowAddr("B3"); // 2
356
-
357
- // 범위 주소 변환
358
- ExcelUtils.parseRangeAddr("A1:C5"); // { s: {r:0,c:0}, e: {r:4,c:2} }
359
- ExcelUtils.stringifyRangeAddr({ s: {r:0,c:0}, e: {r:4,c:2} }); // "A1:C5"
360
-
361
- // 날짜 변환 (JavaScript tick <-> Excel 날짜 숫자)
362
- ExcelUtils.convertTimeTickToNumber(Date.now()); // Excel 날짜 숫자
363
- ExcelUtils.convertNumberToTimeTick(45678); // JavaScript timestamp (ms)
364
-
365
- // 숫자 서식 변환
366
- ExcelUtils.convertNumFmtNameToId("DateOnly"); // 14
367
- ExcelUtils.convertNumFmtIdToName(14); // "DateOnly"
368
- ExcelUtils.convertNumFmtCodeToName("yyyy-mm-dd"); // "DateOnly"
369
- ```
370
-
371
- ## 타입
372
-
373
- ```typescript
374
- // 셀 값 타입
375
- type ExcelValueType = number | string | DateOnly | DateTime | Time | boolean | undefined;
376
-
377
- // 숫자 서식
378
- type ExcelNumberFormat = "number" | "string" | "DateOnly" | "DateTime" | "Time";
379
-
380
- // 좌표
381
- interface ExcelAddressPoint { r: number; c: number; }
382
- interface ExcelAddressRangePoint { s: ExcelAddressPoint; e: ExcelAddressPoint; }
383
-
384
- // 스타일
385
- type ExcelBorderPosition = "left" | "right" | "top" | "bottom";
386
- type ExcelHorizontalAlign = "center" | "left" | "right";
387
- type ExcelVerticalAlign = "center" | "top" | "bottom";
388
- interface ExcelStyleOptions {
389
- background?: string;
390
- border?: ExcelBorderPosition[];
391
- horizontalAlign?: ExcelHorizontalAlign;
392
- verticalAlign?: ExcelVerticalAlign;
393
- numberFormat?: ExcelNumberFormat;
394
- }
395
-
396
- // 셀 타입
397
- type ExcelCellType = "s" | "b" | "str" | "n" | "inlineStr" | "e";
398
- // s: SharedString, b: boolean, str: 수식 결과 문자열,
399
- // n: 숫자, inlineStr: 인라인 문자열, e: 에러
400
- ```