@simplysm/excel 14.0.100 → 14.0.102

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,93 +1,224 @@
1
1
  import type { Bytes } from "@simplysm/core-common";
2
- import { ZipArchive, xml as xmlU } from "@simplysm/core-common";
3
- import type {
4
- ExcelXml,
5
- ExcelXmlContentTypeData,
6
- ExcelXmlDrawingData,
7
- ExcelXmlRelationshipData,
8
- ExcelXmlSharedStringData,
9
- ExcelXmlStyleData,
10
- ExcelXmlWorkbookData,
11
- ExcelXmlWorksheetData,
12
- } from "../types";
13
- import { ExcelXmlContentType } from "../xml/excel-xml-content-type";
14
- import { ExcelXmlDrawing } from "../xml/excel-xml-drawing";
15
- import { ExcelXmlRelationship } from "../xml/excel-xml-relationship";
16
- import { ExcelXmlSharedString } from "../xml/excel-xml-shared-string";
17
- import { ExcelXmlStyle } from "../xml/excel-xml-style";
18
- import { ExcelXmlUnknown } from "../xml/excel-xml-unknown";
19
- import { ExcelXmlWorkbook } from "../xml/excel-xml-workbook";
20
- import { ExcelXmlWorksheet } from "../xml/excel-xml-worksheet";
2
+ import { ZipArchive } from "@simplysm/core-common";
3
+ import { BiffModelFactory } from "../biff/biff-model-factory";
4
+ import type { ExcelFormat } from "../models/excel-format";
5
+ import type { IExcelModel } from "../models/excel-model";
6
+ import type { IExcelModelFactory } from "../models/excel-model-factory";
7
+ import type { IContentTypeModel } from "../models/i-content-type-model";
8
+ import type { IDrawingModel } from "../models/i-drawing-model";
9
+ import type { IRelationshipModel } from "../models/i-relationship-model";
10
+ import type { ISharedStringModel } from "../models/i-shared-string-model";
11
+ import type { IStyleModel } from "../models/i-style-model";
12
+ import type { IWorkbookModel } from "../models/i-workbook-model";
13
+ import type { IWorksheetModel } from "../models/i-worksheet-model";
14
+ import { XmlModelFactory } from "../xml/xml-model-factory";
21
15
 
22
16
  /**
23
17
  * Excel ZIP 아카이브의 파일 캐시를 관리하는 클래스.
24
- * XML 파일은 ExcelXml 객체로 파싱하고, 기타 파일은 바이트 배열로 캐싱한다.
18
+ * 모델 파트는 포맷별 팩토리로 파싱/직렬화하고, 기타 파일(media 등)은 바이트 배열로 캐싱한다.
25
19
  *
26
20
  * @remarks
27
- * ## Lazy Loading 캐시 전략
21
+ * ## 포맷 판별·경로 정규화
28
22
  *
29
- * - 파일은 최초 접근 시에만 ZIP에서 읽고 파싱함
30
- * - 이후 접근은 캐싱된 객체를 반환함
31
- * - 대용량 Excel 파일에서 필요한 부분만 로드하여 메모리 효율적
23
+ * 기존 파일은 `xl/workbook.bin` 존재 여부로 xlsx/xlsb 1회 판별한다(없으면 xlsx).
24
+ * 상위 레이어는 `xl/workbook.xml` 같은 OOXML(.xml) 경로를 사용하므로, xlsb 워크북에서는
25
+ * 이를 `.bin` 경로로 정규화하여 상위 레이어가 포맷을 모르게 한다.
26
+ *
27
+ * ## Lazy Loading
28
+ *
29
+ * 파일은 최초 접근 시에만 ZIP 에서 읽고 파싱하며, 이후 접근은 캐싱된 객체를 반환한다.
32
30
  */
33
31
  export class ZipCache {
34
- private readonly _cache = new Map<string, ExcelXml | Bytes | undefined>();
32
+ private readonly _cache = new Map<string, IExcelModel | Bytes | undefined>();
35
33
  private readonly _zip: ZipArchive;
34
+ private _factory: IExcelModelFactory | undefined;
36
35
 
37
- constructor(arg?: Blob | Bytes) {
36
+ constructor(arg?: Blob | Bytes, format?: ExcelFormat) {
38
37
  this._zip = new ZipArchive(arg);
38
+ if (format != null) {
39
+ this._factory = format === "xlsb" ? new BiffModelFactory() : new XmlModelFactory();
40
+ }
41
+ }
42
+
43
+ /** 워크북 포맷. 미판별 시 xlsx 로 간주. */
44
+ get format(): ExcelFormat {
45
+ return (this._factory ??= new XmlModelFactory()).format;
46
+ }
47
+
48
+ /** 기존 파일 포맷을 1회 판별해 팩토리 확정 (async). */
49
+ private async _resolveFactory(): Promise<IExcelModelFactory> {
50
+ if (this._factory == null) {
51
+ const isXlsb = (await this._zip.get("xl/workbook.bin")) != null;
52
+ this._factory = isXlsb ? new BiffModelFactory() : new XmlModelFactory();
53
+ }
54
+ return this._factory;
55
+ }
56
+
57
+ /** 새(빈) 워크북의 동기 생성 경로용 팩토리. 빈 워크북은 xlsx 로 본다. */
58
+ private get _syncFactory(): IExcelModelFactory {
59
+ return (this._factory ??= new XmlModelFactory());
60
+ }
61
+
62
+ /** 상위 레이어의 .xml 고정 경로를 xlsb 의 실제 .bin 경로로 정규화. */
63
+ private _resolvePath(filePath: string): string {
64
+ if (this._factory?.format !== "xlsb") return filePath;
65
+ switch (filePath) {
66
+ case "xl/workbook.xml":
67
+ return "xl/workbook.bin";
68
+ case "xl/sharedStrings.xml":
69
+ return "xl/sharedStrings.bin";
70
+ case "xl/styles.xml":
71
+ return "xl/styles.bin";
72
+ case "xl/_rels/workbook.xml.rels":
73
+ return "xl/_rels/workbook.bin.rels";
74
+ default: {
75
+ const m = /^(xl\/worksheets\/_rels\/sheet\d+)\.xml\.rels$/.exec(filePath);
76
+ return m != null ? `${m[1]}.bin.rels` : filePath;
77
+ }
78
+ }
39
79
  }
40
80
 
41
- async get(filePath: string): Promise<ExcelXml | Bytes | undefined> {
42
- if (this._cache.has(filePath)) {
43
- return this._cache.get(filePath);
81
+ async get(filePath: string): Promise<IExcelModel | Bytes | undefined> {
82
+ const factory = await this._resolveFactory();
83
+ const path = this._resolvePath(filePath);
84
+
85
+ if (this._cache.has(path)) {
86
+ return this._cache.get(path);
44
87
  }
45
88
 
46
- const fileData = await this._zip.get(filePath);
89
+ const fileData = await this._zip.get(path);
47
90
  if (fileData == null) {
48
- this._cache.set(filePath, undefined);
91
+ this._cache.set(path, undefined);
49
92
  return undefined;
50
93
  }
51
94
 
52
- if (filePath.endsWith(".xml") || filePath.endsWith(".rels")) {
53
- const fileText = new TextDecoder().decode(fileData);
54
- const xml = xmlU.parse(fileText, { stripTagPrefix: true });
55
- if (filePath.endsWith(".rels")) {
56
- this._cache.set(filePath, new ExcelXmlRelationship(xml as ExcelXmlRelationshipData));
57
- } else if (filePath === "[Content_Types].xml") {
58
- this._cache.set(filePath, new ExcelXmlContentType(xml as ExcelXmlContentTypeData));
59
- } else if (filePath === "xl/workbook.xml") {
60
- this._cache.set(filePath, new ExcelXmlWorkbook(xml as ExcelXmlWorkbookData));
61
- } else if (filePath.startsWith("xl/worksheets/sheet") && filePath.endsWith(".xml")) {
62
- this._cache.set(filePath, new ExcelXmlWorksheet(xml as ExcelXmlWorksheetData));
63
- } else if (filePath.startsWith("xl/drawings/drawing") && filePath.endsWith(".xml")) {
64
- this._cache.set(filePath, new ExcelXmlDrawing(xml as ExcelXmlDrawingData));
65
- } else if (filePath === "xl/sharedStrings.xml") {
66
- this._cache.set(filePath, new ExcelXmlSharedString(xml as ExcelXmlSharedStringData));
67
- } else if (filePath === "xl/styles.xml") {
68
- this._cache.set(filePath, new ExcelXmlStyle(xml as ExcelXmlStyleData));
69
- } else {
70
- this._cache.set(filePath, new ExcelXmlUnknown(xml as Record<string, unknown>));
71
- }
95
+ if (factory.isModelPart(path)) {
96
+ this._cache.set(path, factory.parse(path, fileData));
72
97
  } else {
73
- this._cache.set(filePath, fileData);
98
+ this._cache.set(path, fileData);
74
99
  }
75
100
 
76
- return this._cache.get(filePath);
101
+ return this._cache.get(path);
102
+ }
103
+
104
+ set(filePath: string, content: IExcelModel | Bytes): void {
105
+ this._cache.set(this._resolvePath(filePath), content);
106
+ }
107
+
108
+ //#region Factory Delegation (빈 모델 생성)
109
+
110
+ createWorkbook(): IWorkbookModel {
111
+ return this._syncFactory.createWorkbook();
77
112
  }
113
+ createWorksheet(): IWorksheetModel {
114
+ return this._syncFactory.createWorksheet();
115
+ }
116
+ createStyle(): IStyleModel {
117
+ return this._syncFactory.createStyle();
118
+ }
119
+ createSharedString(): ISharedStringModel {
120
+ return this._syncFactory.createSharedString();
121
+ }
122
+ createContentType(): IContentTypeModel {
123
+ return this._syncFactory.createContentType();
124
+ }
125
+ createRelationship(): IRelationshipModel {
126
+ return this._syncFactory.createRelationship();
127
+ }
128
+ createDrawing(): IDrawingModel {
129
+ return this._syncFactory.createDrawing();
130
+ }
131
+
132
+ //#endregion
133
+
134
+ //#region Part 등록 (포맷별 content-type·rel·part 생성)
135
+
136
+ /**
137
+ * 워크시트 파트를 등록한다. content-type override, workbook rels, 빈 worksheet 모델을 포맷에 맞게 생성하고
138
+ * worksheet 파일명(`sheetN.xml` | `sheetN.bin`)을 반환한다.
139
+ */
140
+ async registerWorksheet(relId: number): Promise<string> {
141
+ const xlsb = this.format === "xlsb";
142
+ const fileName = `sheet${relId}.${xlsb ? "bin" : "xml"}`;
143
+
144
+ const typeXml = (await this.get("[Content_Types].xml")) as IContentTypeModel;
145
+ typeXml.add(
146
+ `/xl/worksheets/${fileName}`,
147
+ xlsb
148
+ ? "application/vnd.ms-excel.worksheet"
149
+ : "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
150
+ );
151
+
152
+ const wbRel = (await this.get("xl/_rels/workbook.xml.rels")) as IRelationshipModel;
153
+ wbRel.insert(
154
+ relId,
155
+ `worksheets/${fileName}`,
156
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
157
+ );
78
158
 
79
- set(filePath: string, content: ExcelXml | Bytes): void {
80
- this._cache.set(filePath, content);
159
+ this.set(`xl/worksheets/${fileName}`, this.createWorksheet());
160
+ return fileName;
81
161
  }
82
162
 
163
+ /** sharedStrings 파트를 보장(없으면 생성+등록)하고 모델을 반환한다. */
164
+ async ensureSharedStrings(): Promise<ISharedStringModel> {
165
+ let ss = (await this.get("xl/sharedStrings.xml")) as ISharedStringModel | undefined;
166
+ if (ss == null) {
167
+ const xlsb = this.format === "xlsb";
168
+ ss = this.createSharedString();
169
+ this.set("xl/sharedStrings.xml", ss);
170
+
171
+ const typeXml = (await this.get("[Content_Types].xml")) as IContentTypeModel;
172
+ typeXml.add(
173
+ `/xl/sharedStrings.${xlsb ? "bin" : "xml"}`,
174
+ xlsb
175
+ ? "application/vnd.ms-excel.sharedStrings"
176
+ : "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
177
+ );
178
+
179
+ const wbRel = (await this.get("xl/_rels/workbook.xml.rels")) as IRelationshipModel;
180
+ wbRel.add(
181
+ `sharedStrings.${xlsb ? "bin" : "xml"}`,
182
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
183
+ );
184
+ }
185
+ return ss;
186
+ }
187
+
188
+ /** styles 파트를 보장(없으면 생성+등록)하고 모델을 반환한다. */
189
+ async ensureStyles(): Promise<IStyleModel> {
190
+ let st = (await this.get("xl/styles.xml")) as IStyleModel | undefined;
191
+ if (st == null) {
192
+ const xlsb = this.format === "xlsb";
193
+ st = this.createStyle();
194
+ this.set("xl/styles.xml", st);
195
+
196
+ const typeXml = (await this.get("[Content_Types].xml")) as IContentTypeModel;
197
+ typeXml.add(
198
+ `/xl/styles.${xlsb ? "bin" : "xml"}`,
199
+ xlsb
200
+ ? "application/vnd.ms-excel.styles"
201
+ : "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
202
+ );
203
+
204
+ const wbRel = (await this.get("xl/_rels/workbook.xml.rels")) as IRelationshipModel;
205
+ wbRel.add(
206
+ `styles.${xlsb ? "bin" : "xml"}`,
207
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
208
+ );
209
+ }
210
+ return st;
211
+ }
212
+
213
+ //#endregion
214
+
83
215
  async toBytes(): Promise<Bytes> {
84
216
  for (const filePath of this._cache.keys()) {
85
217
  const content = this._cache.get(filePath);
86
218
  if (content == null) continue;
87
219
 
88
- if ("cleanup" in content) {
89
- content.cleanup();
90
- this._zip.write(filePath, new TextEncoder().encode(xmlU.stringify(content.data)));
220
+ if ("serialize" in content) {
221
+ this._zip.write(filePath, content.serialize());
91
222
  } else {
92
223
  this._zip.write(filePath, content);
93
224
  }
@@ -1,15 +1,18 @@
1
- import type { ExcelXml, ExcelXmlContentTypeData } from "../types";
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ import { xml as xmlU } from "@simplysm/core-common";
3
+ import type { IContentTypeModel } from "../models/i-content-type-model";
4
+ import type { ExcelXmlContentTypeData } from "../types";
2
5
 
3
6
  /**
4
7
  * [Content_Types].xml을 관리하는 클래스.
5
8
  * 파일별 MIME 타입 정보를 관리한다.
6
9
  */
7
- export class ExcelXmlContentType implements ExcelXml {
8
- data: ExcelXmlContentTypeData;
10
+ export class ExcelXmlContentType implements IContentTypeModel {
11
+ private readonly _data: ExcelXmlContentTypeData;
9
12
 
10
13
  constructor(data?: ExcelXmlContentTypeData) {
11
14
  if (data == null) {
12
- this.data = {
15
+ this._data = {
13
16
  Types: {
14
17
  $: {
15
18
  xmlns: "http://schemas.openxmlformats.org/package/2006/content-types",
@@ -40,18 +43,23 @@ export class ExcelXmlContentType implements ExcelXml {
40
43
  },
41
44
  };
42
45
  } else {
43
- this.data = data;
46
+ this._data = data;
44
47
  }
45
48
  }
46
49
 
50
+ /** @internal 테스트·디버그용 내부 트리 접근. 상위 레이어는 인터페이스만 사용. */
51
+ get data(): ExcelXmlContentTypeData {
52
+ return this._data;
53
+ }
54
+
47
55
  add(partName: string, contentType: string): this {
48
56
  // 중복 검사
49
- const exists = this.data.Types.Override.some((item) => item.$.PartName === partName);
57
+ const exists = this._data.Types.Override.some((item) => item.$.PartName === partName);
50
58
  if (exists) {
51
59
  return this;
52
60
  }
53
61
 
54
- this.data.Types.Override.push({
62
+ this._data.Types.Override.push({
55
63
  $: {
56
64
  PartName: partName,
57
65
  ContentType: contentType,
@@ -61,5 +69,7 @@ export class ExcelXmlContentType implements ExcelXml {
61
69
  return this;
62
70
  }
63
71
 
64
- cleanup(): void {}
72
+ serialize(): Bytes {
73
+ return new TextEncoder().encode(xmlU.stringify(this._data));
74
+ }
65
75
  }
@@ -1,15 +1,18 @@
1
- import type { ExcelXml, ExcelXmlDrawingData } from "../types";
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ import { xml as xmlU } from "@simplysm/core-common";
3
+ import type { IDrawingModel } from "../models/i-drawing-model";
4
+ import type { ExcelXmlDrawingData } from "../types";
2
5
 
3
6
  /**
4
7
  * xl/drawings/drawing*.xml 파일을 관리하는 클래스.
5
8
  * 이미지 삽입을 위한 위치 및 참조 정보를 처리한다.
6
9
  */
7
- export class ExcelXmlDrawing implements ExcelXml {
8
- data: ExcelXmlDrawingData;
10
+ export class ExcelXmlDrawing implements IDrawingModel {
11
+ private readonly _data: ExcelXmlDrawingData;
9
12
 
10
13
  constructor(data?: ExcelXmlDrawingData) {
11
14
  if (data == null) {
12
- this.data = {
15
+ this._data = {
13
16
  wsDr: {
14
17
  $: {
15
18
  "xmlns": "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
@@ -20,22 +23,27 @@ export class ExcelXmlDrawing implements ExcelXml {
20
23
  },
21
24
  };
22
25
  } else {
23
- this.data = data;
26
+ this._data = data;
24
27
  }
25
28
  }
26
29
 
30
+ /** @internal 테스트·디버그용 내부 트리 접근. 상위 레이어는 인터페이스만 사용. */
31
+ get data(): ExcelXmlDrawingData {
32
+ return this._data;
33
+ }
34
+
27
35
  addPicture(opts: {
28
36
  from: { r: number; c: number; rOff?: number | string; cOff?: number | string };
29
37
  to: { r: number; c: number; rOff?: number | string; cOff?: number | string };
30
38
  blipRelId: string;
31
39
  }): void {
32
- this.data.wsDr.twoCellAnchor = this.data.wsDr.twoCellAnchor ?? [];
40
+ this._data.wsDr.twoCellAnchor = this._data.wsDr.twoCellAnchor ?? [];
33
41
 
34
- const anchors = this.data.wsDr.twoCellAnchor;
42
+ const anchors = this._data.wsDr.twoCellAnchor;
35
43
  const picId = anchors.length + 1;
36
44
  const name = `Picture ${picId}`;
37
45
 
38
- this.data.wsDr.twoCellAnchor.push({
46
+ this._data.wsDr.twoCellAnchor.push({
39
47
  from: [
40
48
  {
41
49
  col: [opts.from.c.toString()],
@@ -83,5 +91,7 @@ export class ExcelXmlDrawing implements ExcelXml {
83
91
  });
84
92
  }
85
93
 
86
- cleanup(): void {}
94
+ serialize(): Bytes {
95
+ return new TextEncoder().encode(xmlU.stringify(this._data));
96
+ }
87
97
  }
@@ -1,17 +1,19 @@
1
1
  import "@simplysm/core-common";
2
- import { num } from "@simplysm/core-common";
3
- import type { ExcelRelationshipData, ExcelXml, ExcelXmlRelationshipData } from "../types";
2
+ import type { Bytes } from "@simplysm/core-common";
3
+ import { num, xml as xmlU } from "@simplysm/core-common";
4
+ import type { IRelationshipModel } from "../models/i-relationship-model";
5
+ import type { ExcelRelationshipData, ExcelXmlRelationshipData } from "../types";
4
6
 
5
7
  /**
6
8
  * *.rels 파일을 관리하는 클래스.
7
9
  * 파일 간의 참조 관계를 처리한다.
8
10
  */
9
- export class ExcelXmlRelationship implements ExcelXml {
10
- data: ExcelXmlRelationshipData;
11
+ export class ExcelXmlRelationship implements IRelationshipModel {
12
+ private readonly _data: ExcelXmlRelationshipData;
11
13
 
12
14
  constructor(data?: ExcelXmlRelationshipData) {
13
15
  if (data == null) {
14
- this.data = {
16
+ this._data = {
15
17
  Relationships: {
16
18
  $: {
17
19
  xmlns: "http://schemas.openxmlformats.org/package/2006/relationships",
@@ -19,12 +21,17 @@ export class ExcelXmlRelationship implements ExcelXml {
19
21
  },
20
22
  };
21
23
  } else {
22
- this.data = data;
24
+ this._data = data;
23
25
  }
24
26
  }
25
27
 
28
+ /** @internal 테스트·디버그용 내부 트리 접근. 상위 레이어는 인터페이스만 사용. */
29
+ get data(): ExcelXmlRelationshipData {
30
+ return this._data;
31
+ }
32
+
26
33
  getTargetByRelId(rId: number): string | undefined {
27
- return (this.data.Relationships.Relationship ?? []).single((rel) => this._getRelId(rel) === rId)
34
+ return (this._data.Relationships.Relationship ?? []).single((rel) => this._getRelId(rel) === rId)
28
35
  ?.$.Target;
29
36
  }
30
37
 
@@ -34,11 +41,11 @@ export class ExcelXmlRelationship implements ExcelXml {
34
41
  }
35
42
 
36
43
  addAndGetId(target: string, type: string): number {
37
- this.data.Relationships.Relationship = this.data.Relationships.Relationship ?? [];
44
+ this._data.Relationships.Relationship = this._data.Relationships.Relationship ?? [];
38
45
 
39
46
  const newId = (this._lastId ?? 0) + 1;
40
47
 
41
- this.data.Relationships.Relationship.push({
48
+ this._data.Relationships.Relationship.push({
42
49
  $: {
43
50
  Id: `rId${newId}`,
44
51
  Target: target,
@@ -50,16 +57,16 @@ export class ExcelXmlRelationship implements ExcelXml {
50
57
  }
51
58
 
52
59
  insert(rId: number, target: string, type: string): this {
53
- this.data.Relationships.Relationship = this.data.Relationships.Relationship ?? [];
60
+ this._data.Relationships.Relationship = this._data.Relationships.Relationship ?? [];
54
61
 
55
- const shiftRels = this.data.Relationships.Relationship.filter(
62
+ const shiftRels = this._data.Relationships.Relationship.filter(
56
63
  (rel) => this._getRelId(rel) >= rId,
57
64
  );
58
65
  for (const shiftRel of shiftRels) {
59
66
  shiftRel.$.Id = `rId${this._getRelId(shiftRel) + 1}`;
60
67
  }
61
68
 
62
- this.data.Relationships.Relationship.push({
69
+ this._data.Relationships.Relationship.push({
63
70
  $: {
64
71
  Id: `rId${rId}`,
65
72
  Target: target,
@@ -70,10 +77,17 @@ export class ExcelXmlRelationship implements ExcelXml {
70
77
  return this;
71
78
  }
72
79
 
73
- cleanup(): void {}
80
+ findRelByType(type: string): { relId: string; target: string } | undefined {
81
+ const rel = (this._data.Relationships.Relationship ?? []).find((r) => r.$.Type === type);
82
+ return rel != null ? { relId: rel.$.Id, target: rel.$.Target } : undefined;
83
+ }
84
+
85
+ serialize(): Bytes {
86
+ return new TextEncoder().encode(xmlU.stringify(this._data));
87
+ }
74
88
 
75
89
  private get _lastId(): number | undefined {
76
- const rels = this.data.Relationships.Relationship;
90
+ const rels = this._data.Relationships.Relationship;
77
91
  if (!rels || rels.length === 0) return undefined;
78
92
  const maxRel = rels.orderByDesc((rel) => this._getRelId(rel)).first();
79
93
  return maxRel ? this._getRelId(maxRel) : undefined;
@@ -1,5 +1,7 @@
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ import { xml as xmlU } from "@simplysm/core-common";
3
+ import type { ISharedStringModel } from "../models/i-shared-string-model";
1
4
  import type {
2
- ExcelXml,
3
5
  ExcelXmlSharedStringData,
4
6
  ExcelXmlSharedStringDataSi,
5
7
  ExcelXmlSharedStringDataText,
@@ -10,14 +12,14 @@ import "@simplysm/core-common";
10
12
  * xl/sharedStrings.xml을 관리하는 클래스.
11
13
  * 문자열 중복을 방지하여 파일 크기를 최적화한다.
12
14
  */
13
- export class ExcelXmlSharedString implements ExcelXml {
14
- data: ExcelXmlSharedStringData;
15
+ export class ExcelXmlSharedString implements ISharedStringModel {
16
+ private readonly _data: ExcelXmlSharedStringData;
15
17
 
16
18
  private readonly _stringIndexesMap: Map<string, number[]>;
17
19
 
18
20
  constructor(data?: ExcelXmlSharedStringData) {
19
21
  if (data == null) {
20
- this.data = {
22
+ this._data = {
21
23
  sst: {
22
24
  $: {
23
25
  xmlns: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
@@ -25,11 +27,11 @@ export class ExcelXmlSharedString implements ExcelXml {
25
27
  },
26
28
  };
27
29
  } else {
28
- this.data = data;
30
+ this._data = data;
29
31
  }
30
32
 
31
- this._stringIndexesMap = this.data.sst.si
32
- ? this.data.sst.si
33
+ this._stringIndexesMap = this._data.sst.si
34
+ ? this._data.sst.si
33
35
  .map((tag, id) => ({ id, tag }))
34
36
  .filter((item) => !this._getHasInnerStyleOnSiTag(item.tag))
35
37
  .toArrayMap(
@@ -39,24 +41,31 @@ export class ExcelXmlSharedString implements ExcelXml {
39
41
  : new Map<string, number[]>();
40
42
  }
41
43
 
44
+ /** @internal 테스트·디버그용 내부 트리 접근. 상위 레이어는 인터페이스만 사용. */
45
+ get data(): ExcelXmlSharedStringData {
46
+ return this._data;
47
+ }
48
+
42
49
  getIdByString(str: string): number | undefined {
43
50
  return this._stringIndexesMap.get(str)?.[0];
44
51
  }
45
52
 
46
53
  getStringById(id: number): string | undefined {
47
- const si = this.data.sst.si?.[id];
54
+ const si = this._data.sst.si?.[id];
48
55
  return si != null ? this._getStringFromSiTag(si) : undefined;
49
56
  }
50
57
 
51
58
  add(str: string): number {
52
- this.data.sst.si = this.data.sst.si ?? [];
53
- const newLength = this.data.sst.si.push({ t: [str] });
59
+ this._data.sst.si = this._data.sst.si ?? [];
60
+ const newLength = this._data.sst.si.push({ t: [str] });
54
61
  const arr = this._stringIndexesMap.getOrCreate(str, []);
55
62
  arr.push(newLength - 1);
56
63
  return newLength - 1;
57
64
  }
58
65
 
59
- cleanup(): void {}
66
+ serialize(): Bytes {
67
+ return new TextEncoder().encode(xmlU.stringify(this._data));
68
+ }
60
69
 
61
70
  private _getStringFromSiTag(si: ExcelXmlSharedStringDataSi): string {
62
71
  if ("t" in si) {