@simplysm/excel 13.0.0-beta.7 → 13.0.1

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 (167) hide show
  1. package/README.md +15 -0
  2. package/dist/excel-cell.d.ts.map +1 -0
  3. package/dist/excel-cell.js +3 -3
  4. package/dist/excel-cell.js.map +0 -1
  5. package/dist/excel-col.d.ts.map +1 -0
  6. package/dist/excel-col.js +1 -1
  7. package/dist/excel-col.js.map +0 -1
  8. package/dist/excel-row.d.ts.map +1 -0
  9. package/dist/excel-row.js +1 -1
  10. package/dist/excel-row.js.map +0 -1
  11. package/dist/excel-workbook.d.ts.map +1 -0
  12. package/dist/excel-workbook.js +6 -6
  13. package/dist/excel-workbook.js.map +0 -1
  14. package/dist/excel-worksheet.d.ts.map +1 -0
  15. package/dist/excel-worksheet.js +4 -4
  16. package/dist/excel-worksheet.js.map +0 -1
  17. package/dist/excel-wrapper.d.ts.map +1 -0
  18. package/dist/excel-wrapper.js +1 -1
  19. package/dist/excel-wrapper.js.map +0 -1
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +8 -8
  22. package/dist/index.js.map +0 -1
  23. package/dist/types.d.ts.map +1 -0
  24. package/dist/types.js.map +0 -1
  25. package/dist/utils/excel-utils.d.ts.map +1 -0
  26. package/dist/utils/excel-utils.js.map +0 -1
  27. package/dist/utils/zip-cache.d.ts.map +1 -0
  28. package/dist/utils/zip-cache.js +8 -8
  29. package/dist/utils/zip-cache.js.map +0 -1
  30. package/dist/xml/excel-xml-content-type.d.ts.map +1 -0
  31. package/dist/xml/excel-xml-content-type.js.map +0 -1
  32. package/dist/xml/excel-xml-drawing.d.ts.map +1 -0
  33. package/dist/xml/excel-xml-drawing.js.map +0 -1
  34. package/dist/xml/excel-xml-relationship.d.ts.map +1 -0
  35. package/dist/xml/excel-xml-relationship.js.map +0 -1
  36. package/dist/xml/excel-xml-shared-string.d.ts.map +1 -0
  37. package/dist/xml/excel-xml-shared-string.js.map +0 -1
  38. package/dist/xml/excel-xml-style.d.ts.map +1 -0
  39. package/dist/xml/excel-xml-style.js.map +0 -1
  40. package/dist/xml/excel-xml-unknown.d.ts.map +1 -0
  41. package/dist/xml/excel-xml-unknown.js.map +0 -1
  42. package/dist/xml/excel-xml-workbook.d.ts.map +1 -0
  43. package/dist/xml/excel-xml-workbook.js.map +0 -1
  44. package/dist/xml/excel-xml-worksheet.d.ts.map +1 -0
  45. package/dist/xml/excel-xml-worksheet.js +1 -1
  46. package/dist/xml/excel-xml-worksheet.js.map +0 -1
  47. package/package.json +6 -5
  48. package/src/excel-cell.ts +326 -0
  49. package/src/excel-col.ts +43 -0
  50. package/src/excel-row.ts +37 -0
  51. package/src/excel-workbook.ts +206 -0
  52. package/src/excel-worksheet.ts +380 -0
  53. package/src/excel-wrapper.ts +219 -0
  54. package/src/index.ts +13 -0
  55. package/src/types.ts +396 -0
  56. package/src/utils/excel-utils.ts +201 -0
  57. package/src/utils/zip-cache.ts +103 -0
  58. package/src/xml/excel-xml-content-type.ts +64 -0
  59. package/src/xml/excel-xml-drawing.ts +87 -0
  60. package/src/xml/excel-xml-relationship.ts +86 -0
  61. package/src/xml/excel-xml-shared-string.ts +80 -0
  62. package/src/xml/excel-xml-style.ts +393 -0
  63. package/src/xml/excel-xml-unknown.ts +11 -0
  64. package/src/xml/excel-xml-workbook.ts +112 -0
  65. package/src/xml/excel-xml-worksheet.ts +544 -0
  66. package/dist/core-common/src/common.types.d.ts +0 -74
  67. package/dist/core-common/src/common.types.d.ts.map +0 -1
  68. package/dist/core-common/src/env.d.ts +0 -6
  69. package/dist/core-common/src/env.d.ts.map +0 -1
  70. package/dist/core-common/src/errors/argument-error.d.ts +0 -25
  71. package/dist/core-common/src/errors/argument-error.d.ts.map +0 -1
  72. package/dist/core-common/src/errors/not-implemented-error.d.ts +0 -29
  73. package/dist/core-common/src/errors/not-implemented-error.d.ts.map +0 -1
  74. package/dist/core-common/src/errors/sd-error.d.ts +0 -27
  75. package/dist/core-common/src/errors/sd-error.d.ts.map +0 -1
  76. package/dist/core-common/src/errors/timeout-error.d.ts +0 -31
  77. package/dist/core-common/src/errors/timeout-error.d.ts.map +0 -1
  78. package/dist/core-common/src/extensions/arr-ext.d.ts +0 -15
  79. package/dist/core-common/src/extensions/arr-ext.d.ts.map +0 -1
  80. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +0 -19
  81. package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +0 -1
  82. package/dist/core-common/src/extensions/arr-ext.types.d.ts +0 -215
  83. package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +0 -1
  84. package/dist/core-common/src/extensions/map-ext.d.ts +0 -57
  85. package/dist/core-common/src/extensions/map-ext.d.ts.map +0 -1
  86. package/dist/core-common/src/extensions/set-ext.d.ts +0 -36
  87. package/dist/core-common/src/extensions/set-ext.d.ts.map +0 -1
  88. package/dist/core-common/src/features/debounce-queue.d.ts +0 -53
  89. package/dist/core-common/src/features/debounce-queue.d.ts.map +0 -1
  90. package/dist/core-common/src/features/event-emitter.d.ts +0 -66
  91. package/dist/core-common/src/features/event-emitter.d.ts.map +0 -1
  92. package/dist/core-common/src/features/serial-queue.d.ts +0 -47
  93. package/dist/core-common/src/features/serial-queue.d.ts.map +0 -1
  94. package/dist/core-common/src/index.d.ts +0 -32
  95. package/dist/core-common/src/index.d.ts.map +0 -1
  96. package/dist/core-common/src/types/date-only.d.ts +0 -152
  97. package/dist/core-common/src/types/date-only.d.ts.map +0 -1
  98. package/dist/core-common/src/types/date-time.d.ts +0 -96
  99. package/dist/core-common/src/types/date-time.d.ts.map +0 -1
  100. package/dist/core-common/src/types/lazy-gc-map.d.ts +0 -80
  101. package/dist/core-common/src/types/lazy-gc-map.d.ts.map +0 -1
  102. package/dist/core-common/src/types/time.d.ts +0 -68
  103. package/dist/core-common/src/types/time.d.ts.map +0 -1
  104. package/dist/core-common/src/types/uuid.d.ts +0 -35
  105. package/dist/core-common/src/types/uuid.d.ts.map +0 -1
  106. package/dist/core-common/src/utils/bytes.d.ts +0 -51
  107. package/dist/core-common/src/utils/bytes.d.ts.map +0 -1
  108. package/dist/core-common/src/utils/date-format.d.ts +0 -90
  109. package/dist/core-common/src/utils/date-format.d.ts.map +0 -1
  110. package/dist/core-common/src/utils/json.d.ts +0 -34
  111. package/dist/core-common/src/utils/json.d.ts.map +0 -1
  112. package/dist/core-common/src/utils/num.d.ts +0 -60
  113. package/dist/core-common/src/utils/num.d.ts.map +0 -1
  114. package/dist/core-common/src/utils/obj.d.ts +0 -258
  115. package/dist/core-common/src/utils/obj.d.ts.map +0 -1
  116. package/dist/core-common/src/utils/path.d.ts +0 -23
  117. package/dist/core-common/src/utils/path.d.ts.map +0 -1
  118. package/dist/core-common/src/utils/primitive.d.ts +0 -18
  119. package/dist/core-common/src/utils/primitive.d.ts.map +0 -1
  120. package/dist/core-common/src/utils/str.d.ts +0 -103
  121. package/dist/core-common/src/utils/str.d.ts.map +0 -1
  122. package/dist/core-common/src/utils/template-strings.d.ts +0 -84
  123. package/dist/core-common/src/utils/template-strings.d.ts.map +0 -1
  124. package/dist/core-common/src/utils/transferable.d.ts +0 -47
  125. package/dist/core-common/src/utils/transferable.d.ts.map +0 -1
  126. package/dist/core-common/src/utils/wait.d.ts +0 -19
  127. package/dist/core-common/src/utils/wait.d.ts.map +0 -1
  128. package/dist/core-common/src/utils/xml.d.ts +0 -36
  129. package/dist/core-common/src/utils/xml.d.ts.map +0 -1
  130. package/dist/core-common/src/zip/sd-zip.d.ts +0 -80
  131. package/dist/core-common/src/zip/sd-zip.d.ts.map +0 -1
  132. package/dist/excel/src/excel-cell.d.ts.map +0 -1
  133. package/dist/excel/src/excel-col.d.ts.map +0 -1
  134. package/dist/excel/src/excel-row.d.ts.map +0 -1
  135. package/dist/excel/src/excel-workbook.d.ts.map +0 -1
  136. package/dist/excel/src/excel-worksheet.d.ts.map +0 -1
  137. package/dist/excel/src/excel-wrapper.d.ts.map +0 -1
  138. package/dist/excel/src/index.d.ts.map +0 -1
  139. package/dist/excel/src/types.d.ts.map +0 -1
  140. package/dist/excel/src/utils/excel-utils.d.ts.map +0 -1
  141. package/dist/excel/src/utils/zip-cache.d.ts.map +0 -1
  142. package/dist/excel/src/xml/excel-xml-content-type.d.ts.map +0 -1
  143. package/dist/excel/src/xml/excel-xml-drawing.d.ts.map +0 -1
  144. package/dist/excel/src/xml/excel-xml-relationship.d.ts.map +0 -1
  145. package/dist/excel/src/xml/excel-xml-shared-string.d.ts.map +0 -1
  146. package/dist/excel/src/xml/excel-xml-style.d.ts.map +0 -1
  147. package/dist/excel/src/xml/excel-xml-unknown.d.ts.map +0 -1
  148. package/dist/excel/src/xml/excel-xml-workbook.d.ts.map +0 -1
  149. package/dist/excel/src/xml/excel-xml-worksheet.d.ts.map +0 -1
  150. /package/dist/{excel/src/excel-cell.d.ts → excel-cell.d.ts} +0 -0
  151. /package/dist/{excel/src/excel-col.d.ts → excel-col.d.ts} +0 -0
  152. /package/dist/{excel/src/excel-row.d.ts → excel-row.d.ts} +0 -0
  153. /package/dist/{excel/src/excel-workbook.d.ts → excel-workbook.d.ts} +0 -0
  154. /package/dist/{excel/src/excel-worksheet.d.ts → excel-worksheet.d.ts} +0 -0
  155. /package/dist/{excel/src/excel-wrapper.d.ts → excel-wrapper.d.ts} +0 -0
  156. /package/dist/{excel/src/index.d.ts → index.d.ts} +0 -0
  157. /package/dist/{excel/src/types.d.ts → types.d.ts} +0 -0
  158. /package/dist/{excel/src/utils → utils}/excel-utils.d.ts +0 -0
  159. /package/dist/{excel/src/utils → utils}/zip-cache.d.ts +0 -0
  160. /package/dist/{excel/src/xml → xml}/excel-xml-content-type.d.ts +0 -0
  161. /package/dist/{excel/src/xml → xml}/excel-xml-drawing.d.ts +0 -0
  162. /package/dist/{excel/src/xml → xml}/excel-xml-relationship.d.ts +0 -0
  163. /package/dist/{excel/src/xml → xml}/excel-xml-shared-string.d.ts +0 -0
  164. /package/dist/{excel/src/xml → xml}/excel-xml-style.d.ts +0 -0
  165. /package/dist/{excel/src/xml → xml}/excel-xml-unknown.d.ts +0 -0
  166. /package/dist/{excel/src/xml → xml}/excel-xml-workbook.d.ts +0 -0
  167. /package/dist/{excel/src/xml → xml}/excel-xml-worksheet.d.ts +0 -0
@@ -0,0 +1,393 @@
1
+ import type {
2
+ ExcelBorderPosition,
3
+ ExcelHorizontalAlign,
4
+ ExcelVerticalAlign,
5
+ ExcelXml,
6
+ ExcelXmlStyleData,
7
+ ExcelXmlStyleDataBorder,
8
+ ExcelXmlStyleDataFill,
9
+ ExcelXmlStyleDataXf,
10
+ } from "../types";
11
+ import "@simplysm/core-common";
12
+ import { numParseInt, objClone, objEqual } from "@simplysm/core-common";
13
+
14
+ export interface ExcelStyle {
15
+ numFmtId?: string;
16
+ numFmtCode?: string;
17
+ border?: ExcelBorderPosition[];
18
+ background?: string;
19
+ verticalAlign?: ExcelVerticalAlign;
20
+ horizontalAlign?: ExcelHorizontalAlign;
21
+ }
22
+
23
+ /**
24
+ * xl/styles.xml 파일을 관리하는 클래스.
25
+ * 숫자 형식, 배경색, 테두리, 정렬 등의 스타일을 처리한다.
26
+ */
27
+ export class ExcelXmlStyle implements ExcelXml {
28
+ data: ExcelXmlStyleData;
29
+
30
+ constructor(data?: ExcelXmlStyleData) {
31
+ if (data === undefined) {
32
+ this.data = {
33
+ styleSheet: {
34
+ $: {
35
+ xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
36
+ },
37
+ fonts: [
38
+ {
39
+ $: { count: "1" },
40
+ font: [{}],
41
+ },
42
+ ],
43
+ fills: [
44
+ {
45
+ $: { count: "2" },
46
+ fill: [
47
+ { patternFill: [{ $: { patternType: "none" } }] },
48
+ { patternFill: [{ $: { patternType: "gray125" } }] },
49
+ ],
50
+ },
51
+ ],
52
+ borders: [
53
+ {
54
+ $: { count: "1" },
55
+ border: [{}],
56
+ },
57
+ ],
58
+ cellXfs: [
59
+ {
60
+ $: { count: "1" },
61
+ xf: [{ $: { numFmtId: "0" } }],
62
+ },
63
+ ],
64
+ },
65
+ };
66
+ } else {
67
+ this.data = data;
68
+ }
69
+ }
70
+
71
+ add(style: ExcelStyle): string {
72
+ const newXf: ExcelXmlStyleDataXf = { $: {} };
73
+
74
+ if (style.numFmtId !== undefined) {
75
+ newXf.$.numFmtId = style.numFmtId;
76
+ }
77
+
78
+ if (style.numFmtCode !== undefined) {
79
+ newXf.$.numFmtId = this._setNumFmtCode(style.numFmtCode);
80
+ newXf.$.applyNumberFormat = "1";
81
+ }
82
+
83
+ if (style.background !== undefined) {
84
+ const newFill: ExcelXmlStyleDataFill = {
85
+ patternFill: [
86
+ {
87
+ $: { patternType: "solid" },
88
+ fgColor: [{ $: { rgb: style.background.toUpperCase() } }],
89
+ },
90
+ ],
91
+ };
92
+
93
+ newXf.$.applyFill = "1";
94
+ newXf.$.fillId = this._getSameOrCreateFill(newFill);
95
+ }
96
+
97
+ if (style.border !== undefined) {
98
+ const newBorder = this._createBorderFromPositions(style.border);
99
+ newXf.$.applyBorder = "1";
100
+ newXf.$.borderId = this._getSameOrCreateBorder(newBorder);
101
+ }
102
+
103
+ this._applyAlignment(newXf, style);
104
+
105
+ return this._getSameOrCreateXf(newXf);
106
+ }
107
+
108
+ addWithClone(id: string, style: ExcelStyle): string {
109
+ const idNum = numParseInt(id);
110
+ if (idNum == null) {
111
+ throw new Error(`잘못된 스타일 ID: ${id}`);
112
+ }
113
+ const xfArray = this.data.styleSheet.cellXfs[0].xf;
114
+ if (idNum < 0 || idNum >= xfArray.length) {
115
+ throw new Error(`존재하지 않는 스타일 ID: ${id} (범위: 0-${xfArray.length - 1})`);
116
+ }
117
+ const prevXf = xfArray[idNum];
118
+ const cloneXf = objClone(prevXf);
119
+
120
+ if (style.numFmtId !== undefined) {
121
+ cloneXf.$.numFmtId = style.numFmtId;
122
+ }
123
+
124
+ if (style.numFmtCode !== undefined) {
125
+ cloneXf.$.numFmtId = this._setNumFmtCode(style.numFmtCode);
126
+ cloneXf.$.applyNumberFormat = "1";
127
+ }
128
+
129
+ if (style.background !== undefined) {
130
+ const fillIdNum = cloneXf.$.fillId !== undefined ? numParseInt(cloneXf.$.fillId) : undefined;
131
+ const prevFill = fillIdNum !== undefined ? this.data.styleSheet.fills[0].fill[fillIdNum] : undefined;
132
+
133
+ if (prevFill != null) {
134
+ const cloneFill = objClone(prevFill);
135
+ cloneFill.patternFill[0].$.patternType = "solid";
136
+
137
+ if (cloneFill.patternFill[0].fgColor == null) {
138
+ cloneFill.patternFill[0].fgColor = [{ $: { rgb: style.background } }];
139
+ } else {
140
+ cloneFill.patternFill[0].fgColor[0].$.rgb = style.background;
141
+ }
142
+
143
+ cloneXf.$.applyFill = "1";
144
+ cloneXf.$.fillId = this._getSameOrCreateFill(cloneFill);
145
+ } else {
146
+ const newFill: ExcelXmlStyleDataFill = {
147
+ patternFill: [
148
+ {
149
+ $: { patternType: "solid" },
150
+ fgColor: [{ $: { rgb: style.background.toUpperCase() } }],
151
+ },
152
+ ],
153
+ };
154
+ cloneXf.$.applyFill = "1";
155
+ cloneXf.$.fillId = this._getSameOrCreateFill(newFill);
156
+ }
157
+ }
158
+
159
+ if (style.border !== undefined) {
160
+ const borderIdNum = cloneXf.$.borderId !== undefined ? numParseInt(cloneXf.$.borderId) : undefined;
161
+ const prevBorder = borderIdNum !== undefined ? this.data.styleSheet.borders[0].border[borderIdNum] : undefined;
162
+
163
+ if (prevBorder != null) {
164
+ const cloneBorder = objClone(prevBorder);
165
+ this._applyBorderPosition(cloneBorder, "left", style.border.includes("left"));
166
+ this._applyBorderPosition(cloneBorder, "right", style.border.includes("right"));
167
+ this._applyBorderPosition(cloneBorder, "top", style.border.includes("top"));
168
+ this._applyBorderPosition(cloneBorder, "bottom", style.border.includes("bottom"));
169
+
170
+ cloneXf.$.applyBorder = "1";
171
+ cloneXf.$.borderId = this._getSameOrCreateBorder(cloneBorder);
172
+ } else {
173
+ const newBorder = this._createBorderFromPositions(style.border);
174
+ cloneXf.$.applyBorder = "1";
175
+ cloneXf.$.borderId = this._getSameOrCreateBorder(newBorder);
176
+ }
177
+ }
178
+
179
+ this._applyAlignment(cloneXf, style);
180
+
181
+ return this._getSameOrCreateXf(cloneXf);
182
+ }
183
+
184
+ get(id: string): ExcelStyle {
185
+ const idNum = numParseInt(id);
186
+ if (idNum == null) {
187
+ throw new Error(`잘못된 스타일 ID: ${id}`);
188
+ }
189
+ const xf = this.data.styleSheet.cellXfs[0].xf[idNum] as ExcelXmlStyleDataXf | undefined;
190
+
191
+ const result: ExcelStyle = {};
192
+
193
+ if (xf !== undefined) {
194
+ result.numFmtId = xf.$.numFmtId;
195
+
196
+ if (xf.$.fillId !== undefined) {
197
+ const fillIdNum = numParseInt(xf.$.fillId);
198
+ if (fillIdNum != null) {
199
+ const fill = this.data.styleSheet.fills[0].fill[fillIdNum] as ExcelXmlStyleDataFill | undefined;
200
+ if (fill == null) {
201
+ throw new Error(
202
+ `존재하지 않는 fill ID: ${xf.$.fillId} (범위: 0-${this.data.styleSheet.fills[0].fill.length - 1})`,
203
+ );
204
+ }
205
+ result.background = fill.patternFill[0].fgColor?.[0].$.rgb;
206
+ }
207
+ }
208
+
209
+ if (xf.$.borderId !== undefined) {
210
+ const borderIdNum = numParseInt(xf.$.borderId);
211
+ if (borderIdNum == null) {
212
+ throw new Error(`잘못된 border ID: ${xf.$.borderId}`);
213
+ }
214
+ const border = this.data.styleSheet.borders[0].border[borderIdNum] as ExcelXmlStyleDataBorder | undefined;
215
+ if (border == null) {
216
+ throw new Error(
217
+ `존재하지 않는 border ID: ${xf.$.borderId} (범위: 0-${this.data.styleSheet.borders[0].border.length - 1})`,
218
+ );
219
+ }
220
+ if (border.top != null || border.left != null || border.right != null || border.bottom != null) {
221
+ result.border = [];
222
+ if (border.left != null) {
223
+ result.border.push("left");
224
+ }
225
+ if (border.right != null) {
226
+ result.border.push("right");
227
+ }
228
+ if (border.top != null) {
229
+ result.border.push("top");
230
+ }
231
+ if (border.bottom != null) {
232
+ result.border.push("bottom");
233
+ }
234
+ }
235
+ }
236
+
237
+ result.verticalAlign = xf.alignment?.[0].$.vertical;
238
+ result.horizontalAlign = xf.alignment?.[0].$.horizontal;
239
+ }
240
+
241
+ return result;
242
+ }
243
+
244
+ getNumFmtCode(numFmtId: string): string | undefined {
245
+ return (this.data.styleSheet.numFmts?.[0].numFmt ?? []).single((item) => item.$.numFmtId === numFmtId)?.$
246
+ .formatCode;
247
+ }
248
+
249
+ cleanup(): void {
250
+ const result = {} as ExcelXmlStyleData["styleSheet"];
251
+
252
+ // 순서 정렬 (numFmts 맨위로)
253
+
254
+ if (this.data.styleSheet.numFmts != null) {
255
+ result.numFmts = this.data.styleSheet.numFmts;
256
+ }
257
+
258
+ const styleSheetRec = this.data.styleSheet as Record<string, unknown>;
259
+ const resultRec = result as Record<string, unknown>;
260
+ for (const key of Object.keys(styleSheetRec)) {
261
+ if (key === "numFmts") continue;
262
+
263
+ resultRec[key] = styleSheetRec[key];
264
+ }
265
+
266
+ this.data.styleSheet = result;
267
+ }
268
+
269
+ //#region Private Methods
270
+
271
+ private _setNumFmtCode(numFmtCode: string): string {
272
+ // 이미 해당 code가 있으면 넘기기
273
+ const existsNumFmtId = (this.data.styleSheet.numFmts?.[0].numFmt ?? []).single(
274
+ (item) => item.$.formatCode === numFmtCode,
275
+ )?.$.numFmtId;
276
+ if (existsNumFmtId != null) {
277
+ return existsNumFmtId;
278
+ }
279
+
280
+ this.data.styleSheet.numFmts = this.data.styleSheet.numFmts ?? [
281
+ {
282
+ $: { count: "0" },
283
+ numFmt: [],
284
+ },
285
+ ];
286
+
287
+ this.data.styleSheet.numFmts[0].numFmt = this.data.styleSheet.numFmts[0].numFmt ?? [];
288
+
289
+ // Excel 사용자 정의 숫자 형식은 ID 180 이상부터 시작한다 (0-163: 내장 형식, 164-179: 예약됨)
290
+ const numFmts = this.data.styleSheet.numFmts[0].numFmt;
291
+ const maxItem =
292
+ numFmts.length > 0 ? numFmts.orderByDesc((item) => numParseInt(item.$.numFmtId) ?? 180).first() : undefined;
293
+ const maxId = maxItem ? (numParseInt(maxItem.$.numFmtId) ?? 180) : 180;
294
+ const nextNumFmtId = (maxId + 1).toString();
295
+ this.data.styleSheet.numFmts[0].numFmt.push({
296
+ $: {
297
+ numFmtId: nextNumFmtId,
298
+ formatCode: numFmtCode,
299
+ },
300
+ });
301
+ this.data.styleSheet.numFmts[0].$.count = (
302
+ (numParseInt(this.data.styleSheet.numFmts[0].$.count) ?? 0) + 1
303
+ ).toString();
304
+
305
+ return nextNumFmtId;
306
+ }
307
+
308
+ private _applyAlignment(xf: ExcelXmlStyleDataXf, style: ExcelStyle): void {
309
+ if (style.verticalAlign !== undefined) {
310
+ xf.$.applyAlignment = "1";
311
+ if (xf.alignment == null) {
312
+ xf.alignment = [{ $: { vertical: style.verticalAlign } }];
313
+ } else {
314
+ xf.alignment[0].$.vertical = style.verticalAlign;
315
+ }
316
+ }
317
+
318
+ if (style.horizontalAlign !== undefined) {
319
+ xf.$.applyAlignment = "1";
320
+ if (xf.alignment == null) {
321
+ xf.alignment = [{ $: { horizontal: style.horizontalAlign } }];
322
+ } else {
323
+ xf.alignment[0].$.horizontal = style.horizontalAlign;
324
+ }
325
+ }
326
+ }
327
+
328
+ private _createBorderFromPositions(positions: ExcelBorderPosition[]): ExcelXmlStyleDataBorder {
329
+ return {
330
+ ...(positions.includes("left") ? { left: [{ $: { style: "thin" }, color: [{ $: { rgb: "00000000" } }] }] } : {}),
331
+ ...(positions.includes("right")
332
+ ? { right: [{ $: { style: "thin" }, color: [{ $: { rgb: "00000000" } }] }] }
333
+ : {}),
334
+ ...(positions.includes("top") ? { top: [{ $: { style: "thin" }, color: [{ $: { rgb: "00000000" } }] }] } : {}),
335
+ ...(positions.includes("bottom")
336
+ ? { bottom: [{ $: { style: "thin" }, color: [{ $: { rgb: "00000000" } }] }] }
337
+ : {}),
338
+ };
339
+ }
340
+
341
+ private _applyBorderPosition(border: ExcelXmlStyleDataBorder, position: ExcelBorderPosition, enabled: boolean): void {
342
+ if (enabled) {
343
+ const existing = border[position];
344
+ if (existing == null) {
345
+ border[position] = [{ $: { style: "thin" }, color: [{ $: { rgb: "00000000" } }] }];
346
+ } else if (existing[0].color == null) {
347
+ existing[0].color = [{ $: { rgb: "00000000" } }];
348
+ } else {
349
+ existing[0].color[0].$.rgb = "00000000";
350
+ }
351
+ } else {
352
+ delete border[position];
353
+ }
354
+ }
355
+
356
+ private _getSameOrCreateXf(xfItem: ExcelXmlStyleDataXf): string {
357
+ const prevSameXf = this.data.styleSheet.cellXfs[0].xf.single((item) => objEqual(item, xfItem));
358
+
359
+ if (prevSameXf != null) {
360
+ return this.data.styleSheet.cellXfs[0].xf.indexOf(prevSameXf).toString();
361
+ } else {
362
+ this.data.styleSheet.cellXfs[0].xf.push(xfItem);
363
+ this.data.styleSheet.cellXfs[0].$.count = this.data.styleSheet.cellXfs[0].xf.length.toString();
364
+ return (this.data.styleSheet.cellXfs[0].xf.length - 1).toString();
365
+ }
366
+ }
367
+
368
+ private _getSameOrCreateFill(fillItem: ExcelXmlStyleDataFill): string {
369
+ const prevSameFill = this.data.styleSheet.fills[0].fill.single((item) => objEqual(item, fillItem));
370
+
371
+ if (prevSameFill != null) {
372
+ return this.data.styleSheet.fills[0].fill.indexOf(prevSameFill).toString();
373
+ } else {
374
+ this.data.styleSheet.fills[0].fill.push(fillItem);
375
+ this.data.styleSheet.fills[0].$.count = this.data.styleSheet.fills[0].fill.length.toString();
376
+ return (this.data.styleSheet.fills[0].fill.length - 1).toString();
377
+ }
378
+ }
379
+
380
+ private _getSameOrCreateBorder(borderItem: ExcelXmlStyleDataBorder): string {
381
+ const prevSameBorder = this.data.styleSheet.borders[0].border.single((item) => objEqual(item, borderItem));
382
+
383
+ if (prevSameBorder != null) {
384
+ return this.data.styleSheet.borders[0].border.indexOf(prevSameBorder).toString();
385
+ } else {
386
+ this.data.styleSheet.borders[0].border.push(borderItem);
387
+ this.data.styleSheet.borders[0].$.count = this.data.styleSheet.borders[0].border.length.toString();
388
+ return (this.data.styleSheet.borders[0].border.length - 1).toString();
389
+ }
390
+ }
391
+
392
+ //#endregion
393
+ }
@@ -0,0 +1,11 @@
1
+ import type { ExcelXml } from "../types";
2
+
3
+ /**
4
+ * 알 수 없는 형식의 Excel XML 데이터를 보존하는 클래스.
5
+ * 원본 데이터를 손실 없이 유지한다.
6
+ */
7
+ export class ExcelXmlUnknown implements ExcelXml {
8
+ constructor(public readonly data: Record<string, unknown>) {}
9
+
10
+ cleanup(): void {}
11
+ }
@@ -0,0 +1,112 @@
1
+ import "@simplysm/core-common";
2
+ import { numParseInt } from "@simplysm/core-common";
3
+ import type { ExcelXml, ExcelXmlWorkbookData } from "../types";
4
+
5
+ /**
6
+ * xl/workbook.xml 파일을 관리하는 클래스.
7
+ * 워크시트 목록과 관계 ID를 처리한다.
8
+ */
9
+ export class ExcelXmlWorkbook implements ExcelXml {
10
+ data: ExcelXmlWorkbookData;
11
+
12
+ constructor(data?: ExcelXmlWorkbookData) {
13
+ if (data === undefined) {
14
+ this.data = {
15
+ workbook: {
16
+ $: {
17
+ "xmlns": "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
18
+ "xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
19
+ },
20
+ },
21
+ };
22
+ } else {
23
+ this.data = data;
24
+ }
25
+ }
26
+
27
+ get lastWsRelId(): number | undefined {
28
+ const sheets = this.data.workbook.sheets?.[0].sheet;
29
+ if (!sheets || sheets.length === 0) return undefined;
30
+ const maxSheet = sheets.orderByDesc((sheet) => numParseInt(sheet.$["r:id"])!).first();
31
+ return maxSheet ? numParseInt(maxSheet.$["r:id"]) : undefined;
32
+ }
33
+
34
+ get sheetNames(): string[] {
35
+ return this.data.workbook.sheets?.[0].sheet.map((item) => item.$.name) ?? [];
36
+ }
37
+
38
+ addWorksheet(name: string): this {
39
+ const replacedName = this._getReplacedName(name);
40
+
41
+ const newWsRelId = (this.lastWsRelId ?? 0) + 1;
42
+
43
+ this.data.workbook.sheets = this.data.workbook.sheets ?? [{ sheet: [] }];
44
+ this.data.workbook.sheets[0].sheet.push({
45
+ $: {
46
+ "name": replacedName,
47
+ "sheetId": newWsRelId.toString(),
48
+ "r:id": `rId${newWsRelId}`,
49
+ },
50
+ });
51
+
52
+ return this;
53
+ }
54
+
55
+ cleanup(): void {
56
+ const result = {} as ExcelXmlWorkbookData["workbook"];
57
+
58
+ // 순서 정렬 ("sheets"기준 앞뒤로, 나머지는 원래위치대로)
59
+
60
+ const workbookRec = this.data.workbook as Record<string, unknown>;
61
+ const resultRec = result as Record<string, unknown>;
62
+
63
+ for (const key of Object.keys(this.data.workbook)) {
64
+ if (key === "bookViews") continue;
65
+
66
+ if (key === "sheets") {
67
+ if (this.data.workbook.bookViews != null) {
68
+ result.bookViews = this.data.workbook.bookViews;
69
+ }
70
+ result.sheets = this.data.workbook.sheets;
71
+ } else {
72
+ resultRec[key] = workbookRec[key];
73
+ }
74
+ }
75
+
76
+ this.data.workbook = result;
77
+ }
78
+
79
+ initializeView(): void {
80
+ this.data.workbook.bookViews = this.data.workbook.bookViews ?? [{ workbookView: [{}] }];
81
+ }
82
+
83
+ getWsRelIdByName(name: string): number | undefined {
84
+ return numParseInt((this.data.workbook.sheets?.[0].sheet ?? []).single((item) => item.$.name === name)?.$["r:id"]);
85
+ }
86
+
87
+ getWsRelIdByIndex(index: number): number | undefined {
88
+ return numParseInt(this.data.workbook.sheets?.[0].sheet[index]?.$["r:id"]);
89
+ }
90
+
91
+ getWorksheetNameById(id: number): string | undefined {
92
+ return this._getSheetDataById(id)?.$.name;
93
+ }
94
+
95
+ setWorksheetNameById(id: number, newName: string): void {
96
+ const sheetData = this._getSheetDataById(id);
97
+ if (sheetData == null) {
98
+ throw new Error(`워크시트 ID ${id}를 찾을 수 없습니다`);
99
+ }
100
+ const replacedName = this._getReplacedName(newName);
101
+ sheetData.$.name = replacedName;
102
+ }
103
+
104
+ private _getSheetDataById(id: number) {
105
+ return (this.data.workbook.sheets?.[0].sheet ?? []).single((item) => numParseInt(item.$["r:id"]) === id);
106
+ }
107
+
108
+ private _getReplacedName(name: string): string {
109
+ //-- 시트명칭 사용불가 텍스트를 "_"로 변환
110
+ return name.replace(/[:\\/?*\[\]']/g, "_");
111
+ }
112
+ }