@simplysm/excel 14.0.99 → 14.0.101

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 (184) hide show
  1. package/dist/biff/biff-model-factory.d.ts +32 -0
  2. package/dist/biff/biff-model-factory.d.ts.map +1 -0
  3. package/dist/biff/biff-model-factory.js +88 -0
  4. package/dist/biff/biff-model-factory.js.map +1 -0
  5. package/dist/biff/biff-ptg.d.ts +14 -0
  6. package/dist/biff/biff-ptg.d.ts.map +1 -0
  7. package/dist/biff/biff-ptg.js +277 -0
  8. package/dist/biff/biff-ptg.js.map +1 -0
  9. package/dist/biff/biff-records.d.ts +163 -0
  10. package/dist/biff/biff-records.d.ts.map +1 -0
  11. package/dist/biff/biff-records.js +579 -0
  12. package/dist/biff/biff-records.js.map +1 -0
  13. package/dist/biff/biff-shared-string-model.d.ts +13 -0
  14. package/dist/biff/biff-shared-string-model.d.ts.map +1 -0
  15. package/dist/biff/biff-shared-string-model.js +41 -0
  16. package/dist/biff/biff-shared-string-model.js.map +1 -0
  17. package/dist/biff/biff-style-model.d.ts +39 -0
  18. package/dist/biff/biff-style-model.d.ts.map +1 -0
  19. package/dist/biff/biff-style-model.js +182 -0
  20. package/dist/biff/biff-style-model.js.map +1 -0
  21. package/dist/biff/biff-workbook-model.d.ts +17 -0
  22. package/dist/biff/biff-workbook-model.d.ts.map +1 -0
  23. package/dist/biff/biff-workbook-model.js +64 -0
  24. package/dist/biff/biff-workbook-model.js.map +1 -0
  25. package/dist/biff/biff-worksheet-model.d.ts +69 -0
  26. package/dist/biff/biff-worksheet-model.d.ts.map +1 -0
  27. package/dist/biff/biff-worksheet-model.js +495 -0
  28. package/dist/biff/biff-worksheet-model.js.map +1 -0
  29. package/dist/biff/biff12-codec.d.ts +149 -0
  30. package/dist/biff/biff12-codec.d.ts.map +1 -0
  31. package/dist/biff/biff12-codec.js +262 -0
  32. package/dist/biff/biff12-codec.js.map +1 -0
  33. package/dist/excel-cell.d.ts +0 -6
  34. package/dist/excel-cell.d.ts.map +1 -1
  35. package/dist/excel-cell.js +2 -24
  36. package/dist/excel-cell.js.map +1 -1
  37. package/dist/excel-col.js.map +1 -1
  38. package/dist/excel-row.js.map +1 -1
  39. package/dist/excel-workbook.d.ts +4 -21
  40. package/dist/excel-workbook.d.ts.map +1 -1
  41. package/dist/excel-workbook.js +16 -49
  42. package/dist/excel-workbook.js.map +1 -1
  43. package/dist/excel-worksheet.d.ts +0 -11
  44. package/dist/excel-worksheet.d.ts.map +1 -1
  45. package/dist/excel-worksheet.js +9 -46
  46. package/dist/excel-worksheet.js.map +1 -1
  47. package/dist/excel-wrapper.d.ts +0 -10
  48. package/dist/excel-wrapper.d.ts.map +1 -1
  49. package/dist/excel-wrapper.js +0 -10
  50. package/dist/excel-wrapper.js.map +1 -1
  51. package/dist/models/excel-format.d.ts +3 -0
  52. package/dist/models/excel-format.d.ts.map +1 -0
  53. package/dist/models/excel-format.js +2 -0
  54. package/dist/models/excel-format.js.map +1 -0
  55. package/dist/models/excel-model-factory.d.ts +33 -0
  56. package/dist/models/excel-model-factory.d.ts.map +1 -0
  57. package/dist/models/excel-model-factory.js +2 -0
  58. package/dist/models/excel-model-factory.js.map +1 -0
  59. package/dist/models/excel-model.d.ts +12 -0
  60. package/dist/models/excel-model.d.ts.map +1 -0
  61. package/dist/models/excel-model.js +2 -0
  62. package/dist/models/excel-model.js.map +1 -0
  63. package/dist/models/i-content-type-model.d.ts +7 -0
  64. package/dist/models/i-content-type-model.d.ts.map +1 -0
  65. package/dist/models/i-content-type-model.js +2 -0
  66. package/dist/models/i-content-type-model.js.map +1 -0
  67. package/dist/models/i-drawing-model.d.ts +23 -0
  68. package/dist/models/i-drawing-model.d.ts.map +1 -0
  69. package/dist/models/i-drawing-model.js +2 -0
  70. package/dist/models/i-drawing-model.js.map +1 -0
  71. package/dist/models/i-relationship-model.d.ts +16 -0
  72. package/dist/models/i-relationship-model.d.ts.map +1 -0
  73. package/dist/models/i-relationship-model.js +2 -0
  74. package/dist/models/i-relationship-model.js.map +1 -0
  75. package/dist/models/i-shared-string-model.d.ts +9 -0
  76. package/dist/models/i-shared-string-model.d.ts.map +1 -0
  77. package/dist/models/i-shared-string-model.js +2 -0
  78. package/dist/models/i-shared-string-model.js.map +1 -0
  79. package/dist/models/i-style-model.d.ts +18 -0
  80. package/dist/models/i-style-model.d.ts.map +1 -0
  81. package/dist/models/i-style-model.js +2 -0
  82. package/dist/models/i-style-model.js.map +1 -0
  83. package/dist/models/i-workbook-model.d.ts +17 -0
  84. package/dist/models/i-workbook-model.d.ts.map +1 -0
  85. package/dist/models/i-workbook-model.js +2 -0
  86. package/dist/models/i-workbook-model.js.map +1 -0
  87. package/dist/models/i-worksheet-model.d.ts +39 -0
  88. package/dist/models/i-worksheet-model.d.ts.map +1 -0
  89. package/dist/models/i-worksheet-model.js +2 -0
  90. package/dist/models/i-worksheet-model.js.map +1 -0
  91. package/dist/models/shared/excel-cf-spec.d.ts +13 -0
  92. package/dist/models/shared/excel-cf-spec.d.ts.map +1 -0
  93. package/dist/models/shared/excel-cf-spec.js +2 -0
  94. package/dist/models/shared/excel-cf-spec.js.map +1 -0
  95. package/dist/models/shared/excel-style.d.ts +24 -0
  96. package/dist/models/shared/excel-style.d.ts.map +1 -0
  97. package/dist/models/shared/excel-style.js +38 -0
  98. package/dist/models/shared/excel-style.js.map +1 -0
  99. package/dist/types.d.ts +0 -13
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/types.js.map +1 -1
  102. package/dist/utils/excel-style-data.d.ts +3 -5
  103. package/dist/utils/excel-style-data.d.ts.map +1 -1
  104. package/dist/utils/excel-style-data.js +2 -16
  105. package/dist/utils/excel-style-data.js.map +1 -1
  106. package/dist/utils/zip-cache.d.ts +46 -9
  107. package/dist/utils/zip-cache.d.ts.map +1 -1
  108. package/dist/utils/zip-cache.js +138 -52
  109. package/dist/utils/zip-cache.js.map +1 -1
  110. package/dist/xml/excel-xml-content-type.d.ts +8 -4
  111. package/dist/xml/excel-xml-content-type.d.ts.map +1 -1
  112. package/dist/xml/excel-xml-content-type.js +13 -6
  113. package/dist/xml/excel-xml-content-type.js.map +1 -1
  114. package/dist/xml/excel-xml-drawing.d.ts +8 -4
  115. package/dist/xml/excel-xml-drawing.d.ts.map +1 -1
  116. package/dist/xml/excel-xml-drawing.js +14 -7
  117. package/dist/xml/excel-xml-drawing.js.map +1 -1
  118. package/dist/xml/excel-xml-relationship.d.ts +12 -4
  119. package/dist/xml/excel-xml-relationship.d.ts.map +1 -1
  120. package/dist/xml/excel-xml-relationship.js +22 -12
  121. package/dist/xml/excel-xml-relationship.js.map +1 -1
  122. package/dist/xml/excel-xml-shared-string.d.ts +8 -4
  123. package/dist/xml/excel-xml-shared-string.d.ts.map +1 -1
  124. package/dist/xml/excel-xml-shared-string.js +16 -9
  125. package/dist/xml/excel-xml-shared-string.js.map +1 -1
  126. package/dist/xml/excel-xml-style.d.ts +11 -22
  127. package/dist/xml/excel-xml-style.d.ts.map +1 -1
  128. package/dist/xml/excel-xml-style.js +66 -92
  129. package/dist/xml/excel-xml-style.js.map +1 -1
  130. package/dist/xml/excel-xml-unknown.d.ts +6 -5
  131. package/dist/xml/excel-xml-unknown.d.ts.map +1 -1
  132. package/dist/xml/excel-xml-unknown.js +7 -4
  133. package/dist/xml/excel-xml-unknown.js.map +1 -1
  134. package/dist/xml/excel-xml-workbook.d.ts +9 -4
  135. package/dist/xml/excel-xml-workbook.d.ts.map +1 -1
  136. package/dist/xml/excel-xml-workbook.js +28 -20
  137. package/dist/xml/excel-xml-workbook.js.map +1 -1
  138. package/dist/xml/excel-xml-worksheet.d.ts +12 -7
  139. package/dist/xml/excel-xml-worksheet.d.ts.map +1 -1
  140. package/dist/xml/excel-xml-worksheet.js +65 -50
  141. package/dist/xml/excel-xml-worksheet.js.map +1 -1
  142. package/dist/xml/xml-model-factory.d.ts +25 -0
  143. package/dist/xml/xml-model-factory.d.ts.map +1 -0
  144. package/dist/xml/xml-model-factory.js +66 -0
  145. package/dist/xml/xml-model-factory.js.map +1 -0
  146. package/package.json +2 -2
  147. package/src/biff/biff-model-factory.ts +101 -0
  148. package/src/biff/biff-ptg.ts +289 -0
  149. package/src/biff/biff-records.ts +686 -0
  150. package/src/biff/biff-shared-string-model.ts +52 -0
  151. package/src/biff/biff-style-model.ts +219 -0
  152. package/src/biff/biff-workbook-model.ts +83 -0
  153. package/src/biff/biff-worksheet-model.ts +545 -0
  154. package/src/biff/biff12-codec.ts +286 -0
  155. package/src/excel-cell.ts +12 -46
  156. package/src/excel-col.ts +3 -3
  157. package/src/excel-row.ts +3 -3
  158. package/src/excel-workbook.ts +26 -69
  159. package/src/excel-worksheet.ts +36 -107
  160. package/src/excel-wrapper.ts +0 -10
  161. package/src/models/excel-format.ts +2 -0
  162. package/src/models/excel-model-factory.ts +33 -0
  163. package/src/models/excel-model.ts +12 -0
  164. package/src/models/i-content-type-model.ts +7 -0
  165. package/src/models/i-drawing-model.ts +13 -0
  166. package/src/models/i-relationship-model.ts +13 -0
  167. package/src/models/i-shared-string-model.ts +9 -0
  168. package/src/models/i-style-model.ts +18 -0
  169. package/src/models/i-workbook-model.ts +17 -0
  170. package/src/models/i-worksheet-model.ts +40 -0
  171. package/src/models/shared/excel-cf-spec.ts +24 -0
  172. package/src/models/shared/excel-style.ts +65 -0
  173. package/src/types.ts +0 -13
  174. package/src/utils/excel-style-data.ts +4 -25
  175. package/src/utils/zip-cache.ts +189 -58
  176. package/src/xml/excel-xml-content-type.ts +18 -8
  177. package/src/xml/excel-xml-drawing.ts +19 -9
  178. package/src/xml/excel-xml-relationship.ts +28 -14
  179. package/src/xml/excel-xml-shared-string.ts +20 -11
  180. package/src/xml/excel-xml-style.ts +74 -116
  181. package/src/xml/excel-xml-unknown.ts +8 -4
  182. package/src/xml/excel-xml-workbook.ts +34 -22
  183. package/src/xml/excel-xml-worksheet.ts +73 -53
  184. package/src/xml/xml-model-factory.ts +83 -0
@@ -1,11 +1,11 @@
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ import { num, obj, xml as xmlU } from "@simplysm/core-common";
3
+ import "@simplysm/core-common";
4
+ import type { IStyleModel } from "../models/i-style-model";
5
+ import { convertExcelStyleOptions, type ExcelStyle } from "../models/shared/excel-style";
1
6
  import type {
2
- ExcelBorderPosition,
3
7
  ExcelConditionalRuleStyle,
4
8
  ExcelFont,
5
- ExcelHorizontalAlign,
6
- ExcelStyleOptions,
7
- ExcelVerticalAlign,
8
- ExcelXml,
9
9
  ExcelXmlStyleData,
10
10
  ExcelXmlStyleDataBorder,
11
11
  ExcelXmlStyleDataDxf,
@@ -13,73 +13,21 @@ import type {
13
13
  ExcelXmlStyleDataFont,
14
14
  ExcelXmlStyleDataXf,
15
15
  } from "../types";
16
- import "@simplysm/core-common";
17
- import { num, obj } from "@simplysm/core-common";
18
- import { ExcelUtils } from "../utils/excel-utils";
19
-
20
- export interface ExcelStyle {
21
- numFmtId?: string;
22
- numFmtCode?: string;
23
- border?: ExcelBorderPosition[];
24
- background?: string;
25
- verticalAlign?: ExcelVerticalAlign;
26
- horizontalAlign?: ExcelHorizontalAlign;
27
- font?: ExcelFont;
28
- }
29
-
30
- /**
31
- * `ExcelStyleOptions` (사용자 표면) → 내부 `ExcelStyle` 변환.
32
- * cell.setStyle 과 wb.setDefaultStyle 이 공유한다.
33
- *
34
- * - background ARGB 8자리 형식 검증
35
- * - numberFormatCode 가 numberFormat 보다 우선
36
- * - font 는 그대로 전달 (구체 검증은 ExcelXmlStyle 내부에서 수행)
37
- */
38
- export function convertExcelStyleOptions(opts: ExcelStyleOptions): ExcelStyle {
39
- const style: ExcelStyle = {};
40
-
41
- if (opts.background != null) {
42
- if (!/^[0-9A-F]{8}$/i.test(opts.background)) {
43
- throw new Error("잘못된 색상 형식입니다. (형식: 00000000: alpha(반전)+rgb)");
44
- }
45
- style.background = opts.background;
46
- }
47
-
48
- if (opts.border != null) {
49
- style.border = opts.border;
50
- }
51
-
52
- if (opts.horizontalAlign != null) {
53
- style.horizontalAlign = opts.horizontalAlign;
54
- }
55
-
56
- if (opts.verticalAlign != null) {
57
- style.verticalAlign = opts.verticalAlign;
58
- }
59
-
60
- if (opts.numberFormatCode != null) {
61
- style.numFmtCode = opts.numberFormatCode;
62
- } else if (opts.numberFormat != null) {
63
- style.numFmtId = ExcelUtils.convertNumFmtNameToId(opts.numberFormat).toString();
64
- }
65
-
66
- if (opts.font != null) {
67
- style.font = opts.font;
68
- }
16
+ import type { ExcelBorderPosition } from "../types";
69
17
 
70
- return style;
71
- }
18
+ // 구현 중립 위치(models/shared)로 이전. 기존 import 경로 호환을 위해 re-export 유지.
19
+ export { convertExcelStyleOptions, type ExcelStyle };
72
20
 
73
21
  /**
74
22
  * xl/styles.xml을 관리하는 클래스.
75
23
  * 숫자 형식, 배경색, 테두리, 정렬 등의 스타일을 처리한다.
76
24
  */
77
- export class ExcelXmlStyle implements ExcelXml {
78
- data: ExcelXmlStyleData;
25
+ export class ExcelXmlStyle implements IStyleModel {
26
+ private readonly _data: ExcelXmlStyleData;
79
27
 
80
28
  constructor(data?: ExcelXmlStyleData) {
81
29
  if (data == null) {
82
- this.data = {
30
+ this._data = {
83
31
  styleSheet: {
84
32
  $: {
85
33
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
@@ -114,10 +62,15 @@ export class ExcelXmlStyle implements ExcelXml {
114
62
  },
115
63
  };
116
64
  } else {
117
- this.data = data;
65
+ this._data = data;
118
66
  }
119
67
  }
120
68
 
69
+ /** @internal 테스트·디버그용 내부 트리 접근. 상위 레이어는 인터페이스만 사용. */
70
+ get data(): ExcelXmlStyleData {
71
+ return this._data;
72
+ }
73
+
121
74
  add(style: ExcelStyle): string {
122
75
  const newXf: ExcelXmlStyleDataXf = { $: {} };
123
76
  this._applyStyleToXf(newXf, style);
@@ -134,17 +87,17 @@ export class ExcelXmlStyle implements ExcelXml {
134
87
  * 미호출 시 기존 cellXfs[0]·0번 슬롯 모두 그대로 보존된다.
135
88
  */
136
89
  setDefaultStyle(style: ExcelStyle): void {
137
- this.data.styleSheet.fonts[0].font[0] = {};
138
- this.data.styleSheet.fills[0].fill[0] = { patternFill: [{ $: { patternType: "none" } }] };
139
- this.data.styleSheet.borders[0].border[0] = {};
90
+ this._data.styleSheet.fonts[0].font[0] = {};
91
+ this._data.styleSheet.fills[0].fill[0] = { patternFill: [{ $: { patternType: "none" } }] };
92
+ this._data.styleSheet.borders[0].border[0] = {};
140
93
 
141
94
  if (style.font != null) {
142
95
  this._validateFont(style.font);
143
- this.data.styleSheet.fonts[0].font[0] = this._buildFontXml(style.font);
96
+ this._data.styleSheet.fonts[0].font[0] = this._buildFontXml(style.font);
144
97
  }
145
98
 
146
99
  if (style.background != null) {
147
- this.data.styleSheet.fills[0].fill[0] = {
100
+ this._data.styleSheet.fills[0].fill[0] = {
148
101
  patternFill: [
149
102
  {
150
103
  $: { patternType: "solid" },
@@ -155,7 +108,7 @@ export class ExcelXmlStyle implements ExcelXml {
155
108
  }
156
109
 
157
110
  if (style.border != null) {
158
- this.data.styleSheet.borders[0].border[0] = this._createBorderFromPositions(style.border);
111
+ this._data.styleSheet.borders[0].border[0] = this._createBorderFromPositions(style.border);
159
112
  }
160
113
 
161
114
  const newXf: ExcelXmlStyleDataXf = { $: { numFmtId: "0" } };
@@ -170,7 +123,7 @@ export class ExcelXmlStyle implements ExcelXml {
170
123
  }
171
124
 
172
125
  this._applyAlignment(newXf, style);
173
- this.data.styleSheet.cellXfs[0].xf[0] = newXf;
126
+ this._data.styleSheet.cellXfs[0].xf[0] = newXf;
174
127
  }
175
128
 
176
129
  addWithClone(id: string, style: ExcelStyle): string {
@@ -178,7 +131,7 @@ export class ExcelXmlStyle implements ExcelXml {
178
131
  if (idNum == null) {
179
132
  throw new Error(`잘못된 스타일 ID: ${id}`);
180
133
  }
181
- const xfArray = this.data.styleSheet.cellXfs[0].xf;
134
+ const xfArray = this._data.styleSheet.cellXfs[0].xf;
182
135
  if (idNum < 0 || idNum >= xfArray.length) {
183
136
  throw new Error(`존재하지 않는 스타일 ID: ${id} (범위: 0-${xfArray.length - 1})`);
184
137
  }
@@ -197,7 +150,7 @@ export class ExcelXmlStyle implements ExcelXml {
197
150
  if (style.background != null) {
198
151
  const fillIdNum = cloneXf.$.fillId != null ? num.parseInt(cloneXf.$.fillId) : undefined;
199
152
  const prevFill =
200
- fillIdNum != null ? this.data.styleSheet.fills[0].fill[fillIdNum] : undefined;
153
+ fillIdNum != null ? this._data.styleSheet.fills[0].fill[fillIdNum] : undefined;
201
154
 
202
155
  if (prevFill != null) {
203
156
  const cloneFill = obj.clone(prevFill);
@@ -229,7 +182,7 @@ export class ExcelXmlStyle implements ExcelXml {
229
182
  const borderIdNum =
230
183
  cloneXf.$.borderId != null ? num.parseInt(cloneXf.$.borderId) : undefined;
231
184
  const prevBorder =
232
- borderIdNum != null ? this.data.styleSheet.borders[0].border[borderIdNum] : undefined;
185
+ borderIdNum != null ? this._data.styleSheet.borders[0].border[borderIdNum] : undefined;
233
186
 
234
187
  if (prevBorder != null) {
235
188
  const cloneBorder = obj.clone(prevBorder);
@@ -263,7 +216,7 @@ export class ExcelXmlStyle implements ExcelXml {
263
216
  if (idNum == null) {
264
217
  throw new Error(`잘못된 스타일 ID: ${id}`);
265
218
  }
266
- const xf = this.data.styleSheet.cellXfs[0].xf[idNum] as ExcelXmlStyleDataXf | undefined;
219
+ const xf = this._data.styleSheet.cellXfs[0].xf[idNum] as ExcelXmlStyleDataXf | undefined;
267
220
 
268
221
  const result: ExcelStyle = {};
269
222
 
@@ -273,12 +226,12 @@ export class ExcelXmlStyle implements ExcelXml {
273
226
  if (xf.$.fillId != null) {
274
227
  const fillIdNum = num.parseInt(xf.$.fillId);
275
228
  if (fillIdNum != null) {
276
- const fill = this.data.styleSheet.fills[0].fill[fillIdNum] as
229
+ const fill = this._data.styleSheet.fills[0].fill[fillIdNum] as
277
230
  | ExcelXmlStyleDataFill
278
231
  | undefined;
279
232
  if (fill == null) {
280
233
  throw new Error(
281
- `존재하지 않는 fill ID: ${xf.$.fillId} (범위: 0-${this.data.styleSheet.fills[0].fill.length - 1})`,
234
+ `존재하지 않는 fill ID: ${xf.$.fillId} (범위: 0-${this._data.styleSheet.fills[0].fill.length - 1})`,
282
235
  );
283
236
  }
284
237
  result.background = fill.patternFill[0].fgColor?.[0].$.rgb;
@@ -290,12 +243,12 @@ export class ExcelXmlStyle implements ExcelXml {
290
243
  if (borderIdNum == null) {
291
244
  throw new Error(`잘못된 border ID: ${xf.$.borderId}`);
292
245
  }
293
- const border = this.data.styleSheet.borders[0].border[borderIdNum] as
246
+ const border = this._data.styleSheet.borders[0].border[borderIdNum] as
294
247
  | ExcelXmlStyleDataBorder
295
248
  | undefined;
296
249
  if (border == null) {
297
250
  throw new Error(
298
- `존재하지 않는 border ID: ${xf.$.borderId} (범위: 0-${this.data.styleSheet.borders[0].border.length - 1})`,
251
+ `존재하지 않는 border ID: ${xf.$.borderId} (범위: 0-${this._data.styleSheet.borders[0].border.length - 1})`,
299
252
  );
300
253
  }
301
254
  if (
@@ -326,7 +279,7 @@ export class ExcelXmlStyle implements ExcelXml {
326
279
  if (xf.$.fontId != null) {
327
280
  const fontIdNum = num.parseInt(xf.$.fontId);
328
281
  if (fontIdNum != null) {
329
- const font = this.data.styleSheet.fonts[0].font[fontIdNum] as
282
+ const font = this._data.styleSheet.fonts[0].font[fontIdNum] as
330
283
  | ExcelXmlStyleDataFont
331
284
  | undefined;
332
285
  if (font != null) {
@@ -369,7 +322,7 @@ export class ExcelXmlStyle implements ExcelXml {
369
322
  ];
370
323
  }
371
324
 
372
- const dxfs = (this.data.styleSheet.dxfs = this.data.styleSheet.dxfs ?? [
325
+ const dxfs = (this._data.styleSheet.dxfs = this._data.styleSheet.dxfs ?? [
373
326
  { $: { count: "0" }, dxf: [] },
374
327
  ]);
375
328
 
@@ -384,21 +337,26 @@ export class ExcelXmlStyle implements ExcelXml {
384
337
  }
385
338
 
386
339
  getNumFmtCode(numFmtId: string): string | undefined {
387
- return (this.data.styleSheet.numFmts?.[0].numFmt ?? []).single(
340
+ return (this._data.styleSheet.numFmts?.[0].numFmt ?? []).single(
388
341
  (item) => item.$.numFmtId === numFmtId,
389
342
  )?.$.formatCode;
390
343
  }
391
344
 
392
- cleanup(): void {
345
+ serialize(): Bytes {
346
+ this._cleanup();
347
+ return new TextEncoder().encode(xmlU.stringify(this._data));
348
+ }
349
+
350
+ private _cleanup(): void {
393
351
  const result = {} as ExcelXmlStyleData["styleSheet"];
394
352
 
395
353
  // 정렬 순서 (numFmts를 먼저)
396
354
 
397
- if (this.data.styleSheet.numFmts != null) {
398
- result.numFmts = this.data.styleSheet.numFmts;
355
+ if (this._data.styleSheet.numFmts != null) {
356
+ result.numFmts = this._data.styleSheet.numFmts;
399
357
  }
400
358
 
401
- const styleSheetRec = this.data.styleSheet as Record<string, unknown>;
359
+ const styleSheetRec = this._data.styleSheet as Record<string, unknown>;
402
360
  const resultRec = result as Record<string, unknown>;
403
361
  for (const key of Object.keys(styleSheetRec)) {
404
362
  if (key === "numFmts") continue;
@@ -406,45 +364,45 @@ export class ExcelXmlStyle implements ExcelXml {
406
364
  resultRec[key] = styleSheetRec[key];
407
365
  }
408
366
 
409
- this.data.styleSheet = result;
367
+ this._data.styleSheet = result;
410
368
  }
411
369
 
412
370
  //#region Private Methods
413
371
 
414
372
  private _setNumFmtCode(numFmtCode: string): string {
415
373
  // 코드가 이미 존재하면 건너뛰기
416
- const existsNumFmtId = (this.data.styleSheet.numFmts?.[0].numFmt ?? []).single(
374
+ const existsNumFmtId = (this._data.styleSheet.numFmts?.[0].numFmt ?? []).single(
417
375
  (item) => item.$.formatCode === numFmtCode,
418
376
  )?.$.numFmtId;
419
377
  if (existsNumFmtId != null) {
420
378
  return existsNumFmtId;
421
379
  }
422
380
 
423
- this.data.styleSheet.numFmts = this.data.styleSheet.numFmts ?? [
381
+ this._data.styleSheet.numFmts = this._data.styleSheet.numFmts ?? [
424
382
  {
425
383
  $: { count: "0" },
426
384
  numFmt: [],
427
385
  },
428
386
  ];
429
387
 
430
- this.data.styleSheet.numFmts[0].numFmt = this.data.styleSheet.numFmts[0].numFmt ?? [];
388
+ this._data.styleSheet.numFmts[0].numFmt = this._data.styleSheet.numFmts[0].numFmt ?? [];
431
389
 
432
390
  // Excel 사용자 정의 숫자 형식은 ID 180+부터 시작 (0-163: 내장, 164-179: 예약)
433
- const numFmts = this.data.styleSheet.numFmts[0].numFmt;
391
+ const numFmts = this._data.styleSheet.numFmts[0].numFmt;
434
392
  const maxItem =
435
393
  numFmts.length > 0
436
394
  ? numFmts.orderByDesc((item) => num.parseInt(item.$.numFmtId) ?? 180).first()
437
395
  : undefined;
438
396
  const maxId = maxItem ? (num.parseInt(maxItem.$.numFmtId) ?? 180) : 180;
439
397
  const nextNumFmtId = (maxId + 1).toString();
440
- this.data.styleSheet.numFmts[0].numFmt.push({
398
+ this._data.styleSheet.numFmts[0].numFmt.push({
441
399
  $: {
442
400
  numFmtId: nextNumFmtId,
443
401
  formatCode: numFmtCode,
444
402
  },
445
403
  });
446
- this.data.styleSheet.numFmts[0].$.count = (
447
- (num.parseInt(this.data.styleSheet.numFmts[0].$.count) ?? 0) + 1
404
+ this._data.styleSheet.numFmts[0].$.count = (
405
+ (num.parseInt(this._data.styleSheet.numFmts[0].$.count) ?? 0) + 1
448
406
  ).toString();
449
407
 
450
408
  return nextNumFmtId;
@@ -542,13 +500,13 @@ export class ExcelXmlStyle implements ExcelXml {
542
500
  }
543
501
 
544
502
  private _getSameOrCreateFont(item: ExcelXmlStyleDataFont): string {
545
- const prevSameFont = this.data.styleSheet.fonts[0].font.single((f) => obj.equal(f, item));
503
+ const prevSameFont = this._data.styleSheet.fonts[0].font.single((f) => obj.equal(f, item));
546
504
  if (prevSameFont != null) {
547
- return this.data.styleSheet.fonts[0].font.indexOf(prevSameFont).toString();
505
+ return this._data.styleSheet.fonts[0].font.indexOf(prevSameFont).toString();
548
506
  } else {
549
- this.data.styleSheet.fonts[0].font.push(item);
550
- this.data.styleSheet.fonts[0].$.count = this.data.styleSheet.fonts[0].font.length.toString();
551
- return (this.data.styleSheet.fonts[0].font.length - 1).toString();
507
+ this._data.styleSheet.fonts[0].font.push(item);
508
+ this._data.styleSheet.fonts[0].$.count = this._data.styleSheet.fonts[0].font.length.toString();
509
+ return (this._data.styleSheet.fonts[0].font.length - 1).toString();
552
510
  }
553
511
  }
554
512
 
@@ -609,44 +567,44 @@ export class ExcelXmlStyle implements ExcelXml {
609
567
  }
610
568
 
611
569
  private _getSameOrCreateXf(xfItem: ExcelXmlStyleDataXf): string {
612
- const prevSameXf = this.data.styleSheet.cellXfs[0].xf.single((item) => obj.equal(item, xfItem));
570
+ const prevSameXf = this._data.styleSheet.cellXfs[0].xf.single((item) => obj.equal(item, xfItem));
613
571
 
614
572
  if (prevSameXf != null) {
615
- return this.data.styleSheet.cellXfs[0].xf.indexOf(prevSameXf).toString();
573
+ return this._data.styleSheet.cellXfs[0].xf.indexOf(prevSameXf).toString();
616
574
  } else {
617
- this.data.styleSheet.cellXfs[0].xf.push(xfItem);
618
- this.data.styleSheet.cellXfs[0].$.count =
619
- this.data.styleSheet.cellXfs[0].xf.length.toString();
620
- return (this.data.styleSheet.cellXfs[0].xf.length - 1).toString();
575
+ this._data.styleSheet.cellXfs[0].xf.push(xfItem);
576
+ this._data.styleSheet.cellXfs[0].$.count =
577
+ this._data.styleSheet.cellXfs[0].xf.length.toString();
578
+ return (this._data.styleSheet.cellXfs[0].xf.length - 1).toString();
621
579
  }
622
580
  }
623
581
 
624
582
  private _getSameOrCreateFill(fillItem: ExcelXmlStyleDataFill): string {
625
- const prevSameFill = this.data.styleSheet.fills[0].fill.single((item) =>
583
+ const prevSameFill = this._data.styleSheet.fills[0].fill.single((item) =>
626
584
  obj.equal(item, fillItem),
627
585
  );
628
586
 
629
587
  if (prevSameFill != null) {
630
- return this.data.styleSheet.fills[0].fill.indexOf(prevSameFill).toString();
588
+ return this._data.styleSheet.fills[0].fill.indexOf(prevSameFill).toString();
631
589
  } else {
632
- this.data.styleSheet.fills[0].fill.push(fillItem);
633
- this.data.styleSheet.fills[0].$.count = this.data.styleSheet.fills[0].fill.length.toString();
634
- return (this.data.styleSheet.fills[0].fill.length - 1).toString();
590
+ this._data.styleSheet.fills[0].fill.push(fillItem);
591
+ this._data.styleSheet.fills[0].$.count = this._data.styleSheet.fills[0].fill.length.toString();
592
+ return (this._data.styleSheet.fills[0].fill.length - 1).toString();
635
593
  }
636
594
  }
637
595
 
638
596
  private _getSameOrCreateBorder(borderItem: ExcelXmlStyleDataBorder): string {
639
- const prevSameBorder = this.data.styleSheet.borders[0].border.single((item) =>
597
+ const prevSameBorder = this._data.styleSheet.borders[0].border.single((item) =>
640
598
  obj.equal(item, borderItem),
641
599
  );
642
600
 
643
601
  if (prevSameBorder != null) {
644
- return this.data.styleSheet.borders[0].border.indexOf(prevSameBorder).toString();
602
+ return this._data.styleSheet.borders[0].border.indexOf(prevSameBorder).toString();
645
603
  } else {
646
- this.data.styleSheet.borders[0].border.push(borderItem);
647
- this.data.styleSheet.borders[0].$.count =
648
- this.data.styleSheet.borders[0].border.length.toString();
649
- return (this.data.styleSheet.borders[0].border.length - 1).toString();
604
+ this._data.styleSheet.borders[0].border.push(borderItem);
605
+ this._data.styleSheet.borders[0].$.count =
606
+ this._data.styleSheet.borders[0].border.length.toString();
607
+ return (this._data.styleSheet.borders[0].border.length - 1).toString();
650
608
  }
651
609
  }
652
610
 
@@ -1,11 +1,15 @@
1
- import type { ExcelXml } from "../types";
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ import { xml as xmlU } from "@simplysm/core-common";
3
+ import type { IExcelModel } from "../models/excel-model";
2
4
 
3
5
  /**
4
6
  * 알 수 없는 형식의 Excel XML 데이터를 보존하는 클래스.
5
7
  * 원본 데이터를 손실 없이 유지한다.
6
8
  */
7
- export class ExcelXmlUnknown implements ExcelXml {
8
- constructor(public readonly data: Record<string, unknown>) {}
9
+ export class ExcelXmlUnknown implements IExcelModel {
10
+ constructor(private readonly _data: Record<string, unknown>) {}
9
11
 
10
- cleanup(): void {}
12
+ serialize(): Bytes {
13
+ return new TextEncoder().encode(xmlU.stringify(this._data));
14
+ }
11
15
  }
@@ -1,17 +1,19 @@
1
1
  import "@simplysm/core-common";
2
- import { num } from "@simplysm/core-common";
3
- import type { ExcelXml, ExcelXmlWorkbookData } from "../types";
2
+ import type { Bytes } from "@simplysm/core-common";
3
+ import { num, xml as xmlU } from "@simplysm/core-common";
4
+ import type { IWorkbookModel } from "../models/i-workbook-model";
5
+ import type { ExcelXmlWorkbookData } from "../types";
4
6
 
5
7
  /**
6
8
  * xl/workbook.xml을 관리하는 클래스.
7
9
  * 워크시트 목록 및 관계 ID를 처리한다.
8
10
  */
9
- export class ExcelXmlWorkbook implements ExcelXml {
10
- data: ExcelXmlWorkbookData;
11
+ export class ExcelXmlWorkbook implements IWorkbookModel {
12
+ private readonly _data: ExcelXmlWorkbookData;
11
13
 
12
14
  constructor(data?: ExcelXmlWorkbookData) {
13
15
  if (data == null) {
14
- this.data = {
16
+ this._data = {
15
17
  workbook: {
16
18
  $: {
17
19
  "xmlns": "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
@@ -20,26 +22,31 @@ export class ExcelXmlWorkbook implements ExcelXml {
20
22
  },
21
23
  };
22
24
  } else {
23
- this.data = data;
25
+ this._data = data;
24
26
  }
25
27
  }
26
28
 
29
+ /** @internal 테스트·디버그용 내부 트리 접근. 상위 레이어는 인터페이스만 사용. */
30
+ get data(): ExcelXmlWorkbookData {
31
+ return this._data;
32
+ }
33
+
27
34
  get lastWsRelId(): number | undefined {
28
- const sheets = this.data.workbook.sheets?.[0].sheet;
35
+ const sheets = this._data.workbook.sheets?.[0].sheet;
29
36
  if (!sheets || sheets.length === 0) return undefined;
30
37
  const maxSheet = sheets.orderByDesc((sheet) => num.parseInt(sheet.$["r:id"])!).first();
31
38
  return maxSheet ? num.parseInt(maxSheet.$["r:id"]) : undefined;
32
39
  }
33
40
 
34
41
  get lastSheetId(): number | undefined {
35
- const sheets = this.data.workbook.sheets?.[0].sheet;
42
+ const sheets = this._data.workbook.sheets?.[0].sheet;
36
43
  if (!sheets || sheets.length === 0) return undefined;
37
44
  const maxSheet = sheets.orderByDesc((sheet) => num.parseInt(sheet.$.sheetId)!).first();
38
45
  return maxSheet ? num.parseInt(maxSheet.$.sheetId) : undefined;
39
46
  }
40
47
 
41
48
  get sheetNames(): string[] {
42
- return this.data.workbook.sheets?.[0].sheet.map((item) => item.$.name) ?? [];
49
+ return this._data.workbook.sheets?.[0].sheet.map((item) => item.$.name) ?? [];
43
50
  }
44
51
 
45
52
  addWorksheet(name: string): this {
@@ -48,8 +55,8 @@ export class ExcelXmlWorkbook implements ExcelXml {
48
55
  const newWsRelId = (this.lastWsRelId ?? 0) + 1;
49
56
  const newSheetId = (this.lastSheetId ?? 0) + 1;
50
57
 
51
- this.data.workbook.sheets = this.data.workbook.sheets ?? [{ sheet: [] }];
52
- this.data.workbook.sheets[0].sheet.push({
58
+ this._data.workbook.sheets = this._data.workbook.sheets ?? [{ sheet: [] }];
59
+ this._data.workbook.sheets[0].sheet.push({
53
60
  $: {
54
61
  "name": replacedName,
55
62
  "sheetId": newSheetId.toString(),
@@ -60,44 +67,49 @@ export class ExcelXmlWorkbook implements ExcelXml {
60
67
  return this;
61
68
  }
62
69
 
63
- cleanup(): void {
70
+ serialize(): Bytes {
71
+ this._cleanup();
72
+ return new TextEncoder().encode(xmlU.stringify(this._data));
73
+ }
74
+
75
+ private _cleanup(): void {
64
76
  const result = {} as ExcelXmlWorkbookData["workbook"];
65
77
 
66
78
  // 정렬 순서 ("sheets" 기준, 나머지는 원래 위치 유지)
67
79
 
68
- const workbookRec = this.data.workbook as Record<string, unknown>;
80
+ const workbookRec = this._data.workbook as Record<string, unknown>;
69
81
  const resultRec = result as Record<string, unknown>;
70
82
 
71
- for (const key of Object.keys(this.data.workbook)) {
83
+ for (const key of Object.keys(this._data.workbook)) {
72
84
  if (key === "bookViews") continue;
73
85
 
74
86
  if (key === "sheets") {
75
- if (this.data.workbook.bookViews != null) {
76
- result.bookViews = this.data.workbook.bookViews;
87
+ if (this._data.workbook.bookViews != null) {
88
+ result.bookViews = this._data.workbook.bookViews;
77
89
  }
78
- result.sheets = this.data.workbook.sheets;
90
+ result.sheets = this._data.workbook.sheets;
79
91
  } else {
80
92
  resultRec[key] = workbookRec[key];
81
93
  }
82
94
  }
83
95
 
84
- this.data.workbook = result;
96
+ this._data.workbook = result;
85
97
  }
86
98
 
87
99
  initializeView(): void {
88
- this.data.workbook.bookViews = this.data.workbook.bookViews ?? [{ workbookView: [{}] }];
100
+ this._data.workbook.bookViews = this._data.workbook.bookViews ?? [{ workbookView: [{}] }];
89
101
  }
90
102
 
91
103
  getWsRelIdByName(name: string): number | undefined {
92
104
  return num.parseInt(
93
- (this.data.workbook.sheets?.[0].sheet ?? []).single((item) => item.$.name === name)?.$[
105
+ (this._data.workbook.sheets?.[0].sheet ?? []).single((item) => item.$.name === name)?.$[
94
106
  "r:id"
95
107
  ],
96
108
  );
97
109
  }
98
110
 
99
111
  getWsRelIdByIndex(index: number): number | undefined {
100
- return num.parseInt(this.data.workbook.sheets?.[0].sheet[index]?.$["r:id"]);
112
+ return num.parseInt(this._data.workbook.sheets?.[0].sheet[index]?.$["r:id"]);
101
113
  }
102
114
 
103
115
  getWorksheetNameById(id: number): string | undefined {
@@ -114,7 +126,7 @@ export class ExcelXmlWorkbook implements ExcelXml {
114
126
  }
115
127
 
116
128
  private _getSheetDataById(id: number) {
117
- return (this.data.workbook.sheets?.[0].sheet ?? []).single(
129
+ return (this._data.workbook.sheets?.[0].sheet ?? []).single(
118
130
  (item) => num.parseInt(item.$["r:id"]) === id,
119
131
  );
120
132
  }