@simplysm/excel 1.0.138 → 13.0.0-beta.2
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/.cache/typecheck-browser.tsbuildinfo +1 -0
- package/.cache/typecheck-node.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-browser.tsbuildinfo +1 -0
- package/.cache/typecheck-tests-node.tsbuildinfo +1 -0
- package/README.md +491 -0
- package/dist/core-common/src/common.types.d.ts +74 -0
- package/dist/core-common/src/common.types.d.ts.map +1 -0
- package/dist/core-common/src/env.d.ts +6 -0
- package/dist/core-common/src/env.d.ts.map +1 -0
- package/dist/core-common/src/errors/argument-error.d.ts +25 -0
- package/dist/core-common/src/errors/argument-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/not-implemented-error.d.ts +29 -0
- package/dist/core-common/src/errors/not-implemented-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/sd-error.d.ts +27 -0
- package/dist/core-common/src/errors/sd-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/timeout-error.d.ts +31 -0
- package/dist/core-common/src/errors/timeout-error.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.d.ts +15 -0
- package/dist/core-common/src/extensions/arr-ext.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +19 -0
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.types.d.ts +215 -0
- package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +1 -0
- package/dist/core-common/src/extensions/map-ext.d.ts +57 -0
- package/dist/core-common/src/extensions/map-ext.d.ts.map +1 -0
- package/dist/core-common/src/extensions/set-ext.d.ts +36 -0
- package/dist/core-common/src/extensions/set-ext.d.ts.map +1 -0
- package/dist/core-common/src/features/debounce-queue.d.ts +53 -0
- package/dist/core-common/src/features/debounce-queue.d.ts.map +1 -0
- package/dist/core-common/src/features/event-emitter.d.ts +66 -0
- package/dist/core-common/src/features/event-emitter.d.ts.map +1 -0
- package/dist/core-common/src/features/serial-queue.d.ts +47 -0
- package/dist/core-common/src/features/serial-queue.d.ts.map +1 -0
- package/dist/core-common/src/index.d.ts +32 -0
- package/dist/core-common/src/index.d.ts.map +1 -0
- package/dist/core-common/src/types/date-only.d.ts +152 -0
- package/dist/core-common/src/types/date-only.d.ts.map +1 -0
- package/dist/core-common/src/types/date-time.d.ts +96 -0
- package/dist/core-common/src/types/date-time.d.ts.map +1 -0
- package/dist/core-common/src/types/lazy-gc-map.d.ts +80 -0
- package/dist/core-common/src/types/lazy-gc-map.d.ts.map +1 -0
- package/dist/core-common/src/types/time.d.ts +68 -0
- package/dist/core-common/src/types/time.d.ts.map +1 -0
- package/dist/core-common/src/types/uuid.d.ts +35 -0
- package/dist/core-common/src/types/uuid.d.ts.map +1 -0
- package/dist/core-common/src/utils/bytes.d.ts +51 -0
- package/dist/core-common/src/utils/bytes.d.ts.map +1 -0
- package/dist/core-common/src/utils/date-format.d.ts +90 -0
- package/dist/core-common/src/utils/date-format.d.ts.map +1 -0
- package/dist/core-common/src/utils/json.d.ts +34 -0
- package/dist/core-common/src/utils/json.d.ts.map +1 -0
- package/dist/core-common/src/utils/num.d.ts +60 -0
- package/dist/core-common/src/utils/num.d.ts.map +1 -0
- package/dist/core-common/src/utils/obj.d.ts +258 -0
- package/dist/core-common/src/utils/obj.d.ts.map +1 -0
- package/dist/core-common/src/utils/path.d.ts +23 -0
- package/dist/core-common/src/utils/path.d.ts.map +1 -0
- package/dist/core-common/src/utils/primitive.d.ts +18 -0
- package/dist/core-common/src/utils/primitive.d.ts.map +1 -0
- package/dist/core-common/src/utils/str.d.ts +103 -0
- package/dist/core-common/src/utils/str.d.ts.map +1 -0
- package/dist/core-common/src/utils/template-strings.d.ts +84 -0
- package/dist/core-common/src/utils/template-strings.d.ts.map +1 -0
- package/dist/core-common/src/utils/transferable.d.ts +47 -0
- package/dist/core-common/src/utils/transferable.d.ts.map +1 -0
- package/dist/core-common/src/utils/wait.d.ts +19 -0
- package/dist/core-common/src/utils/wait.d.ts.map +1 -0
- package/dist/core-common/src/utils/xml.d.ts +36 -0
- package/dist/core-common/src/utils/xml.d.ts.map +1 -0
- package/dist/core-common/src/zip/sd-zip.d.ts +80 -0
- package/dist/core-common/src/zip/sd-zip.d.ts.map +1 -0
- package/dist/excel/src/excel-cell.d.ts +68 -0
- package/dist/excel/src/excel-cell.d.ts.map +1 -0
- package/dist/excel/src/excel-col.d.ts +19 -0
- package/dist/excel/src/excel-col.d.ts.map +1 -0
- package/dist/excel/src/excel-row.d.ts +17 -0
- package/dist/excel/src/excel-row.d.ts.map +1 -0
- package/dist/excel/src/excel-workbook.d.ts +66 -0
- package/dist/excel/src/excel-workbook.d.ts.map +1 -0
- package/dist/excel/src/excel-worksheet.d.ts +102 -0
- package/dist/excel/src/excel-worksheet.d.ts.map +1 -0
- package/dist/excel/src/excel-wrapper.d.ts +42 -0
- package/dist/excel/src/excel-wrapper.d.ts.map +1 -0
- package/dist/excel/src/index.d.ts +9 -0
- package/dist/excel/src/index.d.ts.map +1 -0
- package/dist/excel/src/types.d.ts +445 -0
- package/dist/excel/src/types.d.ts.map +1 -0
- package/dist/excel/src/utils/excel-utils.d.ts +50 -0
- package/dist/excel/src/utils/excel-utils.d.ts.map +1 -0
- package/dist/excel/src/utils/zip-cache.d.ts +23 -0
- package/dist/excel/src/utils/zip-cache.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-content-type.d.ts +12 -0
- package/dist/excel/src/xml/excel-xml-content-type.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-drawing.d.ts +26 -0
- package/dist/excel/src/xml/excel-xml-drawing.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-relationship.d.ts +18 -0
- package/dist/excel/src/xml/excel-xml-relationship.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-shared-string.d.ts +19 -0
- package/dist/excel/src/xml/excel-xml-shared-string.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-style.d.ts +31 -0
- package/dist/excel/src/xml/excel-xml-style.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-unknown.d.ts +11 -0
- package/dist/excel/src/xml/excel-xml-unknown.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-workbook.d.ts +22 -0
- package/dist/excel/src/xml/excel-xml-workbook.d.ts.map +1 -0
- package/dist/excel/src/xml/excel-xml-worksheet.d.ts +103 -0
- package/dist/excel/src/xml/excel-xml-worksheet.d.ts.map +1 -0
- package/dist/excel-cell.js +261 -0
- package/dist/excel-cell.js.map +7 -0
- package/dist/excel-col.js +36 -0
- package/dist/excel-col.js.map +7 -0
- package/dist/excel-row.js +31 -0
- package/dist/excel-row.js.map +7 -0
- package/dist/excel-workbook.js +137 -0
- package/dist/excel-workbook.js.map +7 -0
- package/dist/excel-worksheet.js +279 -0
- package/dist/excel-worksheet.js.map +7 -0
- package/dist/excel-wrapper.js +220 -0
- package/dist/excel-wrapper.js.map +7 -0
- package/dist/index.js +9 -15
- package/dist/index.js.map +7 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +7 -0
- package/dist/utils/excel-utils.js +162 -0
- package/dist/utils/excel-utils.js.map +7 -0
- package/dist/utils/zip-cache.js +74 -0
- package/dist/utils/zip-cache.js.map +7 -0
- package/dist/xml/excel-xml-content-type.js +57 -0
- package/dist/xml/excel-xml-content-type.js.map +7 -0
- package/dist/xml/excel-xml-drawing.js +77 -0
- package/dist/xml/excel-xml-drawing.js.map +7 -0
- package/dist/xml/excel-xml-relationship.js +72 -0
- package/dist/xml/excel-xml-relationship.js.map +7 -0
- package/dist/xml/excel-xml-shared-string.js +61 -0
- package/dist/xml/excel-xml-shared-string.js.map +7 -0
- package/dist/xml/excel-xml-style.js +313 -0
- package/dist/xml/excel-xml-style.js.map +7 -0
- package/dist/xml/excel-xml-unknown.js +11 -0
- package/dist/xml/excel-xml-unknown.js.map +7 -0
- package/dist/xml/excel-xml-workbook.js +94 -0
- package/dist/xml/excel-xml-workbook.js.map +7 -0
- package/dist/xml/excel-xml-worksheet.js +405 -0
- package/dist/xml/excel-xml-worksheet.js.map +7 -0
- package/package.json +13 -7
- package/src/excel-cell.ts +326 -0
- package/src/excel-col.ts +43 -0
- package/src/excel-row.ts +37 -0
- package/src/excel-workbook.ts +206 -0
- package/src/excel-worksheet.ts +380 -0
- package/src/excel-wrapper.ts +219 -0
- package/src/index.ts +13 -9
- package/src/types.ts +396 -0
- package/src/utils/excel-utils.ts +201 -0
- package/src/utils/zip-cache.ts +103 -0
- package/src/xml/excel-xml-content-type.ts +64 -0
- package/src/xml/excel-xml-drawing.ts +87 -0
- package/src/xml/excel-xml-relationship.ts +86 -0
- package/src/xml/excel-xml-shared-string.ts +80 -0
- package/src/xml/excel-xml-style.ts +393 -0
- package/src/xml/excel-xml-unknown.ts +11 -0
- package/src/xml/excel-xml-workbook.ts +112 -0
- package/src/xml/excel-xml-worksheet.ts +544 -0
- package/tests/excel-cell.spec.ts +407 -0
- package/tests/excel-col.spec.ts +112 -0
- package/tests/excel-row.spec.ts +71 -0
- package/tests/excel-workbook.spec.ts +166 -0
- package/tests/excel-worksheet.spec.ts +389 -0
- package/tests/excel-wrapper.spec.ts +275 -0
- package/tests/fixtures/logo.png +0 -0
- package/tests/image-insert.spec.ts +188 -0
- package/tests/utils/excel-utils.spec.ts +240 -0
- package/dist/ExcelCell.d.ts +0 -13
- package/dist/ExcelCell.js +0 -161
- package/dist/ExcelCell.js.map +0 -1
- package/dist/ExcelCellStyle.d.ts +0 -31
- package/dist/ExcelCellStyle.js +0 -312
- package/dist/ExcelCellStyle.js.map +0 -1
- package/dist/ExcelColumn.d.ts +0 -8
- package/dist/ExcelColumn.js +0 -49
- package/dist/ExcelColumn.js.map +0 -1
- package/dist/ExcelRow.d.ts +0 -7
- package/dist/ExcelRow.js +0 -21
- package/dist/ExcelRow.js.map +0 -1
- package/dist/ExcelWorkbook.d.ts +0 -24
- package/dist/ExcelWorkbook.js +0 -418
- package/dist/ExcelWorkbook.js.map +0 -1
- package/dist/ExcelWorksheet.d.ts +0 -14
- package/dist/ExcelWorksheet.js +0 -31
- package/dist/ExcelWorksheet.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/utils/ExcelUtils.d.ts +0 -14
- package/dist/utils/ExcelUtils.js +0 -66
- package/dist/utils/ExcelUtils.js.map +0 -1
- package/dist/utils/XmlConvert.d.ts +0 -4
- package/dist/utils/XmlConvert.js +0 -64
- package/dist/utils/XmlConvert.js.map +0 -1
- package/src/ExcelCell.ts +0 -163
- package/src/ExcelCellStyle.ts +0 -297
- package/src/ExcelColumn.ts +0 -46
- package/src/ExcelRow.ts +0 -17
- package/src/ExcelWorkbook.ts +0 -369
- package/src/ExcelWorksheet.ts +0 -27
- package/src/utils/ExcelUtils.ts +0 -68
- package/src/utils/XmlConvert.ts +0 -20
- package/tsconfig.build.json +0 -18
- package/tsconfig.json +0 -18
- package/tslint.json +0 -5
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import type { Bytes } from "@simplysm/core-common";
|
|
2
|
+
import "@simplysm/core-common";
|
|
3
|
+
import { strIsNullOrEmpty } from "@simplysm/core-common";
|
|
4
|
+
import mime from "mime";
|
|
5
|
+
import type { ExcelCell } from "./excel-cell";
|
|
6
|
+
import { ExcelCol } from "./excel-col";
|
|
7
|
+
import { ExcelRow } from "./excel-row";
|
|
8
|
+
import type { ExcelAddressPoint, ExcelAddressRangePoint, ExcelValueType } from "./types";
|
|
9
|
+
import type { ZipCache } from "./utils/zip-cache";
|
|
10
|
+
import type { ExcelXmlContentType } from "./xml/excel-xml-content-type";
|
|
11
|
+
import { ExcelXmlDrawing } from "./xml/excel-xml-drawing";
|
|
12
|
+
import { ExcelXmlRelationship } from "./xml/excel-xml-relationship";
|
|
13
|
+
import type { ExcelXmlWorkbook } from "./xml/excel-xml-workbook";
|
|
14
|
+
import type { ExcelXmlWorksheet } from "./xml/excel-xml-worksheet";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Excel 워크시트를 나타내는 클래스.
|
|
18
|
+
* 셀 접근, 행/열 복사, 데이터 테이블 처리, 이미지 삽입 등의 기능을 제공한다.
|
|
19
|
+
*/
|
|
20
|
+
export class ExcelWorksheet {
|
|
21
|
+
private readonly _rowMap = new Map<number, ExcelRow>();
|
|
22
|
+
private readonly _colMap = new Map<number, ExcelCol>();
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly _zipCache: ZipCache,
|
|
26
|
+
private readonly _relId: number,
|
|
27
|
+
private readonly _targetFileName: string,
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
//#region Name Methods
|
|
31
|
+
|
|
32
|
+
/** 워크시트 이름 반환 */
|
|
33
|
+
async getName(): Promise<string> {
|
|
34
|
+
const wbXmlData = await this._getWbData();
|
|
35
|
+
const name = wbXmlData.getWorksheetNameById(this._relId);
|
|
36
|
+
if (name == null) {
|
|
37
|
+
throw new Error(`워크시트 ID ${this._relId}에 해당하는 이름을 찾을 수 없습니다`);
|
|
38
|
+
}
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 워크시트 이름 변경 */
|
|
43
|
+
async setName(newName: string): Promise<void> {
|
|
44
|
+
const wbXmlData = await this._getWbData();
|
|
45
|
+
wbXmlData.setWorksheetNameById(this._relId, newName);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
|
|
50
|
+
//#region Cell Access Methods
|
|
51
|
+
|
|
52
|
+
/** 행 객체 반환 (0-based) */
|
|
53
|
+
row(r: number): ExcelRow {
|
|
54
|
+
return this._rowMap.getOrCreate(r, new ExcelRow(this._zipCache, this._targetFileName, r));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 셀 객체 반환 (0-based 행/열) */
|
|
58
|
+
cell(r: number, c: number): ExcelCell {
|
|
59
|
+
return this.row(r).cell(c);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 열 객체 반환 (0-based) */
|
|
63
|
+
col(c: number): ExcelCol {
|
|
64
|
+
return this._colMap.getOrCreate(c, new ExcelCol(this._zipCache, this._targetFileName, c));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
|
|
69
|
+
//#region Copy Methods
|
|
70
|
+
|
|
71
|
+
/** 소스 행의 스타일을 타겟 행에 복사 */
|
|
72
|
+
async copyRowStyle(srcR: number, targetR: number): Promise<void> {
|
|
73
|
+
const range = await this.getRange();
|
|
74
|
+
|
|
75
|
+
for (let c = range.s.c; c <= range.e.c; c++) {
|
|
76
|
+
await this.copyCellStyle({ r: srcR, c: c }, { r: targetR, c: c });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 소스 셀의 스타일을 타겟 셀에 복사 */
|
|
81
|
+
async copyCellStyle(srcAddr: ExcelAddressPoint, targetAddr: ExcelAddressPoint): Promise<void> {
|
|
82
|
+
const wsData = await this._getWsData();
|
|
83
|
+
|
|
84
|
+
const styleId = wsData.getCellStyleId(srcAddr);
|
|
85
|
+
if (styleId != null) {
|
|
86
|
+
wsData.setCellStyleId(targetAddr, styleId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 소스 행을 타겟 행에 복사 (덮어쓰기) */
|
|
91
|
+
async copyRow(srcR: number, targetR: number): Promise<void> {
|
|
92
|
+
const wsData = await this._getWsData();
|
|
93
|
+
wsData.copyRow(srcR, targetR);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 소스 셀을 타겟 셀에 복사 */
|
|
97
|
+
async copyCell(srcAddr: ExcelAddressPoint, targetAddr: ExcelAddressPoint): Promise<void> {
|
|
98
|
+
const wsData = await this._getWsData();
|
|
99
|
+
wsData.copyCell(srcAddr, targetAddr);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 소스 행을 타겟 위치에 삽입 복사.
|
|
104
|
+
* 타겟 위치 이하의 기존 행들은 한 칸씩 아래로 밀린다.
|
|
105
|
+
* @param srcR 복사할 소스 행 인덱스 (0-based)
|
|
106
|
+
* @param targetR 삽입할 타겟 행 인덱스 (0-based)
|
|
107
|
+
*/
|
|
108
|
+
async insertCopyRow(srcR: number, targetR: number): Promise<void> {
|
|
109
|
+
const wsData = await this._getWsData();
|
|
110
|
+
const range = wsData.range;
|
|
111
|
+
|
|
112
|
+
// targetR 이하 모든 병합 셀의 행 인덱스 +1
|
|
113
|
+
const mergeCells = wsData.getMergeCells();
|
|
114
|
+
for (const mc of mergeCells) {
|
|
115
|
+
if (mc.s.r >= targetR) mc.s.r++;
|
|
116
|
+
if (mc.e.r >= targetR) mc.e.r++;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// srcR >= targetR인 경우, 밀림 후 srcR 위치가 변경되므로 보정
|
|
120
|
+
const adjustedSrcR = srcR >= targetR ? srcR + 1 : srcR;
|
|
121
|
+
|
|
122
|
+
for (let r = range.e.r; r >= targetR; r--) {
|
|
123
|
+
await this.copyRow(r, r + 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await this.copyRow(adjustedSrcR, targetR);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
|
|
131
|
+
//#region Range Methods
|
|
132
|
+
|
|
133
|
+
/** 워크시트의 데이터 범위 반환 */
|
|
134
|
+
async getRange(): Promise<ExcelAddressRangePoint> {
|
|
135
|
+
const xml = await this._getWsData();
|
|
136
|
+
return xml.range;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 워크시트의 모든 셀을 2차원 배열로 반환 */
|
|
140
|
+
async getCells(): Promise<ExcelCell[][]> {
|
|
141
|
+
const xml = await this._getWsData();
|
|
142
|
+
const range = xml.range;
|
|
143
|
+
const promises: Promise<ExcelCell[]>[] = [];
|
|
144
|
+
|
|
145
|
+
for (let r = range.s.r; r <= range.e.r; r++) {
|
|
146
|
+
promises.push(this.row(r).getCells());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return Promise.all(promises);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
//#endregion
|
|
153
|
+
|
|
154
|
+
//#region Data Methods
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 워크시트 데이터를 테이블(레코드 배열) 형식으로 반환.
|
|
158
|
+
* @param opt.headerRowIndex 헤더 행 인덱스 (기본값: 첫 번째 행)
|
|
159
|
+
* @param opt.checkEndColIndex 데이터 종료를 판단할 열 인덱스. 해당 열이 비어있으면 데이터 끝으로 간주
|
|
160
|
+
* @param opt.usableHeaderNameFn 사용할 헤더를 필터링하는 함수
|
|
161
|
+
*/
|
|
162
|
+
async getDataTable(opt?: {
|
|
163
|
+
headerRowIndex?: number;
|
|
164
|
+
checkEndColIndex?: number;
|
|
165
|
+
usableHeaderNameFn?: (headerName: string) => boolean;
|
|
166
|
+
}): Promise<Record<string, ExcelValueType>[]> {
|
|
167
|
+
const result: Record<string, ExcelValueType>[] = [];
|
|
168
|
+
const headerMap = new Map<string, number>();
|
|
169
|
+
|
|
170
|
+
const xml = await this._getWsData();
|
|
171
|
+
const range = xml.range;
|
|
172
|
+
const startRow = opt?.headerRowIndex ?? range.s.r;
|
|
173
|
+
|
|
174
|
+
for (let c = range.s.c; c <= range.e.c; c++) {
|
|
175
|
+
const headerName = await this.cell(startRow, c).getVal();
|
|
176
|
+
if (typeof headerName === "string") {
|
|
177
|
+
if (opt?.usableHeaderNameFn == null || opt.usableHeaderNameFn(headerName)) {
|
|
178
|
+
headerMap.set(headerName, c);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let r = startRow + 1; r <= range.e.r; r++) {
|
|
184
|
+
if (opt?.checkEndColIndex !== undefined && (await this.cell(r, opt.checkEndColIndex).getVal()) === undefined) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const record: Record<string, ExcelValueType> = {};
|
|
189
|
+
for (const header of headerMap.keys()) {
|
|
190
|
+
const c = headerMap.get(header)!;
|
|
191
|
+
record[header] = await this.cell(r, c).getVal();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
result.push(record);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 2차원 배열 데이터를 워크시트에 기록
|
|
202
|
+
* @param matrix 2차원 배열 데이터 (행 우선, 0번 인덱스가 첫 번째 행)
|
|
203
|
+
*/
|
|
204
|
+
async setDataMatrix(matrix: ExcelValueType[][]): Promise<void> {
|
|
205
|
+
for (let r = 0; r < matrix.length; r++) {
|
|
206
|
+
for (let c = 0; c < matrix[r].length; c++) {
|
|
207
|
+
await this.cell(r, c).setVal(matrix[r][c]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 레코드 배열을 워크시트에 기록
|
|
214
|
+
* @param records 레코드 배열. 첫 행에 헤더가 자동 생성되고, 이후 행에 데이터가 기록된다.
|
|
215
|
+
*/
|
|
216
|
+
async setRecords(records: Record<string, ExcelValueType>[]): Promise<void> {
|
|
217
|
+
const headers = records
|
|
218
|
+
.flatMap((item) => Object.keys(item))
|
|
219
|
+
.distinct()
|
|
220
|
+
.filter((item) => !strIsNullOrEmpty(item));
|
|
221
|
+
|
|
222
|
+
for (let c = 0; c < headers.length; c++) {
|
|
223
|
+
await this.cell(0, c).setVal(headers[c]);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (let r = 1; r < records.length + 1; r++) {
|
|
227
|
+
for (let c = 0; c < headers.length; c++) {
|
|
228
|
+
await this.cell(r, c).setVal(records[r - 1][headers[c]]);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
|
|
235
|
+
//#region View Methods
|
|
236
|
+
|
|
237
|
+
/** 워크시트 확대/축소 비율 설정 (퍼센트) */
|
|
238
|
+
async setZoom(percent: number): Promise<void> {
|
|
239
|
+
const wbXml = await this._getWbData();
|
|
240
|
+
wbXml.initializeView();
|
|
241
|
+
|
|
242
|
+
const wsXml = await this._getWsData();
|
|
243
|
+
wsXml.setZoom(percent);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** 행/열 틀 고정 설정 */
|
|
247
|
+
async setFix(point: { r?: number; c?: number }): Promise<void> {
|
|
248
|
+
const wbXml = await this._getWbData();
|
|
249
|
+
wbXml.initializeView();
|
|
250
|
+
|
|
251
|
+
const wsXml = await this._getWsData();
|
|
252
|
+
wsXml.setFix(point);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
|
|
257
|
+
//#region Image Methods
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 워크시트에 이미지를 삽입.
|
|
261
|
+
* @param opts.bytes 이미지 바이너리 데이터
|
|
262
|
+
* @param opts.ext 이미지 확장자 (png, jpg 등)
|
|
263
|
+
* @param opts.from 이미지 시작 위치 (0-based 행/열 인덱스, rOff/cOff는 EMU 단위 오프셋)
|
|
264
|
+
* @param opts.to 이미지 끝 위치 (생략 시 from 위치에 원본 크기로 삽입)
|
|
265
|
+
*/
|
|
266
|
+
async addImage(opts: {
|
|
267
|
+
bytes: Bytes;
|
|
268
|
+
ext: string;
|
|
269
|
+
from: { r: number; c: number; rOff?: number | string; cOff?: number | string };
|
|
270
|
+
to?: { r: number; c: number; rOff?: number | string; cOff?: number | string };
|
|
271
|
+
}): Promise<void> {
|
|
272
|
+
const mimeType = mime.getType(opts.ext);
|
|
273
|
+
if (mimeType == null) {
|
|
274
|
+
throw new Error(`확장자 '${opts.ext}'의 MIME 타입을 확인할 수 없습니다`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 1. media 파일명 결정 및 저장
|
|
278
|
+
let mediaIndex = 1;
|
|
279
|
+
while ((await this._zipCache.get(`xl/media/image${mediaIndex}.${opts.ext}`)) !== undefined) {
|
|
280
|
+
mediaIndex++;
|
|
281
|
+
}
|
|
282
|
+
const mediaPath = `xl/media/image${mediaIndex}.${opts.ext}`;
|
|
283
|
+
this._zipCache.set(mediaPath, opts.bytes);
|
|
284
|
+
|
|
285
|
+
// 2. [Content_Types].xml 갱신
|
|
286
|
+
const typeXml = (await this._zipCache.get("[Content_Types].xml")) as ExcelXmlContentType;
|
|
287
|
+
typeXml.add(`/xl/media/image${mediaIndex}.${opts.ext}`, mimeType);
|
|
288
|
+
|
|
289
|
+
// 3. worksheet의 기존 drawing 확인
|
|
290
|
+
const wsXml = await this._getWsData();
|
|
291
|
+
const sheetRelsPath = `xl/worksheets/_rels/${this._targetFileName}.rels`;
|
|
292
|
+
let sheetRels = (await this._zipCache.get(sheetRelsPath)) as ExcelXmlRelationship | undefined;
|
|
293
|
+
|
|
294
|
+
// 기존 drawing 찾기
|
|
295
|
+
let drawingIndex: number | undefined;
|
|
296
|
+
let drawingPath: string | undefined;
|
|
297
|
+
let drawing: ExcelXmlDrawing | undefined;
|
|
298
|
+
let drawingRels: ExcelXmlRelationship | undefined;
|
|
299
|
+
|
|
300
|
+
if (sheetRels != null) {
|
|
301
|
+
const existingDrawingRel = sheetRels.data.Relationships.Relationship?.find(
|
|
302
|
+
(r) => r.$.Type === "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
303
|
+
);
|
|
304
|
+
if (existingDrawingRel != null) {
|
|
305
|
+
// 기존 drawing 경로에서 인덱스 추출
|
|
306
|
+
const match = existingDrawingRel.$.Target.match(/drawing(\d+)\.xml$/);
|
|
307
|
+
if (match != null) {
|
|
308
|
+
drawingIndex = parseInt(match[1], 10);
|
|
309
|
+
drawingPath = `xl/drawings/drawing${drawingIndex}.xml`;
|
|
310
|
+
drawing = (await this._zipCache.get(drawingPath)) as ExcelXmlDrawing | undefined;
|
|
311
|
+
drawingRels = (await this._zipCache.get(`xl/drawings/_rels/drawing${drawingIndex}.xml.rels`)) as
|
|
312
|
+
| ExcelXmlRelationship
|
|
313
|
+
| undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 4. drawing이 없으면 새로 생성
|
|
319
|
+
if (drawingIndex == null || drawingPath == null || drawing == null) {
|
|
320
|
+
drawingIndex = 1;
|
|
321
|
+
while ((await this._zipCache.get(`xl/drawings/drawing${drawingIndex}.xml`)) !== undefined) {
|
|
322
|
+
drawingIndex++;
|
|
323
|
+
}
|
|
324
|
+
drawingPath = `xl/drawings/drawing${drawingIndex}.xml`;
|
|
325
|
+
drawing = new ExcelXmlDrawing();
|
|
326
|
+
|
|
327
|
+
// [Content_Types].xml에 drawing 타입 추가
|
|
328
|
+
typeXml.add("/" + drawingPath, "application/vnd.openxmlformats-officedocument.drawing+xml");
|
|
329
|
+
|
|
330
|
+
// worksheet의 rels에 drawing 추가
|
|
331
|
+
sheetRels = sheetRels ?? new ExcelXmlRelationship();
|
|
332
|
+
const sheetRelNum = sheetRels.addAndGetId(
|
|
333
|
+
`../drawings/drawing${drawingIndex}.xml`,
|
|
334
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
335
|
+
);
|
|
336
|
+
const drawingRelIdOnWorksheet = `rId${sheetRelNum}`;
|
|
337
|
+
this._zipCache.set(sheetRelsPath, sheetRels);
|
|
338
|
+
|
|
339
|
+
// worksheet XML에 drawing 추가
|
|
340
|
+
wsXml.data.worksheet.$["xmlns:r"] =
|
|
341
|
+
wsXml.data.worksheet.$["xmlns:r"] ?? "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
342
|
+
wsXml.data.worksheet.drawing = wsXml.data.worksheet.drawing ?? [];
|
|
343
|
+
wsXml.data.worksheet.drawing.push({ $: { "r:id": drawingRelIdOnWorksheet } });
|
|
344
|
+
this._zipCache.set(`xl/worksheets/${this._targetFileName}`, wsXml);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 5. drawing rels 준비 (없으면 새로 생성)
|
|
348
|
+
drawingRels = drawingRels ?? new ExcelXmlRelationship();
|
|
349
|
+
const mediaFileName = mediaPath.slice(3);
|
|
350
|
+
const drawingTarget = `../${mediaFileName}`;
|
|
351
|
+
const relNum = drawingRels.addAndGetId(
|
|
352
|
+
drawingTarget,
|
|
353
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
354
|
+
);
|
|
355
|
+
this._zipCache.set(`xl/drawings/_rels/drawing${drawingIndex}.xml.rels`, drawingRels);
|
|
356
|
+
|
|
357
|
+
// 6. drawing에 이미지 추가
|
|
358
|
+
const blipRelId = `rId${relNum}`;
|
|
359
|
+
drawing.addPicture({
|
|
360
|
+
from: opts.from,
|
|
361
|
+
to: opts.to ?? { r: opts.from.r + 1, c: opts.from.c + 1 },
|
|
362
|
+
blipRelId: blipRelId,
|
|
363
|
+
});
|
|
364
|
+
this._zipCache.set(drawingPath, drawing);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
|
|
369
|
+
//#region Private Methods
|
|
370
|
+
|
|
371
|
+
private async _getWsData(): Promise<ExcelXmlWorksheet> {
|
|
372
|
+
return (await this._zipCache.get(`xl/worksheets/${this._targetFileName}`)) as ExcelXmlWorksheet;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private async _getWbData(): Promise<ExcelXmlWorkbook> {
|
|
376
|
+
return (await this._zipCache.get("xl/workbook.xml")) as ExcelXmlWorkbook;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
//#endregion
|
|
380
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Bytes } from "@simplysm/core-common";
|
|
2
|
+
import { DateOnly, DateTime, numParseFloat, Time } from "@simplysm/core-common";
|
|
3
|
+
import { type z, ZodBoolean, ZodDefault, ZodNullable, ZodNumber, ZodOptional, ZodString } from "zod";
|
|
4
|
+
import { ExcelWorkbook } from "./excel-workbook";
|
|
5
|
+
import type { ExcelValueType } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Zod 스키마 기반 Excel 래퍼
|
|
9
|
+
*
|
|
10
|
+
* 스키마에서 타입 정보를 추론하여 타입 안전한 읽기/쓰기 제공
|
|
11
|
+
*/
|
|
12
|
+
export class ExcelWrapper<T extends z.ZodObject<z.ZodRawShape>> {
|
|
13
|
+
/**
|
|
14
|
+
* @param _schema Zod 스키마 (레코드 구조 정의)
|
|
15
|
+
* @param _displayNameMap 필드명-표시명 매핑 (Excel 헤더로 사용)
|
|
16
|
+
*/
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly _schema: T,
|
|
19
|
+
private readonly _displayNameMap: Record<keyof z.infer<T>, string>,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Excel 파일 읽기 → 레코드 배열
|
|
24
|
+
*/
|
|
25
|
+
async read(file: Bytes | Blob, wsNameOrIndex: string | number = 0): Promise<z.infer<T>[]> {
|
|
26
|
+
await using wb = new ExcelWorkbook(file);
|
|
27
|
+
|
|
28
|
+
const ws = await wb.getWorksheet(wsNameOrIndex);
|
|
29
|
+
const wsName = await ws.getName();
|
|
30
|
+
|
|
31
|
+
const displayNames = Object.values(this._displayNameMap);
|
|
32
|
+
const rawData = await ws.getDataTable({
|
|
33
|
+
usableHeaderNameFn: (headerName) => displayNames.includes(headerName),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (rawData.length === 0) {
|
|
37
|
+
throw new Error(`[${wsName}] 엑셀파일에서 데이터를 찾을 수 없습니다. (기대 헤더: ${displayNames.join(", ")})`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const reverseMap = this._getReverseDisplayNameMap();
|
|
41
|
+
const shape = this._schema.shape;
|
|
42
|
+
const result: z.infer<T>[] = [];
|
|
43
|
+
|
|
44
|
+
for (const row of rawData) {
|
|
45
|
+
const record: Record<string, unknown> = {};
|
|
46
|
+
let hasNonNullValue = false;
|
|
47
|
+
|
|
48
|
+
for (const [displayName, fieldKey] of reverseMap) {
|
|
49
|
+
const rawValue = row[displayName];
|
|
50
|
+
const fieldSchema = shape[fieldKey] as z.ZodType;
|
|
51
|
+
|
|
52
|
+
if (rawValue != null && rawValue !== "") {
|
|
53
|
+
hasNonNullValue = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
record[fieldKey] = this._convertValue(rawValue, fieldSchema);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!hasNonNullValue) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Zod 스키마로 검증
|
|
64
|
+
const parseResult = this._schema.safeParse(record);
|
|
65
|
+
if (!parseResult.success) {
|
|
66
|
+
const errors = parseResult.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
|
|
67
|
+
throw new Error(`[${wsName}] 데이터 검증 실패: ${errors}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
result.push(parseResult.data);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 레코드 배열 → Excel 워크북
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* 반환된 워크북은 호출자가 리소스를 관리해야 합니다.
|
|
81
|
+
* `await using`을 사용하거나 작업 완료 후 `close()`를 호출하세요.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* await using wb = await wrapper.write("Sheet1", records);
|
|
86
|
+
* const bytes = await wb.getBytes();
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
async write(wsName: string, records: Partial<z.infer<T>>[]): Promise<ExcelWorkbook> {
|
|
90
|
+
const wb = new ExcelWorkbook();
|
|
91
|
+
const ws = await wb.createWorksheet(wsName);
|
|
92
|
+
|
|
93
|
+
const keys = Object.keys(this._displayNameMap) as (keyof z.infer<T>)[];
|
|
94
|
+
const headers = keys.map((key) => this._displayNameMap[key]);
|
|
95
|
+
|
|
96
|
+
// 헤더 행 작성
|
|
97
|
+
for (let c = 0; c < headers.length; c++) {
|
|
98
|
+
await ws.cell(0, c).setVal(headers[c]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 데이터 행 작성
|
|
102
|
+
for (let r = 0; r < records.length; r++) {
|
|
103
|
+
for (let c = 0; c < keys.length; c++) {
|
|
104
|
+
const key = keys[c];
|
|
105
|
+
const value = records[r][key] as ExcelValueType;
|
|
106
|
+
await ws.cell(r + 1, c).setVal(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 테두리 스타일 적용
|
|
111
|
+
for (let r = 0; r < records.length + 1; r++) {
|
|
112
|
+
for (let c = 0; c < keys.length; c++) {
|
|
113
|
+
await ws.cell(r, c).setStyle({
|
|
114
|
+
border: ["left", "right", "top", "bottom"],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 필수 필드 헤더 강조 (노란색)
|
|
120
|
+
const shape = this._schema.shape;
|
|
121
|
+
for (let c = 0; c < keys.length; c++) {
|
|
122
|
+
const fieldKey = keys[c] as string;
|
|
123
|
+
const fieldSchema = shape[fieldKey] as z.ZodType;
|
|
124
|
+
|
|
125
|
+
if (this._isRequired(fieldSchema) && !this._isBoolean(fieldSchema)) {
|
|
126
|
+
await ws.cell(0, c).setStyle({
|
|
127
|
+
background: "00FFFF00",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 뷰 설정
|
|
133
|
+
await ws.setZoom(85);
|
|
134
|
+
await ws.setFix({ r: 0 });
|
|
135
|
+
|
|
136
|
+
return wb;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#region Private Methods
|
|
140
|
+
|
|
141
|
+
private _getReverseDisplayNameMap(): Map<string, string> {
|
|
142
|
+
const map = new Map<string, string>();
|
|
143
|
+
for (const [fieldKey, displayName] of Object.entries(this._displayNameMap)) {
|
|
144
|
+
map.set(displayName, fieldKey);
|
|
145
|
+
}
|
|
146
|
+
return map;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private _convertValue(rawValue: ExcelValueType, fieldSchema: z.ZodType): unknown {
|
|
150
|
+
if (rawValue == null || rawValue === "") {
|
|
151
|
+
return this._getDefaultForSchema(fieldSchema);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const innerSchema = this._unwrapSchema(fieldSchema);
|
|
155
|
+
|
|
156
|
+
if (innerSchema instanceof ZodString) {
|
|
157
|
+
return typeof rawValue === "string" ? rawValue : String(rawValue);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (innerSchema instanceof ZodNumber) {
|
|
161
|
+
if (typeof rawValue === "number") return rawValue;
|
|
162
|
+
return numParseFloat(String(rawValue));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (innerSchema instanceof ZodBoolean) {
|
|
166
|
+
if (typeof rawValue === "boolean") return rawValue;
|
|
167
|
+
if (rawValue === "1" || rawValue === "true") return true;
|
|
168
|
+
if (rawValue === "0" || rawValue === "false") return false;
|
|
169
|
+
return Boolean(rawValue);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// DateOnly, DateTime, Time은 instanceof로 처리
|
|
173
|
+
if (rawValue instanceof DateOnly || rawValue instanceof DateTime || rawValue instanceof Time) {
|
|
174
|
+
return rawValue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return rawValue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private _unwrapSchema(schema: z.ZodType): z.ZodType {
|
|
181
|
+
if (schema instanceof ZodOptional || schema instanceof ZodNullable) {
|
|
182
|
+
return this._unwrapSchema(schema.unwrap() as z.ZodType);
|
|
183
|
+
}
|
|
184
|
+
if (schema instanceof ZodDefault) {
|
|
185
|
+
return this._unwrapSchema(schema.removeDefault() as z.ZodType);
|
|
186
|
+
}
|
|
187
|
+
return schema;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private _getDefaultForSchema(schema: z.ZodType): unknown {
|
|
191
|
+
if (schema instanceof ZodDefault) {
|
|
192
|
+
// ZodDefault.parse(undefined)는 기본값을 반환함
|
|
193
|
+
return schema.parse(undefined);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (schema instanceof ZodOptional || schema instanceof ZodNullable) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Boolean 필수 필드의 기본값은 false
|
|
201
|
+
const innerSchema = this._unwrapSchema(schema);
|
|
202
|
+
if (innerSchema instanceof ZodBoolean) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private _isRequired(schema: z.ZodType): boolean {
|
|
210
|
+
return !(schema instanceof ZodOptional) && !(schema instanceof ZodNullable) && !(schema instanceof ZodDefault);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _isBoolean(schema: z.ZodType): boolean {
|
|
214
|
+
const innerSchema = this._unwrapSchema(schema);
|
|
215
|
+
return innerSchema instanceof ZodBoolean;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
1
|
+
// 타입 및 유틸리티
|
|
2
|
+
export * from "./types";
|
|
3
|
+
export * from "./utils/excel-utils";
|
|
4
|
+
|
|
5
|
+
// 핵심 클래스
|
|
6
|
+
export * from "./excel-cell";
|
|
7
|
+
export * from "./excel-row";
|
|
8
|
+
export * from "./excel-col";
|
|
9
|
+
export * from "./excel-worksheet";
|
|
10
|
+
export * from "./excel-workbook";
|
|
11
|
+
|
|
12
|
+
// 래퍼 클래스
|
|
13
|
+
export * from "./excel-wrapper";
|