@xapi-js/core 1.2.0 → 1.4.0

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/dist/index.js CHANGED
@@ -1,6 +1,3 @@
1
- import { StaxXmlWriterSync } from "stax-xml";
2
- import * as txml from "txml";
3
-
4
1
  //#region src/types.ts
5
2
  /**
6
3
  * Defines the possible types of rows in an X-API dataset (e.g., "insert", "update", "delete").
@@ -53,7 +50,6 @@ var InvalidXmlError = class extends Error {
53
50
  this.name = "InvalidXmlError";
54
51
  }
55
52
  };
56
-
57
53
  //#endregion
58
54
  //#region src/xapi-data.ts
59
55
  /**
@@ -138,18 +134,18 @@ var XapiRoot = class {
138
134
  return this.datasets.length;
139
135
  }
140
136
  /**
141
- * Iterates over the parameters.
142
- * @returns An IterableIterator for parameters.
137
+ * Returns all parameters as an array.
138
+ * @returns An array of parameters.
143
139
  */
144
- *iterParameters() {
145
- for (const param of this.parameters.params) yield param;
140
+ getParameters() {
141
+ return this.parameters.params;
146
142
  }
147
143
  /**
148
- * Iterates over the datasets.
149
- * @returns An IterableIterator for datasets.
144
+ * Returns all datasets as an array.
145
+ * @returns An array of datasets.
150
146
  */
151
- *iterDatasets() {
152
- for (const dataset of this.datasets) yield dataset;
147
+ getDatasets() {
148
+ return this.datasets;
153
149
  }
154
150
  };
155
151
  /**
@@ -277,25 +273,25 @@ var Dataset = class {
277
273
  } else throw new Error(`Row index ${rowIdx} out of bounds in dataset ${this.id}`);
278
274
  }
279
275
  /**
280
- * Iterates over the constant column definitions.
281
- * @returns An IterableIterator for constant columns.
276
+ * Returns all constant column definitions as an array.
277
+ * @returns An array of constant columns.
282
278
  */
283
- *iterConstColumns() {
284
- for (const column of this.constColumns) yield column;
279
+ getConstColumns() {
280
+ return this.constColumns;
285
281
  }
286
282
  /**
287
- * Iterates over the column definitions.
288
- * @returns An IterableIterator for columns.
283
+ * Returns all column definitions as an array.
284
+ * @returns An array of columns.
289
285
  */
290
- *iterColumns() {
291
- for (const column of this.columns) yield column;
286
+ getColumns() {
287
+ return this.columns;
292
288
  }
293
289
  /**
294
- * Iterates over the rows in the dataset.
295
- * @returns An IterableIterator for rows.
290
+ * Returns all rows in the dataset as an array.
291
+ * @returns An array of rows.
296
292
  */
297
- *iterRows() {
298
- for (const row of this.rows) yield row;
293
+ getRows() {
294
+ return this.rows;
299
295
  }
300
296
  /**
301
297
  * Returns the number of column definitions.
@@ -319,35 +315,30 @@ var Dataset = class {
319
315
  return this.rows.length;
320
316
  }
321
317
  };
322
-
323
318
  //#endregion
324
319
  //#region src/utils.ts
325
320
  function arrayBufferToString(buffer) {
326
321
  return new TextDecoder().decode(buffer);
327
322
  }
328
323
  /**
329
- * Creates an array of entities for parsing XML, including control characters.
330
- * @returns An array of objects, each with an `entity` (e.g., "") and its `value` (e.g., "\x01").
324
+ * Pre-defined XML entities for parsing, including control characters.
325
+ * Static array to avoid repeated allocation.
331
326
  */
332
- function makeParseEntities() {
333
- const entities$1 = [];
334
- for (let i = 1; i <= 32; i++) entities$1.push({
327
+ const PARSE_ENTITIES = (() => {
328
+ const entities = [];
329
+ for (let i = 1; i <= 32; i++) entities.push({
335
330
  entity: `&#${i};`,
336
331
  value: String.fromCharCode(i)
337
332
  });
338
- return entities$1;
339
- }
333
+ return entities;
334
+ })();
340
335
  /**
341
- * Creates an array of entities for writing XML, including control characters.
336
+ * Creates an array of entities for parsing XML, including control characters.
337
+ * @deprecated Use PARSE_ENTITIES directly for better performance
342
338
  * @returns An array of objects, each with an `entity` (e.g., "&#1;") and its `value` (e.g., "\x01").
343
339
  */
344
- function makeWriterEntities() {
345
- const entities$1 = [];
346
- for (let i = 1; i <= 32; i++) entities$1.push({
347
- entity: `&#${i};`,
348
- value: String.fromCharCode(i)
349
- });
350
- return entities$1;
340
+ function makeParseEntities() {
341
+ return PARSE_ENTITIES.slice();
351
342
  }
352
343
  /**
353
344
  * Converts a Base64 string to a Uint8Array.
@@ -476,10 +467,10 @@ function stringToReadableStream(str) {
476
467
  function convertToColumnType(value, type) {
477
468
  switch (type) {
478
469
  case "INT":
479
- case "BIGDECIMAL":
480
470
  const intValue = parseInt(value, 10);
481
471
  return isNaN(intValue) ? value : intValue;
482
472
  case "FLOAT":
473
+ case "BIGDECIMAL":
483
474
  const floatValue = parseFloat(value);
484
475
  return isNaN(floatValue) ? value : floatValue;
485
476
  case "DECIMAL":
@@ -517,19 +508,338 @@ function convertToString(value, type) {
517
508
  default: return String(value);
518
509
  }
519
510
  }
520
- const entities = makeParseEntities();
511
+ /**
512
+ * Pre-compiled regex for control character entities.
513
+ * Created once to avoid repeated compilation.
514
+ */
515
+ const CONTROL_CHAR_ENTITY_REGEX = new RegExp(PARSE_ENTITIES.map((e) => e.entity.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
516
+ /**
517
+ * Map for fast entity lookup during decoding.
518
+ */
519
+ const ENTITY_MAP = new Map(PARSE_ENTITIES.map((e) => [e.entity, e.value]));
521
520
  function _unescapeXml(str) {
522
521
  if (!str) return str;
523
- const regex = new RegExp(entities.map((e) => e.entity).join("|"), "g");
524
- return str.replace(regex, (match) => {
525
- const entity = entities.find((e) => e.entity === match);
526
- return entity ? entity.value : match;
522
+ if (str.indexOf("&") === -1) return str;
523
+ let result = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
524
+ result = result.replace(CONTROL_CHAR_ENTITY_REGEX, (match) => {
525
+ return ENTITY_MAP.get(match) || match;
527
526
  });
527
+ return result;
528
528
  }
529
529
  function isXapiRoot(value) {
530
530
  return value instanceof XapiRoot;
531
531
  }
532
-
532
+ /**
533
+ * Escapes XML special characters in a string.
534
+ * @param str - The string to escape.
535
+ * @returns The escaped string.
536
+ */
537
+ function escapeXml(str) {
538
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
539
+ }
540
+ /**
541
+ * Encodes control characters as XML entities, excluding standard whitespace.
542
+ * Encodes 0x01-0x08, 0x0B-0x0C, 0x0E-0x1F but NOT 0x09 (tab), 0x0A (LF), 0x0D (CR), 0x20 (space).
543
+ * @param str - The string to encode.
544
+ * @returns The encoded string.
545
+ */
546
+ function encodeControlChars(str) {
547
+ let result = "";
548
+ for (let i = 0; i < str.length; i++) {
549
+ const charCode = str.charCodeAt(i);
550
+ if (charCode >= 1 && charCode <= 8 || charCode >= 11 && charCode <= 12 || charCode >= 14 && charCode <= 31) result += `&#${charCode};`;
551
+ else result += str[i];
552
+ }
553
+ return result;
554
+ }
555
+ /**
556
+ * A string builder for generating formatted XML.
557
+ */
558
+ var XmlStringBuilder = class {
559
+ lines = [];
560
+ indentLevel = 0;
561
+ indentString = " ";
562
+ /**
563
+ * Writes the XML declaration.
564
+ * @param version - The XML version (default: "1.0").
565
+ * @param encoding - The encoding (default: "UTF-8").
566
+ */
567
+ writeDeclaration(version = "1.0", encoding = "UTF-8") {
568
+ this.lines.push(`<?xml version="${version}" encoding="${encoding}"?>`);
569
+ }
570
+ /**
571
+ * Writes a start element tag.
572
+ * @param name - The element name.
573
+ * @param attributes - Optional attributes object.
574
+ * @param selfClosing - Whether to create a self-closing tag.
575
+ */
576
+ writeStartElement(name, attributes, selfClosing) {
577
+ let tag = `${this.indentString.repeat(this.indentLevel)}<${name}`;
578
+ if (attributes) for (const [key, value] of Object.entries(attributes)) {
579
+ const encodedValue = encodeControlChars(escapeXml(value));
580
+ tag += ` ${key}="${encodedValue}"`;
581
+ }
582
+ if (selfClosing) {
583
+ tag += "/>";
584
+ this.lines.push(tag);
585
+ } else {
586
+ tag += ">";
587
+ this.lines.push(tag);
588
+ this.indentLevel++;
589
+ }
590
+ }
591
+ /**
592
+ * Writes an end element tag.
593
+ * @param name - The element name.
594
+ */
595
+ writeEndElement(name) {
596
+ this.indentLevel--;
597
+ const indent = this.indentString.repeat(this.indentLevel);
598
+ this.lines.push(`${indent}</${name}>`);
599
+ }
600
+ /**
601
+ * Writes an element with text content.
602
+ * @param name - The element name.
603
+ * @param attributes - Optional attributes object.
604
+ * @param text - The text content.
605
+ */
606
+ writeElementWithText(name, attributes, text) {
607
+ let tag = `${this.indentString.repeat(this.indentLevel)}<${name}`;
608
+ if (attributes) for (const [key, value] of Object.entries(attributes)) {
609
+ const encodedValue = encodeControlChars(escapeXml(value));
610
+ tag += ` ${key}="${encodedValue}"`;
611
+ }
612
+ const encodedText = encodeControlChars(escapeXml(text));
613
+ tag += `>${encodedText}</${name}>`;
614
+ this.lines.push(tag);
615
+ }
616
+ /**
617
+ * Returns the generated XML string.
618
+ * @returns The complete XML string with line breaks.
619
+ */
620
+ toString() {
621
+ return this.lines.join("\n") + "\n";
622
+ }
623
+ };
624
+ /**
625
+ * Parses XML string into an XML node structure.
626
+ * This is a lightweight parser optimized for X-API XML structure.
627
+ *
628
+ * @param xml - The XML string to parse.
629
+ * @returns An array of parsed XML nodes.
630
+ */
631
+ function parseXml(xml) {
632
+ if (!xml || xml.trim() === "") return [];
633
+ return new XapiXmlParser(xml).parse();
634
+ }
635
+ /**
636
+ * Character codes for fast comparison
637
+ */
638
+ const CHAR_SPACE = 32;
639
+ const CHAR_TAB = 9;
640
+ const CHAR_LF = 10;
641
+ const CHAR_CR = 13;
642
+ const CHAR_GT = 62;
643
+ const CHAR_SLASH = 47;
644
+ const CHAR_EQUALS = 61;
645
+ const CHAR_QUOTE = 34;
646
+ const CHAR_APOS = 39;
647
+ /**
648
+ * A lightweight XML parser optimized for X-API structure.
649
+ */
650
+ var XapiXmlParser = class {
651
+ xml;
652
+ pos = 0;
653
+ length;
654
+ constructor(xml) {
655
+ this.xml = xml;
656
+ this.length = xml.length;
657
+ }
658
+ parse() {
659
+ const nodes = [];
660
+ while (this.pos < this.length) {
661
+ this.skipWhitespace();
662
+ if (this.pos >= this.length) break;
663
+ if (this.matches("<?xml")) {
664
+ this.skipUntil("?>");
665
+ this.pos += 2;
666
+ continue;
667
+ }
668
+ if (this.matches("<!--")) {
669
+ this.skipUntil("-->");
670
+ this.pos += 3;
671
+ continue;
672
+ }
673
+ if (this.current() === "<") {
674
+ const node = this.parseElement();
675
+ if (node) nodes.push(node);
676
+ } else this.pos++;
677
+ }
678
+ return nodes;
679
+ }
680
+ parseElement() {
681
+ if (this.current() !== "<") return null;
682
+ this.pos++;
683
+ if (this.current() === "/") return null;
684
+ const tagName = this.parseTagName();
685
+ if (!tagName) return null;
686
+ const node = {
687
+ tagName,
688
+ attributes: void 0,
689
+ children: void 0
690
+ };
691
+ const attributes = this.parseAttributes();
692
+ if (attributes && Object.keys(attributes).length > 0) node.attributes = attributes;
693
+ this.skipWhitespace();
694
+ if (this.matches("/>")) {
695
+ this.pos += 2;
696
+ return node;
697
+ }
698
+ if (this.current() === ">") this.pos++;
699
+ const children = [];
700
+ let textContent = "";
701
+ while (this.pos < this.length) {
702
+ if (this.matches("<![CDATA[")) {
703
+ const cdataText = this.parseCDATA();
704
+ if (cdataText !== null) textContent += cdataText;
705
+ continue;
706
+ }
707
+ if (this.matches("</")) {
708
+ if (textContent) children.push(textContent);
709
+ this.pos += 2;
710
+ this.skipUntil(">");
711
+ this.pos++;
712
+ break;
713
+ }
714
+ if (this.current() === "<") {
715
+ if (textContent.trim()) {
716
+ children.push(textContent);
717
+ textContent = "";
718
+ }
719
+ const child = this.parseElement();
720
+ if (child) children.push(child);
721
+ } else {
722
+ textContent += this.current();
723
+ this.pos++;
724
+ }
725
+ }
726
+ if (children.length > 0) node.children = children;
727
+ return node;
728
+ }
729
+ parseTagName() {
730
+ const start = this.pos;
731
+ while (this.pos < this.length) {
732
+ const code = this.currentCharCode();
733
+ if (this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
734
+ this.pos++;
735
+ }
736
+ return this.xml.substring(start, this.pos);
737
+ }
738
+ parseAttributes() {
739
+ const attrs = {};
740
+ while (this.pos < this.length) {
741
+ this.skipWhitespace();
742
+ const ch = this.current();
743
+ if (ch === ">" || ch === "/" || this.matches("/>")) break;
744
+ const attrName = this.parseAttributeName();
745
+ if (!attrName) break;
746
+ this.skipWhitespace();
747
+ if (this.current() === "=") this.pos++;
748
+ this.skipWhitespace();
749
+ attrs[attrName] = this.parseAttributeValue();
750
+ }
751
+ return Object.keys(attrs).length > 0 ? attrs : void 0;
752
+ }
753
+ parseAttributeName() {
754
+ const start = this.pos;
755
+ while (this.pos < this.length) {
756
+ const code = this.currentCharCode();
757
+ if (code === CHAR_EQUALS || this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
758
+ this.pos++;
759
+ }
760
+ return this.xml.substring(start, this.pos);
761
+ }
762
+ parseAttributeValue() {
763
+ const quoteCode = this.currentCharCode();
764
+ if (quoteCode !== CHAR_QUOTE && quoteCode !== CHAR_APOS) {
765
+ const start = this.pos;
766
+ while (this.pos < this.length) {
767
+ const code = this.currentCharCode();
768
+ if (this.isWhitespace(code) || code === CHAR_GT || code === CHAR_SLASH) break;
769
+ this.pos++;
770
+ }
771
+ return this.xml.substring(start, this.pos);
772
+ }
773
+ this.pos++;
774
+ const start = this.pos;
775
+ while (this.pos < this.length) {
776
+ if (this.currentCharCode() === quoteCode) {
777
+ const value = this.xml.substring(start, this.pos);
778
+ this.pos++;
779
+ return value;
780
+ }
781
+ this.pos++;
782
+ }
783
+ return this.xml.substring(start, this.pos);
784
+ }
785
+ parseCDATA() {
786
+ if (!this.matches("<![CDATA[")) return null;
787
+ this.pos += 9;
788
+ const endPos = this.findString("]]>");
789
+ if (endPos === -1) {
790
+ const content = this.xml.substring(this.pos);
791
+ this.pos = this.length;
792
+ return content;
793
+ }
794
+ const content = this.xml.substring(this.pos, endPos);
795
+ this.pos = endPos + 3;
796
+ return content;
797
+ }
798
+ skipWhitespace() {
799
+ while (this.pos < this.length) {
800
+ const code = this.currentCharCode();
801
+ if (code !== CHAR_SPACE && code !== CHAR_TAB && code !== CHAR_LF && code !== CHAR_CR) break;
802
+ this.pos++;
803
+ }
804
+ }
805
+ isWhitespace(code) {
806
+ return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LF || code === CHAR_CR;
807
+ }
808
+ skipUntil(target) {
809
+ const pos = this.findString(target);
810
+ if (pos !== -1) this.pos = pos;
811
+ else this.pos = this.length;
812
+ }
813
+ current() {
814
+ return this.xml[this.pos];
815
+ }
816
+ currentCharCode() {
817
+ return this.xml.charCodeAt(this.pos);
818
+ }
819
+ /**
820
+ * Checks if the string at the given position matches the target string.
821
+ * This avoids creating substring instances for comparison.
822
+ * @param str - The target string to match
823
+ * @param pos - The position to check from (default: current position)
824
+ */
825
+ matches(str, pos = this.pos) {
826
+ const len = str.length;
827
+ if (pos + len > this.length) return false;
828
+ for (let i = 0; i < len; i++) if (this.xml.charCodeAt(pos + i) !== str.charCodeAt(i)) return false;
829
+ return true;
830
+ }
831
+ /**
832
+ * Finds the position of a target string starting from the given position.
833
+ * @param str - The string to find
834
+ * @param startPos - Starting position (default: current position)
835
+ * @returns The position where the string is found, or -1 if not found
836
+ */
837
+ findString(str, startPos = this.pos) {
838
+ const maxPos = this.length - str.length;
839
+ for (let pos = startPos; pos <= maxPos; pos++) if (this.matches(str, pos)) return pos;
840
+ return -1;
841
+ }
842
+ };
533
843
  //#endregion
534
844
  //#region src/handler.ts
535
845
  let _options = {
@@ -545,12 +855,12 @@ function initXapi(options) {
545
855
  _options = { ...options };
546
856
  }
547
857
  /**
548
- * Parses an XML string into an XapiRoot object using txml.
858
+ * Parses an XML string into an XapiRoot object using custom parser.
549
859
  * @param xml - The string containing the XML data.
550
- * @returns A Promise that resolves to an XapiRoot object.
860
+ * @returns An XapiRoot object.
551
861
  */
552
862
  function parse(xml) {
553
- const parsedXml = txml.parse(xml);
863
+ const parsedXml = parseXml(xml);
554
864
  const xapiRoot = new XapiRoot();
555
865
  let rootElement;
556
866
  for (let i = 0; i < parsedXml.length; i++) if (parsedXml[i].tagName === "Root") {
@@ -561,121 +871,130 @@ function parse(xml) {
561
871
  const rootChildren = rootElement.children;
562
872
  if (rootChildren && rootChildren.length > 0) for (let i = 0; i < rootChildren.length; i++) {
563
873
  const child = rootChildren[i];
874
+ if (typeof child === "string") continue;
564
875
  const tagName = child.tagName;
565
876
  if (tagName === "Parameters") parseParameters(child, xapiRoot);
566
- else if (tagName === "Dataset") {
567
- if (!child.attributes || !child.attributes.id) throw new InvalidXmlError("Dataset element must have an 'id' attribute");
568
- const datasetId = child.attributes.id;
569
- const dataset = new Dataset(datasetId);
570
- xapiRoot.addDataset(dataset);
571
- const datasetChildren = child.children;
572
- let columnInfoElement;
573
- let rowsElement;
574
- if (datasetChildren) for (let j = 0; j < datasetChildren.length; j++) {
575
- const datasetChild = datasetChildren[j];
576
- const childTagName = datasetChild.tagName;
577
- if (childTagName === "ColumnInfo") columnInfoElement = datasetChild;
578
- else if (childTagName === "Rows") rowsElement = datasetChild;
579
- if (columnInfoElement && rowsElement) break;
580
- }
581
- parseColumnInfo(columnInfoElement, dataset);
582
- parseRows(rowsElement, dataset);
583
- }
877
+ else if (tagName === "Datasets") {
878
+ for (const datasetElement of child.children ?? []) if (typeof datasetElement !== "string" && datasetElement.tagName === "Dataset") parseDataset(datasetElement, xapiRoot);
879
+ } else if (tagName === "Dataset") parseDataset(child, xapiRoot);
584
880
  }
585
881
  return xapiRoot;
586
882
  }
883
+ function parseDataset(child, xapiRoot) {
884
+ if (!child.attributes || !child.attributes.id) throw new InvalidXmlError("Dataset element must have an 'id' attribute");
885
+ const datasetId = child.attributes.id;
886
+ const dataset = new Dataset(datasetId);
887
+ xapiRoot.addDataset(dataset);
888
+ const datasetChildren = child.children;
889
+ let columnInfoElement;
890
+ let rowsElement;
891
+ if (datasetChildren) for (let j = 0; j < datasetChildren.length; j++) {
892
+ const datasetChild = datasetChildren[j];
893
+ if (typeof datasetChild === "string") continue;
894
+ const childTagName = datasetChild.tagName;
895
+ if (childTagName === "ColumnInfo") columnInfoElement = datasetChild;
896
+ else if (childTagName === "Rows") rowsElement = datasetChild;
897
+ if (columnInfoElement && rowsElement) break;
898
+ }
899
+ parseColumnInfo(columnInfoElement, dataset);
900
+ parseRows(rowsElement, dataset);
901
+ }
587
902
  function parseValue(value, type = "STRING") {
588
903
  value = _unescapeXml(value);
589
904
  return _options.parseToTypes ? convertToColumnType(value, type) : value;
590
905
  }
591
906
  function parseColumnInfo(columnInfoElement, dataset) {
592
- if (!columnInfoElement) throw new InvalidXmlError("ColumnInfo element is missing in the dataset");
907
+ if (!columnInfoElement || typeof columnInfoElement === "string") throw new InvalidXmlError("ColumnInfo element is missing in the dataset");
593
908
  const children = columnInfoElement.children;
594
909
  if (!children) return;
595
910
  for (let i = 0; i < children.length; i++) {
596
911
  const colInfo = children[i];
597
- const tagName = colInfo.tagName;
598
- if (tagName === "ConstColumn") {
599
- const attrs = colInfo.attributes;
600
- const sizeStr = attrs?.size;
601
- dataset.addConstColumn({
602
- id: attrs?.id,
603
- size: sizeStr ? parseInt(sizeStr, 10) : 0,
604
- type: attrs?.type || "STRING",
605
- value: parseValue(attrs?.value, attrs?.type)
606
- });
607
- } else if (tagName === "Column") {
608
- const attrs = colInfo.attributes;
609
- const sizeStr = attrs?.size;
610
- dataset.addColumn({
611
- id: attrs?.id,
612
- size: sizeStr ? parseInt(sizeStr, 10) : 0,
613
- type: attrs?.type || "STRING"
614
- });
912
+ if (typeof colInfo !== "string") {
913
+ const tagName = colInfo.tagName;
914
+ if (tagName === "ConstColumn") {
915
+ const attrs = colInfo.attributes;
916
+ const sizeStr = attrs?.size;
917
+ dataset.addConstColumn({
918
+ id: attrs?.id,
919
+ size: sizeStr ? parseInt(sizeStr, 10) : 0,
920
+ type: attrs?.type || "STRING",
921
+ value: parseValue(attrs?.value, attrs?.type)
922
+ });
923
+ } else if (tagName === "Column") {
924
+ const attrs = colInfo.attributes;
925
+ const sizeStr = attrs?.size;
926
+ dataset.addColumn({
927
+ id: attrs?.id,
928
+ size: sizeStr ? parseInt(sizeStr, 10) : 0,
929
+ type: attrs?.type || "STRING"
930
+ });
931
+ }
615
932
  }
616
933
  }
617
934
  }
618
935
  function parseRows(rowsElement, dataset) {
936
+ if (typeof rowsElement === "string") return;
619
937
  const rows = rowsElement?.children;
620
938
  if (!rows) return;
621
939
  const datasetRows = dataset.rows;
622
940
  for (let i = 0; i < rows.length; i++) {
623
941
  const r = rows[i];
624
- const tagName = r.tagName;
625
- if (tagName === "Row") {
626
- const rowIndex = dataset.newRow();
627
- const currentRow = datasetRows[rowIndex];
628
- currentRow.type = r.attributes?.type || void 0;
629
- const cols = r.children;
630
- if (!cols) continue;
631
- for (let j = 0; j < cols.length; j++) {
632
- const col = cols[j];
633
- const colTagName = col.tagName;
634
- if (colTagName === "Col") {
635
- const colId = col.attributes?.id;
636
- const value = col.children?.[0];
637
- const columnInfo = dataset.getColumnInfo(colId);
638
- if (!columnInfo) throw new InvalidXmlError(`Column with id ${colId} not found in dataset ${dataset.id}`);
639
- const castedValue = parseValue(value, columnInfo.type);
640
- currentRow.cols.push({
641
- id: colId,
642
- value: castedValue
643
- });
644
- } else if (colTagName === "OrgRow") {
645
- const orgRow = [];
646
- currentRow.orgRow = orgRow;
647
- const orgCols = col.children;
648
- if (!orgCols) continue;
649
- for (let k = 0; k < orgCols.length; k++) {
650
- const orgCol = orgCols[k];
651
- if (orgCol.tagName === "Col") {
652
- const colId = orgCol.attributes?.id;
653
- const value = orgCol.children?.[0];
942
+ if (typeof r !== "string") {
943
+ const tagName = r.tagName;
944
+ if (tagName === "Row") {
945
+ const currentRow = datasetRows[dataset.newRow()];
946
+ currentRow.type = r.attributes?.type || void 0;
947
+ const cols = r.children;
948
+ if (cols) for (let j = 0; j < cols.length; j++) {
949
+ const col = cols[j];
950
+ if (typeof col !== "string") {
951
+ const colTagName = col.tagName;
952
+ if (colTagName === "Col") {
953
+ const colId = col.attributes?.id;
954
+ const value = col.children?.[0];
654
955
  const columnInfo = dataset.getColumnInfo(colId);
956
+ if (!columnInfo) throw new InvalidXmlError(`Column with id ${colId} not found in dataset ${dataset.id}`);
655
957
  const castedValue = parseValue(value, columnInfo.type);
656
- orgRow.push({
958
+ currentRow.cols.push({
657
959
  id: colId,
658
960
  value: castedValue
659
961
  });
962
+ } else if (colTagName === "OrgRow") {
963
+ const orgRow = [];
964
+ currentRow.orgRow = orgRow;
965
+ const orgCols = col.children;
966
+ if (orgCols) for (let k = 0; k < orgCols.length; k++) {
967
+ const orgCol = orgCols[k];
968
+ if (typeof orgCol !== "string" && orgCol.tagName === "Col") {
969
+ const colId = orgCol.attributes?.id;
970
+ const value = orgCol.children?.[0];
971
+ const castedValue = parseValue(value, dataset.getColumnInfo(colId).type);
972
+ orgRow.push({
973
+ id: colId,
974
+ value: castedValue
975
+ });
976
+ }
977
+ }
660
978
  }
661
979
  }
662
980
  }
663
- }
664
- } else if (tagName === "Col") throw new InvalidXmlError("Row must be defined before Col");
665
- else if (tagName === "OrgRow") throw new InvalidXmlError("Row must be defined before OrgRow");
981
+ } else if (tagName === "Col") throw new InvalidXmlError("Row must be defined before Col");
982
+ else if (tagName === "OrgRow") throw new InvalidXmlError("Row must be defined before OrgRow");
983
+ }
666
984
  }
667
985
  }
668
986
  function parseParameters(parametersElement, xapiRoot) {
987
+ if (typeof parametersElement === "string") return;
669
988
  if (!parametersElement) return;
670
989
  const children = parametersElement.children;
671
990
  if (!children) return;
672
991
  for (let i = 0; i < children.length; i++) {
673
992
  const p = children[i];
674
- if (p.tagName === "Parameter") {
993
+ if (typeof p !== "string" && p.tagName === "Parameter") {
675
994
  const attrs = p.attributes;
676
995
  const id = attrs?.id;
677
996
  const type = attrs?.type || "STRING";
678
- const value = p.children?.[0];
997
+ const value = p.children?.[0] ?? attrs?.value;
679
998
  xapiRoot.addParameter({
680
999
  id,
681
1000
  type,
@@ -685,90 +1004,178 @@ function parseParameters(parametersElement, xapiRoot) {
685
1004
  }
686
1005
  }
687
1006
  function write(root) {
688
- const writer = new StaxXmlWriterSync({
689
- addEntities: makeWriterEntities(),
690
- encoding: "UTF-8",
691
- indentString: " ",
692
- prettyPrint: true
693
- });
694
- writer.writeStartDocument("1.0", "UTF-8");
695
- writer.writeStartElement("Root", { attributes: {
1007
+ const builder = new XmlStringBuilder();
1008
+ builder.writeDeclaration("1.0", "UTF-8");
1009
+ builder.writeStartElement("Root", {
696
1010
  xmlns: _options.xapiVersion?.xmlns || NexaVersion.xmlns,
697
1011
  version: _options.xapiVersion?.version || NexaVersion.version
698
- } });
699
- if (root.parameterSize() > 0) writeParameters(writer, root.iterParameters());
1012
+ });
1013
+ if (root.parameterSize() > 0) writeParameters(builder, root.getParameters());
700
1014
  if (root.datasetSize() > 0) {
701
- writer.writeStartElement("Datasets");
702
- for (const dataset of root.iterDatasets()) writeDataset(writer, dataset);
703
- writer.writeEndElement();
1015
+ builder.writeStartElement("Datasets");
1016
+ for (const dataset of root.getDatasets()) writeDataset(builder, dataset);
1017
+ builder.writeEndElement("Datasets");
704
1018
  }
705
- writer.writeEndElement();
706
- return writer.getXmlString();
1019
+ builder.writeEndElement("Root");
1020
+ return builder.toString();
707
1021
  }
708
- function writeParameters(writer, iterator) {
709
- if (iterator) {
710
- writer.writeStartElement("Parameters");
711
- for (const parameter of iterator) {
712
- writer.writeStartElement("Parameter", { attributes: { id: parameter.id } });
713
- if (parameter.type !== void 0) writer.writeAttribute("type", parameter.type);
714
- if (parameter.value !== void 0) if (typeof parameter.value === "string") writer.writeAttribute("value", parameter.value);
715
- else if (parameter.value instanceof Date) writer.writeAttribute("value", dateToString(parameter.value, parameter.type));
716
- else if (parameter.value instanceof Uint8Array) writer.writeAttribute("value", uint8ArrayToBase64(parameter.value));
717
- else writer.writeAttribute("value", String(parameter.value));
718
- writer.writeEndElement();
1022
+ function writeParameters(builder, parameters) {
1023
+ if (parameters) {
1024
+ builder.writeStartElement("Parameters");
1025
+ for (const parameter of parameters) {
1026
+ const attrs = { id: parameter.id };
1027
+ if (parameter.type !== void 0) attrs.type = parameter.type;
1028
+ if (parameter.value !== void 0) if (typeof parameter.value === "string") attrs.value = parameter.value;
1029
+ else if (parameter.value instanceof Date) attrs.value = dateToString(parameter.value, parameter.type);
1030
+ else if (parameter.value instanceof Uint8Array) attrs.value = uint8ArrayToBase64(parameter.value);
1031
+ else attrs.value = String(parameter.value);
1032
+ builder.writeStartElement("Parameter", attrs, true);
719
1033
  }
720
- writer.writeEndElement();
1034
+ builder.writeEndElement("Parameters");
721
1035
  }
722
1036
  }
723
- function writeDataset(writer, dataset) {
724
- writer.writeStartElement("Dataset", { attributes: { id: dataset.id } });
1037
+ function writeDataset(builder, dataset) {
1038
+ builder.writeStartElement("Dataset", { id: dataset.id });
725
1039
  if (dataset.constColumnSize() > 0 || dataset.columnSize() > 0) {
726
- writer.writeStartElement("ColumnInfo");
727
- for (const constCol of dataset.iterConstColumns()) writer.writeStartElement("ConstColumn", {
728
- attributes: {
729
- id: constCol.id,
730
- size: String(constCol.size),
731
- type: constCol.type,
732
- value: constCol.value !== void 0 ? String(constCol.value) : ""
733
- },
734
- selfClosing: true
735
- });
736
- for (const col of dataset.iterColumns()) writer.writeStartElement("Column", {
737
- attributes: {
738
- id: col.id,
739
- size: String(col.size),
740
- type: col.type
741
- },
742
- selfClosing: true
743
- });
744
- writer.writeEndElement();
1040
+ builder.writeStartElement("ColumnInfo");
1041
+ for (const constCol of dataset.getConstColumns()) builder.writeStartElement("ConstColumn", {
1042
+ id: constCol.id,
1043
+ size: String(constCol.size),
1044
+ type: constCol.type,
1045
+ value: constCol.value !== void 0 ? String(constCol.value) : ""
1046
+ }, true);
1047
+ for (const col of dataset.getColumns()) builder.writeStartElement("Column", {
1048
+ id: col.id,
1049
+ size: String(col.size),
1050
+ type: col.type
1051
+ }, true);
1052
+ builder.writeEndElement("ColumnInfo");
745
1053
  }
746
- writer.writeStartElement("Rows");
747
- for (const row of dataset.iterRows()) {
748
- if (row.type) writer.writeStartElement("Row", { attributes: { type: row.type } });
749
- else writer.writeStartElement("Row");
750
- for (const col of row.cols) writeColumn(writer, dataset, col);
1054
+ builder.writeStartElement("Rows");
1055
+ for (const row of dataset.getRows()) {
1056
+ if (row.type) builder.writeStartElement("Row", { type: row.type });
1057
+ else builder.writeStartElement("Row");
1058
+ for (const col of row.cols) writeColumn(builder, dataset, col);
751
1059
  if (row.orgRow && row.orgRow.length > 0) {
752
- writer.writeStartElement("OrgRow");
753
- for (const orgCol of row.orgRow) writeColumn(writer, dataset, orgCol);
754
- writer.writeEndElement();
1060
+ builder.writeStartElement("OrgRow");
1061
+ for (const orgCol of row.orgRow) writeColumn(builder, dataset, orgCol);
1062
+ builder.writeEndElement("OrgRow");
755
1063
  }
756
- writer.writeEndElement();
1064
+ builder.writeEndElement("Row");
757
1065
  }
758
- writer.writeEndElement();
759
- writer.writeEndElement();
1066
+ builder.writeEndElement("Rows");
1067
+ builder.writeEndElement("Dataset");
760
1068
  }
761
- function writeColumn(writer, dataset, col) {
1069
+ function writeColumn(builder, dataset, col) {
762
1070
  if (col.value !== void 0 && col.value !== null) {
763
1071
  const colInfo = dataset.getColumnInfo(col.id);
764
- writer.writeStartElement("Col", { attributes: { id: col.id } });
765
- writer.writeCharacters(convertToString(col.value, colInfo.type));
766
- writer.writeEndElement();
767
- } else writer.writeStartElement("Col", {
768
- attributes: { id: col.id },
769
- selfClosing: true
770
- });
1072
+ builder.writeElementWithText("Col", { id: col.id }, convertToString(col.value, colInfo.type));
1073
+ } else builder.writeStartElement("Col", { id: col.id }, true);
1074
+ }
1075
+ //#endregion
1076
+ //#region src/schema.ts
1077
+ const defaultSizes = {
1078
+ STRING: 256,
1079
+ INT: 10,
1080
+ FLOAT: 20,
1081
+ DECIMAL: 20,
1082
+ BIGDECIMAL: 30,
1083
+ DATE: 8,
1084
+ DATETIME: 17,
1085
+ TIME: 6,
1086
+ BLOB: 1e3
1087
+ };
1088
+ function column(type, options = {}) {
1089
+ return {
1090
+ kind: "xapi-column",
1091
+ type,
1092
+ size: options.size ?? defaultSizes[type],
1093
+ optional: options.optional ?? false
1094
+ };
1095
+ }
1096
+ function defineDataset(columns) {
1097
+ return {
1098
+ kind: "xapi-dataset",
1099
+ columns
1100
+ };
1101
+ }
1102
+ function defineRoot(definition) {
1103
+ return {
1104
+ kind: "xapi-root",
1105
+ datasets: definition.datasets,
1106
+ parameters: definition.parameters ?? {}
1107
+ };
1108
+ }
1109
+ function defineOperation(definition) {
1110
+ return {
1111
+ kind: "xapi-operation",
1112
+ ...definition
1113
+ };
1114
+ }
1115
+ const xapi = {
1116
+ string: (options) => column("STRING", options),
1117
+ int: (options) => column("INT", options),
1118
+ float: (options) => column("FLOAT", options),
1119
+ decimal: (options) => column("DECIMAL", options),
1120
+ bigdecimal: (options) => column("BIGDECIMAL", options),
1121
+ date: (options) => column("DATE", options),
1122
+ datetime: (options) => column("DATETIME", options),
1123
+ time: (options) => column("TIME", options),
1124
+ blob: (options) => column("BLOB", options),
1125
+ dataset: defineDataset,
1126
+ root: defineRoot,
1127
+ operation: defineOperation
1128
+ };
1129
+ function encodeRoot(schema, value) {
1130
+ const root = new XapiRoot();
1131
+ for (const [id, parameterSchema] of Object.entries(schema.parameters)) {
1132
+ const parameterValue = value.parameters[id];
1133
+ if (parameterValue !== void 0 || !parameterSchema.optional) root.addParameter({
1134
+ id,
1135
+ type: parameterSchema.type,
1136
+ value: parameterValue
1137
+ });
1138
+ }
1139
+ for (const [datasetId, datasetSchema] of Object.entries(schema.datasets)) {
1140
+ const dataset = new Dataset(datasetId);
1141
+ for (const [id, columnSchema] of Object.entries(datasetSchema.columns)) dataset.addColumn({
1142
+ id,
1143
+ type: columnSchema.type,
1144
+ size: columnSchema.size
1145
+ });
1146
+ const rows = value.datasets[datasetId];
1147
+ for (const row of rows) {
1148
+ const rowIndex = dataset.newRow();
1149
+ for (const id of Object.keys(datasetSchema.columns)) dataset.setColumn(rowIndex, id, row[id]);
1150
+ }
1151
+ root.addDataset(dataset);
1152
+ }
1153
+ return root;
1154
+ }
1155
+ function decodeRoot(schema, root) {
1156
+ const parameters = {};
1157
+ for (const id of Object.keys(schema.parameters)) {
1158
+ const value = root.getParameter(id)?.value;
1159
+ if (value !== void 0) parameters[id] = value;
1160
+ }
1161
+ const datasets = {};
1162
+ for (const [datasetId, datasetSchema] of Object.entries(schema.datasets)) {
1163
+ const dataset = root.getDataset(datasetId);
1164
+ if (!dataset) {
1165
+ datasets[datasetId] = [];
1166
+ continue;
1167
+ }
1168
+ const constants = Object.fromEntries(dataset.getConstColumns().map(({ id, value }) => [id, value]));
1169
+ datasets[datasetId] = dataset.getRows().map((row) => {
1170
+ const value = { ...constants };
1171
+ for (const column of row.cols) if (column.id in datasetSchema.columns) value[column.id] = column.value;
1172
+ return value;
1173
+ });
1174
+ }
1175
+ return {
1176
+ parameters,
1177
+ datasets
1178
+ };
771
1179
  }
772
-
773
1180
  //#endregion
774
- export { ColumnTypeError, Dataset, InvalidXmlError, NexaVersion, StringWritableStream, XapiRoot, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, initXapi, isXapiRoot, makeParseEntities, makeWriterEntities, parse, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write };
1181
+ export { ColumnTypeError, Dataset, InvalidXmlError, NexaVersion, StringWritableStream, XapiRoot, XmlStringBuilder, XplatformVersion, _unescapeXml, arrayBufferToString, base64ToUint8Array, columnType, convertToColumnType, convertToString, dateToString, decodeRoot, defineDataset, defineOperation, defineRoot, encodeControlChars, encodeRoot, escapeXml, initXapi, isXapiRoot, makeParseEntities, parse, parseXml, rowType, stringToDate, stringToReadableStream, uint8ArrayToBase64, write, xapi };