@@ -51,6 +51,141 @@ class QueryParser {
5151 } ;
5252 }
5353
54+ /**
55+ * XML 구조 검증 (element명과 속성명)
56+ * @param {Object } parsed - 파싱된 XML 객체
57+ * @returns {Object } { valid: boolean, errors: string[] }
58+ */
59+ validateXMLStructure ( parsed ) {
60+ const errors = [ ] ;
61+
62+ // 허용되는 최상위 element
63+ const allowedRootElements = [ 'queries' ] ;
64+
65+ // 허용되는 element와 그 속성 정의
66+ const allowedElements = {
67+ queries : [ 'excel' , 'vars' , 'dynamicVars' , 'queryDefs' , 'sheet' ] ,
68+ excel : [ ] , // 자식 element 없음
69+ vars : [ 'var' ] ,
70+ var : [ ] , // 자식 element 없음
71+ dynamicVars : [ 'dynamicVar' ] ,
72+ dynamicVar : [ ] , // 자식 element 없음
73+ queryDefs : [ 'queryDef' ] ,
74+ queryDef : [ ] , // 자식 element 없음
75+ sheet : [ 'params' ] ,
76+ params : [ 'param' ] ,
77+ param : [ ] // 자식 element 없음
78+ } ;
79+
80+ // 허용되는 속성 정의
81+ const allowedAttributes = {
82+ excel : [ 'db' , 'output' , 'maxRows' , 'style' , 'aggregateInfoTemplate' ] ,
83+ var : [ 'name' ] ,
84+ dynamicVar : [ 'name' , 'description' , 'type' , 'database' ] ,
85+ queryDef : [ 'id' , 'description' ] ,
86+ sheet : [ 'name' , 'use' , 'queryRef' , 'aggregateColumn' , 'aggregateInfoTemplate' , 'maxRows' , 'db' , 'style' ] ,
87+ param : [ 'name' ]
88+ } ;
89+
90+ // 최상위 element 검증
91+ const rootElementNames = Object . keys ( parsed ) ;
92+ const invalidRootElements = rootElementNames . filter ( name => ! allowedRootElements . includes ( name ) ) ;
93+ if ( invalidRootElements . length > 0 ) {
94+ errors . push ( `허용되지 않는 최상위 element: ${ invalidRootElements . join ( ', ' ) } ` ) ;
95+ }
96+
97+ // queries element 검증
98+ if ( parsed . queries ) {
99+ const queries = parsed . queries ;
100+ const queryKeys = Object . keys ( queries ) ;
101+ const invalidElements = queryKeys . filter ( key => ! allowedElements . queries . includes ( key ) ) ;
102+ if ( invalidElements . length > 0 ) {
103+ errors . push ( `queries 내 허용되지 않는 element: ${ invalidElements . join ( ', ' ) } ` ) ;
104+ }
105+
106+ // excel element 속성 검증
107+ if ( queries . excel && queries . excel [ 0 ] && queries . excel [ 0 ] . $ ) {
108+ const excelAttrs = Object . keys ( queries . excel [ 0 ] . $ ) ;
109+ const invalidAttrs = excelAttrs . filter ( attr => ! allowedAttributes . excel . includes ( attr ) ) ;
110+ if ( invalidAttrs . length > 0 ) {
111+ errors . push ( `excel element의 허용되지 않는 속성: ${ invalidAttrs . join ( ', ' ) } ` ) ;
112+ }
113+ }
114+
115+ // var elements 검증
116+ if ( queries . vars && queries . vars [ 0 ] && queries . vars [ 0 ] . var ) {
117+ queries . vars [ 0 ] . var . forEach ( ( v , i ) => {
118+ if ( v . $ ) {
119+ const attrs = Object . keys ( v . $ ) ;
120+ const invalidAttrs = attrs . filter ( attr => ! allowedAttributes . var . includes ( attr ) ) ;
121+ if ( invalidAttrs . length > 0 ) {
122+ errors . push ( `var element #${ i + 1 } 의 허용되지 않는 속성: ${ invalidAttrs . join ( ', ' ) } ` ) ;
123+ }
124+ }
125+ } ) ;
126+ }
127+
128+ // dynamicVar elements 검증
129+ if ( queries . dynamicVars && queries . dynamicVars [ 0 ] && queries . dynamicVars [ 0 ] . dynamicVar ) {
130+ queries . dynamicVars [ 0 ] . dynamicVar . forEach ( ( dv , i ) => {
131+ if ( dv . $ ) {
132+ const attrs = Object . keys ( dv . $ ) ;
133+ const invalidAttrs = attrs . filter ( attr => ! allowedAttributes . dynamicVar . includes ( attr ) ) ;
134+ if ( invalidAttrs . length > 0 ) {
135+ errors . push ( `dynamicVar element #${ i + 1 } 의 허용되지 않는 속성: ${ invalidAttrs . join ( ', ' ) } ` ) ;
136+ }
137+ }
138+ } ) ;
139+ }
140+
141+ // queryDef elements 검증
142+ if ( queries . queryDefs && queries . queryDefs [ 0 ] && queries . queryDefs [ 0 ] . queryDef ) {
143+ queries . queryDefs [ 0 ] . queryDef . forEach ( ( qd , i ) => {
144+ if ( qd . $ ) {
145+ const attrs = Object . keys ( qd . $ ) ;
146+ const invalidAttrs = attrs . filter ( attr => ! allowedAttributes . queryDef . includes ( attr ) ) ;
147+ if ( invalidAttrs . length > 0 ) {
148+ errors . push ( `queryDef element #${ i + 1 } 의 허용되지 않는 속성: ${ invalidAttrs . join ( ', ' ) } ` ) ;
149+ }
150+ }
151+ } ) ;
152+ }
153+
154+ // sheet elements 검증
155+ if ( queries . sheet ) {
156+ const sheets = Array . isArray ( queries . sheet ) ? queries . sheet : [ queries . sheet ] ;
157+ sheets . forEach ( ( sheet , i ) => {
158+ if ( sheet . $ ) {
159+ const attrs = Object . keys ( sheet . $ ) ;
160+ const invalidAttrs = attrs . filter ( attr => ! allowedAttributes . sheet . includes ( attr ) ) ;
161+ if ( invalidAttrs . length > 0 ) {
162+ errors . push ( `sheet element #${ i + 1 } 의 허용되지 않는 속성: ${ invalidAttrs . join ( ', ' ) } ` ) ;
163+ }
164+ }
165+
166+ // params 검증
167+ if ( sheet . params && sheet . params [ 0 ] && sheet . params [ 0 ] . param ) {
168+ const params = Array . isArray ( sheet . params [ 0 ] . param ) ? sheet . params [ 0 ] . param : [ sheet . params [ 0 ] . param ] ;
169+ params . forEach ( ( param , j ) => {
170+ if ( param . $ ) {
171+ const attrs = Object . keys ( param . $ ) ;
172+ const invalidAttrs = attrs . filter ( attr => ! allowedAttributes . param . includes ( attr ) ) ;
173+ if ( invalidAttrs . length > 0 ) {
174+ errors . push ( `sheet #${ i + 1 } 의 param element #${ j + 1 } 의 허용되지 않는 속성: ${ invalidAttrs . join ( ', ' ) } ` ) ;
175+ }
176+ }
177+ } ) ;
178+ }
179+ } ) ;
180+ }
181+ }
182+
183+ return {
184+ valid : errors . length === 0 ,
185+ errors
186+ } ;
187+ }
188+
54189 /**
55190 * XML 파일에서 쿼리 로드
56191 * @param {string } xmlPath - XML 파일 경로
@@ -62,6 +197,16 @@ class QueryParser {
62197 const parsed = await xml2js . parseStringPromise ( xml , { trim : true } ) ;
63198 if ( ! parsed . queries || ! parsed . queries . sheet ) throw new Error ( 'Invalid XML format' ) ;
64199
200+ // XML 구조 검증
201+ const structureValidation = this . validateXMLStructure ( parsed ) ;
202+ if ( ! structureValidation . valid ) {
203+ console . error ( '\n❌ XML 구조 검증 실패:' ) ;
204+ structureValidation . errors . forEach ( error => {
205+ console . error ( ` - ${ error } ` ) ;
206+ } ) ;
207+ throw new Error ( 'XML 구조 검증 실패' ) ;
208+ }
209+
65210 // 쿼리 정의 파싱
66211 let queryDefs = { } ;
67212 if ( parsed . queries . queryDefs && parsed . queries . queryDefs [ 0 ] && parsed . queries . queryDefs [ 0 ] . queryDef ) {
@@ -70,16 +215,22 @@ class QueryParser {
70215 const queryName = queryDef . $ . id || queryDef . $ . name ;
71216 const queryText = ( queryDef . _ || queryDef [ '#text' ] || queryDef . __cdata || '' ) . toString ( ) . trim ( ) ;
72217
218+ console . log ( `[DEBUG] queryDef 파싱: id="${ queryName } ", queryText 길이=${ queryText . length } ` ) ;
219+
73220 if ( queryText ) {
74221 queryDefs [ queryName ] = {
75222 name : queryName ,
76223 description : queryDef . $ . description || '' ,
77224 query : queryText
78225 } ;
226+ console . log ( `[DEBUG] queryDef 추가됨: ${ queryName } ` ) ;
227+ } else {
228+ console . warn ( `[WARN] queryDef "${ queryName } "의 쿼리 텍스트가 비어있습니다.` ) ;
79229 }
80230 }
81231 }
82232 }
233+ console . log ( `[DEBUG] 총 ${ Object . keys ( queryDefs ) . length } 개의 queryDef 로드됨: ${ Object . keys ( queryDefs ) . join ( ', ' ) } ` ) ;
83234
84235 // 전역 변수 파싱
85236 let globalVars = { } ;
@@ -188,17 +339,6 @@ class QueryParser {
188339 }
189340 }
190341
191- // 시트명 검증
192- const sheetNameValidation = this . validateSheetName ( s . $ . name , i ) ;
193- if ( ! sheetNameValidation . valid ) {
194- console . error ( `\n❌ 시트명 검증 실패 (시트 #${ i + 1 } ):` ) ;
195- console . error ( ` 시트명: "${ s . $ . name } "` ) ;
196- sheetNameValidation . errors . forEach ( error => {
197- console . error ( ` - ${ error } ` ) ;
198- } ) ;
199- throw new Error ( `시트명 검증 실패: "${ s . $ . name } "` ) ;
200- }
201-
202342 // queryRef 속성이 있으면 쿼리 정의에서 참조
203343 if ( s . $ . queryRef ) {
204344 const queryRef = s . $ . queryRef ;
@@ -257,17 +397,6 @@ class QueryParser {
257397 let query = sheet . query || '' ;
258398 let sheetParams = sheet . params || { } ;
259399
260- // 시트명 검증
261- const sheetNameValidation = this . validateSheetName ( sheet . name , i ) ;
262- if ( ! sheetNameValidation . valid ) {
263- console . error ( `\n❌ 시트명 검증 실패 (시트 #${ i + 1 } ):` ) ;
264- console . error ( ` 시트명: "${ sheet . name } "` ) ;
265- sheetNameValidation . errors . forEach ( error => {
266- console . error ( ` - ${ error } ` ) ;
267- } ) ;
268- throw new Error ( `시트명 검증 실패: "${ sheet . name } "` ) ;
269- }
270-
271400 // queryRef가 있으면 쿼리 정의에서 참조
272401 if ( sheet . queryRef ) {
273402 if ( queryDefs [ sheet . queryRef ] ) {
0 commit comments