1+ import { _options } from "." ;
2+ import { Column , ColumnType , Parameter , Parameters , Row , XapiValueType } from "./types" ;
3+
4+
5+ function stringToDate ( value : string ) : Date | undefined {
6+ if ( ! value ) return undefined ;
7+ const strlen = value . length ;
8+ switch ( strlen ) {
9+ case 8 : // yyyyMMdd
10+ return new Date (
11+ parseInt ( value . substring ( 0 , 4 ) , 10 ) ,
12+ parseInt ( value . substring ( 4 , 6 ) , 10 ) - 1 ,
13+ parseInt ( value . substring ( 6 , 8 ) , 10 )
14+ ) ;
15+ case 14 : // yyyyMMddHHmmss
16+ return new Date (
17+ parseInt ( value . substring ( 0 , 4 ) , 10 ) ,
18+ parseInt ( value . substring ( 4 , 6 ) , 10 ) - 1 ,
19+ parseInt ( value . substring ( 6 , 8 ) , 10 ) ,
20+ parseInt ( value . substring ( 8 , 10 ) , 10 ) ,
21+ parseInt ( value . substring ( 10 , 12 ) , 10 ) ,
22+ parseInt ( value . substring ( 12 , 14 ) , 10 )
23+ ) ;
24+ case 16 : // yyyyMMddHHmmssSSS
25+ return new Date (
26+ parseInt ( value . substring ( 0 , 4 ) , 10 ) ,
27+ parseInt ( value . substring ( 4 , 6 ) , 10 ) - 1 ,
28+ parseInt ( value . substring ( 6 , 8 ) , 10 ) ,
29+ parseInt ( value . substring ( 8 , 10 ) , 10 ) ,
30+ parseInt ( value . substring ( 10 , 12 ) , 10 ) ,
31+ parseInt ( value . substring ( 12 , 14 ) , 10 ) ,
32+ parseInt ( value . substring ( 14 , 16 ) , 10 )
33+ ) ;
34+ case 6 : // HHmmss
35+ return new Date (
36+ 1970 ,
37+ 0 ,
38+ 1 ,
39+ parseInt ( value . substring ( 0 , 2 ) , 10 ) ,
40+ parseInt ( value . substring ( 2 , 4 ) , 10 ) ,
41+ parseInt ( value . substring ( 4 , 6 ) , 10 )
42+ ) ;
43+ }
44+ }
45+
46+ function dateToString ( date : Date , type : Extract < ColumnType , "DATE" | "DATETIME" | "TIME" > ) : string {
47+ const year = date . getFullYear ( ) . toString ( ) . padStart ( 4 , '0' ) ;
48+ const month = ( date . getMonth ( ) + 1 ) . toString ( ) . padStart ( 2 , '0' ) ;
49+ const day = date . getDate ( ) . toString ( ) . padStart ( 2 , '0' ) ;
50+ const hours = date . getHours ( ) . toString ( ) . padStart ( 2 , '0' ) ;
51+ const minutes = date . getMinutes ( ) . toString ( ) . padStart ( 2 , '0' ) ;
52+ const seconds = date . getSeconds ( ) . toString ( ) . padStart ( 2 , '0' ) ;
53+
54+ switch ( type ) {
55+ case "DATE" :
56+ return `${ year } ${ month } ${ day } ` ;
57+ case "DATETIME" :
58+ return `${ year } ${ month } ${ day } ${ hours } ${ minutes } ${ seconds } ` ;
59+ case "TIME" :
60+ return `${ hours } ${ minutes } ${ seconds } ` ;
61+ default :
62+ return '' ;
63+ }
64+ }
65+
66+ /**
67+ * 문자열을 HTML 엔티티로 변환(Escape)합니다.
68+ * - 기본 HTML 특수 문자 (<, >, &, ", ')를 변환합니다.
69+ * - ASCII 32번(공백) 이하의 제어 문자 및 공백을 변환합니다.
70+ * - 줄바꿈(\n)은 <br> 태그로 변환됩니다.
71+ * - 공백(' ')은 로 변환됩니다.
72+ * - 탭(\t) 및 기타 제어 문자는 숫자 엔티티(&#...;)로 변환됩니다.
73+ * @param str 변환할 원본 문자열
74+ * @returns 변환된 HTML 엔티티 문자열
75+ */
76+ export function escapeHtml ( str : string ) : string {
77+ if ( ! str ) {
78+ return "" ;
79+ }
80+
81+ // 변환 규칙이 명확한 문자들을 위한 매핑
82+ const entityMap : { [ key : string ] : string } = {
83+ '<' : '<' ,
84+ '>' : '>' ,
85+ '&' : '&' ,
86+ '"' : '"' ,
87+ "'" : ''' ,
88+ ' ' : ' ' ,
89+ '\n' : '<br>' ,
90+ } ;
91+
92+ // 정규식 설명:
93+ // /[<>&"' \n\x00-\x1f]/g
94+ // [<>&"' \n] : 위 entityMap에서 처리할 기본 특수 문자, 공백, 줄바꿈을 찾습니다.
95+ // | : 또는
96+ // [\x00-\x1f] : ASCII 0번부터 31번까지의 제어 문자를 찾습니다.
97+ // g : 전역(global) 플래그로, 문자열 전체에서 찾습니다.
98+ const regex = / [ < > & " ' \n \x00 - \x1f ] / g;
99+
100+ return str . replace ( regex , ( match ) => {
101+ // entityMap에 정의된 문자인 경우, 매핑된 값을 반환합니다.
102+ if ( entityMap [ match ] ) {
103+ return entityMap [ match ] ;
104+ }
105+ // 그 외의 제어 문자인 경우, 숫자 엔티티로 변환합니다.
106+ // (예: \t -> 	)
107+ return `&#${ match . charCodeAt ( 0 ) } ;` ;
108+ } ) ;
109+ }
110+
111+ /**
112+ * HTML 엔티티를 원래 문자로 복원(Unescape)합니다.
113+ * - escapeHtml 함수에 의해 변환된 문자열을 원래대로 되돌립니다.
114+ * @param str 변환된 HTML 엔티티 문자열
115+ * @returns 복원된 원본 문자열
116+ */
117+ export function unescapeHtml ( str : string ) : string {
118+ if ( ! str ) {
119+ return "" ;
120+ }
121+
122+ // 역변환을 위한 매핑
123+ const textEntityMap : { [ key : string ] : string } = {
124+ '<' : '<' ,
125+ '>' : '>' ,
126+ '&' : '&' ,
127+ '"' : '"' ,
128+ ''' : "'" ,
129+ ' ' : ' ' ,
130+ } ;
131+
132+ // 정규식 설명:
133+ // /<|>|&|"|'| |<br\s*\/?>|&#(\d+);/gi
134+ // <|>... : 명명된 엔티티들을 찾습니다.
135+ // <br\s*\/?> : <br>, <br/> 등 다양한 형태의 br 태그를 찾습니다.
136+ // &#(\d+); : 숫자 엔티티(&#...)를 찾고, 숫자 부분을 그룹으로 캡처합니다.
137+ // g: 전역, i: 대소문자 무시
138+ const regex = / & l t ; | & g t ; | & a m p ; | & q u o t ; | & # 3 9 ; | & n b s p ; | < b r \s * \/ ? > | & # ( \d + ) ; / gi;
139+
140+ return str . replace ( regex , ( match , capturedNumber ) => {
141+ // 1. 명명된 엔티티 또는 br 태그 처리
142+ const lowerCaseMatch = match . toLowerCase ( ) ;
143+ if ( textEntityMap [ lowerCaseMatch ] ) {
144+ return textEntityMap [ lowerCaseMatch ] ;
145+ }
146+ if ( lowerCaseMatch . startsWith ( '<br' ) ) {
147+ return '\n' ;
148+ }
149+
150+ // 2. 숫자 엔티티(&#...;) 처리
151+ if ( capturedNumber ) {
152+ return String . fromCharCode ( parseInt ( capturedNumber , 10 ) ) ;
153+ }
154+
155+ // 예외 케이스 처리
156+ return match ;
157+ } ) ;
158+ }
159+
160+ export class XapiRoot {
161+ datasets : Dataset [ ] = [ ] ;
162+ parameters : Parameters = { params : [ ] } ;
163+
164+ constructor ( datasets : Dataset [ ] = [ ] , parameters : Parameters = { params : [ ] } ) {
165+ this . datasets = datasets ;
166+ this . parameters = parameters ;
167+ }
168+
169+ addDataset ( dataset : Dataset ) : void {
170+ this . datasets . push ( dataset ) ;
171+ }
172+
173+ addParameter ( parameter : Parameter ) : void {
174+ this . parameters . params . push ( parameter ) ;
175+ }
176+
177+ setParameters ( parameters : Parameters ) : void {
178+ this . parameters = parameters ;
179+ }
180+
181+ setParameter ( id : string , value : XapiValueType ) : void {
182+ const param = this . parameters . params . find ( p => p . id === id ) ;
183+ if ( param ) {
184+ param . value = value ;
185+ } else {
186+ this . addParameter ( { id, value } ) ;
187+ }
188+ }
189+
190+
191+ }
192+
193+ export class Dataset {
194+ id : string ;
195+ columns : Column [ ] = [ ] ;
196+ rows : Row [ ] = [ ] ;
197+ private _columnIndexMap : Map < string , number > = new Map ( ) ;
198+ constructor ( id : string , columns : Column [ ] = [ ] , rows : Row [ ] = [ ] ) {
199+ this . id = id ;
200+ this . columns = columns ;
201+ this . rows = rows ;
202+ }
203+
204+ addColumn ( column : Column ) : void {
205+ this . columns . push ( column ) ;
206+ this . _columnIndexMap . set ( column . id , this . columns . length - 1 ) ;
207+ }
208+
209+ addRow ( row : Row ) : void {
210+ this . rows . push ( row ) ;
211+ }
212+ getColumnIndex ( columnId : string ) : number | undefined {
213+ return this . _columnIndexMap . get ( columnId ) ;
214+ }
215+
216+ getColumn ( rowIdx : number , columnId : string ) : XapiValueType | undefined {
217+ const colIndex = this . getColumnIndex ( columnId ) ;
218+ let retVal : XapiValueType | undefined = undefined ;
219+ if ( colIndex !== undefined && rowIdx < this . rows . length ) {
220+ retVal = this . rows [ rowIdx ] . cols [ colIndex ] ?. value ;
221+ }
222+ if ( _options . castToColumnType && retVal !== undefined ) {
223+ }
224+ return retVal ;
225+ }
226+ }
0 commit comments