196 lines
5.5 KiB
Plaintext
196 lines
5.5 KiB
Plaintext
---
|
|
description:
|
|
globs:
|
|
alwaysApply: false
|
|
---
|
|
# xlsx-js-style 스타일 보존 규칙
|
|
|
|
## **핵심 원칙**
|
|
- xlsx-js-style의 공식 API 구조를 직접 활용하여 스타일 변환
|
|
- 복잡한 색상 변환 로직 대신 공식 COLOR_STYLE 형식 지원
|
|
- 배경색과 테두리 색상 누락 방지를 위한 완전한 스타일 매핑
|
|
|
|
## **공식 xlsx-js-style API 활용**
|
|
|
|
### **색상 처리 (COLOR_STYLE)**
|
|
```typescript
|
|
// ✅ DO: 공식 COLOR_STYLE 형식 모두 지원
|
|
function convertXlsxColorToLuckysheet(colorObj: any): string {
|
|
// RGB 형태: {rgb: "FFCC00"}
|
|
if (colorObj.rgb) { /* RGB 처리 */ }
|
|
|
|
// Theme 색상: {theme: 4} 또는 {theme: 1, tint: 0.4}
|
|
if (typeof colorObj.theme === 'number') { /* Theme 처리 */ }
|
|
|
|
// Indexed 색상: Excel 기본 색상표
|
|
if (typeof colorObj.indexed === 'number') { /* Indexed 처리 */ }
|
|
}
|
|
|
|
// ❌ DON'T: 특정 색상 형식만 처리
|
|
function badColorConvert(colorObj: any): string {
|
|
return colorObj.rgb || "rgb(0,0,0)"; // rgb만 처리하고 theme, indexed 무시
|
|
}
|
|
```
|
|
|
|
### **스타일 객체 변환**
|
|
```typescript
|
|
// ✅ DO: 공식 스타일 속성 완전 매핑
|
|
function convertXlsxStyleToLuckysheet(xlsxStyle: any): any {
|
|
const luckyStyle: any = {};
|
|
|
|
// 폰트: {name: "Courier", sz: 24, bold: true, color: {rgb: "FF0000"}}
|
|
if (xlsxStyle.font) {
|
|
if (xlsxStyle.font.name) luckyStyle.ff = xlsxStyle.font.name;
|
|
if (xlsxStyle.font.sz) luckyStyle.fs = xlsxStyle.font.sz;
|
|
if (xlsxStyle.font.bold) luckyStyle.bl = 1;
|
|
if (xlsxStyle.font.color) {
|
|
luckyStyle.fc = convertXlsxColorToLuckysheet(xlsxStyle.font.color);
|
|
}
|
|
}
|
|
|
|
// 배경: {fgColor: {rgb: "E9E9E9"}}
|
|
if (xlsxStyle.fill?.fgColor) {
|
|
luckyStyle.bg = convertXlsxColorToLuckysheet(xlsxStyle.fill.fgColor);
|
|
}
|
|
|
|
// 테두리: {top: {style: "thin", color: {rgb: "000000"}}}
|
|
if (xlsxStyle.border) {
|
|
luckyStyle.bd = {};
|
|
if (xlsxStyle.border.top) {
|
|
luckyStyle.bd.t = {
|
|
style: convertBorderStyleToLuckysheet(xlsxStyle.border.top.style),
|
|
color: convertXlsxColorToLuckysheet(xlsxStyle.border.top.color)
|
|
};
|
|
}
|
|
}
|
|
|
|
return luckyStyle;
|
|
}
|
|
|
|
// ❌ DON'T: 수동으로 스타일 속성 하나씩 처리
|
|
luckyCell.v.s = {
|
|
ff: cell.s.font?.name || "Arial",
|
|
bg: cell.s.fill?.fgColor?.rgb || "rgb(255,255,255)" // 직접 rgb만 처리
|
|
};
|
|
```
|
|
|
|
## **배경색과 테두리 색상 누락 방지**
|
|
|
|
### **배경색 처리**
|
|
```typescript
|
|
// ✅ DO: fgColor와 bgColor 모두 확인
|
|
if (xlsxStyle.fill) {
|
|
if (xlsxStyle.fill.fgColor) {
|
|
luckyStyle.bg = convertXlsxColorToLuckysheet(xlsxStyle.fill.fgColor);
|
|
} else if (xlsxStyle.fill.bgColor) {
|
|
luckyStyle.bg = convertXlsxColorToLuckysheet(xlsxStyle.fill.bgColor);
|
|
}
|
|
}
|
|
|
|
// ❌ DON'T: fgColor만 확인
|
|
if (xlsxStyle.fill?.fgColor) {
|
|
luckyStyle.bg = xlsxStyle.fill.fgColor.rgb; // 다른 색상 형식 무시
|
|
}
|
|
```
|
|
|
|
### **테두리 색상 처리**
|
|
```typescript
|
|
// ✅ DO: 모든 테두리 방향과 색상 형식 지원
|
|
if (xlsxStyle.border) {
|
|
['top', 'bottom', 'left', 'right'].forEach(side => {
|
|
if (xlsxStyle.border[side]) {
|
|
luckyStyle.bd[side[0]] = {
|
|
style: convertBorderStyleToLuckysheet(xlsxStyle.border[side].style),
|
|
color: convertXlsxColorToLuckysheet(xlsxStyle.border[side].color)
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
// ❌ DON'T: 하드코딩된 색상 사용
|
|
luckyStyle.bd.t = {
|
|
style: 1,
|
|
color: "rgb(0,0,0)" // 실제 색상 무시
|
|
};
|
|
```
|
|
|
|
## **Excel Tint 처리**
|
|
```typescript
|
|
// ✅ DO: Excel tint 공식 적용
|
|
function applyTintToRgbColor(rgbColor: string, tint: number): string {
|
|
const applyTint = (color: number, tint: number): number => {
|
|
if (tint < 0) {
|
|
return Math.round(color * (1 + tint));
|
|
} else {
|
|
return Math.round(color * (1 - tint) + (255 - 255 * (1 - tint)));
|
|
}
|
|
};
|
|
// RGB 각 채널에 tint 적용
|
|
}
|
|
|
|
// ❌ DON'T: tint 무시
|
|
if (colorObj.theme) {
|
|
return themeColors[colorObj.theme]; // tint 무시
|
|
}
|
|
```
|
|
|
|
## **오류 방지 패턴**
|
|
|
|
### **안전한 스타일 읽기**
|
|
```typescript
|
|
// ✅ DO: 옵셔널 체이닝과 타입 검사
|
|
workbook = XLSX.read(arrayBuffer, {
|
|
cellStyles: true // 스타일 정보 보존
|
|
});
|
|
|
|
// 스타일 정보 확인
|
|
if (cell.s) {
|
|
console.log(`🎨 셀 ${cellAddress}에 스타일 정보:`, cell.s);
|
|
luckyCell.v.s = convertXlsxStyleToLuckysheet(cell.s);
|
|
}
|
|
|
|
// ❌ DON'T: 스타일 옵션 누락
|
|
workbook = XLSX.read(arrayBuffer); // cellStyles 옵션 없음
|
|
```
|
|
|
|
### **스타일 쓰기 보존**
|
|
```typescript
|
|
// ✅ DO: 쓰기 시에도 스타일 보존
|
|
const xlsxData = XLSX.write(workbook, {
|
|
type: "array",
|
|
bookType: "xlsx",
|
|
cellStyles: true // 스타일 정보 보존
|
|
});
|
|
|
|
// ❌ DON'T: 쓰기 시 스타일 누락
|
|
const xlsxData = XLSX.write(workbook, {
|
|
type: "array",
|
|
bookType: "xlsx"
|
|
// cellStyles 옵션 없음
|
|
});
|
|
```
|
|
|
|
## **디버깅 및 검증**
|
|
|
|
### **스타일 정보 로깅**
|
|
```typescript
|
|
// ✅ DO: 개발 모드에서 스타일 정보 상세 분석
|
|
if (import.meta.env.DEV && cell.s) {
|
|
console.log(`🎨 셀 ${cellAddress} 스타일:`, {
|
|
font: cell.s.font,
|
|
fill: cell.s.fill,
|
|
border: cell.s.border,
|
|
alignment: cell.s.alignment
|
|
});
|
|
}
|
|
|
|
// ❌ DON'T: 스타일 정보 무시
|
|
// 스타일 관련 로그 없음
|
|
```
|
|
|
|
## **참고 사항**
|
|
- [xlsx-js-style GitHub](https://github.com/gitbrent/xlsx-js-style) 공식 문서 참조
|
|
- 공식 COLOR_STYLE 형식: `{rgb: "FFCC00"}`, `{theme: 4}`, `{theme: 1, tint: 0.4}`
|
|
- 공식 BORDER_STYLE 값: `thin`, `medium`, `thick`, `dotted`, `dashed` 등
|
|
- Excel 테마 색상과 tint 처리는 공식 Excel 색상 공식 사용
|