@@ -25,6 +25,91 @@ function validateFilename(filepath) {
2525 return true ;
2626}
2727
28+ // 엑셀 스타일 템플릿 로더
29+ let styleTemplates = null ;
30+
31+ async function loadStyleTemplates ( ) {
32+ if ( styleTemplates ) return styleTemplates ;
33+
34+ const templatePath = path . join ( __dirname , '..' , 'templates' , 'excel-styles.xml' ) ;
35+
36+ try {
37+ const xml = fs . readFileSync ( templatePath , 'utf8' ) ;
38+ const parsed = await xml2js . parseStringPromise ( xml , { trim : true } ) ;
39+
40+ styleTemplates = { } ;
41+ if ( parsed . excelStyles && parsed . excelStyles . style ) {
42+ for ( const style of parsed . excelStyles . style ) {
43+ if ( style . $ && style . $ . id ) {
44+ const styleId = style . $ . id ;
45+ const styleName = style . $ . name || styleId ;
46+ const description = style . $ . description || '' ;
47+
48+ styleTemplates [ styleId ] = {
49+ id : styleId ,
50+ name : styleName ,
51+ description : description ,
52+ header : parseStyleSection ( style . header && style . header [ 0 ] ) ,
53+ body : parseStyleSection ( style . body && style . body [ 0 ] )
54+ } ;
55+ }
56+ }
57+ }
58+
59+ console . log ( `📋 로드된 스타일 템플릿: ${ Object . keys ( styleTemplates ) . length } 개` ) ;
60+ return styleTemplates ;
61+ } catch ( error ) {
62+ console . warn ( `⚠️ 스타일 템플릿 로드 실패: ${ templatePath } ` ) ;
63+ console . warn ( ` 오류: ${ error . message } ` ) ;
64+ console . warn ( ` 💡 기본 스타일을 사용합니다.` ) ;
65+ return { } ;
66+ }
67+ }
68+
69+ // 스타일 섹션 파싱
70+ function parseStyleSection ( section ) {
71+ if ( ! section ) return { } ;
72+
73+ const result = { } ;
74+
75+ if ( section . font && section . font [ 0 ] && section . font [ 0 ] . $ ) {
76+ result . font = section . font [ 0 ] . $ ;
77+ }
78+ if ( section . fill && section . fill [ 0 ] && section . fill [ 0 ] . $ ) {
79+ result . fill = section . fill [ 0 ] . $ ;
80+ }
81+ if ( section . colwidths && section . colwidths [ 0 ] && section . colwidths [ 0 ] . $ ) {
82+ result . colwidths = section . colwidths [ 0 ] . $ ;
83+ }
84+ if ( section . alignment && section . alignment [ 0 ] && section . alignment [ 0 ] . $ ) {
85+ result . alignment = section . alignment [ 0 ] . $ ;
86+ }
87+ if ( section . border && section . border [ 0 ] ) {
88+ result . border = parseXmlBorder ( section . border [ 0 ] ) ;
89+ }
90+
91+ return result ;
92+ }
93+
94+ // 스타일 ID로 스타일 가져오기
95+ async function getStyleById ( styleId ) {
96+ const templates = await loadStyleTemplates ( ) ;
97+ return templates [ styleId ] || templates [ 'default' ] || null ;
98+ }
99+
100+ // 사용 가능한 스타일 목록 출력
101+ async function listAvailableStyles ( ) {
102+ const templates = await loadStyleTemplates ( ) ;
103+
104+ console . log ( '\n📋 사용 가능한 엑셀 스타일 템플릿:' ) ;
105+ console . log ( '─' . repeat ( 60 ) ) ;
106+
107+ for ( const [ id , style ] of Object . entries ( templates ) ) {
108+ console . log ( ` ${ id . padEnd ( 12 ) } | ${ style . name . padEnd ( 15 ) } | ${ style . description } ` ) ;
109+ }
110+ console . log ( '─' . repeat ( 60 ) ) ;
111+ }
112+
28113function substituteVars ( str , vars ) {
29114 return str . replace ( / \$ \{ ( \w + ) \} / g, ( _ , v ) => {
30115 const value = vars [ v ] ;
@@ -268,15 +353,23 @@ function isSheetEnabled(sheetDef) {
268353}
269354
270355async function main ( ) {
271- printAvailableXmlFiles ( ) ;
272-
273356 const argv = yargs
274357 . option ( 'query' , { alias : 'q' , describe : '쿼리 정의 파일 경로 (JSON)' , default : '' } )
275358 . option ( 'xml' , { alias : 'x' , describe : '쿼리 정의 파일 경로 (XML)' , default : '' } )
276359 . option ( 'config' , { alias : 'c' , describe : 'DB 접속 정보 파일' , default : 'config/dbinfo.json' } )
277360 . option ( 'var' , { alias : 'v' , describe : '쿼리 변수 (key=value)' , array : true , default : [ ] } )
361+ . option ( 'style' , { alias : 's' , describe : '엑셀 스타일 템플릿 ID' , default : 'default' } )
362+ . option ( 'list-styles' , { describe : '사용 가능한 스타일 템플릿 목록 출력' , boolean : true } )
278363 . help ( ) . argv ;
279364
365+ printAvailableXmlFiles ( ) ;
366+
367+ // 스타일 목록 출력 옵션 처리
368+ if ( argv [ 'list-styles' ] ) {
369+ await listAvailableStyles ( ) ;
370+ return ;
371+ }
372+
280373 // CLI 변수 파싱
281374 const cliVars = { } ;
282375 for ( const v of argv . var ) {
@@ -348,6 +441,19 @@ async function main() {
348441 let createSeparateToc = false ; // 별도 목차 파일 생성 여부
349442 let globalMaxRows = null ; // 전역 최대 조회 건수
350443
444+ // 스타일 템플릿 적용
445+ const selectedStyle = await getStyleById ( argv . style ) ;
446+ if ( selectedStyle ) {
447+ console . log ( `🎨 적용된 스타일: ${ selectedStyle . name } (${ selectedStyle . description } )` ) ;
448+ excelStyle = {
449+ header : selectedStyle . header || { } ,
450+ body : selectedStyle . body || { }
451+ } ;
452+ } else {
453+ console . warn ( `⚠️ 스타일 템플릿을 찾을 수 없습니다: ${ argv . style } ` ) ;
454+ console . warn ( ` 💡 기본 스타일을 사용합니다.` ) ;
455+ }
456+
351457 if ( argv . xml && fs . existsSync ( resolvePath ( argv . xml ) ) ) {
352458 let xml ;
353459 try {
@@ -374,29 +480,28 @@ async function main() {
374480 // excel 엘리먼트의 maxRows 읽기
375481 if ( excel . $ && excel . $ . maxRows ) globalMaxRows = parseInt ( excel . $ . maxRows ) ;
376482
377- excelStyle . header = { } ;
378- excelStyle . body = { } ;
483+ // XML에서 스타일 속성이 있으면 템플릿 스타일을 덮어씀
379484 if ( excel . header && excel . header [ 0 ] ) {
380485 const h = excel . header [ 0 ] ;
381- if ( h . font && h . font [ 0 ] && h . font [ 0 ] . $ ) excelStyle . header . font = h . font [ 0 ] . $ ;
382- if ( h . fill && h . fill [ 0 ] && h . fill [ 0 ] . $ ) excelStyle . header . fill = h . fill [ 0 ] . $ ;
383- if ( h . colwidths && h . colwidths [ 0 ] && h . colwidths [ 0 ] . $ ) excelStyle . header . colwidths = h . colwidths [ 0 ] . $ ;
486+ if ( h . font && h . font [ 0 ] && h . font [ 0 ] . $ ) excelStyle . header . font = { ... excelStyle . header . font , ... h . font [ 0 ] . $ } ;
487+ if ( h . fill && h . fill [ 0 ] && h . fill [ 0 ] . $ ) excelStyle . header . fill = { ... excelStyle . header . fill , ... h . fill [ 0 ] . $ } ;
488+ if ( h . colwidths && h . colwidths [ 0 ] && h . colwidths [ 0 ] . $ ) excelStyle . header . colwidths = { ... excelStyle . header . colwidths , ... h . colwidths [ 0 ] . $ } ;
384489 if ( h . alignment && h . alignment [ 0 ] && h . alignment [ 0 ] . $ ) {
385- excelStyle . header . alignment = h . alignment [ 0 ] . $ ;
490+ excelStyle . header . alignment = { ... excelStyle . header . alignment , ... h . alignment [ 0 ] . $ } ;
386491 }
387492 if ( h . border && h . border [ 0 ] ) {
388- excelStyle . header . border = parseXmlBorder ( h . border [ 0 ] ) ;
493+ excelStyle . header . border = { ... excelStyle . header . border , ... parseXmlBorder ( h . border [ 0 ] ) } ;
389494 }
390495 }
391496 if ( excel . body && excel . body [ 0 ] ) {
392497 const b = excel . body [ 0 ] ;
393- if ( b . font && b . font [ 0 ] && b . font [ 0 ] . $ ) excelStyle . body . font = b . font [ 0 ] . $ ;
394- if ( b . fill && b . fill [ 0 ] && b . fill [ 0 ] . $ ) excelStyle . body . fill = b . fill [ 0 ] . $ ;
498+ if ( b . font && b . font [ 0 ] && b . font [ 0 ] . $ ) excelStyle . body . font = { ... excelStyle . body . font , ... b . font [ 0 ] . $ } ;
499+ if ( b . fill && b . fill [ 0 ] && b . fill [ 0 ] . $ ) excelStyle . body . fill = { ... excelStyle . body . fill , ... b . fill [ 0 ] . $ } ;
395500 if ( b . alignment && b . alignment [ 0 ] && b . alignment [ 0 ] . $ ) {
396- excelStyle . body . alignment = b . alignment [ 0 ] . $ ;
501+ excelStyle . body . alignment = { ... excelStyle . body . alignment , ... b . alignment [ 0 ] . $ } ;
397502 }
398503 if ( b . border && b . border [ 0 ] ) {
399- excelStyle . body . border = parseXmlBorder ( b . border [ 0 ] ) ;
504+ excelStyle . body . border = { ... excelStyle . body . border , ... parseXmlBorder ( b . border [ 0 ] ) } ;
400505 }
401506 }
402507 }
0 commit comments