@xapi-js/core 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,12 +5,107 @@ This package provides core utilities and types for working with X-API data.
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
+ # npm
9
+ npm install @xapi-ts/core
10
+
11
+ # yarn
12
+ yarn add @xapi-ts/core
13
+
14
+ # pnpm
8
15
  pnpm add @xapi-ts/core
16
+
17
+ # bun
18
+ bun add @xapi-ts/core
19
+
20
+ # deno
21
+ deno add @xapi-ts/core
9
22
  ```
10
23
 
11
24
  ## Usage
12
25
 
13
- (Add usage examples here)
26
+ `@xapi-ts/core` provides the fundamental classes and functions for working with X-API data.
27
+
28
+ ### Parsing X-API XML
29
+
30
+ You can parse an X-API XML string into an `XapiRoot` object:
31
+
32
+ ```typescript
33
+ import { parse, XapiRoot } from '@xapi-ts/core';
34
+
35
+ const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
36
+ <Root xmlns="http://www.tobesoft.com/platform/Dataset" ver="4000">
37
+ <Parameters>
38
+ <Parameter id="service">stock</Parameter>
39
+ <Parameter id="method">search</Parameter>
40
+ </Parameters>
41
+ </Root>`;
42
+
43
+ async function parseXapi() {
44
+ const xapi = await parse(xmlString);
45
+ console.log('Service:', xapi.getParameter('service')?.value);
46
+ console.log('Method:', xapi.getParameter('method')?.value);
47
+ }
48
+
49
+ parseXapi();
50
+ ```
51
+
52
+ ### Creating and Manipulating XapiRoot
53
+
54
+ You can create an `XapiRoot` object and add parameters and datasets programmatically:
55
+
56
+ ```typescript
57
+ import { XapiRoot, Dataset } from '@xapi-ts/core';
58
+
59
+ const xapi = new XapiRoot();
60
+
61
+ // Add parameters
62
+ xapi.addParameter({ id: 'resultCode', value: '0' });
63
+ xapi.addParameter({ id: 'resultMsg', value: 'SUCCESS' });
64
+
65
+ // Create and add a dataset
66
+ const usersDataset = new Dataset('users');
67
+ usersDataset.addColumn({ id: 'id', type: 'INT' });
68
+ usersDataset.addColumn({ id: 'name', type: 'STRING' });
69
+
70
+ usersDataset.newRow();
71
+ usersDataset.setColumn(0, 'id', 1);
72
+ usersDataset.setColumn(0, 'name', 'Alice');
73
+
74
+ usersDataset.newRow();
75
+ usersDataset.setColumn(1, 'id', 2);
76
+ usersDataset.setColumn(1, 'name', 'Bob');
77
+
78
+ xapi.addDataset(usersDataset);
79
+
80
+ console.log('XapiRoot created:', xapi);
81
+ ```
82
+
83
+ ### Serializing XapiRoot to XML
84
+
85
+ You can serialize an `XapiRoot` object back into an X-API XML string:
86
+
87
+ ```typescript
88
+ import { writeString, XapiRoot, Dataset } from '@xapi-ts/core';
89
+
90
+ const xapi = new XapiRoot();
91
+ xapi.addParameter({ id: 'status', value: 'OK' });
92
+
93
+ const productsDataset = new Dataset('products');
94
+ productsDataset.addColumn({ id: 'productId', type: 'STRING' });
95
+ productsDataset.addColumn({ id: 'price', type: 'INT' });
96
+ productsDataset.newRow();
97
+ productsDataset.setColumn(0, 'productId', 'P001');
98
+ productsDataset.setColumn(0, 'price', 1000);
99
+ xapi.addDataset(productsDataset);
100
+
101
+ async function writeXapi() {
102
+ const xmlOutput = await writeString(xapi);
103
+ console.log('생성된 XML:
104
+ ', xmlOutput);
105
+ }
106
+
107
+ writeXapi();
108
+ ```
14
109
 
15
110
  ---
16
111
 
@@ -21,9 +116,103 @@ pnpm add @xapi-ts/core
21
116
  ## 설치
22
117
 
23
118
  ```bash
119
+ # npm
120
+ npm install @xapi-ts/core
121
+
122
+ # yarn
123
+ yarn add @xapi-ts/core
124
+
125
+ # pnpm
24
126
  pnpm add @xapi-ts/core
127
+
128
+ # bun
129
+ bun add @xapi-ts/core
130
+
131
+ # deno
132
+ deno add @xapi-ts/core
25
133
  ```
26
134
 
27
135
  ## 사용법
28
136
 
29
- (여기에 사용 예시를 추가하세요)
137
+ `@xapi-ts/core`는 X-API 데이터를 다루기 위한 기본적인 클래스와 함수를 제공합니다.
138
+
139
+ ### X-API XML 파싱
140
+
141
+ X-API XML 문자열을 `XapiRoot` 객체로 파싱할 수 있습니다:
142
+
143
+ ```typescript
144
+ import { parse, XapiRoot } from '@xapi-ts/core';
145
+
146
+ const xmlString = `<?xml version="1.0" encoding="UTF-8"?>
147
+ <Root xmlns="http://www.tobesoft.com/platform/Dataset" ver="4000">
148
+ <Parameters>
149
+ <Parameter id="service">stock</Parameter>
150
+ <Parameter id="method">search</Parameter>
151
+ </Parameters>
152
+ </Root>`;
153
+
154
+ async function parseXapi() {
155
+ const xapi = await parse(xmlString);
156
+ console.log('서비스:', xapi.getParameter('service')?.value);
157
+ console.log('메서드:', xapi.getParameter('method')?.value);
158
+ }
159
+
160
+ parseXapi();
161
+ ```
162
+
163
+ ### XapiRoot 생성 및 조작
164
+
165
+ `XapiRoot` 객체를 생성하고 매개변수 및 데이터셋을 프로그래밍 방식으로 추가할 수 있습니다:
166
+
167
+ ```typescript
168
+ import { XapiRoot, Dataset } from '@xapi-ts/core';
169
+
170
+ const xapi = new XapiRoot();
171
+
172
+ // 매개변수 추가
173
+ xapi.addParameter({ id: 'resultCode', value: '0' });
174
+ xapi.addParameter({ id: 'resultMsg', value: 'SUCCESS' });
175
+
176
+ // 데이터셋 생성 및 추가
177
+ const usersDataset = new Dataset('users');
178
+ usersDataset.addColumn({ id: 'id', type: 'INT' });
179
+ usersDataset.addColumn({ id: 'name', type: 'STRING' });
180
+ let rowIdx: number;
181
+ rowIdx = usersDataset.newRow();
182
+ usersDataset.setColumn(rowIdx, 'id', 1);
183
+ usersDataset.setColumn(rowIdx, 'name', 'Alice');
184
+
185
+ rowIdx = usersDataset.newRow();
186
+ usersDataset.setColumn(rowIdx, 'id', 2);
187
+ usersDataset.setColumn(rowIdx, 'name', 'Bob');
188
+
189
+ xapi.addDataset(usersDataset);
190
+
191
+ console.log('XapiRoot 생성됨:', xapi);
192
+ ```
193
+
194
+ ### XapiRoot를 XML로 직렬화
195
+
196
+ `XapiRoot` 객체를 X-API XML 문자열로 다시 직렬화할 수 있습니다:
197
+
198
+ ```typescript
199
+ import { write, XapiRoot, Dataset } from '@xapi-ts/core';
200
+
201
+ const xapi = new XapiRoot();
202
+ xapi.addParameter({ id: 'status', value: 'OK' });
203
+
204
+ const productsDataset = new Dataset('products');
205
+ productsDataset.addColumn({ id: 'productId', type: 'STRING' });
206
+ productsDataset.addColumn({ id: 'price', type: 'INT' });
207
+ productsDataset.newRow();
208
+ productsDataset.setColumn(0, 'productId', 'P001');
209
+ productsDataset.setColumn(0, 'price', 1000);
210
+ xapi.addDataset(productsDataset);
211
+
212
+ async function writeXapi() {
213
+ const xmlOutput = await write(xapi);
214
+ console.log('생성된 XML:\n', xmlOutput);
215
+ }
216
+
217
+ writeXapi();
218
+ ```
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,10 +32,13 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  ColumnTypeError: () => ColumnTypeError,
24
34
  Dataset: () => Dataset,
35
+ InvalidXmlError: () => InvalidXmlError,
25
36
  NexaVersion: () => NexaVersion,
26
37
  StringWritableStream: () => StringWritableStream,
27
38
  XapiRoot: () => XapiRoot,
28
39
  XplatformVersion: () => XplatformVersion,
40
+ _unescapeXml: () => _unescapeXml,
41
+ arrayBufferToString: () => arrayBufferToString,
29
42
  base64ToUint8Array: () => base64ToUint8Array,
30
43
  columnType: () => columnType,
31
44
  convertToColumnType: () => convertToColumnType,
@@ -34,7 +47,7 @@ __export(index_exports, {
34
47
  initXapi: () => initXapi,
35
48
  makeParseEntities: () => makeParseEntities,
36
49
  makeWriterEntities: () => makeWriterEntities,
37
- parse: () => parse,
50
+ parse: () => parse2,
38
51
  rowType: () => rowType,
39
52
  stringToDate: () => stringToDate,
40
53
  stringToReadableStream: () => stringToReadableStream,
@@ -46,6 +59,7 @@ module.exports = __toCommonJS(index_exports);
46
59
 
47
60
  // src/handler.ts
48
61
  var import_stax_xml = require("stax-xml");
62
+ var txml = __toESM(require("txml"), 1);
49
63
 
50
64
  // src/types.ts
51
65
  var rowType = ["insert", "update", "delete"];
@@ -64,21 +78,31 @@ var ColumnTypeError = class extends Error {
64
78
  this.name = "ColumnTypeError";
65
79
  }
66
80
  };
81
+ var InvalidXmlError = class extends Error {
82
+ constructor(message) {
83
+ super(message);
84
+ this.name = "InvalidXmlError";
85
+ }
86
+ };
67
87
 
68
88
  // src/utils.ts
89
+ function arrayBufferToString(buffer) {
90
+ const decoder = new TextDecoder();
91
+ return decoder.decode(buffer);
92
+ }
69
93
  function makeParseEntities() {
70
- const entities = [];
94
+ const entities2 = [];
71
95
  for (let i = 1; i <= 32; i++) {
72
- entities.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
96
+ entities2.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
73
97
  }
74
- return entities;
98
+ return entities2;
75
99
  }
76
100
  function makeWriterEntities() {
77
- const entities = [];
101
+ const entities2 = [];
78
102
  for (let i = 1; i <= 32; i++) {
79
- entities.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
103
+ entities2.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
80
104
  }
81
- return entities;
105
+ return entities2;
82
106
  }
83
107
  function base64ToUint8Array(base64String) {
84
108
  const binaryString = atob(base64String);
@@ -156,6 +180,7 @@ function dateToString(date, type) {
156
180
  }
157
181
  }
158
182
  var StringWritableStream = class extends WritableStream {
183
+ result = "";
159
184
  constructor() {
160
185
  const decoder = new TextDecoder();
161
186
  super({
@@ -166,7 +191,6 @@ var StringWritableStream = class extends WritableStream {
166
191
  this.result += decoder.decode();
167
192
  }
168
193
  });
169
- this.result = "";
170
194
  }
171
195
  /**
172
196
  * Returns the collected string result.
@@ -227,23 +251,34 @@ function convertToString(value, type) {
227
251
  return dateToString(value, type);
228
252
  case "BLOB":
229
253
  return uint8ArrayToBase64(value);
254
+ case "STRING":
255
+ return String(value);
230
256
  default:
231
257
  return String(value);
232
258
  }
233
259
  }
260
+ var entities = makeParseEntities();
261
+ function _unescapeXml(str) {
262
+ if (!str) return str;
263
+ const regex = new RegExp(entities.map((e) => e.entity).join("|"), "g");
264
+ return str.replace(regex, (match) => {
265
+ const entity = entities.find((e) => e.entity === match);
266
+ return entity ? entity.value : match;
267
+ });
268
+ }
234
269
 
235
270
  // src/xapi-data.ts
236
271
  var XapiRoot = class {
272
+ /** An array of datasets within the X-API root. */
273
+ datasets = [];
274
+ /** Parameters associated with the X-API root. */
275
+ parameters = { params: [] };
237
276
  /**
238
277
  * Creates an instance of XapiRoot.
239
278
  * @param datasets - Initial array of datasets.
240
279
  * @param parameters - Initial parameters.
241
280
  */
242
281
  constructor(datasets = [], parameters = { params: [] }) {
243
- /** An array of datasets within the X-API root. */
244
- this.datasets = [];
245
- /** Parameters associated with the X-API root. */
246
- this.parameters = { params: [] };
247
282
  this.datasets = datasets;
248
283
  this.parameters = parameters;
249
284
  }
@@ -331,6 +366,13 @@ var XapiRoot = class {
331
366
  }
332
367
  };
333
368
  var Dataset = class {
369
+ /** The ID of the dataset. */
370
+ id;
371
+ constColumns = [];
372
+ columns = [];
373
+ /** An array of rows in the dataset. */
374
+ rows = [];
375
+ _columnIndexMap = /* @__PURE__ */ new Map();
334
376
  /**
335
377
  * Creates an instance of Dataset.
336
378
  * @param id - The ID of the dataset.
@@ -339,11 +381,6 @@ var Dataset = class {
339
381
  * @param rows - Initial array of rows.
340
382
  */
341
383
  constructor(id, constColumns = [], columns = [], rows = []) {
342
- this.constColumns = [];
343
- this.columns = [];
344
- /** An array of rows in the dataset. */
345
- this.rows = [];
346
- this._columnIndexMap = /* @__PURE__ */ new Map();
347
384
  this.id = id;
348
385
  this.constColumns = constColumns;
349
386
  this.columns = columns;
@@ -524,127 +561,105 @@ function initXapi(options) {
524
561
  ...options
525
562
  };
526
563
  }
527
- async function parse(reader) {
528
- let _stream;
529
- if (typeof reader === "string") {
530
- _stream = new ReadableStream({
531
- start(controller) {
532
- controller.enqueue(new TextEncoder().encode(reader));
533
- controller.close();
534
- }
535
- });
536
- } else {
537
- _stream = reader;
538
- }
539
- const xmlParser = new import_stax_xml.StaxXmlParser(_stream, {
540
- addEntities: makeParseEntities(),
541
- encoding: "UTF-8"
542
- });
564
+ function parse2(xml) {
565
+ const parsedXml = txml.parse(xml);
543
566
  const xapiRoot = new XapiRoot();
544
- for await (const event of xmlParser) {
545
- if (event.type === import_stax_xml.XmlEventType.START_ELEMENT) {
546
- const startEvent = event;
547
- switch (startEvent.localName) {
548
- case "Parameter":
549
- let paramValue = void 0;
550
- const charEvent = (await xmlParser.next()).value;
551
- if (charEvent.type === import_stax_xml.XmlEventType.CHARACTERS) {
552
- paramValue = charEvent.value;
553
- }
554
- xapiRoot.addParameter({
555
- id: startEvent.attributes["id"],
556
- type: startEvent.attributes["type"] || "STRING",
557
- value: _options.parseToTypes ? convertToColumnType(paramValue, startEvent.attributes["type"] || "STRING") : paramValue
558
- });
559
- break;
560
- case "Dataset":
561
- xapiRoot.addDataset(await parseDataset(startEvent, xmlParser));
562
- break;
567
+ const rootElement = parsedXml.find((node) => {
568
+ const rootNode = node;
569
+ return rootNode.tagName === "Root";
570
+ });
571
+ if (rootElement === void 0) return xapiRoot;
572
+ const parametersElement = rootElement.children?.find((node) => node.tagName === "Parameters");
573
+ parseParameters(parametersElement, xapiRoot);
574
+ const datasetsElements = rootElement.children?.filter((node) => node.tagName === "Dataset");
575
+ if (datasetsElements && datasetsElements?.length && datasetsElements.length > 0) {
576
+ for (const datasetsElement of datasetsElements) {
577
+ if (!datasetsElement.attributes || !datasetsElement.attributes.id) {
578
+ throw new InvalidXmlError("Dataset element must have an 'id' attribute");
563
579
  }
580
+ const datasetId = datasetsElement.attributes?.id;
581
+ const dataset = new Dataset(datasetId);
582
+ xapiRoot.addDataset(dataset);
583
+ const columnInfoElement = datasetsElement.children?.find((node) => node.tagName === "ColumnInfo");
584
+ parseColumnInfo(columnInfoElement, dataset);
585
+ const rowsElement = datasetsElement.children?.find((node) => node.tagName === "Rows");
586
+ parseRows(rowsElement, dataset);
564
587
  }
565
588
  }
566
589
  return xapiRoot;
567
590
  }
568
- async function parseDataset(initEvent, xmlParser) {
569
- const dataset = new Dataset(initEvent.attributes["id"]);
570
- let currentRowIndex = -1;
571
- for await (const event of xmlParser) {
572
- if (event.type === import_stax_xml.XmlEventType.END_ELEMENT && event.localName === "Dataset") {
573
- break;
574
- } else if (event.type === import_stax_xml.XmlEventType.START_ELEMENT) {
575
- const startEvent = event;
576
- switch (startEvent.localName) {
577
- case "ConstColumn":
578
- dataset.addConstColumn({
579
- id: startEvent.attributes["id"],
580
- size: parseInt(startEvent.attributes["size"] || "0", 10),
581
- type: startEvent.attributes["type"] || "STRING",
582
- value: startEvent.attributes["value"] || void 0
583
- });
584
- break;
585
- case "Column":
586
- dataset.addColumn({
587
- id: startEvent.attributes["id"],
588
- size: parseInt(startEvent.attributes["size"] || "0", 10),
589
- type: startEvent.attributes["type"] || "STRING"
591
+ function parseValue(value, type = "STRING") {
592
+ value = _unescapeXml(value);
593
+ return _options.parseToTypes ? convertToColumnType(value, type) : value;
594
+ }
595
+ function parseColumnInfo(columnInfoElement, dataset) {
596
+ if (!columnInfoElement) throw new InvalidXmlError("ColumnInfo element is missing in the dataset");
597
+ columnInfoElement.children?.forEach((colInfo) => {
598
+ if (colInfo.tagName === "ConstColumn") {
599
+ dataset.addConstColumn({
600
+ id: colInfo.attributes?.id,
601
+ size: parseInt(colInfo.attributes?.size || "0", 10),
602
+ type: colInfo.attributes?.type || "STRING",
603
+ value: parseValue(colInfo.attributes?.value, colInfo.attributes?.type)
604
+ });
605
+ } else if (colInfo.tagName === "Column") {
606
+ dataset.addColumn({
607
+ id: colInfo.attributes?.id,
608
+ size: parseInt(colInfo.attributes?.size || "0", 10),
609
+ type: colInfo.attributes?.type || "STRING"
610
+ });
611
+ }
612
+ });
613
+ }
614
+ function parseRows(rowsElement, dataset) {
615
+ rowsElement?.children?.forEach((r) => {
616
+ if (r.tagName === "Row") {
617
+ const rowIndex = dataset.newRow();
618
+ dataset.rows[rowIndex].type = r.attributes?.type || void 0;
619
+ r.children?.forEach((col) => {
620
+ if (col.tagName === "Col") {
621
+ const colId = col.attributes?.id;
622
+ const value = col.children?.[0];
623
+ const columnInfo = dataset.getColumnInfo(colId);
624
+ if (!columnInfo) throw new InvalidXmlError(`Column with id ${colId} not found in dataset ${dataset.id}`);
625
+ const castedValue = parseValue(value, columnInfo.type);
626
+ dataset.rows[rowIndex].cols.push({ id: colId, value: castedValue });
627
+ } else if (col.tagName === "OrgRow") {
628
+ dataset.rows[rowIndex].orgRow = [];
629
+ col.children?.forEach((orgCol) => {
630
+ if (orgCol.tagName === "Col") {
631
+ const colId = orgCol.attributes?.id;
632
+ const value = orgCol.children?.[0];
633
+ const columnInfo = dataset.getColumnInfo(colId);
634
+ const castedValue = parseValue(value, columnInfo.type);
635
+ if (dataset && dataset.rows && dataset.rows[rowIndex] && dataset.rows[rowIndex].orgRow) {
636
+ dataset.rows[rowIndex].orgRow.push({ id: colId, value: castedValue });
637
+ }
638
+ }
590
639
  });
591
- break;
592
- case "Row":
593
- currentRowIndex = dataset.newRow();
594
- dataset.rows[currentRowIndex].type = startEvent.attributes["type"] || void 0;
595
- break;
596
- case "Col": {
597
- await parseCol(xmlParser, startEvent, dataset, currentRowIndex, false);
598
- break;
599
640
  }
600
- case "OrgRow":
601
- if (currentRowIndex < 0) {
602
- throw new Error("Row must be defined before OrgRow");
603
- }
604
- dataset.rows[currentRowIndex].orgRow = [];
605
- for await (const orgRowEvent of xmlParser) {
606
- if (orgRowEvent.type === import_stax_xml.XmlEventType.END_ELEMENT && orgRowEvent.localName === "OrgRow") {
607
- break;
608
- }
609
- if (orgRowEvent.type === import_stax_xml.XmlEventType.START_ELEMENT && orgRowEvent.localName === "Col") {
610
- const orgColEvent = orgRowEvent;
611
- await parseCol(xmlParser, orgColEvent, dataset, currentRowIndex, true);
612
- }
613
- }
614
- }
641
+ });
642
+ } else if (r.tagName === "Col") {
643
+ throw new InvalidXmlError("Row must be defined before Col");
644
+ } else if (r.tagName === "OrgRow") {
645
+ throw new InvalidXmlError("Row must be defined before OrgRow");
615
646
  }
616
- }
617
- return dataset;
647
+ });
618
648
  }
619
- async function parseCol(xmlParser, startEvent, dataset, currentRowIndex, isOrgRow) {
620
- if (currentRowIndex < 0) {
621
- throw new Error("Row must be defined before Col");
622
- }
623
- const colId = startEvent.attributes["id"];
624
- const colIndex = dataset.getColumnIndex(colId);
625
- if (colIndex === void 0 || colIndex < 0) {
626
- throw new Error(`Column with id ${colId} not found in dataset ${dataset.id}`);
627
- }
628
- const colCharEvent = (await xmlParser.next()).value;
629
- let value;
630
- if (colCharEvent.type === import_stax_xml.XmlEventType.CDATA) {
631
- value = colCharEvent.value;
632
- } else if (colCharEvent.type === import_stax_xml.XmlEventType.CHARACTERS) {
633
- value = colCharEvent.value;
634
- }
635
- let castedValue = value;
636
- if (_options.parseToTypes) {
637
- const columnInfo = dataset.getColumnInfo(colId);
638
- if (!columnInfo || columnType.includes(columnInfo.type) === false) {
639
- throw new Error(`Column type for ${colId} not found in dataset ${dataset.id}`);
649
+ function parseParameters(parametersElement, xapiRoot) {
650
+ if (!parametersElement) return;
651
+ parametersElement.children?.forEach((p) => {
652
+ if (p.tagName === "Parameter") {
653
+ const id = p.attributes?.id;
654
+ const type = p.attributes?.type || "STRING";
655
+ const value = p.children?.[0];
656
+ xapiRoot.addParameter({
657
+ id,
658
+ type,
659
+ value: parseValue(value, type)
660
+ });
640
661
  }
641
- castedValue = convertToColumnType(value, columnInfo.type);
642
- }
643
- if (!isOrgRow) {
644
- dataset.rows[currentRowIndex].cols.push({ id: colId, value: castedValue });
645
- } else {
646
- dataset.rows[currentRowIndex].orgRow.push({ id: colId, value: castedValue });
647
- }
662
+ });
648
663
  }
649
664
  async function writeString(root) {
650
665
  const stringStream = new StringWritableStream();
@@ -765,10 +780,13 @@ async function writeColumn(writer, dataset, col) {
765
780
  0 && (module.exports = {
766
781
  ColumnTypeError,
767
782
  Dataset,
783
+ InvalidXmlError,
768
784
  NexaVersion,
769
785
  StringWritableStream,
770
786
  XapiRoot,
771
787
  XplatformVersion,
788
+ _unescapeXml,
789
+ arrayBufferToString,
772
790
  base64ToUint8Array,
773
791
  columnType,
774
792
  convertToColumnType,
package/dist/index.d.cts CHANGED
@@ -127,6 +127,9 @@ interface XapiOptions {
127
127
  declare class ColumnTypeError extends Error {
128
128
  constructor(message: string);
129
129
  }
130
+ declare class InvalidXmlError extends Error {
131
+ constructor(message: string);
132
+ }
130
133
 
131
134
  /**
132
135
  * Represents the root of an X-API XML structure, containing datasets and parameters.
@@ -312,17 +315,19 @@ declare class Dataset {
312
315
  /**
313
316
  * Initializes the X-API handler with the provided options.
314
317
  * @param options - The options to initialize the X-API handler.
318
+ * @returns void
315
319
  */
316
320
  declare function initXapi(options: XapiOptions): void;
317
321
  /**
318
- * Parses an XML stream or string into an XapiRoot object.
319
- * @param reader - The ReadableStream or string containing the XML data.
322
+ * Parses an XML string into an XapiRoot object using txml.
323
+ * @param xml - The string containing the XML data.
320
324
  * @returns A Promise that resolves to an XapiRoot object.
321
325
  */
322
- declare function parse(reader: ReadableStream | string): Promise<XapiRoot>;
326
+ declare function parse(xml: string): XapiRoot;
323
327
  declare function writeString(root: XapiRoot): Promise<string>;
324
328
  declare function write(stream: WritableStream<Uint8Array>, root: XapiRoot): Promise<void>;
325
329
 
330
+ declare function arrayBufferToString(buffer: ArrayBuffer): string;
326
331
  /**
327
332
  * Creates an array of entities for parsing XML, including control characters.
328
333
  * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
@@ -398,5 +403,6 @@ declare function convertToColumnType(value: XapiValueType, type: ColumnType): Xa
398
403
  * @returns The string representation of the value.
399
404
  */
400
405
  declare function convertToString(value: XapiValueType, type: ColumnType): string;
406
+ declare function _unescapeXml(str?: string): string | undefined;
401
407
 
402
- export { type Col, type Column, type ColumnInfo, type ColumnType, ColumnTypeError, type ConstColumn, Dataset, NexaVersion, type Parameter, type Row, type RowType, type Rows, StringWritableStream, type XapiOptions, type XapiParameters, XapiRoot, type XapiValueType, type XapiVersion, XplatformVersion, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, initXapi, makeParseEntities, makeWriterEntities, parse, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, writeString };
408
+ export { type Col, type Column, type ColumnInfo, type ColumnType, ColumnTypeError, type ConstColumn, Dataset, InvalidXmlError, NexaVersion, type Parameter, type Row, type RowType, type Rows, StringWritableStream, type XapiOptions, type XapiParameters, XapiRoot, type XapiValueType, type XapiVersion, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, initXapi, makeParseEntities, makeWriterEntities, parse, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, writeString };
package/dist/index.d.ts CHANGED
@@ -127,6 +127,9 @@ interface XapiOptions {
127
127
  declare class ColumnTypeError extends Error {
128
128
  constructor(message: string);
129
129
  }
130
+ declare class InvalidXmlError extends Error {
131
+ constructor(message: string);
132
+ }
130
133
 
131
134
  /**
132
135
  * Represents the root of an X-API XML structure, containing datasets and parameters.
@@ -312,17 +315,19 @@ declare class Dataset {
312
315
  /**
313
316
  * Initializes the X-API handler with the provided options.
314
317
  * @param options - The options to initialize the X-API handler.
318
+ * @returns void
315
319
  */
316
320
  declare function initXapi(options: XapiOptions): void;
317
321
  /**
318
- * Parses an XML stream or string into an XapiRoot object.
319
- * @param reader - The ReadableStream or string containing the XML data.
322
+ * Parses an XML string into an XapiRoot object using txml.
323
+ * @param xml - The string containing the XML data.
320
324
  * @returns A Promise that resolves to an XapiRoot object.
321
325
  */
322
- declare function parse(reader: ReadableStream | string): Promise<XapiRoot>;
326
+ declare function parse(xml: string): XapiRoot;
323
327
  declare function writeString(root: XapiRoot): Promise<string>;
324
328
  declare function write(stream: WritableStream<Uint8Array>, root: XapiRoot): Promise<void>;
325
329
 
330
+ declare function arrayBufferToString(buffer: ArrayBuffer): string;
326
331
  /**
327
332
  * Creates an array of entities for parsing XML, including control characters.
328
333
  * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
@@ -398,5 +403,6 @@ declare function convertToColumnType(value: XapiValueType, type: ColumnType): Xa
398
403
  * @returns The string representation of the value.
399
404
  */
400
405
  declare function convertToString(value: XapiValueType, type: ColumnType): string;
406
+ declare function _unescapeXml(str?: string): string | undefined;
401
407
 
402
- export { type Col, type Column, type ColumnInfo, type ColumnType, ColumnTypeError, type ConstColumn, Dataset, NexaVersion, type Parameter, type Row, type RowType, type Rows, StringWritableStream, type XapiOptions, type XapiParameters, XapiRoot, type XapiValueType, type XapiVersion, XplatformVersion, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, initXapi, makeParseEntities, makeWriterEntities, parse, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, writeString };
408
+ export { type Col, type Column, type ColumnInfo, type ColumnType, ColumnTypeError, type ConstColumn, Dataset, InvalidXmlError, NexaVersion, type Parameter, type Row, type RowType, type Rows, StringWritableStream, type XapiOptions, type XapiParameters, XapiRoot, type XapiValueType, type XapiVersion, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, initXapi, makeParseEntities, makeWriterEntities, parse, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, writeString };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/handler.ts
2
- import { StaxXmlParser, StaxXmlWriter, XmlEventType } from "stax-xml";
2
+ import { StaxXmlWriter } from "stax-xml";
3
+ import * as txml from "txml";
3
4
 
4
5
  // src/types.ts
5
6
  var rowType = ["insert", "update", "delete"];
@@ -18,21 +19,31 @@ var ColumnTypeError = class extends Error {
18
19
  this.name = "ColumnTypeError";
19
20
  }
20
21
  };
22
+ var InvalidXmlError = class extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "InvalidXmlError";
26
+ }
27
+ };
21
28
 
22
29
  // src/utils.ts
30
+ function arrayBufferToString(buffer) {
31
+ const decoder = new TextDecoder();
32
+ return decoder.decode(buffer);
33
+ }
23
34
  function makeParseEntities() {
24
- const entities = [];
35
+ const entities2 = [];
25
36
  for (let i = 1; i <= 32; i++) {
26
- entities.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
37
+ entities2.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
27
38
  }
28
- return entities;
39
+ return entities2;
29
40
  }
30
41
  function makeWriterEntities() {
31
- const entities = [];
42
+ const entities2 = [];
32
43
  for (let i = 1; i <= 32; i++) {
33
- entities.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
44
+ entities2.push({ entity: `&#${i};`, value: String.fromCharCode(i) });
34
45
  }
35
- return entities;
46
+ return entities2;
36
47
  }
37
48
  function base64ToUint8Array(base64String) {
38
49
  const binaryString = atob(base64String);
@@ -110,6 +121,7 @@ function dateToString(date, type) {
110
121
  }
111
122
  }
112
123
  var StringWritableStream = class extends WritableStream {
124
+ result = "";
113
125
  constructor() {
114
126
  const decoder = new TextDecoder();
115
127
  super({
@@ -120,7 +132,6 @@ var StringWritableStream = class extends WritableStream {
120
132
  this.result += decoder.decode();
121
133
  }
122
134
  });
123
- this.result = "";
124
135
  }
125
136
  /**
126
137
  * Returns the collected string result.
@@ -181,23 +192,34 @@ function convertToString(value, type) {
181
192
  return dateToString(value, type);
182
193
  case "BLOB":
183
194
  return uint8ArrayToBase64(value);
195
+ case "STRING":
196
+ return String(value);
184
197
  default:
185
198
  return String(value);
186
199
  }
187
200
  }
201
+ var entities = makeParseEntities();
202
+ function _unescapeXml(str) {
203
+ if (!str) return str;
204
+ const regex = new RegExp(entities.map((e) => e.entity).join("|"), "g");
205
+ return str.replace(regex, (match) => {
206
+ const entity = entities.find((e) => e.entity === match);
207
+ return entity ? entity.value : match;
208
+ });
209
+ }
188
210
 
189
211
  // src/xapi-data.ts
190
212
  var XapiRoot = class {
213
+ /** An array of datasets within the X-API root. */
214
+ datasets = [];
215
+ /** Parameters associated with the X-API root. */
216
+ parameters = { params: [] };
191
217
  /**
192
218
  * Creates an instance of XapiRoot.
193
219
  * @param datasets - Initial array of datasets.
194
220
  * @param parameters - Initial parameters.
195
221
  */
196
222
  constructor(datasets = [], parameters = { params: [] }) {
197
- /** An array of datasets within the X-API root. */
198
- this.datasets = [];
199
- /** Parameters associated with the X-API root. */
200
- this.parameters = { params: [] };
201
223
  this.datasets = datasets;
202
224
  this.parameters = parameters;
203
225
  }
@@ -285,6 +307,13 @@ var XapiRoot = class {
285
307
  }
286
308
  };
287
309
  var Dataset = class {
310
+ /** The ID of the dataset. */
311
+ id;
312
+ constColumns = [];
313
+ columns = [];
314
+ /** An array of rows in the dataset. */
315
+ rows = [];
316
+ _columnIndexMap = /* @__PURE__ */ new Map();
288
317
  /**
289
318
  * Creates an instance of Dataset.
290
319
  * @param id - The ID of the dataset.
@@ -293,11 +322,6 @@ var Dataset = class {
293
322
  * @param rows - Initial array of rows.
294
323
  */
295
324
  constructor(id, constColumns = [], columns = [], rows = []) {
296
- this.constColumns = [];
297
- this.columns = [];
298
- /** An array of rows in the dataset. */
299
- this.rows = [];
300
- this._columnIndexMap = /* @__PURE__ */ new Map();
301
325
  this.id = id;
302
326
  this.constColumns = constColumns;
303
327
  this.columns = columns;
@@ -478,127 +502,105 @@ function initXapi(options) {
478
502
  ...options
479
503
  };
480
504
  }
481
- async function parse(reader) {
482
- let _stream;
483
- if (typeof reader === "string") {
484
- _stream = new ReadableStream({
485
- start(controller) {
486
- controller.enqueue(new TextEncoder().encode(reader));
487
- controller.close();
488
- }
489
- });
490
- } else {
491
- _stream = reader;
492
- }
493
- const xmlParser = new StaxXmlParser(_stream, {
494
- addEntities: makeParseEntities(),
495
- encoding: "UTF-8"
496
- });
505
+ function parse2(xml) {
506
+ const parsedXml = txml.parse(xml);
497
507
  const xapiRoot = new XapiRoot();
498
- for await (const event of xmlParser) {
499
- if (event.type === XmlEventType.START_ELEMENT) {
500
- const startEvent = event;
501
- switch (startEvent.localName) {
502
- case "Parameter":
503
- let paramValue = void 0;
504
- const charEvent = (await xmlParser.next()).value;
505
- if (charEvent.type === XmlEventType.CHARACTERS) {
506
- paramValue = charEvent.value;
507
- }
508
- xapiRoot.addParameter({
509
- id: startEvent.attributes["id"],
510
- type: startEvent.attributes["type"] || "STRING",
511
- value: _options.parseToTypes ? convertToColumnType(paramValue, startEvent.attributes["type"] || "STRING") : paramValue
512
- });
513
- break;
514
- case "Dataset":
515
- xapiRoot.addDataset(await parseDataset(startEvent, xmlParser));
516
- break;
508
+ const rootElement = parsedXml.find((node) => {
509
+ const rootNode = node;
510
+ return rootNode.tagName === "Root";
511
+ });
512
+ if (rootElement === void 0) return xapiRoot;
513
+ const parametersElement = rootElement.children?.find((node) => node.tagName === "Parameters");
514
+ parseParameters(parametersElement, xapiRoot);
515
+ const datasetsElements = rootElement.children?.filter((node) => node.tagName === "Dataset");
516
+ if (datasetsElements && datasetsElements?.length && datasetsElements.length > 0) {
517
+ for (const datasetsElement of datasetsElements) {
518
+ if (!datasetsElement.attributes || !datasetsElement.attributes.id) {
519
+ throw new InvalidXmlError("Dataset element must have an 'id' attribute");
517
520
  }
521
+ const datasetId = datasetsElement.attributes?.id;
522
+ const dataset = new Dataset(datasetId);
523
+ xapiRoot.addDataset(dataset);
524
+ const columnInfoElement = datasetsElement.children?.find((node) => node.tagName === "ColumnInfo");
525
+ parseColumnInfo(columnInfoElement, dataset);
526
+ const rowsElement = datasetsElement.children?.find((node) => node.tagName === "Rows");
527
+ parseRows(rowsElement, dataset);
518
528
  }
519
529
  }
520
530
  return xapiRoot;
521
531
  }
522
- async function parseDataset(initEvent, xmlParser) {
523
- const dataset = new Dataset(initEvent.attributes["id"]);
524
- let currentRowIndex = -1;
525
- for await (const event of xmlParser) {
526
- if (event.type === XmlEventType.END_ELEMENT && event.localName === "Dataset") {
527
- break;
528
- } else if (event.type === XmlEventType.START_ELEMENT) {
529
- const startEvent = event;
530
- switch (startEvent.localName) {
531
- case "ConstColumn":
532
- dataset.addConstColumn({
533
- id: startEvent.attributes["id"],
534
- size: parseInt(startEvent.attributes["size"] || "0", 10),
535
- type: startEvent.attributes["type"] || "STRING",
536
- value: startEvent.attributes["value"] || void 0
537
- });
538
- break;
539
- case "Column":
540
- dataset.addColumn({
541
- id: startEvent.attributes["id"],
542
- size: parseInt(startEvent.attributes["size"] || "0", 10),
543
- type: startEvent.attributes["type"] || "STRING"
532
+ function parseValue(value, type = "STRING") {
533
+ value = _unescapeXml(value);
534
+ return _options.parseToTypes ? convertToColumnType(value, type) : value;
535
+ }
536
+ function parseColumnInfo(columnInfoElement, dataset) {
537
+ if (!columnInfoElement) throw new InvalidXmlError("ColumnInfo element is missing in the dataset");
538
+ columnInfoElement.children?.forEach((colInfo) => {
539
+ if (colInfo.tagName === "ConstColumn") {
540
+ dataset.addConstColumn({
541
+ id: colInfo.attributes?.id,
542
+ size: parseInt(colInfo.attributes?.size || "0", 10),
543
+ type: colInfo.attributes?.type || "STRING",
544
+ value: parseValue(colInfo.attributes?.value, colInfo.attributes?.type)
545
+ });
546
+ } else if (colInfo.tagName === "Column") {
547
+ dataset.addColumn({
548
+ id: colInfo.attributes?.id,
549
+ size: parseInt(colInfo.attributes?.size || "0", 10),
550
+ type: colInfo.attributes?.type || "STRING"
551
+ });
552
+ }
553
+ });
554
+ }
555
+ function parseRows(rowsElement, dataset) {
556
+ rowsElement?.children?.forEach((r) => {
557
+ if (r.tagName === "Row") {
558
+ const rowIndex = dataset.newRow();
559
+ dataset.rows[rowIndex].type = r.attributes?.type || void 0;
560
+ r.children?.forEach((col) => {
561
+ if (col.tagName === "Col") {
562
+ const colId = col.attributes?.id;
563
+ const value = col.children?.[0];
564
+ const columnInfo = dataset.getColumnInfo(colId);
565
+ if (!columnInfo) throw new InvalidXmlError(`Column with id ${colId} not found in dataset ${dataset.id}`);
566
+ const castedValue = parseValue(value, columnInfo.type);
567
+ dataset.rows[rowIndex].cols.push({ id: colId, value: castedValue });
568
+ } else if (col.tagName === "OrgRow") {
569
+ dataset.rows[rowIndex].orgRow = [];
570
+ col.children?.forEach((orgCol) => {
571
+ if (orgCol.tagName === "Col") {
572
+ const colId = orgCol.attributes?.id;
573
+ const value = orgCol.children?.[0];
574
+ const columnInfo = dataset.getColumnInfo(colId);
575
+ const castedValue = parseValue(value, columnInfo.type);
576
+ if (dataset && dataset.rows && dataset.rows[rowIndex] && dataset.rows[rowIndex].orgRow) {
577
+ dataset.rows[rowIndex].orgRow.push({ id: colId, value: castedValue });
578
+ }
579
+ }
544
580
  });
545
- break;
546
- case "Row":
547
- currentRowIndex = dataset.newRow();
548
- dataset.rows[currentRowIndex].type = startEvent.attributes["type"] || void 0;
549
- break;
550
- case "Col": {
551
- await parseCol(xmlParser, startEvent, dataset, currentRowIndex, false);
552
- break;
553
581
  }
554
- case "OrgRow":
555
- if (currentRowIndex < 0) {
556
- throw new Error("Row must be defined before OrgRow");
557
- }
558
- dataset.rows[currentRowIndex].orgRow = [];
559
- for await (const orgRowEvent of xmlParser) {
560
- if (orgRowEvent.type === XmlEventType.END_ELEMENT && orgRowEvent.localName === "OrgRow") {
561
- break;
562
- }
563
- if (orgRowEvent.type === XmlEventType.START_ELEMENT && orgRowEvent.localName === "Col") {
564
- const orgColEvent = orgRowEvent;
565
- await parseCol(xmlParser, orgColEvent, dataset, currentRowIndex, true);
566
- }
567
- }
568
- }
582
+ });
583
+ } else if (r.tagName === "Col") {
584
+ throw new InvalidXmlError("Row must be defined before Col");
585
+ } else if (r.tagName === "OrgRow") {
586
+ throw new InvalidXmlError("Row must be defined before OrgRow");
569
587
  }
570
- }
571
- return dataset;
588
+ });
572
589
  }
573
- async function parseCol(xmlParser, startEvent, dataset, currentRowIndex, isOrgRow) {
574
- if (currentRowIndex < 0) {
575
- throw new Error("Row must be defined before Col");
576
- }
577
- const colId = startEvent.attributes["id"];
578
- const colIndex = dataset.getColumnIndex(colId);
579
- if (colIndex === void 0 || colIndex < 0) {
580
- throw new Error(`Column with id ${colId} not found in dataset ${dataset.id}`);
581
- }
582
- const colCharEvent = (await xmlParser.next()).value;
583
- let value;
584
- if (colCharEvent.type === XmlEventType.CDATA) {
585
- value = colCharEvent.value;
586
- } else if (colCharEvent.type === XmlEventType.CHARACTERS) {
587
- value = colCharEvent.value;
588
- }
589
- let castedValue = value;
590
- if (_options.parseToTypes) {
591
- const columnInfo = dataset.getColumnInfo(colId);
592
- if (!columnInfo || columnType.includes(columnInfo.type) === false) {
593
- throw new Error(`Column type for ${colId} not found in dataset ${dataset.id}`);
590
+ function parseParameters(parametersElement, xapiRoot) {
591
+ if (!parametersElement) return;
592
+ parametersElement.children?.forEach((p) => {
593
+ if (p.tagName === "Parameter") {
594
+ const id = p.attributes?.id;
595
+ const type = p.attributes?.type || "STRING";
596
+ const value = p.children?.[0];
597
+ xapiRoot.addParameter({
598
+ id,
599
+ type,
600
+ value: parseValue(value, type)
601
+ });
594
602
  }
595
- castedValue = convertToColumnType(value, columnInfo.type);
596
- }
597
- if (!isOrgRow) {
598
- dataset.rows[currentRowIndex].cols.push({ id: colId, value: castedValue });
599
- } else {
600
- dataset.rows[currentRowIndex].orgRow.push({ id: colId, value: castedValue });
601
- }
603
+ });
602
604
  }
603
605
  async function writeString(root) {
604
606
  const stringStream = new StringWritableStream();
@@ -718,10 +720,13 @@ async function writeColumn(writer, dataset, col) {
718
720
  export {
719
721
  ColumnTypeError,
720
722
  Dataset,
723
+ InvalidXmlError,
721
724
  NexaVersion,
722
725
  StringWritableStream,
723
726
  XapiRoot,
724
727
  XplatformVersion,
728
+ _unescapeXml,
729
+ arrayBufferToString,
725
730
  base64ToUint8Array,
726
731
  columnType,
727
732
  convertToColumnType,
@@ -730,7 +735,7 @@ export {
730
735
  initXapi,
731
736
  makeParseEntities,
732
737
  makeWriterEntities,
733
- parse,
738
+ parse2 as parse,
734
739
  rowType,
735
740
  stringToDate,
736
741
  stringToReadableStream,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xapi-js/core",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.1.1",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -32,12 +32,14 @@
32
32
  "typescript": "^5.3.3"
33
33
  },
34
34
  "dependencies": {
35
- "stax-xml": "^0.2.4"
35
+ "stax-xml": "^0.2.4",
36
+ "txml": "^5.1.1"
36
37
  },
37
38
  "scripts": {
38
39
  "build": "tsup src/index.ts --format cjs,esm --dts",
39
40
  "test": "vitest run",
40
41
  "test:watch": "vitest",
41
- "coverage": "vitest run --coverage"
42
+ "coverage": "vitest run --coverage",
43
+ "benchmark": "node test/performance.test.mjs"
42
44
  }
43
45
  }