@simplysm/excel 13.0.85 → 13.0.87

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/README.md +282 -253
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,371 +1,400 @@
1
1
  # @simplysm/excel
2
2
 
3
- Excel file (.xlsx) processing library for reading, writing, and manipulating workbooks. Supports both low-level cell operations and high-level schema-based data mapping via Zod.
3
+ Excel 파일(.xlsx) 처리 라이브러리. 지연 로딩 아키텍처로 대용량 파일도 메모리 효율적으로 처리한다.
4
4
 
5
- Works in both Node.js and browser environments. Uses lazy-loading architecture for memory-efficient handling of large files.
6
-
7
- ## Installation
5
+ ## 설치
8
6
 
9
7
  ```bash
10
8
  npm install @simplysm/excel
11
9
  ```
12
10
 
13
- **Peer dependency:** `@simplysm/core-common`
14
-
15
- ## Quick Start
16
-
17
- ### Create and export a workbook
18
-
19
- ```typescript
20
- import { ExcelWorkbook } from "@simplysm/excel";
11
+ **의존성:** `@simplysm/core-common`, `mime`, `zod`
21
12
 
22
- await using wb = new ExcelWorkbook();
23
- const ws = await wb.addWorksheet("Sheet1");
13
+ ## 아키텍처
24
14
 
25
- await ws.cell(0, 0).setValue("Name");
26
- await ws.cell(0, 1).setValue("Age");
27
- await ws.cell(1, 0).setValue("Alice");
28
- await ws.cell(1, 1).setValue(30);
15
+ ZIP 내부의 XML을 지연 로딩(Lazy Loading)하여 필요한 시점에만 파싱한다. 대용량 SharedStrings나 Styles XML이 있어도 해당 셀에 접근할 때만 로드되므로 메모리 효율적이다.
29
16
 
30
- const bytes = await wb.toBytes(); // Uint8Array
31
- const blob = await wb.toBlob(); // Blob (for browser downloads)
32
17
  ```
33
-
34
- ### Read an existing workbook
35
-
36
- ```typescript
37
- import { ExcelWorkbook } from "@simplysm/excel";
38
-
39
- await using wb = new ExcelWorkbook(fileBytes); // Uint8Array or Blob
40
- const ws = await wb.getWorksheet(0); // by index
41
- // const ws = await wb.getWorksheet("Sheet1"); // or by name
42
-
43
- const value = await ws.cell(0, 0).getValue(); // read cell value
18
+ ExcelWorkbook (워크북)
19
+ ├── ExcelWorksheet (워크시트)
20
+ │ ├── ExcelRow (행) → ExcelCell (셀)
21
+ │ └── ExcelCol (열) → ExcelCell (셀)
22
+ ├── ZipCache (ZIP 파일 캐시, 지연 로딩)
23
+ └── ExcelWrapper (Zod 스키마 기반 래퍼)
44
24
  ```
45
25
 
46
- ### Schema-based read/write with ExcelWrapper
47
-
48
- ```typescript
49
- import { z } from "zod";
50
- import { ExcelWrapper } from "@simplysm/excel";
26
+ **모든 행/열/셀 인덱스는 0-based이다.**
51
27
 
52
- const schema = z.object({
53
- name: z.string().describe("Name"), // .describe() sets the Excel header
54
- age: z.number().describe("Age"),
55
- email: z.string().optional().describe("Email"),
56
- active: z.boolean().default(false).describe("Active"),
57
- });
28
+ ## 리소스 관리
58
29
 
59
- const wrapper = new ExcelWrapper(schema);
60
-
61
- // Write records to Excel
62
- await using wb = await wrapper.write("Users", [
63
- { name: "Alice", age: 30, email: "alice@example.com", active: true },
64
- { name: "Bob", age: 25 },
65
- ]);
66
- const bytes = await wb.toBytes();
67
-
68
- // Read records from Excel
69
- const records = await wrapper.read(bytes, "Users");
70
- // records[0] => { name: "Alice", age: 30, email: "alice@example.com", active: true }
71
- ```
72
-
73
- ## API Reference
74
-
75
- ### ExcelWorkbook
76
-
77
- Excel workbook processing class. Manages ZIP resources internally and uses lazy-loading for memory efficiency.
78
-
79
- **Resource management:** Always release resources after use via `await using` (recommended) or `close()`.
30
+ ExcelWorkbook은 내부적으로 ZIP 리소스를 관리하므로, 사용 후 반드시 리소스를 해제해야 한다.
80
31
 
81
32
  ```typescript
82
- // Recommended: automatic cleanup
33
+ // 방법 1: await using (권장)
83
34
  await using wb = new ExcelWorkbook(bytes);
35
+ const ws = await wb.getWorksheet(0);
36
+ // ... 작업 수행
37
+ // 스코프 종료 시 자동 해제
84
38
 
85
- // Alternative: manual cleanup
39
+ // 방법 2: try-finally
86
40
  const wb = new ExcelWorkbook(bytes);
87
41
  try {
88
- // ... operations
42
+ const ws = await wb.getWorksheet(0);
43
+ // ... 작업 수행
89
44
  } finally {
90
45
  await wb.close();
91
46
  }
92
47
  ```
93
48
 
94
- #### Constructor
49
+ ## 워크북 (ExcelWorkbook)
50
+
51
+ ### 생성/열기
95
52
 
96
53
  ```typescript
97
- new ExcelWorkbook() // create empty workbook
98
- new ExcelWorkbook(bytes: Bytes) // open from Uint8Array
99
- new ExcelWorkbook(blob: Blob) // open from Blob
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);
100
62
  ```
101
63
 
102
- #### Methods
64
+ ### 워크시트 관리
103
65
 
104
- | Method | Returns | Description |
105
- |--------|---------|-------------|
106
- | `getWorksheetNames()` | `Promise<string[]>` | Return all worksheet names |
107
- | `getWorksheet(index: number)` | `Promise<ExcelWorksheet>` | Get worksheet by 0-based index |
108
- | `getWorksheet(name: string)` | `Promise<ExcelWorksheet>` | Get worksheet by name |
109
- | `addWorksheet(name: string)` | `Promise<ExcelWorksheet>` | Create a new worksheet |
110
- | `toBytes()` | `Promise<Bytes>` | Export workbook as byte array |
111
- | `toBlob()` | `Promise<Blob>` | Export workbook as Blob |
112
- | `close()` | `Promise<void>` | Release resources (safe to call multiple times) |
66
+ ```typescript
67
+ // 워크시트 이름 목록
68
+ const names = await wb.getWorksheetNames(); // string[]
113
69
 
114
- ---
70
+ // 워크시트 조회 (인덱스: 0-based)
71
+ const ws = await wb.getWorksheet(0);
72
+ const ws = await wb.getWorksheet("Sheet1");
115
73
 
116
- ### ExcelWorksheet
74
+ // 워크시트 추가
75
+ const ws = await wb.addWorksheet("새시트");
117
76
 
118
- Represents a worksheet. Provides cell access, row/column operations, data table processing, and image insertion.
77
+ // 워크시트 이름 변경
78
+ await ws.setName("새이름");
79
+ const name = await ws.getName();
80
+ ```
119
81
 
120
- #### Cell Access
82
+ ### 파일 내보내기
121
83
 
122
- | Method | Returns | Description |
123
- |--------|---------|-------------|
124
- | `cell(r, c)` | `ExcelCell` | Get cell at row `r`, column `c` (0-based) |
125
- | `row(r)` | `ExcelRow` | Get row object (0-based) |
126
- | `col(c)` | `ExcelCol` | Get column object (0-based) |
127
- | `getCells()` | `Promise<ExcelCell[][]>` | Get all cells as a 2D array |
128
- | `getRange()` | `Promise<ExcelAddressRangePoint>` | Get the data range of the worksheet |
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
+ ```
129
88
 
130
- #### Name
89
+ ## 워크시트 (ExcelWorksheet)
131
90
 
132
- | Method | Returns | Description |
133
- |--------|---------|-------------|
134
- | `getName()` | `Promise<string>` | Get worksheet name |
135
- | `setName(name)` | `Promise<void>` | Rename worksheet |
91
+ ### 셀/행/열 접근
136
92
 
137
- #### Data Operations
93
+ 모든 인덱스는 0-based이다.
138
94
 
139
- | Method | Returns | Description |
140
- |--------|---------|-------------|
141
- | `getDataTable(opt?)` | `Promise<Record<string, ExcelValueType>[]>` | Read data as record array (first row = headers) |
142
- | `setDataMatrix(matrix)` | `Promise<void>` | Write 2D array data |
143
- | `setRecords(records)` | `Promise<void>` | Write record array (auto-generates headers) |
95
+ ```typescript
96
+ // 셀 접근
97
+ const cell = ws.cell(0, 0); // A1 (r=0, c=0)
144
98
 
145
- **`getDataTable` options:**
99
+ // 행 접근
100
+ const row = ws.row(0); // 첫 번째 행
101
+ const cells = await row.getCells(); // 행의 모든 셀
146
102
 
147
- | Option | Type | Description |
148
- |--------|------|-------------|
149
- | `headerRowIndex` | `number` | Header row index (default: first row of range) |
150
- | `checkEndColIndex` | `number` | Column index to detect data end (stops when empty) |
151
- | `usableHeaderNameFn` | `(name: string) => boolean` | Filter function for usable headers |
103
+ // 접근
104
+ const col = ws.col(0); // 첫 번째 열
105
+ const cells = await col.getCells(); // 열의 모든
152
106
 
153
- #### Copy Operations
107
+ // 열에서 특정 행의 셀
108
+ const cell = ws.col(2).cell(5); // C6
109
+ // 행에서 특정 열의 셀
110
+ const cell = ws.row(5).cell(2); // C6
111
+ ```
154
112
 
155
- | Method | Returns | Description |
156
- |--------|---------|-------------|
157
- | `copyRow(srcR, targetR)` | `Promise<void>` | Copy row (overwrite target) |
158
- | `copyCell(srcAddr, targetAddr)` | `Promise<void>` | Copy cell |
159
- | `copyRowStyle(srcR, targetR)` | `Promise<void>` | Copy row style only |
160
- | `copyCellStyle(srcAddr, targetAddr)` | `Promise<void>` | Copy cell style only |
161
- | `insertCopyRow(srcR, targetR)` | `Promise<void>` | Insert-copy row (shifts existing rows down) |
113
+ ### 데이터 범위
162
114
 
163
- #### View Settings
115
+ ```typescript
116
+ const range = await ws.getRange();
117
+ // { s: { r: 0, c: 0 }, e: { r: 9, c: 4 } }
164
118
 
165
- | Method | Returns | Description |
166
- |--------|---------|-------------|
167
- | `setZoom(percent)` | `Promise<void>` | Set zoom scale |
168
- | `freezeAt({ r?, c? })` | `Promise<void>` | Freeze panes at row/column |
119
+ // 모든 셀을 2차원 배열로
120
+ const cells = await ws.getCells(); // ExcelCell[][]
121
+ ```
169
122
 
170
- #### Image
123
+ ### 행 복사
171
124
 
172
125
  ```typescript
173
- await ws.addImage({
174
- bytes: imageBytes, // image binary data (Uint8Array)
175
- ext: "png", // file extension
176
- from: { r: 0, c: 0 }, // start position (0-based)
177
- to: { r: 5, c: 3 }, // end position (optional)
178
- });
179
- ```
126
+ // 행 복사 (덮어쓰기)
127
+ await ws.copyRow(0, 5); // 0번 행을 5번 행으로 복사
180
128
 
181
- | Parameter | Type | Description |
182
- |-----------|------|-------------|
183
- | `bytes` | `Bytes` | Image binary data |
184
- | `ext` | `string` | Image extension (png, jpg, etc.) |
185
- | `from` | `{ r, c, rOff?, cOff? }` | Start position. `rOff`/`cOff` are EMU offsets. |
186
- | `to` | `{ r, c, rOff?, cOff? }` | End position. Defaults to one cell from `from`. |
129
+ // 삽입 복사 (기존 행은 아래로 밀림)
130
+ await ws.insertCopyRow(0, 5); // 0번 행을 5번 위치에 삽입 복사
187
131
 
188
- ---
132
+ // 행 스타일만 복사
133
+ await ws.copyRowStyle(0, 5);
134
+ ```
135
+
136
+ ### 셀 복사
189
137
 
190
- ### ExcelCell
138
+ ```typescript
139
+ // 셀 복사 (값 + 스타일)
140
+ await ws.copyCell({ r: 0, c: 0 }, { r: 1, c: 1 });
191
141
 
192
- Represents a single cell. All methods are async for lazy-loading efficiency.
142
+ // 스타일만 복사
143
+ await ws.copyCellStyle({ r: 0, c: 0 }, { r: 1, c: 1 });
144
+ ```
193
145
 
194
- #### Properties
146
+ ### 뷰 설정
195
147
 
196
- | Property | Type | Description |
197
- |----------|------|-------------|
198
- | `addr` | `ExcelAddressPoint` | Cell address (`{ r, c }`, 0-based) |
148
+ ```typescript
149
+ // 줌 설정 (퍼센트)
150
+ await ws.setZoom(150);
199
151
 
200
- #### Value
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
+ ```
201
157
 
202
- | Method | Returns | Description |
203
- |--------|---------|-------------|
204
- | `getValue()` | `Promise<ExcelValueType>` | Get cell value |
205
- | `setValue(val)` | `Promise<void>` | Set cell value (`undefined` deletes the cell) |
206
- | `getFormula()` | `Promise<string \| undefined>` | Get cell formula |
207
- | `setFormula(val)` | `Promise<void>` | Set cell formula (`undefined` removes it) |
158
+ ### 데이터 테이블
208
159
 
209
- Supported value types (`ExcelValueType`): `string`, `number`, `boolean`, `DateOnly`, `DateTime`, `Time`, `undefined`
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
+ });
210
171
 
211
- #### Style
172
+ // 2차원 배열 쓰기
173
+ await ws.setDataMatrix([
174
+ ["이름", "나이", "이메일"],
175
+ ["Alice", 30, "alice@example.com"],
176
+ ["Bob", 25, "bob@example.com"],
177
+ ]);
212
178
 
213
- | Method | Returns | Description |
214
- |--------|---------|-------------|
215
- | `setStyle(opts)` | `Promise<void>` | Set cell style |
216
- | `getStyleId()` | `Promise<string \| undefined>` | Get raw style ID |
217
- | `setStyleId(id)` | `Promise<void>` | Set raw style ID |
179
+ // 레코드 배열 쓰기 (헤더 자동 생성)
180
+ await ws.setRecords([
181
+ { name: "Alice", age: 30 },
182
+ { name: "Bob", age: 25 },
183
+ ]);
184
+ ```
218
185
 
219
- **Style options (`ExcelStyleOptions`):**
186
+ ### 이미지 삽입
220
187
 
221
188
  ```typescript
222
- await cell.setStyle({
223
- background: "00FF0000", // ARGB hex (8 digits)
224
- border: ["left", "right", "top", "bottom"], // border positions
225
- horizontalAlign: "center", // "left" | "center" | "right"
226
- verticalAlign: "center", // "top" | "center" | "bottom"
227
- numberFormat: "number", // "number" | "string" | "DateOnly" | "DateTime" | "Time"
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
+ },
228
202
  });
229
203
  ```
230
204
 
231
- #### Merge
205
+ ## 셀 (ExcelCell)
232
206
 
233
- | Method | Returns | Description |
234
- |--------|---------|-------------|
235
- | `merge(r, c)` | `Promise<void>` | Merge from this cell to end position `(r, c)` |
207
+ ### 읽기/쓰기
236
208
 
237
209
  ```typescript
238
- // Merge A1:C3 (3 rows x 3 columns)
239
- await ws.cell(0, 0).merge(2, 2);
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
240
222
  ```
241
223
 
242
- ---
243
-
244
- ### ExcelRow
224
+ ### 수식
245
225
 
246
- Represents a worksheet row.
226
+ ```typescript
227
+ await ws.cell(1, 0).setFormula("SUM(B1:B10)");
228
+ await ws.cell(1, 0).setFormula(undefined); // 수식 제거
247
229
 
248
- | Method | Returns | Description |
249
- |--------|---------|-------------|
250
- | `cell(c)` | `ExcelCell` | Get cell at column `c` (0-based) |
251
- | `getCells()` | `Promise<ExcelCell[]>` | Get all cells in the row |
230
+ const formula = await ws.cell(1, 0).getFormula(); // string | undefined
231
+ ```
252
232
 
253
- ---
233
+ ### 셀 병합
254
234
 
255
- ### ExcelCol
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
+ ```
256
241
 
257
- Represents a worksheet column.
242
+ ### 스타일
258
243
 
259
- | Method | Returns | Description |
260
- |--------|---------|-------------|
261
- | `cell(r)` | `ExcelCell` | Get cell at row `r` (0-based) |
262
- | `getCells()` | `Promise<ExcelCell[]>` | Get all cells in the column |
263
- | `setWidth(size)` | `Promise<void>` | Set column width |
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
+ });
264
252
 
265
- ---
253
+ // 스타일 ID 직접 접근
254
+ const styleId = await ws.cell(0, 0).getStyleId();
255
+ await ws.cell(0, 0).setStyleId(styleId);
256
+ ```
266
257
 
267
- ### ExcelWrapper\<TSchema\>
258
+ **ExcelStyleOptions:**
268
259
 
269
- Zod schema-based Excel wrapper for type-safe read/write. Define a Zod object schema where each field's `.describe()` sets the Excel column header name.
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"` |
270
267
 
271
- #### Constructor
268
+ ## 열 너비
272
269
 
273
270
  ```typescript
274
- new ExcelWrapper(schema: z.ZodObject<z.ZodRawShape>)
271
+ await ws.col(0).setWidth(20); // A열 너비 설정
275
272
  ```
276
273
 
277
- #### Methods
274
+ ## ExcelWrapper (Zod 스키마 기반)
278
275
 
279
- | Method | Returns | Description |
280
- |--------|---------|-------------|
281
- | `read(file, wsNameOrIndex?, options?)` | `Promise<z.infer<TSchema>[]>` | Read Excel file into typed record array |
282
- | `write(wsName, records, options?)` | `Promise<ExcelWorkbook>` | Write records to a new workbook |
276
+ Zod 스키마를 기반으로 타입 안전한 Excel 읽기/쓰기를 제공한다.
283
277
 
284
- **`read` parameters:**
278
+ ### 스키마 정의
285
279
 
286
- | Parameter | Type | Default | Description |
287
- |-----------|------|---------|-------------|
288
- | `file` | `Bytes \| Blob` | | Excel file data |
289
- | `wsNameOrIndex` | `string \| number` | `0` | Worksheet name or index |
290
- | `options.excludes` | `(keyof T)[]` | | Fields to exclude from reading |
280
+ ```typescript
281
+ import { ExcelWrapper } from "@simplysm/excel";
282
+ import { z } from "zod";
291
283
 
292
- **`write` parameters:**
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
+ });
293
290
 
294
- | Parameter | Type | Description |
295
- |-----------|------|-------------|
296
- | `wsName` | `string` | Worksheet name |
297
- | `records` | `Partial<T>[]` | Record array to write |
298
- | `options.excludes` | `(keyof T)[]` | Fields to exclude from columns |
291
+ const wrapper = new ExcelWrapper(schema);
292
+ ```
299
293
 
300
- **Write behavior:**
301
- - Generates headers from schema field descriptions
302
- - Applies border style to all data cells
303
- - Highlights required field headers with yellow background
304
- - Sets zoom to 85% and freezes the header row
294
+ - `.describe("헤더이름")`: Excel 헤더에 표시될 이름 지정 (미지정 시 필드 키 사용)
295
+ - `z.optional()` / `z.nullable()` / `z.default()`: 선택 필드 (헤더 강조 없음)
296
+ - 필수 필드(boolean 제외)는 `write()` 헤더가 노란색으로 강조됨
305
297
 
306
- **Supported Zod types:** `z.string()`, `z.number()`, `z.boolean()`, `z.instanceof(DateOnly)`, `z.instanceof(DateTime)`, `z.instanceof(Time)`. Supports `z.optional()`, `z.nullable()`, `z.default()` wrappers.
298
+ ### 읽기
307
299
 
308
- ---
300
+ ```typescript
301
+ const records = await wrapper.read(fileBytes, "Sheet1");
302
+ // z.infer<typeof schema>[] 타입으로 반환
309
303
 
310
- ### ExcelUtils
304
+ // 인덱스로 시트 지정 (기본: 0)
305
+ const records = await wrapper.read(fileBytes, 0);
311
306
 
312
- Static utility class for Excel address conversion and date/number processing.
307
+ // 특정 필드 제외
308
+ const records = await wrapper.read(fileBytes, "Sheet1", {
309
+ excludes: ["email"],
310
+ });
311
+ ```
313
312
 
314
- #### Address Conversion
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`
315
319
 
316
- | Method | Returns | Description |
317
- |--------|---------|-------------|
318
- | `stringifyAddr({ r, c })` | `string` | Coordinates to "A1" format |
319
- | `stringifyRowAddr(r)` | `string` | Row index to string (0 -> "1") |
320
- | `stringifyColAddr(c)` | `string` | Column index to string (0 -> "A", 26 -> "AA") |
321
- | `parseCellAddr(addr)` | `ExcelAddressPoint` | "B3" -> `{ r: 2, c: 1 }` |
322
- | `parseRowAddr(addr)` | `number` | Extract row index from address |
323
- | `parseColAddr(addr)` | `number` | Extract column index from address |
324
- | `parseRangeAddr(rangeAddr)` | `ExcelAddressRangePoint` | "A1:C3" -> `{ s: {r:0,c:0}, e: {r:2,c:2} }` |
325
- | `stringifyRangeAddr(point)` | `string` | Range coordinates to "A1:C3" format |
320
+ ### 쓰기
326
321
 
327
- #### Date/Number Conversion
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();
328
329
 
329
- | Method | Returns | Description |
330
- |--------|---------|-------------|
331
- | `convertTimeTickToNumber(tick)` | `number` | JS timestamp (ms) to Excel date number |
332
- | `convertNumberToTimeTick(value)` | `number` | Excel date number to JS timestamp (ms) |
330
+ // 특정 필드 제외
331
+ await using wb = await wrapper.write("Sheet1", records, {
332
+ excludes: ["email"],
333
+ });
334
+ ```
333
335
 
334
- #### Number Format
336
+ `write()` 자동 적용 사항:
337
+ - 모든 셀에 테두리(상하좌우) 적용
338
+ - 필수 필드(boolean 제외) 헤더에 노란색 배경
339
+ - 줌 85%, 첫 행 틀고정
335
340
 
336
- | Method | Returns | Description |
337
- |--------|---------|-------------|
338
- | `convertNumFmtIdToName(id)` | `ExcelNumberFormat` | Built-in format ID to name |
339
- | `convertNumFmtCodeToName(code)` | `ExcelNumberFormat` | Format code string to name |
340
- | `convertNumFmtNameToId(name)` | `number` | Format name to built-in ID |
341
+ ## 유틸리티 (ExcelUtils)
341
342
 
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
+ ```
343
370
 
344
- ### Types
371
+ ## 타입
345
372
 
346
373
  ```typescript
347
- /** Supported cell value types */
374
+ // 타입
348
375
  type ExcelValueType = number | string | DateOnly | DateTime | Time | boolean | undefined;
349
376
 
350
- /** Number format names */
377
+ // 숫자 서식
351
378
  type ExcelNumberFormat = "number" | "string" | "DateOnly" | "DateTime" | "Time";
352
379
 
353
- /** Cell address (0-based) */
380
+ // 좌표
354
381
  interface ExcelAddressPoint { r: number; c: number; }
355
-
356
- /** Range address (0-based) */
357
382
  interface ExcelAddressRangePoint { s: ExcelAddressPoint; e: ExcelAddressPoint; }
358
383
 
359
- /** Cell style options */
384
+ // 스타일
385
+ type ExcelBorderPosition = "left" | "right" | "top" | "bottom";
386
+ type ExcelHorizontalAlign = "center" | "left" | "right";
387
+ type ExcelVerticalAlign = "center" | "top" | "bottom";
360
388
  interface ExcelStyleOptions {
361
- background?: string; // ARGB hex (e.g. "00FF0000")
362
- border?: ExcelBorderPosition[]; // "left" | "right" | "top" | "bottom"
363
- horizontalAlign?: ExcelHorizontalAlign; // "left" | "center" | "right"
364
- verticalAlign?: ExcelVerticalAlign; // "top" | "center" | "bottom"
389
+ background?: string;
390
+ border?: ExcelBorderPosition[];
391
+ horizontalAlign?: ExcelHorizontalAlign;
392
+ verticalAlign?: ExcelVerticalAlign;
365
393
  numberFormat?: ExcelNumberFormat;
366
394
  }
367
395
 
368
- type ExcelBorderPosition = "left" | "right" | "top" | "bottom";
369
- type ExcelHorizontalAlign = "center" | "left" | "right";
370
- type ExcelVerticalAlign = "center" | "top" | "bottom";
396
+ // 타입
397
+ type ExcelCellType = "s" | "b" | "str" | "n" | "inlineStr" | "e";
398
+ // s: SharedString, b: boolean, str: 수식 결과 문자열,
399
+ // n: 숫자, inlineStr: 인라인 문자열, e: 에러
371
400
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/excel",
3
- "version": "13.0.85",
3
+ "version": "13.0.87",
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.85"
24
+ "@simplysm/core-common": "13.0.87"
25
25
  }
26
26
  }