Skip to content

Commit b54bde4

Browse files
committed
initial commit
1 parent 21c695d commit b54bde4

6 files changed

Lines changed: 331 additions & 2 deletions

File tree

bun.lockb

344 Bytes
Binary file not shown.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@
3232
"devDependencies": {
3333
"bun-plugin-dts": "^0.3.0",
3434
"@types/bun": "^1.1.10"
35+
},
36+
"dependencies": {
37+
"stax-xml": "^0.1.0"
3538
}
3639
}

src/handler.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { StaxXmlParser } from "stax-xml";
2+
import { XapiRoot } from "./xapi-data";
3+
4+
5+
async function parse(reader: ReadableStream | string): Promise<XapiRoot> {
6+
// Implement your parsing logic here
7+
let _stream: ReadableStream;
8+
if (typeof reader === "string") {
9+
// If the reader is a string, convert it to a ReadableStream
10+
_stream = new ReadableStream({
11+
start(controller) {
12+
controller.enqueue(new TextEncoder().encode(reader));
13+
controller.close();
14+
}
15+
});
16+
}
17+
else {
18+
_stream = reader;
19+
}
20+
const xmlParser = new StaxXmlParser(_stream,);
21+
return new XapiRoot();
22+
}

src/index.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,21 @@
1-
export const one = 1
2-
export const two = 2
1+
import { NexaVersion, XapiOptions } from "./types";
2+
3+
const defaultOptions: XapiOptions = {
4+
xapiVersion: NexaVersion,
5+
castToColumnType: true // Default to true for casting values to their respective column types
6+
};
7+
8+
export let _options: XapiOptions = {
9+
...defaultOptions
10+
};
11+
12+
13+
export function initXapi(options: XapiOptions) {
14+
// Initialize the XAPI with the provided options
15+
console.log("Initializing XAPI with options:", options);
16+
// Here you would typically set up the environment, load necessary libraries, etc.
17+
// how to set added options to _options
18+
_options = {
19+
...options
20+
};
21+
}

src/types.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
export type XapiValueType = string | number | Blob | null;
2+
3+
export interface Col {
4+
id: string;
5+
value: XapiValueType;
6+
}
7+
8+
export interface Row {
9+
cols: Col[];
10+
orgRow?: Col[];
11+
}
12+
13+
export interface Rows {
14+
rows: Row[];
15+
}
16+
17+
export type ColumnType = "STRING" | "INT" | "FLOAT" | "DECIMAL" | "BIGDECIMAL" | "DATE" | "DATETIME" | "TIME" | "BLOB";
18+
19+
export interface Column {
20+
id: string;
21+
size: number;
22+
type: ColumnType;
23+
}
24+
25+
export type ConstColumn = Column & { value: XapiValueType };
26+
27+
export interface ColumnInfo {
28+
constCols?: ConstColumn[];
29+
cols?: Column[];
30+
}
31+
32+
export interface Parameter {
33+
id: string;
34+
type?: ColumnType;
35+
value?: XapiValueType;
36+
}
37+
38+
export interface Parameters {
39+
params: Parameter[];
40+
}
41+
42+
export interface XapiVersion {
43+
xmlns: string;
44+
version: string;
45+
}
46+
export const XplatformVersion = {
47+
xmlns: "http://www.tobesoft.com/platform/Dataset",
48+
version: "4000"
49+
} as const;
50+
51+
export const NexaVersion = {
52+
xmlns: "http://www.nexacroplatform.com/platform/dataset",
53+
version: "4000"
54+
} as const;
55+
56+
export interface XapiOptions {
57+
xapiVersion?: typeof XplatformVersion | typeof NexaVersion;
58+
castToColumnType?: boolean; // If true, will cast values to their respective column types
59+
}

src/xapi-data.ts

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
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+
* - 공백(' ')은 &nbsp;로 변환됩니다.
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+
'<': '&lt;',
84+
'>': '&gt;',
85+
'&': '&amp;',
86+
'"': '&quot;',
87+
"'": '&#39;',
88+
' ': '&nbsp;',
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 -> &#9;)
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+
'&lt;': '<',
125+
'&gt;': '>',
126+
'&amp;': '&',
127+
'&quot;': '"',
128+
'&#39;': "'",
129+
'&nbsp;': ' ',
130+
};
131+
132+
// 정규식 설명:
133+
// /&lt;|&gt;|&amp;|&quot;|&#39;|&nbsp;|<br\s*\/?>|&#(\d+);/gi
134+
// &lt;|&gt;... : 명명된 엔티티들을 찾습니다.
135+
// <br\s*\/?> : <br>, <br/> 등 다양한 형태의 br 태그를 찾습니다.
136+
// &#(\d+); : 숫자 엔티티(&#...)를 찾고, 숫자 부분을 그룹으로 캡처합니다.
137+
// g: 전역, i: 대소문자 무시
138+
const regex = /&lt;|&gt;|&amp;|&quot;|&#39;|&nbsp;|<br\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

Comments
 (0)