@simplysm/excel 14.0.100 → 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
@@ -5,24 +5,22 @@ import mime from "mime";
5
5
  import { ExcelCell } from "./excel-cell";
6
6
  import { ExcelCol } from "./excel-col";
7
7
  import { ExcelRow } from "./excel-row";
8
+ import type { IContentTypeModel } from "./models/i-content-type-model";
9
+ import type { IDrawingModel } from "./models/i-drawing-model";
10
+ import type { IRelationshipModel } from "./models/i-relationship-model";
11
+ import type { ISharedStringModel } from "./models/i-shared-string-model";
12
+ import type { IStyleModel } from "./models/i-style-model";
13
+ import type { IWorkbookModel } from "./models/i-workbook-model";
14
+ import type { IWorksheetModel } from "./models/i-worksheet-model";
15
+ import type { ICfRuleSpec } from "./models/shared/excel-cf-spec";
8
16
  import type {
9
17
  ExcelAddressPoint,
10
18
  ExcelAddressRangePoint,
11
19
  ExcelConditionalRule,
12
20
  ExcelValueType,
13
- ExcelXmlCfRuleData,
14
21
  } from "./types";
15
22
  import { ExcelUtils } from "./utils/excel-utils";
16
23
  import type { ZipCache } from "./utils/zip-cache";
17
- import type { ExcelXmlContentType } from "./xml/excel-xml-content-type";
18
- import { ExcelXmlDrawing } from "./xml/excel-xml-drawing";
19
- import { ExcelXmlRelationship } from "./xml/excel-xml-relationship";
20
- import type { ExcelXmlSharedString } from "./xml/excel-xml-shared-string";
21
- import { ExcelXmlSharedString as ExcelXmlSharedStringClass } from "./xml/excel-xml-shared-string";
22
- import type { ExcelXmlStyle } from "./xml/excel-xml-style";
23
- import { ExcelXmlStyle as ExcelXmlStyleClass } from "./xml/excel-xml-style";
24
- import type { ExcelXmlWorkbook } from "./xml/excel-xml-workbook";
25
- import type { ExcelXmlWorksheet } from "./xml/excel-xml-worksheet";
26
24
 
27
25
  /**
28
26
  * Excel 워크시트를 나타내는 클래스.
@@ -339,17 +337,6 @@ export class ExcelWorksheet {
339
337
  * @remarks
340
338
  * 같은 시트에 여러 번 호출하면 호출마다 `<conditionalFormatting>` 블록이 누적된다.
341
339
  * 정적 셀 스타일과 조건부 서식의 합성은 Excel native CF 오버레이에 위임한다.
342
- *
343
- * @example
344
- * ```typescript
345
- * await ws.addConditionalFormat({
346
- * ref: "B2:B100",
347
- * rules: [
348
- * { type: "cellIs", op: "<", value: 1000, style: { background: "00FF0000" } },
349
- * { type: "cellIs", op: "<", value: 4999, style: { background: "00FFFF00" } },
350
- * ],
351
- * });
352
- * ```
353
340
  */
354
341
  async addConditionalFormat(opts: {
355
342
  ref: string;
@@ -361,15 +348,7 @@ export class ExcelWorksheet {
361
348
  const styleData = await this._ensureStyleData();
362
349
  const topLeft = opts.ref.split(":")[0];
363
350
 
364
- const dxfRules: {
365
- dxfId: string;
366
- cfRule: {
367
- type: ExcelXmlCfRuleData["$"]["type"];
368
- operator?: ExcelXmlCfRuleData["$"]["operator"];
369
- formula: string[];
370
- text?: string;
371
- };
372
- }[] = [];
351
+ const dxfRules: { dxfId: string; cfRule: ICfRuleSpec }[] = [];
373
352
 
374
353
  for (const rule of opts.rules) {
375
354
  const dxfId = styleData.addDxf(rule.style);
@@ -383,12 +362,7 @@ export class ExcelWorksheet {
383
362
  private static _buildCfRuleSpec(
384
363
  rule: ExcelConditionalRule,
385
364
  topLeft: string,
386
- ): {
387
- type: ExcelXmlCfRuleData["$"]["type"];
388
- operator?: ExcelXmlCfRuleData["$"]["operator"];
389
- formula: string[];
390
- text?: string;
391
- } {
365
+ ): ICfRuleSpec {
392
366
  const encodeLiteral = (v: number | string): string =>
393
367
  typeof v === "number" ? v.toString() : `"${v.replaceAll('"', '""')}"`;
394
368
 
@@ -434,7 +408,7 @@ export class ExcelWorksheet {
434
408
  }
435
409
  }
436
410
 
437
- const operator: ExcelXmlCfRuleData["$"]["operator"] = (() => {
411
+ const operator: ICfRuleSpec["operator"] = (() => {
438
412
  switch (rule.op) {
439
413
  case "<":
440
414
  return "lessThan";
@@ -493,36 +467,34 @@ export class ExcelWorksheet {
493
467
  this._zipCache.set(mediaPath, opts.bytes);
494
468
 
495
469
  // 2. [Content_Types].xml 갱신
496
- const typeXml = (await this._zipCache.get("[Content_Types].xml")) as ExcelXmlContentType;
470
+ const typeXml = (await this._zipCache.get("[Content_Types].xml")) as IContentTypeModel;
497
471
  typeXml.add(`/xl/media/image${mediaIndex}.${opts.ext}`, mimeType);
498
472
 
499
473
  // 3. 워크시트의 기존 drawing 확인
500
474
  const wsXml = await this._getWsData();
501
475
  const sheetRelsPath = `xl/worksheets/_rels/${this._targetFileName}.rels`;
502
- let sheetRels = (await this._zipCache.get(sheetRelsPath)) as ExcelXmlRelationship | undefined;
476
+ let sheetRels = (await this._zipCache.get(sheetRelsPath)) as IRelationshipModel | undefined;
503
477
 
504
478
  // 기존 drawing 찾기
505
479
  let drawingIndex: number | undefined;
506
480
  let drawingPath: string | undefined;
507
- let drawing: ExcelXmlDrawing | undefined;
508
- let drawingRels: ExcelXmlRelationship | undefined;
481
+ let drawing: IDrawingModel | undefined;
482
+ let drawingRels: IRelationshipModel | undefined;
509
483
 
510
484
  if (sheetRels != null) {
511
- const existingDrawingRel = sheetRels.data.Relationships.Relationship?.find(
512
- (r) =>
513
- r.$.Type ===
514
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
485
+ const existingDrawingRel = sheetRels.findRelByType(
486
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
515
487
  );
516
488
  if (existingDrawingRel != null) {
517
489
  // 기존 drawing 경로에서 인덱스 추출
518
- const match = existingDrawingRel.$.Target.match(/drawing(\d+)\.xml$/);
490
+ const match = existingDrawingRel.target.match(/drawing(\d+)\.xml$/);
519
491
  if (match != null) {
520
492
  drawingIndex = parseInt(match[1], 10);
521
493
  drawingPath = `xl/drawings/drawing${drawingIndex}.xml`;
522
- drawing = (await this._zipCache.get(drawingPath)) as ExcelXmlDrawing | undefined;
494
+ drawing = (await this._zipCache.get(drawingPath)) as IDrawingModel | undefined;
523
495
  drawingRels = (await this._zipCache.get(
524
496
  `xl/drawings/_rels/drawing${drawingIndex}.xml.rels`,
525
- )) as ExcelXmlRelationship | undefined;
497
+ )) as IRelationshipModel | undefined;
526
498
  }
527
499
  }
528
500
  }
@@ -534,13 +506,13 @@ export class ExcelWorksheet {
534
506
  drawingIndex++;
535
507
  }
536
508
  drawingPath = `xl/drawings/drawing${drawingIndex}.xml`;
537
- drawing = new ExcelXmlDrawing();
509
+ drawing = this._zipCache.createDrawing();
538
510
 
539
511
  // [Content_Types].xml에 drawing 타입 추가
540
512
  typeXml.add("/" + drawingPath, "application/vnd.openxmlformats-officedocument.drawing+xml");
541
513
 
542
514
  // 워크시트 rels에 drawing 추가
543
- sheetRels = sheetRels ?? new ExcelXmlRelationship();
515
+ sheetRels = sheetRels ?? this._zipCache.createRelationship();
544
516
  const sheetRelNum = sheetRels.addAndGetId(
545
517
  `../drawings/drawing${drawingIndex}.xml`,
546
518
  "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
@@ -549,16 +521,12 @@ export class ExcelWorksheet {
549
521
  this._zipCache.set(sheetRelsPath, sheetRels);
550
522
 
551
523
  // 워크시트 XML에 drawing 추가
552
- wsXml.data.worksheet.$["xmlns:r"] =
553
- wsXml.data.worksheet.$["xmlns:r"] ??
554
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
555
- wsXml.data.worksheet.drawing = wsXml.data.worksheet.drawing ?? [];
556
- wsXml.data.worksheet.drawing.push({ $: { "r:id": drawingRelIdOnWorksheet } });
524
+ wsXml.setDrawingRelId(drawingRelIdOnWorksheet);
557
525
  this._zipCache.set(`xl/worksheets/${this._targetFileName}`, wsXml);
558
526
  }
559
527
 
560
528
  // 5. drawing rels 준비 (없으면 생성)
561
- drawingRels = drawingRels ?? new ExcelXmlRelationship();
529
+ drawingRels = drawingRels ?? this._zipCache.createRelationship();
562
530
  const mediaFileName = mediaPath.slice(3);
563
531
  const drawingTarget = `../${mediaFileName}`;
564
532
  const relNum = drawingRels.addAndGetId(
@@ -581,66 +549,27 @@ export class ExcelWorksheet {
581
549
 
582
550
  //#region Private Methods
583
551
 
584
- private async _getWsData(): Promise<ExcelXmlWorksheet> {
585
- return (await this._zipCache.get(`xl/worksheets/${this._targetFileName}`)) as ExcelXmlWorksheet;
552
+ private async _getWsData(): Promise<IWorksheetModel> {
553
+ return (await this._zipCache.get(`xl/worksheets/${this._targetFileName}`)) as IWorksheetModel;
586
554
  }
587
555
 
588
- private async _getWbData(): Promise<ExcelXmlWorkbook> {
589
- return (await this._zipCache.get("xl/workbook.xml")) as ExcelXmlWorkbook;
556
+ private async _getWbData(): Promise<IWorkbookModel> {
557
+ return (await this._zipCache.get("xl/workbook.xml")) as IWorkbookModel;
590
558
  }
591
559
 
592
- private async _ensureSsData(): Promise<ExcelXmlSharedString> {
593
- let ssData = (await this._zipCache.get("xl/sharedStrings.xml")) as
594
- | ExcelXmlSharedString
595
- | undefined;
596
- if (ssData == null) {
597
- ssData = new ExcelXmlSharedStringClass();
598
- this._zipCache.set("xl/sharedStrings.xml", ssData);
599
-
600
- const typeData = (await this._zipCache.get("[Content_Types].xml")) as ExcelXmlContentType;
601
- typeData.add(
602
- "/xl/sharedStrings.xml",
603
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
604
- );
605
-
606
- const wbRelData = (await this._zipCache.get(
607
- "xl/_rels/workbook.xml.rels",
608
- )) as ExcelXmlRelationship;
609
- wbRelData.add(
610
- "sharedStrings.xml",
611
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
612
- );
613
- }
614
- return ssData;
560
+ private async _ensureSsData(): Promise<ISharedStringModel> {
561
+ return this._zipCache.ensureSharedStrings();
615
562
  }
616
563
 
617
- private async _ensureStyleData(): Promise<ExcelXmlStyle> {
618
- let styleData = (await this._zipCache.get("xl/styles.xml")) as ExcelXmlStyle | undefined;
619
- if (styleData == null) {
620
- styleData = new ExcelXmlStyleClass();
621
- this._zipCache.set("xl/styles.xml", styleData);
622
-
623
- const typeData = (await this._zipCache.get("[Content_Types].xml")) as ExcelXmlContentType;
624
- typeData.add(
625
- "/xl/styles.xml",
626
- "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
627
- );
628
-
629
- const wbRelData = (await this._zipCache.get(
630
- "xl/_rels/workbook.xml.rels",
631
- )) as ExcelXmlRelationship;
632
- wbRelData.add(
633
- "styles.xml",
634
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
635
- );
636
- }
637
- return styleData;
564
+ private async _ensureStyleData(): Promise<IStyleModel> {
565
+ // styles 파트 content-type·rel 은 포맷(xlsx/.xml, xlsb/.bin) 맞춰야 하므로 zipCache 에 위임.
566
+ return this._zipCache.ensureStyles();
638
567
  }
639
568
 
640
569
  private _setCellValueSync(
641
- wsData: ExcelXmlWorksheet,
642
- ssData: ExcelXmlSharedString,
643
- styleData: ExcelXmlStyle,
570
+ wsData: IWorksheetModel,
571
+ ssData: ISharedStringModel,
572
+ styleData: IStyleModel,
644
573
  addr: ExcelAddressPoint,
645
574
  val: ExcelValueType,
646
575
  ): void {
@@ -97,16 +97,6 @@ export class ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>> {
97
97
  * @remarks
98
98
  * 반환된 워크북의 리소스 관리는 호출자의 책임이다.
99
99
  * 사용 후 `close()`를 호출해야 한다.
100
- *
101
- * @example
102
- * ```typescript
103
- * const wb = await wrapper.write("Sheet1", records);
104
- * try {
105
- * const bytes = await wb.toBytes();
106
- * } finally {
107
- * await wb.close();
108
- * }
109
- * ```
110
100
  */
111
101
  async write(
112
102
  wsName: string,
@@ -0,0 +1,2 @@
1
+ /** 워크북 컨테이너 포맷. xlsx = OOXML XML, xlsb = BIFF12 바이너리. */
2
+ export type ExcelFormat = "xlsx" | "xlsb";
@@ -0,0 +1,33 @@
1
+ import type { Bytes } from "@simplysm/core-common";
2
+ import type { ExcelFormat } from "./excel-format";
3
+ import type { IExcelModel } from "./excel-model";
4
+ import type { IContentTypeModel } from "./i-content-type-model";
5
+ import type { IDrawingModel } from "./i-drawing-model";
6
+ import type { IRelationshipModel } from "./i-relationship-model";
7
+ import type { ISharedStringModel } from "./i-shared-string-model";
8
+ import type { IStyleModel } from "./i-style-model";
9
+ import type { IWorkbookModel } from "./i-workbook-model";
10
+ import type { IWorksheetModel } from "./i-worksheet-model";
11
+
12
+ /**
13
+ * 포맷별 파트 모델 팩토리 계약.
14
+ *
15
+ * - `createXxx()`: 빈(신규) 파트 모델 생성 — 상위 레이어의 `new XmlXxx()` 직접 생성을 대체.
16
+ * - `parse()`: ZIP 내부 파일 바이트를 경로 패턴에 맞는 파트 모델로 역직렬화 — `ZipCache.get` 의 분기 대체.
17
+ *
18
+ * `ZipCache` 는 워크북 포맷에 맞는 팩토리 1개를 보유하고 모든 파트 생성·복원을 위임한다.
19
+ */
20
+ export interface IExcelModelFactory {
21
+ readonly format: ExcelFormat;
22
+ /** 이 파일 경로를 모델로 파싱할 파트인지(true) 원시 바이트로 둘지(false) 판정. */
23
+ isModelPart(filePath: string): boolean;
24
+ createWorkbook(): IWorkbookModel;
25
+ createWorksheet(): IWorksheetModel;
26
+ createStyle(): IStyleModel;
27
+ createSharedString(): ISharedStringModel;
28
+ createContentType(): IContentTypeModel;
29
+ createRelationship(): IRelationshipModel;
30
+ createDrawing(): IDrawingModel;
31
+ /** 경로 패턴 → 적절한 파트 모델로 역직렬화. 매칭 없으면 unknown(패스스루) 모델. */
32
+ parse(filePath: string, bytes: Bytes): IExcelModel;
33
+ }
@@ -0,0 +1,12 @@
1
+ import type { Bytes } from "@simplysm/core-common";
2
+
3
+ /**
4
+ * Excel ZIP 파트 1개의 포맷 중립 모델 계약.
5
+ *
6
+ * 구현체(xml/biff)는 자신이 담당하는 파트를 메모리 모델로 들고 있다가, 직렬화 시점에
7
+ * 자기 포맷의 바이트로 변환한다. 직렬화 직전 정규화(OOXML 자식 순서 재배치 등)는 구현 내부에서 수행한다.
8
+ */
9
+ export interface IExcelModel {
10
+ /** 이 파트를 자기 포맷의 바이트로 직렬화. */
11
+ serialize(): Bytes;
12
+ }
@@ -0,0 +1,7 @@
1
+ import type { IExcelModel } from "./excel-model";
2
+
3
+ /** [Content_Types].xml 파트 모델 계약. (xlsb 도 이 파트는 XML.) */
4
+ export interface IContentTypeModel extends IExcelModel {
5
+ /** 파트별 ContentType override 추가. 중복 PartName 은 무시. */
6
+ add(partName: string, contentType: string): this;
7
+ }
@@ -0,0 +1,13 @@
1
+ import type { IExcelModel } from "./excel-model";
2
+
3
+ /**
4
+ * xl/drawings/drawing*.xml 파트 모델 계약.
5
+ * xlsb 도 drawing 은 OOXML XML 을 그대로 사용하므로 단일(xml) 구현으로 충분하다.
6
+ */
7
+ export interface IDrawingModel extends IExcelModel {
8
+ addPicture(opts: {
9
+ from: { r: number; c: number; rOff?: number | string; cOff?: number | string };
10
+ to: { r: number; c: number; rOff?: number | string; cOff?: number | string };
11
+ blipRelId: string;
12
+ }): void;
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { IExcelModel } from "./excel-model";
2
+
3
+ /** *.rels 파트 모델 계약. (xlsb 도 이 파트는 XML.) */
4
+ export interface IRelationshipModel extends IExcelModel {
5
+ getTargetByRelId(rId: number): string | undefined;
6
+ add(target: string, type: string): this;
7
+ /** 관계 추가 후 rId 숫자부 반환. */
8
+ addAndGetId(target: string, type: string): number;
9
+ /** 지정 rId 위치에 삽입하고 이후 id 를 시프트. */
10
+ insert(rId: number, target: string, type: string): this;
11
+ /** 특정 Type 의 첫 관계를 찾아 relId(`rId..`)·target 반환. addImage 의 drawing rel 탐색용. */
12
+ findRelByType(type: string): { relId: string; target: string } | undefined;
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { IExcelModel } from "./excel-model";
2
+
3
+ /** xl/sharedStrings.{xml,bin} 파트 모델 계약. 문자열 중복 제거 테이블. */
4
+ export interface ISharedStringModel extends IExcelModel {
5
+ getIdByString(str: string): number | undefined;
6
+ getStringById(id: number): string | undefined;
7
+ /** 문자열 추가 후 인덱스 반환. */
8
+ add(str: string): number;
9
+ }
@@ -0,0 +1,18 @@
1
+ import type { ExcelConditionalRuleStyle } from "../types";
2
+ import type { ExcelStyle } from "./shared/excel-style";
3
+ import type { IExcelModel } from "./excel-model";
4
+
5
+ /** xl/styles.{xml,bin} 파트 모델 계약. numFmt·font·fill·border·xf·dxf 관리. */
6
+ export interface IStyleModel extends IExcelModel {
7
+ /** 스타일을 등록하고 styleId(문자열 핸들) 반환. 동일 스타일은 재사용. */
8
+ add(style: ExcelStyle): string;
9
+ /** 기존 styleId 를 clone 후 일부 속성만 덮어쓴 새 스타일 등록. */
10
+ addWithClone(id: string, style: ExcelStyle): string;
11
+ /** styleId 의 스타일 역조회. */
12
+ get(id: string): ExcelStyle;
13
+ getNumFmtCode(numFmtId: string): string | undefined;
14
+ /** 조건부 서식 dxf 등록 후 dxfId 반환. */
15
+ addDxf(style: ExcelConditionalRuleStyle): string;
16
+ /** 워크북 전역 기본 스타일 (0번 슬롯) 설정. */
17
+ setDefaultStyle(style: ExcelStyle): void;
18
+ }
@@ -0,0 +1,17 @@
1
+ import type { IExcelModel } from "./excel-model";
2
+
3
+ /** xl/workbook.{xml,bin} 파트 모델 계약. 워크시트 목록·관계 ID 관리. */
4
+ export interface IWorkbookModel extends IExcelModel {
5
+ /** 등록된 워크시트 이름 목록. */
6
+ readonly sheetNames: string[];
7
+ /** 최대 워크시트 관계 ID (rId 숫자부). 없으면 undefined. */
8
+ readonly lastWsRelId: number | undefined;
9
+ /** 새 워크시트 엔트리 추가 (이름 sanitize·relId/sheetId 자동 증가). */
10
+ addWorksheet(name: string): this;
11
+ getWsRelIdByName(name: string): number | undefined;
12
+ getWsRelIdByIndex(index: number): number | undefined;
13
+ getWorksheetNameById(id: number): string | undefined;
14
+ setWorksheetNameById(id: number, newName: string): void;
15
+ /** bookViews 기본 골격 보장 (zoom·freeze 전 선행). */
16
+ initializeView(): void;
17
+ }
@@ -0,0 +1,40 @@
1
+ import type { ExcelAddressPoint, ExcelAddressRangePoint, ExcelCellType } from "../types";
2
+ import type { ICfRuleSpec } from "./shared/excel-cf-spec";
3
+ import type { IExcelModel } from "./excel-model";
4
+
5
+ /** xl/worksheets/sheet*.{xml,bin} 파트 모델 계약. 셀·병합·뷰·CF 관리. */
6
+ export interface IWorksheetModel extends IExcelModel {
7
+ /** 데이터가 존재하는 셀 범위 (s: 좌상단, e: 우하단). */
8
+ readonly range: ExcelAddressRangePoint;
9
+
10
+ getCellStyleId(addr: ExcelAddressPoint): string | undefined;
11
+ setCellStyleId(addr: ExcelAddressPoint, styleId: string | undefined): void;
12
+ getCellType(addr: ExcelAddressPoint): ExcelCellType | undefined;
13
+ setCellType(addr: ExcelAddressPoint, type: ExcelCellType | undefined): void;
14
+ getCellVal(addr: ExcelAddressPoint): string | undefined;
15
+ setCellVal(addr: ExcelAddressPoint, val: string | undefined): void;
16
+ getCellFormula(addr: ExcelAddressPoint): string | undefined;
17
+ setCellFormula(addr: ExcelAddressPoint, val: string | undefined): void;
18
+ deleteCell(addr: ExcelAddressPoint): void;
19
+
20
+ setMergeCells(startAddr: ExcelAddressPoint, endAddr: ExcelAddressPoint): void;
21
+ getMergeCells(): ExcelAddressRangePoint[];
22
+ shiftMergeCells(fromRow: number, delta: number): void;
23
+
24
+ copyRow(sourceR: number, targetR: number, options?: { skipMerge?: boolean }): void;
25
+ copyCell(sourceAddr: ExcelAddressPoint, targetAddr: ExcelAddressPoint): void;
26
+
27
+ addConditionalFormatting(
28
+ sqref: string,
29
+ rules: { dxfId: string; cfRule: ICfRuleSpec }[],
30
+ ): void;
31
+
32
+ setTabColor(rgb: string): void;
33
+ setZoom(percent: number): void;
34
+ freezeAt(point: { r?: number; c?: number }): void;
35
+ setAutoFilter(range: ExcelAddressRangePoint): void;
36
+ setColWidth(colIndex: string, width: string): void;
37
+
38
+ /** 워크시트에 drawing 파트 참조(rId) 연결. addImage 가 .data 직접 조작 대신 호출. */
39
+ setDrawingRelId(relId: string): void;
40
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 포맷 중립 조건부 서식 규칙 spec.
3
+ *
4
+ * 사용자 표면 `ExcelConditionalRule` 을 워크시트 모델이 소비하기 좋은 형태로 정규화한 것.
5
+ * xml 은 `<cfRule>` 엘리먼트로, biff 는 CF 레코드군으로 직렬화한다.
6
+ */
7
+ export interface ICfRuleSpec {
8
+ type: "cellIs" | "containsText" | "notContainsText" | "beginsWith" | "endsWith" | "expression";
9
+ operator?:
10
+ | "lessThan"
11
+ | "lessThanOrEqual"
12
+ | "equal"
13
+ | "notEqual"
14
+ | "greaterThanOrEqual"
15
+ | "greaterThan"
16
+ | "between"
17
+ | "notBetween"
18
+ | "containsText"
19
+ | "notContains"
20
+ | "beginsWith"
21
+ | "endsWith";
22
+ text?: string;
23
+ formula: string[];
24
+ }
@@ -0,0 +1,65 @@
1
+ import type {
2
+ ExcelBorderPosition,
3
+ ExcelFont,
4
+ ExcelHorizontalAlign,
5
+ ExcelStyleOptions,
6
+ ExcelVerticalAlign,
7
+ } from "../../types";
8
+ import { ExcelUtils } from "../../utils/excel-utils";
9
+
10
+ /**
11
+ * 포맷 중립 셀 스타일. 사용자 표면 `ExcelStyleOptions` 를 내부 표현으로 정규화한 것으로,
12
+ * xml/biff 두 구현의 스타일 모델이 공유한다.
13
+ */
14
+ export interface ExcelStyle {
15
+ numFmtId?: string;
16
+ numFmtCode?: string;
17
+ border?: ExcelBorderPosition[];
18
+ background?: string;
19
+ verticalAlign?: ExcelVerticalAlign;
20
+ horizontalAlign?: ExcelHorizontalAlign;
21
+ font?: ExcelFont;
22
+ }
23
+
24
+ /**
25
+ * `ExcelStyleOptions` (사용자 표면) → 내부 `ExcelStyle` 변환.
26
+ * cell.setStyle 과 wb.setDefaultStyle 이 공유한다.
27
+ *
28
+ * - background ARGB 8자리 형식 검증
29
+ * - numberFormatCode 가 numberFormat 보다 우선
30
+ * - font 는 그대로 전달 (구체 검증은 스타일 모델 내부에서 수행)
31
+ */
32
+ export function convertExcelStyleOptions(opts: ExcelStyleOptions): ExcelStyle {
33
+ const style: ExcelStyle = {};
34
+
35
+ if (opts.background != null) {
36
+ if (!/^[0-9A-F]{8}$/i.test(opts.background)) {
37
+ throw new Error("잘못된 색상 형식입니다. (형식: 00000000: alpha(반전)+rgb)");
38
+ }
39
+ style.background = opts.background;
40
+ }
41
+
42
+ if (opts.border != null) {
43
+ style.border = opts.border;
44
+ }
45
+
46
+ if (opts.horizontalAlign != null) {
47
+ style.horizontalAlign = opts.horizontalAlign;
48
+ }
49
+
50
+ if (opts.verticalAlign != null) {
51
+ style.verticalAlign = opts.verticalAlign;
52
+ }
53
+
54
+ if (opts.numberFormatCode != null) {
55
+ style.numFmtCode = opts.numberFormatCode;
56
+ } else if (opts.numberFormat != null) {
57
+ style.numFmtId = ExcelUtils.convertNumFmtNameToId(opts.numberFormat).toString();
58
+ }
59
+
60
+ if (opts.font != null) {
61
+ style.font = opts.font;
62
+ }
63
+
64
+ return style;
65
+ }
package/src/types.ts CHANGED
@@ -475,19 +475,6 @@ export interface ExcelFont {
475
475
 
476
476
  /**
477
477
  * 셀 스타일 옵션
478
- * @example
479
- * ```typescript
480
- * await cell.setStyle({
481
- * background: "00FF0000", // 빨강
482
- * border: ["left", "right", "top", "bottom"],
483
- * horizontalAlign: "center",
484
- * verticalAlign: "center",
485
- * numberFormat: "number",
486
- * });
487
- *
488
- * // 임의의 Excel formatCode 지정
489
- * await cell.setStyle({ numberFormatCode: "0.000000" });
490
- * ```
491
478
  */
492
479
  export interface ExcelStyleOptions {
493
480
  /** 배경색 (ARGB 형식, 예: "00FF0000") */
@@ -1,31 +1,10 @@
1
1
  import type { ZipCache } from "./zip-cache";
2
- import { ExcelXmlContentType } from "../xml/excel-xml-content-type";
3
- import { ExcelXmlRelationship } from "../xml/excel-xml-relationship";
4
- import { ExcelXmlStyle } from "../xml/excel-xml-style";
2
+ import type { IStyleModel } from "../models/i-style-model";
5
3
 
6
4
  /**
7
- * `xl/styles.xml` 가져오거나, 없으면 새로 만들어 ZipCache 에 등록한다.
8
- * Content_Types / workbook.xml.rels 에도 styles.xml 항목을 추가한다.
9
- *
5
+ * `xl/styles` 파트를 가져오거나 없으면 생성·등록한다 (포맷별 처리는 ZipCache 에 위임).
10
6
  * `ExcelCell.setStyle` 과 `ExcelWorkbook.setDefaultStyle` 양쪽이 공유한다.
11
7
  */
12
- export async function getOrCreateStyleData(zipCache: ZipCache): Promise<ExcelXmlStyle> {
13
- let styleData = (await zipCache.get("xl/styles.xml")) as ExcelXmlStyle | undefined;
14
- if (styleData == null) {
15
- styleData = new ExcelXmlStyle();
16
- zipCache.set("xl/styles.xml", styleData);
17
-
18
- const typeData = (await zipCache.get("[Content_Types].xml")) as ExcelXmlContentType;
19
- typeData.add(
20
- "/xl/styles.xml",
21
- "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
22
- );
23
-
24
- const wbRelData = (await zipCache.get("xl/_rels/workbook.xml.rels")) as ExcelXmlRelationship;
25
- wbRelData.add(
26
- "styles.xml",
27
- "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
28
- );
29
- }
30
- return styleData;
8
+ export async function getOrCreateStyleData(zipCache: ZipCache): Promise<IStyleModel> {
9
+ return zipCache.ensureStyles();
31
10
  }