@lucianpacurar/iso20022.js 0.2.13 → 0.2.14

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
@@ -2,85 +2,35 @@
2
2
 
3
3
  var Dinero = require('dinero.js');
4
4
 
5
- var validator$2 = {};
6
-
7
- var util$3 = {};
8
-
9
- (function (exports) {
10
-
11
- const nameStartChar = ':A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
12
- const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
13
- const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*';
14
- const regexName = new RegExp('^' + nameRegexp + '$');
15
-
16
- const getAllMatches = function(string, regex) {
17
- const matches = [];
18
- let match = regex.exec(string);
19
- while (match) {
20
- const allmatches = [];
21
- allmatches.startIndex = regex.lastIndex - match[0].length;
22
- const len = match.length;
23
- for (let index = 0; index < len; index++) {
24
- allmatches.push(match[index]);
25
- }
26
- matches.push(allmatches);
27
- match = regex.exec(string);
28
- }
29
- return matches;
30
- };
31
-
32
- const isName = function(string) {
33
- const match = regexName.exec(string);
34
- return !(match === null || typeof match === 'undefined');
35
- };
36
-
37
- exports.isExist = function(v) {
38
- return typeof v !== 'undefined';
39
- };
40
-
41
- exports.isEmptyObject = function(obj) {
42
- return Object.keys(obj).length === 0;
43
- };
44
-
45
- /**
46
- * Copy all the properties of a into b.
47
- * @param {*} target
48
- * @param {*} a
49
- */
50
- exports.merge = function(target, a, arrayMode) {
51
- if (a) {
52
- const keys = Object.keys(a); // will return an array of own properties
53
- const len = keys.length; //don't make it inline
54
- for (let i = 0; i < len; i++) {
55
- if (arrayMode === 'strict') {
56
- target[keys[i]] = [ a[keys[i]] ];
57
- } else {
58
- target[keys[i]] = a[keys[i]];
59
- }
60
- }
61
- }
62
- };
63
- /* exports.merge =function (b,a){
64
- return Object.assign(b,a);
65
- } */
66
-
67
- exports.getValue = function(v) {
68
- if (exports.isExist(v)) {
69
- return v;
70
- } else {
71
- return '';
72
- }
73
- };
74
-
75
- // const fakeCall = function(a) {return a;};
76
- // const fakeCallNoReturn = function() {};
77
-
78
- exports.isName = isName;
79
- exports.getAllMatches = getAllMatches;
80
- exports.nameRegexp = nameRegexp;
81
- } (util$3));
82
-
83
- const util$2 = util$3;
5
+ const nameStartChar = ':A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD';
6
+ const nameChar = nameStartChar + '\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040';
7
+ const nameRegexp = '[' + nameStartChar + '][' + nameChar + ']*';
8
+ const regexName = new RegExp('^' + nameRegexp + '$');
9
+
10
+ function getAllMatches(string, regex) {
11
+ const matches = [];
12
+ let match = regex.exec(string);
13
+ while (match) {
14
+ const allmatches = [];
15
+ allmatches.startIndex = regex.lastIndex - match[0].length;
16
+ const len = match.length;
17
+ for (let index = 0; index < len; index++) {
18
+ allmatches.push(match[index]);
19
+ }
20
+ matches.push(allmatches);
21
+ match = regex.exec(string);
22
+ }
23
+ return matches;
24
+ }
25
+
26
+ const isName = function (string) {
27
+ const match = regexName.exec(string);
28
+ return !(match === null || typeof match === 'undefined');
29
+ };
30
+
31
+ function isExist(v) {
32
+ return typeof v !== 'undefined';
33
+ }
84
34
 
85
35
  const defaultOptions$2 = {
86
36
  allowBooleanAttributes: false, //A tag can have attributes without any value
@@ -88,7 +38,7 @@ const defaultOptions$2 = {
88
38
  };
89
39
 
90
40
  //const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
91
- validator$2.validate = function (xmlData, options) {
41
+ function validate(xmlData, options) {
92
42
  options = Object.assign({}, defaultOptions$2, options);
93
43
 
94
44
  //xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
@@ -266,8 +216,7 @@ validator$2.validate = function (xmlData, options) {
266
216
  }
267
217
 
268
218
  return true;
269
- };
270
-
219
+ }
271
220
  function isWhiteSpace(char){
272
221
  return char === ' ' || char === '\t' || char === '\n' || char === '\r';
273
222
  }
@@ -397,7 +346,7 @@ function validateAttributeString(attrStr, options) {
397
346
 
398
347
  //if(attrStr.trim().length === 0) return true; //empty string
399
348
 
400
- const matches = util$2.getAllMatches(attrStr, validAttrStrRegxp);
349
+ const matches = getAllMatches(attrStr, validAttrStrRegxp);
401
350
  const attrNames = {};
402
351
 
403
352
  for (let i = 0; i < matches.length; i++) {
@@ -475,13 +424,13 @@ function getErrorObject(code, message, lineNumber) {
475
424
  }
476
425
 
477
426
  function validateAttrName(attrName) {
478
- return util$2.isName(attrName);
427
+ return isName(attrName);
479
428
  }
480
429
 
481
430
  // const startsWithXML = /^xml/i;
482
431
 
483
432
  function validateTagName(tagname) {
484
- return util$2.isName(tagname) /* && !tagname.match(startsWithXML) */;
433
+ return isName(tagname) /* && !tagname.match(startsWithXML) */;
485
434
  }
486
435
 
487
436
  //this function returns the line number for the character at the given index
@@ -500,55 +449,100 @@ function getPositionFromMatch(match) {
500
449
  return match.startIndex + match[1].length;
501
450
  }
502
451
 
503
- var OptionsBuilder = {};
504
-
505
452
  const defaultOptions$1 = {
506
- preserveOrder: false,
507
- attributeNamePrefix: '@_',
508
- attributesGroupName: false,
509
- textNodeName: '#text',
510
- ignoreAttributes: true,
511
- removeNSPrefix: false, // remove NS from tag name or attribute name if true
512
- allowBooleanAttributes: false, //a tag can have attributes without any value
513
- //ignoreRootElement : false,
514
- parseTagValue: true,
515
- parseAttributeValue: false,
516
- trimValues: true, //Trim string values of tag and attributes
517
- cdataPropName: false,
518
- numberParseOptions: {
519
- hex: true,
520
- leadingZeros: true,
521
- eNotation: true
522
- },
523
- tagValueProcessor: function(tagName, val) {
524
- return val;
525
- },
526
- attributeValueProcessor: function(attrName, val) {
527
- return val;
528
- },
529
- stopNodes: [], //nested tags will not be parsed even for errors
530
- alwaysCreateTextNode: false,
531
- isArray: () => false,
532
- commentPropName: false,
533
- unpairedTags: [],
534
- processEntities: true,
535
- htmlEntities: false,
536
- ignoreDeclaration: false,
537
- ignorePiTags: false,
538
- transformTagName: false,
539
- transformAttributeName: false,
540
- updateTag: function(tagName, jPath, attrs){
541
- return tagName
542
- },
543
- // skipEmptyListItem: false
453
+ preserveOrder: false,
454
+ attributeNamePrefix: '@_',
455
+ attributesGroupName: false,
456
+ textNodeName: '#text',
457
+ ignoreAttributes: true,
458
+ removeNSPrefix: false, // remove NS from tag name or attribute name if true
459
+ allowBooleanAttributes: false, //a tag can have attributes without any value
460
+ //ignoreRootElement : false,
461
+ parseTagValue: true,
462
+ parseAttributeValue: false,
463
+ trimValues: true, //Trim string values of tag and attributes
464
+ cdataPropName: false,
465
+ numberParseOptions: {
466
+ hex: true,
467
+ leadingZeros: true,
468
+ eNotation: true
469
+ },
470
+ tagValueProcessor: function (tagName, val) {
471
+ return val;
472
+ },
473
+ attributeValueProcessor: function (attrName, val) {
474
+ return val;
475
+ },
476
+ stopNodes: [], //nested tags will not be parsed even for errors
477
+ alwaysCreateTextNode: false,
478
+ isArray: () => false,
479
+ commentPropName: false,
480
+ unpairedTags: [],
481
+ processEntities: true,
482
+ htmlEntities: false,
483
+ ignoreDeclaration: false,
484
+ ignorePiTags: false,
485
+ transformTagName: false,
486
+ transformAttributeName: false,
487
+ updateTag: function (tagName, jPath, attrs) {
488
+ return tagName
489
+ },
490
+ // skipEmptyListItem: false
491
+ captureMetaData: false,
544
492
  };
545
-
546
- const buildOptions$1 = function(options) {
547
- return Object.assign({}, defaultOptions$1, options);
493
+
494
+ /**
495
+ * Normalizes processEntities option for backward compatibility
496
+ * @param {boolean|object} value
497
+ * @returns {object} Always returns normalized object
498
+ */
499
+ function normalizeProcessEntities(value) {
500
+ // Boolean backward compatibility
501
+ if (typeof value === 'boolean') {
502
+ return {
503
+ enabled: value, // true or false
504
+ maxEntitySize: 10000,
505
+ maxExpansionDepth: 10,
506
+ maxTotalExpansions: 1000,
507
+ maxExpandedLength: 100000,
508
+ allowedTags: null,
509
+ tagFilter: null
510
+ };
511
+ }
512
+
513
+ // Object config - merge with defaults
514
+ if (typeof value === 'object' && value !== null) {
515
+ return {
516
+ enabled: value.enabled !== false, // default true if not specified
517
+ maxEntitySize: value.maxEntitySize ?? 10000,
518
+ maxExpansionDepth: value.maxExpansionDepth ?? 10,
519
+ maxTotalExpansions: value.maxTotalExpansions ?? 1000,
520
+ maxExpandedLength: value.maxExpandedLength ?? 100000,
521
+ allowedTags: value.allowedTags ?? null,
522
+ tagFilter: value.tagFilter ?? null
523
+ };
524
+ }
525
+
526
+ // Default to enabled with limits
527
+ return normalizeProcessEntities(true);
528
+ }
529
+
530
+ const buildOptions = function (options) {
531
+ const built = Object.assign({}, defaultOptions$1, options);
532
+
533
+ // Always normalize processEntities for backward compatibility and validation
534
+ built.processEntities = normalizeProcessEntities(built.processEntities);
535
+ //console.debug(built.processEntities)
536
+ return built;
548
537
  };
549
538
 
550
- OptionsBuilder.buildOptions = buildOptions$1;
551
- OptionsBuilder.defaultOptions = defaultOptions$1;
539
+ let METADATA_SYMBOL$1;
540
+
541
+ if (typeof Symbol !== "function") {
542
+ METADATA_SYMBOL$1 = "@@xmlMetadata";
543
+ } else {
544
+ METADATA_SYMBOL$1 = Symbol("XML Node Metadata");
545
+ }
552
546
 
553
547
  class XmlNode{
554
548
  constructor(tagname) {
@@ -561,279 +555,522 @@ class XmlNode{
561
555
  if(key === "__proto__") key = "#__proto__";
562
556
  this.child.push( {[key]: val });
563
557
  }
564
- addChild(node) {
558
+ addChild(node, startIndex) {
565
559
  if(node.tagname === "__proto__") node.tagname = "#__proto__";
566
560
  if(node[":@"] && Object.keys(node[":@"]).length > 0){
567
561
  this.child.push( { [node.tagname]: node.child, [":@"]: node[":@"] });
568
562
  }else {
569
563
  this.child.push( { [node.tagname]: node.child });
570
564
  }
571
- };
565
+ // if requested, add the startIndex
566
+ if (startIndex !== undefined) {
567
+ // Note: for now we just overwrite the metadata. If we had more complex metadata,
568
+ // we might need to do an object append here: metadata = { ...metadata, startIndex }
569
+ this.child[this.child.length - 1][METADATA_SYMBOL$1] = { startIndex };
570
+ }
571
+ }
572
+ /** symbol used for metadata */
573
+ static getMetaDataSymbol() {
574
+ return METADATA_SYMBOL$1;
575
+ }
572
576
  }
573
577
 
574
- var xmlNode$1 = XmlNode;
578
+ class DocTypeReader {
579
+ constructor(options) {
580
+ this.suppressValidationErr = !options;
581
+ this.options = options;
582
+ }
583
+
584
+ readDocType(xmlData, i) {
585
+
586
+ const entities = {};
587
+ if (xmlData[i + 3] === 'O' &&
588
+ xmlData[i + 4] === 'C' &&
589
+ xmlData[i + 5] === 'T' &&
590
+ xmlData[i + 6] === 'Y' &&
591
+ xmlData[i + 7] === 'P' &&
592
+ xmlData[i + 8] === 'E') {
593
+ i = i + 9;
594
+ let angleBracketsCount = 1;
595
+ let hasBody = false, comment = false;
596
+ let exp = "";
597
+ for (; i < xmlData.length; i++) {
598
+ if (xmlData[i] === '<' && !comment) { //Determine the tag type
599
+ if (hasBody && hasSeq(xmlData, "!ENTITY", i)) {
600
+ i += 7;
601
+ let entityName, val;
602
+ [entityName, val, i] = this.readEntityExp(xmlData, i + 1, this.suppressValidationErr);
603
+ if (val.indexOf("&") === -1) { //Parameter entities are not supported
604
+ const escaped = entityName.replace(/[.\-+*:]/g, '\\.');
605
+ entities[entityName] = {
606
+ regx: RegExp(`&${escaped};`, "g"),
607
+ val: val
608
+ };
609
+ }
610
+ }
611
+ else if (hasBody && hasSeq(xmlData, "!ELEMENT", i)) {
612
+ i += 8;//Not supported
613
+ const { index } = this.readElementExp(xmlData, i + 1);
614
+ i = index;
615
+ } else if (hasBody && hasSeq(xmlData, "!ATTLIST", i)) {
616
+ i += 8;//Not supported
617
+ // const {index} = this.readAttlistExp(xmlData,i+1);
618
+ // i = index;
619
+ } else if (hasBody && hasSeq(xmlData, "!NOTATION", i)) {
620
+ i += 9;//Not supported
621
+ const { index } = this.readNotationExp(xmlData, i + 1, this.suppressValidationErr);
622
+ i = index;
623
+ } else if (hasSeq(xmlData, "!--", i)) comment = true;
624
+ else throw new Error(`Invalid DOCTYPE`);
625
+
626
+ angleBracketsCount++;
627
+ exp = "";
628
+ } else if (xmlData[i] === '>') { //Read tag content
629
+ if (comment) {
630
+ if (xmlData[i - 1] === "-" && xmlData[i - 2] === "-") {
631
+ comment = false;
632
+ angleBracketsCount--;
633
+ }
634
+ } else {
635
+ angleBracketsCount--;
636
+ }
637
+ if (angleBracketsCount === 0) {
638
+ break;
639
+ }
640
+ } else if (xmlData[i] === '[') {
641
+ hasBody = true;
642
+ } else {
643
+ exp += xmlData[i];
644
+ }
645
+ }
646
+ if (angleBracketsCount !== 0) {
647
+ throw new Error(`Unclosed DOCTYPE`);
648
+ }
649
+ } else {
650
+ throw new Error(`Invalid Tag instead of DOCTYPE`);
651
+ }
652
+ return { entities, i };
653
+ }
654
+ readEntityExp(xmlData, i) {
655
+ //External entities are not supported
656
+ // <!ENTITY ext SYSTEM "http://normal-website.com" >
575
657
 
576
- const util$1 = util$3;
658
+ //Parameter entities are not supported
659
+ // <!ENTITY entityname "&anotherElement;">
577
660
 
578
- //TODO: handle comments
579
- function readDocType$1(xmlData, i){
580
-
581
- const entities = {};
582
- if( xmlData[i + 3] === 'O' &&
583
- xmlData[i + 4] === 'C' &&
584
- xmlData[i + 5] === 'T' &&
585
- xmlData[i + 6] === 'Y' &&
586
- xmlData[i + 7] === 'P' &&
587
- xmlData[i + 8] === 'E')
588
- {
589
- i = i+9;
590
- let angleBracketsCount = 1;
591
- let hasBody = false, comment = false;
592
- let exp = "";
593
- for(;i<xmlData.length;i++){
594
- if (xmlData[i] === '<' && !comment) { //Determine the tag type
595
- if( hasBody && isEntity(xmlData, i)){
596
- i += 7;
597
- [entityName, val,i] = readEntityExp(xmlData,i+1);
598
- if(val.indexOf("&") === -1) //Parameter entities are not supported
599
- entities[ validateEntityName(entityName) ] = {
600
- regx : RegExp( `&${entityName};`,"g"),
601
- val: val
602
- };
661
+ //Internal entities are supported
662
+ // <!ENTITY entityname "replacement text">
663
+
664
+ // Skip leading whitespace after <!ENTITY
665
+ i = skipWhitespace(xmlData, i);
666
+
667
+ // Read entity name
668
+ let entityName = "";
669
+ while (i < xmlData.length && !/\s/.test(xmlData[i]) && xmlData[i] !== '"' && xmlData[i] !== "'") {
670
+ entityName += xmlData[i];
671
+ i++;
672
+ }
673
+ validateEntityName(entityName);
674
+
675
+ // Skip whitespace after entity name
676
+ i = skipWhitespace(xmlData, i);
677
+
678
+ // Check for unsupported constructs (external entities or parameter entities)
679
+ if (!this.suppressValidationErr) {
680
+ if (xmlData.substring(i, i + 6).toUpperCase() === "SYSTEM") {
681
+ throw new Error("External entities are not supported");
682
+ } else if (xmlData[i] === "%") {
683
+ throw new Error("Parameter entities are not supported");
684
+ }
685
+ }
686
+
687
+ // Read entity value (internal entity)
688
+ let entityValue = "";
689
+ [i, entityValue] = this.readIdentifierVal(xmlData, i, "entity");
690
+
691
+ // Validate entity size
692
+ if (this.options.enabled !== false &&
693
+ this.options.maxEntitySize &&
694
+ entityValue.length > this.options.maxEntitySize) {
695
+ throw new Error(
696
+ `Entity "${entityName}" size (${entityValue.length}) exceeds maximum allowed size (${this.options.maxEntitySize})`
697
+ );
698
+ }
699
+
700
+ i--;
701
+ return [entityName, entityValue, i];
702
+ }
703
+
704
+ readNotationExp(xmlData, i) {
705
+ // Skip leading whitespace after <!NOTATION
706
+ i = skipWhitespace(xmlData, i);
707
+
708
+ // Read notation name
709
+ let notationName = "";
710
+ while (i < xmlData.length && !/\s/.test(xmlData[i])) {
711
+ notationName += xmlData[i];
712
+ i++;
713
+ }
714
+ !this.suppressValidationErr && validateEntityName(notationName);
715
+
716
+ // Skip whitespace after notation name
717
+ i = skipWhitespace(xmlData, i);
718
+
719
+ // Check identifier type (SYSTEM or PUBLIC)
720
+ const identifierType = xmlData.substring(i, i + 6).toUpperCase();
721
+ if (!this.suppressValidationErr && identifierType !== "SYSTEM" && identifierType !== "PUBLIC") {
722
+ throw new Error(`Expected SYSTEM or PUBLIC, found "${identifierType}"`);
723
+ }
724
+ i += identifierType.length;
725
+
726
+ // Skip whitespace after identifier type
727
+ i = skipWhitespace(xmlData, i);
728
+
729
+ // Read public identifier (if PUBLIC)
730
+ let publicIdentifier = null;
731
+ let systemIdentifier = null;
732
+
733
+ if (identifierType === "PUBLIC") {
734
+ [i, publicIdentifier] = this.readIdentifierVal(xmlData, i, "publicIdentifier");
735
+
736
+ // Skip whitespace after public identifier
737
+ i = skipWhitespace(xmlData, i);
738
+
739
+ // Optionally read system identifier
740
+ if (xmlData[i] === '"' || xmlData[i] === "'") {
741
+ [i, systemIdentifier] = this.readIdentifierVal(xmlData, i, "systemIdentifier");
742
+ }
743
+ } else if (identifierType === "SYSTEM") {
744
+ // Read system identifier (mandatory for SYSTEM)
745
+ [i, systemIdentifier] = this.readIdentifierVal(xmlData, i, "systemIdentifier");
746
+
747
+ if (!this.suppressValidationErr && !systemIdentifier) {
748
+ throw new Error("Missing mandatory system identifier for SYSTEM notation");
749
+ }
750
+ }
751
+
752
+ return { notationName, publicIdentifier, systemIdentifier, index: --i };
753
+ }
754
+
755
+ readIdentifierVal(xmlData, i, type) {
756
+ let identifierVal = "";
757
+ const startChar = xmlData[i];
758
+ if (startChar !== '"' && startChar !== "'") {
759
+ throw new Error(`Expected quoted string, found "${startChar}"`);
760
+ }
761
+ i++;
762
+
763
+ while (i < xmlData.length && xmlData[i] !== startChar) {
764
+ identifierVal += xmlData[i];
765
+ i++;
766
+ }
767
+
768
+ if (xmlData[i] !== startChar) {
769
+ throw new Error(`Unterminated ${type} value`);
770
+ }
771
+ i++;
772
+ return [i, identifierVal];
773
+ }
774
+
775
+ readElementExp(xmlData, i) {
776
+ // <!ELEMENT br EMPTY>
777
+ // <!ELEMENT div ANY>
778
+ // <!ELEMENT title (#PCDATA)>
779
+ // <!ELEMENT book (title, author+)>
780
+ // <!ELEMENT name (content-model)>
781
+
782
+ // Skip leading whitespace after <!ELEMENT
783
+ i = skipWhitespace(xmlData, i);
784
+
785
+ // Read element name
786
+ let elementName = "";
787
+ while (i < xmlData.length && !/\s/.test(xmlData[i])) {
788
+ elementName += xmlData[i];
789
+ i++;
790
+ }
791
+
792
+ // Validate element name
793
+ if (!this.suppressValidationErr && !isName(elementName)) {
794
+ throw new Error(`Invalid element name: "${elementName}"`);
795
+ }
796
+
797
+ // Skip whitespace after element name
798
+ i = skipWhitespace(xmlData, i);
799
+ let contentModel = "";
800
+ // Expect '(' to start content model
801
+ if (xmlData[i] === "E" && hasSeq(xmlData, "MPTY", i)) i += 4;
802
+ else if (xmlData[i] === "A" && hasSeq(xmlData, "NY", i)) i += 2;
803
+ else if (xmlData[i] === "(") {
804
+ i++; // Move past '('
805
+
806
+ // Read content model
807
+ while (i < xmlData.length && xmlData[i] !== ")") {
808
+ contentModel += xmlData[i];
809
+ i++;
810
+ }
811
+ if (xmlData[i] !== ")") {
812
+ throw new Error("Unterminated content model");
813
+ }
814
+
815
+ } else if (!this.suppressValidationErr) {
816
+ throw new Error(`Invalid Element Expression, found "${xmlData[i]}"`);
817
+ }
818
+
819
+ return {
820
+ elementName,
821
+ contentModel: contentModel.trim(),
822
+ index: i
823
+ };
824
+ }
825
+
826
+ readAttlistExp(xmlData, i) {
827
+ // Skip leading whitespace after <!ATTLIST
828
+ i = skipWhitespace(xmlData, i);
829
+
830
+ // Read element name
831
+ let elementName = "";
832
+ while (i < xmlData.length && !/\s/.test(xmlData[i])) {
833
+ elementName += xmlData[i];
834
+ i++;
835
+ }
836
+
837
+ // Validate element name
838
+ validateEntityName(elementName);
839
+
840
+ // Skip whitespace after element name
841
+ i = skipWhitespace(xmlData, i);
842
+
843
+ // Read attribute name
844
+ let attributeName = "";
845
+ while (i < xmlData.length && !/\s/.test(xmlData[i])) {
846
+ attributeName += xmlData[i];
847
+ i++;
848
+ }
849
+
850
+ // Validate attribute name
851
+ if (!validateEntityName(attributeName)) {
852
+ throw new Error(`Invalid attribute name: "${attributeName}"`);
853
+ }
854
+
855
+ // Skip whitespace after attribute name
856
+ i = skipWhitespace(xmlData, i);
857
+
858
+ // Read attribute type
859
+ let attributeType = "";
860
+ if (xmlData.substring(i, i + 8).toUpperCase() === "NOTATION") {
861
+ attributeType = "NOTATION";
862
+ i += 8; // Move past "NOTATION"
863
+
864
+ // Skip whitespace after "NOTATION"
865
+ i = skipWhitespace(xmlData, i);
866
+
867
+ // Expect '(' to start the list of notations
868
+ if (xmlData[i] !== "(") {
869
+ throw new Error(`Expected '(', found "${xmlData[i]}"`);
870
+ }
871
+ i++; // Move past '('
872
+
873
+ // Read the list of allowed notations
874
+ let allowedNotations = [];
875
+ while (i < xmlData.length && xmlData[i] !== ")") {
876
+ let notation = "";
877
+ while (i < xmlData.length && xmlData[i] !== "|" && xmlData[i] !== ")") {
878
+ notation += xmlData[i];
879
+ i++;
603
880
  }
604
- else if( hasBody && isElement(xmlData, i)) i += 8;//Not supported
605
- else if( hasBody && isAttlist(xmlData, i)) i += 8;//Not supported
606
- else if( hasBody && isNotation(xmlData, i)) i += 9;//Not supported
607
- else if( isComment) comment = true;
608
- else throw new Error("Invalid DOCTYPE");
609
-
610
- angleBracketsCount++;
611
- exp = "";
612
- } else if (xmlData[i] === '>') { //Read tag content
613
- if(comment){
614
- if( xmlData[i - 1] === "-" && xmlData[i - 2] === "-"){
615
- comment = false;
616
- angleBracketsCount--;
617
- }
618
- }else {
619
- angleBracketsCount--;
881
+
882
+ // Validate notation name
883
+ notation = notation.trim();
884
+ if (!validateEntityName(notation)) {
885
+ throw new Error(`Invalid notation name: "${notation}"`);
620
886
  }
621
- if (angleBracketsCount === 0) {
622
- break;
887
+
888
+ allowedNotations.push(notation);
889
+
890
+ // Skip '|' separator or exit loop
891
+ if (xmlData[i] === "|") {
892
+ i++; // Move past '|'
893
+ i = skipWhitespace(xmlData, i); // Skip optional whitespace after '|'
623
894
  }
624
- }else if( xmlData[i] === '['){
625
- hasBody = true;
626
- }else {
627
- exp += xmlData[i];
895
+ }
896
+
897
+ if (xmlData[i] !== ")") {
898
+ throw new Error("Unterminated list of notations");
899
+ }
900
+ i++; // Move past ')'
901
+
902
+ // Store the allowed notations as part of the attribute type
903
+ attributeType += " (" + allowedNotations.join("|") + ")";
904
+ } else {
905
+ // Handle simple types (e.g., CDATA, ID, IDREF, etc.)
906
+ while (i < xmlData.length && !/\s/.test(xmlData[i])) {
907
+ attributeType += xmlData[i];
908
+ i++;
909
+ }
910
+
911
+ // Validate simple attribute type
912
+ const validTypes = ["CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN", "NMTOKENS"];
913
+ if (!this.suppressValidationErr && !validTypes.includes(attributeType.toUpperCase())) {
914
+ throw new Error(`Invalid attribute type: "${attributeType}"`);
628
915
  }
629
916
  }
630
- if(angleBracketsCount !== 0){
631
- throw new Error(`Unclosed DOCTYPE`);
917
+
918
+ // Skip whitespace after attribute type
919
+ i = skipWhitespace(xmlData, i);
920
+
921
+ // Read default value
922
+ let defaultValue = "";
923
+ if (xmlData.substring(i, i + 8).toUpperCase() === "#REQUIRED") {
924
+ defaultValue = "#REQUIRED";
925
+ i += 8;
926
+ } else if (xmlData.substring(i, i + 7).toUpperCase() === "#IMPLIED") {
927
+ defaultValue = "#IMPLIED";
928
+ i += 7;
929
+ } else {
930
+ [i, defaultValue] = this.readIdentifierVal(xmlData, i, "ATTLIST");
931
+ }
932
+
933
+ return {
934
+ elementName,
935
+ attributeName,
936
+ attributeType,
937
+ defaultValue,
938
+ index: i
632
939
  }
633
- }else {
634
- throw new Error(`Invalid Tag instead of DOCTYPE`);
635
940
  }
636
- return {entities, i};
637
941
  }
638
942
 
639
- function readEntityExp(xmlData,i){
640
- //External entities are not supported
641
- // <!ENTITY ext SYSTEM "http://normal-website.com" >
642
943
 
643
- //Parameter entities are not supported
644
- // <!ENTITY entityname "&anotherElement;">
645
944
 
646
- //Internal entities are supported
647
- // <!ENTITY entityname "replacement text">
648
-
649
- //read EntityName
650
- let entityName = "";
651
- for (; i < xmlData.length && (xmlData[i] !== "'" && xmlData[i] !== '"' ); i++) {
652
- // if(xmlData[i] === " ") continue;
653
- // else
654
- entityName += xmlData[i];
655
- }
656
- entityName = entityName.trim();
657
- if(entityName.indexOf(" ") !== -1) throw new Error("External entites are not supported");
658
-
659
- //read Entity Value
660
- const startChar = xmlData[i++];
661
- let val = "";
662
- for (; i < xmlData.length && xmlData[i] !== startChar ; i++) {
663
- val += xmlData[i];
664
- }
665
- return [entityName, val, i];
666
- }
945
+ const skipWhitespace = (data, index) => {
946
+ while (index < data.length && /\s/.test(data[index])) {
947
+ index++;
948
+ }
949
+ return index;
950
+ };
667
951
 
668
- function isComment(xmlData, i){
669
- if(xmlData[i+1] === '!' &&
670
- xmlData[i+2] === '-' &&
671
- xmlData[i+3] === '-') return true
672
- return false
673
- }
674
- function isEntity(xmlData, i){
675
- if(xmlData[i+1] === '!' &&
676
- xmlData[i+2] === 'E' &&
677
- xmlData[i+3] === 'N' &&
678
- xmlData[i+4] === 'T' &&
679
- xmlData[i+5] === 'I' &&
680
- xmlData[i+6] === 'T' &&
681
- xmlData[i+7] === 'Y') return true
682
- return false
683
- }
684
- function isElement(xmlData, i){
685
- if(xmlData[i+1] === '!' &&
686
- xmlData[i+2] === 'E' &&
687
- xmlData[i+3] === 'L' &&
688
- xmlData[i+4] === 'E' &&
689
- xmlData[i+5] === 'M' &&
690
- xmlData[i+6] === 'E' &&
691
- xmlData[i+7] === 'N' &&
692
- xmlData[i+8] === 'T') return true
693
- return false
694
- }
695
952
 
696
- function isAttlist(xmlData, i){
697
- if(xmlData[i+1] === '!' &&
698
- xmlData[i+2] === 'A' &&
699
- xmlData[i+3] === 'T' &&
700
- xmlData[i+4] === 'T' &&
701
- xmlData[i+5] === 'L' &&
702
- xmlData[i+6] === 'I' &&
703
- xmlData[i+7] === 'S' &&
704
- xmlData[i+8] === 'T') return true
705
- return false
706
- }
707
- function isNotation(xmlData, i){
708
- if(xmlData[i+1] === '!' &&
709
- xmlData[i+2] === 'N' &&
710
- xmlData[i+3] === 'O' &&
711
- xmlData[i+4] === 'T' &&
712
- xmlData[i+5] === 'A' &&
713
- xmlData[i+6] === 'T' &&
714
- xmlData[i+7] === 'I' &&
715
- xmlData[i+8] === 'O' &&
716
- xmlData[i+9] === 'N') return true
717
- return false
953
+
954
+ function hasSeq(data, seq, i) {
955
+ for (let j = 0; j < seq.length; j++) {
956
+ if (seq[j] !== data[i + j + 1]) return false;
957
+ }
958
+ return true;
718
959
  }
719
960
 
720
- function validateEntityName(name){
721
- if (util$1.isName(name))
722
- return name;
961
+ function validateEntityName(name) {
962
+ if (isName(name))
963
+ return name;
723
964
  else
724
965
  throw new Error(`Invalid entity name ${name}`);
725
966
  }
726
967
 
727
- var DocTypeReader = readDocType$1;
728
-
729
968
  const hexRegex = /^[-+]?0x[a-fA-F0-9]+$/;
730
- const numRegex = /^([\-\+])?(0*)(\.[0-9]+([eE]\-?[0-9]+)?|[0-9]+(\.[0-9]+([eE]\-?[0-9]+)?)?)$/;
731
- // const octRegex = /0x[a-z0-9]+/;
969
+ const numRegex = /^([\-\+])?(0*)([0-9]*(\.[0-9]*)?)$/;
970
+ // const octRegex = /^0x[a-z0-9]+/;
732
971
  // const binRegex = /0x[a-z0-9]+/;
733
972
 
734
-
735
- //polyfill
736
- if (!Number.parseInt && window.parseInt) {
737
- Number.parseInt = window.parseInt;
738
- }
739
- if (!Number.parseFloat && window.parseFloat) {
740
- Number.parseFloat = window.parseFloat;
741
- }
742
-
743
-
973
+
744
974
  const consider = {
745
975
  hex : true,
976
+ // oct: false,
746
977
  leadingZeros: true,
747
978
  decimalPoint: "\.",
748
- eNotation: true
979
+ eNotation: true,
749
980
  //skipLike: /regex/
750
981
  };
751
982
 
752
- function toNumber$1(str, options = {}){
753
- // const options = Object.assign({}, consider);
754
- // if(opt.leadingZeros === false){
755
- // options.leadingZeros = false;
756
- // }else if(opt.hex === false){
757
- // options.hex = false;
758
- // }
759
-
983
+ function toNumber(str, options = {}){
760
984
  options = Object.assign({}, consider, options );
761
985
  if(!str || typeof str !== "string" ) return str;
762
986
 
763
987
  let trimmedStr = str.trim();
764
- // if(trimmedStr === "0.0") return 0;
765
- // else if(trimmedStr === "+0.0") return 0;
766
- // else if(trimmedStr === "-0.0") return -0;
767
-
988
+
768
989
  if(options.skipLike !== undefined && options.skipLike.test(trimmedStr)) return str;
990
+ else if(str==="0") return 0;
769
991
  else if (options.hex && hexRegex.test(trimmedStr)) {
770
- return Number.parseInt(trimmedStr, 16);
771
- // } else if (options.parseOct && octRegex.test(str)) {
992
+ return parse_int(trimmedStr, 16);
993
+ // }else if (options.oct && octRegex.test(str)) {
772
994
  // return Number.parseInt(val, 8);
995
+ }else if (trimmedStr.includes('e') || trimmedStr.includes('E')) { //eNotation
996
+ return resolveEnotation(str,trimmedStr,options);
773
997
  // }else if (options.parseBin && binRegex.test(str)) {
774
998
  // return Number.parseInt(val, 2);
775
999
  }else {
776
1000
  //separate negative sign, leading zeros, and rest number
777
1001
  const match = numRegex.exec(trimmedStr);
1002
+ // +00.123 => [ , '+', '00', '.123', ..
778
1003
  if(match){
779
- const sign = match[1];
1004
+ const sign = match[1] || "";
780
1005
  const leadingZeros = match[2];
781
1006
  let numTrimmedByZeros = trimZeros(match[3]); //complete num without leading zeros
1007
+ const decimalAdjacentToLeadingZeros = sign ? // 0., -00., 000.
1008
+ str[leadingZeros.length+1] === "."
1009
+ : str[leadingZeros.length] === ".";
1010
+
782
1011
  //trim ending zeros for floating number
783
-
784
- const eNotation = match[4] || match[6];
785
- if(!options.leadingZeros && leadingZeros.length > 0 && sign && trimmedStr[2] !== ".") return str; //-0123
786
- else if(!options.leadingZeros && leadingZeros.length > 0 && !sign && trimmedStr[1] !== ".") return str; //0123
1012
+ if(!options.leadingZeros //leading zeros are not allowed
1013
+ && (leadingZeros.length > 1
1014
+ || (leadingZeros.length === 1 && !decimalAdjacentToLeadingZeros))){
1015
+ // 00, 00.3, +03.24, 03, 03.24
1016
+ return str;
1017
+ }
787
1018
  else {//no leading zeros or leading zeros are allowed
788
1019
  const num = Number(trimmedStr);
789
- const numStr = "" + num;
790
- if(numStr.search(/[eE]/) !== -1){ //given number is long and parsed to eNotation
791
- if(options.eNotation) return num;
792
- else return str;
793
- }else if(eNotation){ //given number has enotation
1020
+ const parsedStr = String(num);
1021
+
1022
+ if( num === 0) return num;
1023
+ if(parsedStr.search(/[eE]/) !== -1){ //given number is long and parsed to eNotation
794
1024
  if(options.eNotation) return num;
795
1025
  else return str;
796
1026
  }else if(trimmedStr.indexOf(".") !== -1){ //floating number
797
- // const decimalPart = match[5].substr(1);
798
- // const intPart = trimmedStr.substr(0,trimmedStr.indexOf("."));
799
-
800
-
801
- // const p = numStr.indexOf(".");
802
- // const givenIntPart = numStr.substr(0,p);
803
- // const givenDecPart = numStr.substr(p+1);
804
- if(numStr === "0" && (numTrimmedByZeros === "") ) return num; //0.0
805
- else if(numStr === numTrimmedByZeros) return num; //0.456. 0.79000
806
- else if( sign && numStr === "-"+numTrimmedByZeros) return num;
1027
+ if(parsedStr === "0") return num; //0.0
1028
+ else if(parsedStr === numTrimmedByZeros) return num; //0.456. 0.79000
1029
+ else if( parsedStr === `${sign}${numTrimmedByZeros}`) return num;
807
1030
  else return str;
808
1031
  }
809
1032
 
1033
+ let n = leadingZeros? numTrimmedByZeros : trimmedStr;
810
1034
  if(leadingZeros){
811
- // if(numTrimmedByZeros === numStr){
812
- // if(options.leadingZeros) return num;
813
- // else return str;
814
- // }else return str;
815
- if(numTrimmedByZeros === numStr) return num;
816
- else if(sign+numTrimmedByZeros === numStr) return num;
817
- else return str;
1035
+ // -009 => -9
1036
+ return (n === parsedStr) || (sign+n === parsedStr) ? num : str
1037
+ }else {
1038
+ // +9
1039
+ return (n === parsedStr) || (n === sign+parsedStr) ? num : str
818
1040
  }
819
-
820
- if(trimmedStr === numStr) return num;
821
- else if(trimmedStr === sign+numStr) return num;
822
- // else{
823
- // //number with +/- sign
824
- // trimmedStr.test(/[-+][0-9]);
825
-
826
- // }
827
- return str;
828
1041
  }
829
- // else if(!eNotation && trimmedStr && trimmedStr !== Number(trimmedStr) ) return str;
830
-
831
1042
  }else { //non-numeric string
832
1043
  return str;
833
1044
  }
834
1045
  }
835
1046
  }
836
1047
 
1048
+ const eNotationRegx = /^([-+])?(0*)(\d*(\.\d*)?[eE][-\+]?\d+)$/;
1049
+ function resolveEnotation(str,trimmedStr,options){
1050
+ if(!options.eNotation) return str;
1051
+ const notation = trimmedStr.match(eNotationRegx);
1052
+ if(notation){
1053
+ let sign = notation[1] || "";
1054
+ const eChar = notation[3].indexOf("e") === -1 ? "E" : "e";
1055
+ const leadingZeros = notation[2];
1056
+ const eAdjacentToLeadingZeros = sign ? // 0E.
1057
+ str[leadingZeros.length+1] === eChar
1058
+ : str[leadingZeros.length] === eChar;
1059
+
1060
+ if(leadingZeros.length > 1 && eAdjacentToLeadingZeros) return str;
1061
+ else if(leadingZeros.length === 1
1062
+ && (notation[3].startsWith(`.${eChar}`) || notation[3][0] === eChar)){
1063
+ return Number(trimmedStr);
1064
+ }else if(options.leadingZeros && !eAdjacentToLeadingZeros){ //accept with leading zeros
1065
+ //remove leading 0s
1066
+ trimmedStr = (notation[1] || "") + notation[3];
1067
+ return Number(trimmedStr);
1068
+ }else return str;
1069
+ }else {
1070
+ return str;
1071
+ }
1072
+ }
1073
+
837
1074
  /**
838
1075
  *
839
1076
  * @param {string} numStr without leading zeros
@@ -844,14 +1081,21 @@ function trimZeros(numStr){
844
1081
  numStr = numStr.replace(/0+$/, ""); //remove ending zeros
845
1082
  if(numStr === ".") numStr = "0";
846
1083
  else if(numStr[0] === ".") numStr = "0"+numStr;
847
- else if(numStr[numStr.length-1] === ".") numStr = numStr.substr(0,numStr.length-1);
1084
+ else if(numStr[numStr.length-1] === ".") numStr = numStr.substring(0,numStr.length-1);
848
1085
  return numStr;
849
1086
  }
850
1087
  return numStr;
851
1088
  }
852
- var strnum = toNumber$1;
853
1089
 
854
- function getIgnoreAttributesFn$2(ignoreAttributes) {
1090
+ function parse_int(numStr, base){
1091
+ //polyfill
1092
+ if(parseInt) return parseInt(numStr, base);
1093
+ else if(Number.parseInt) return Number.parseInt(numStr, base);
1094
+ else if(window && window.parseInt) return window.parseInt(numStr, base);
1095
+ else throw new Error("parseInt, Number.parseInt, window.parseInt are not supported")
1096
+ }
1097
+
1098
+ function getIgnoreAttributesFn(ignoreAttributes) {
855
1099
  if (typeof ignoreAttributes === 'function') {
856
1100
  return ignoreAttributes
857
1101
  }
@@ -870,16 +1114,6 @@ function getIgnoreAttributesFn$2(ignoreAttributes) {
870
1114
  return () => false
871
1115
  }
872
1116
 
873
- var ignoreAttributes = getIgnoreAttributesFn$2;
874
-
875
- ///@ts-check
876
-
877
- const util = util$3;
878
- const xmlNode = xmlNode$1;
879
- const readDocType = DocTypeReader;
880
- const toNumber = strnum;
881
- const getIgnoreAttributesFn$1 = ignoreAttributes;
882
-
883
1117
  // const regx =
884
1118
  // '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
885
1119
  // .replace(/NAME/g, util.nameRegexp);
@@ -887,19 +1121,19 @@ const getIgnoreAttributesFn$1 = ignoreAttributes;
887
1121
  //const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
888
1122
  //const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
889
1123
 
890
- let OrderedObjParser$1 = class OrderedObjParser{
891
- constructor(options){
1124
+ class OrderedObjParser {
1125
+ constructor(options) {
892
1126
  this.options = options;
893
1127
  this.currentNode = null;
894
1128
  this.tagsNodeStack = [];
895
1129
  this.docTypeEntities = {};
896
1130
  this.lastEntities = {
897
- "apos" : { regex: /&(apos|#39|#x27);/g, val : "'"},
898
- "gt" : { regex: /&(gt|#62|#x3E);/g, val : ">"},
899
- "lt" : { regex: /&(lt|#60|#x3C);/g, val : "<"},
900
- "quot" : { regex: /&(quot|#34|#x22);/g, val : "\""},
1131
+ "apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
1132
+ "gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
1133
+ "lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
1134
+ "quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
901
1135
  };
902
- this.ampEntity = { regex: /&(amp|#38|#x26);/g, val : "&"};
1136
+ this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
903
1137
  this.htmlEntities = {
904
1138
  "space": { regex: /&(nbsp|#160);/g, val: " " },
905
1139
  // "lt" : { regex: /&(lt|#60);/g, val: "<" },
@@ -907,15 +1141,15 @@ let OrderedObjParser$1 = class OrderedObjParser{
907
1141
  // "amp" : { regex: /&(amp|#38);/g, val: "&" },
908
1142
  // "quot" : { regex: /&(quot|#34);/g, val: "\"" },
909
1143
  // "apos" : { regex: /&(apos|#39);/g, val: "'" },
910
- "cent" : { regex: /&(cent|#162);/g, val: "¢" },
911
- "pound" : { regex: /&(pound|#163);/g, val: "£" },
912
- "yen" : { regex: /&(yen|#165);/g, val: "¥" },
913
- "euro" : { regex: /&(euro|#8364);/g, val: "€" },
914
- "copyright" : { regex: /&(copy|#169);/g, val: "©" },
915
- "reg" : { regex: /&(reg|#174);/g, val: "®" },
916
- "inr" : { regex: /&(inr|#8377);/g, val: "₹" },
917
- "num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 10)) },
918
- "num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCharCode(Number.parseInt(str, 16)) },
1144
+ "cent": { regex: /&(cent|#162);/g, val: "¢" },
1145
+ "pound": { regex: /&(pound|#163);/g, val: "£" },
1146
+ "yen": { regex: /&(yen|#165);/g, val: "¥" },
1147
+ "euro": { regex: /&(euro|#8364);/g, val: "€" },
1148
+ "copyright": { regex: /&(copy|#169);/g, val: "©" },
1149
+ "reg": { regex: /&(reg|#174);/g, val: "®" },
1150
+ "inr": { regex: /&(inr|#8377);/g, val: "₹" },
1151
+ "num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => fromCodePoint(str, 10, "&#") },
1152
+ "num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => fromCodePoint(str, 16, "&#x") },
919
1153
  };
920
1154
  this.addExternalEntities = addExternalEntities;
921
1155
  this.parseXml = parseXml;
@@ -927,18 +1161,35 @@ let OrderedObjParser$1 = class OrderedObjParser{
927
1161
  this.readStopNodeData = readStopNodeData;
928
1162
  this.saveTextToParentTag = saveTextToParentTag;
929
1163
  this.addChild = addChild;
930
- this.ignoreAttributesFn = getIgnoreAttributesFn$1(this.options.ignoreAttributes);
1164
+ this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes);
1165
+ this.entityExpansionCount = 0;
1166
+ this.currentExpandedLength = 0;
1167
+
1168
+ if (this.options.stopNodes && this.options.stopNodes.length > 0) {
1169
+ this.stopNodesExact = new Set();
1170
+ this.stopNodesWildcard = new Set();
1171
+ for (let i = 0; i < this.options.stopNodes.length; i++) {
1172
+ const stopNodeExp = this.options.stopNodes[i];
1173
+ if (typeof stopNodeExp !== 'string') continue;
1174
+ if (stopNodeExp.startsWith("*.")) {
1175
+ this.stopNodesWildcard.add(stopNodeExp.substring(2));
1176
+ } else {
1177
+ this.stopNodesExact.add(stopNodeExp);
1178
+ }
1179
+ }
1180
+ }
931
1181
  }
932
1182
 
933
- };
1183
+ }
934
1184
 
935
- function addExternalEntities(externalEntities){
1185
+ function addExternalEntities(externalEntities) {
936
1186
  const entKeys = Object.keys(externalEntities);
937
1187
  for (let i = 0; i < entKeys.length; i++) {
938
1188
  const ent = entKeys[i];
1189
+ const escaped = ent.replace(/[.\-+*:]/g, '\\.');
939
1190
  this.lastEntities[ent] = {
940
- regex: new RegExp("&"+ent+";","g"),
941
- val : externalEntities[ent]
1191
+ regex: new RegExp("&" + escaped + ";", "g"),
1192
+ val: externalEntities[ent]
942
1193
  };
943
1194
  }
944
1195
  }
@@ -957,23 +1208,23 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode,
957
1208
  if (this.options.trimValues && !dontTrim) {
958
1209
  val = val.trim();
959
1210
  }
960
- if(val.length > 0){
961
- if(!escapeEntities) val = this.replaceEntitiesValue(val);
962
-
1211
+ if (val.length > 0) {
1212
+ if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
1213
+
963
1214
  const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
964
- if(newval === null || newval === undefined){
1215
+ if (newval === null || newval === undefined) {
965
1216
  //don't parse
966
1217
  return val;
967
- }else if(typeof newval !== typeof val || newval !== val){
1218
+ } else if (typeof newval !== typeof val || newval !== val) {
968
1219
  //overwrite
969
1220
  return newval;
970
- }else if(this.options.trimValues){
1221
+ } else if (this.options.trimValues) {
971
1222
  return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
972
- }else {
1223
+ } else {
973
1224
  const trimmedVal = val.trim();
974
- if(trimmedVal === val){
1225
+ if (trimmedVal === val) {
975
1226
  return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
976
- }else {
1227
+ } else {
977
1228
  return val;
978
1229
  }
979
1230
  }
@@ -1004,7 +1255,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
1004
1255
  // attrStr = attrStr.replace(/\r?\n/g, ' ');
1005
1256
  //attrStr = attrStr || attrStr.trim();
1006
1257
 
1007
- const matches = util.getAllMatches(attrStr, attrsRegx);
1258
+ const matches = getAllMatches(attrStr, attrsRegx);
1008
1259
  const len = matches.length; //don't make it inline
1009
1260
  const attrs = {};
1010
1261
  for (let i = 0; i < len; i++) {
@@ -1018,20 +1269,20 @@ function buildAttributesMap(attrStr, jPath, tagName) {
1018
1269
  if (this.options.transformAttributeName) {
1019
1270
  aName = this.options.transformAttributeName(aName);
1020
1271
  }
1021
- if(aName === "__proto__") aName = "#__proto__";
1272
+ if (aName === "__proto__") aName = "#__proto__";
1022
1273
  if (oldVal !== undefined) {
1023
1274
  if (this.options.trimValues) {
1024
1275
  oldVal = oldVal.trim();
1025
1276
  }
1026
- oldVal = this.replaceEntitiesValue(oldVal);
1277
+ oldVal = this.replaceEntitiesValue(oldVal, tagName, jPath);
1027
1278
  const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
1028
- if(newVal === null || newVal === undefined){
1279
+ if (newVal === null || newVal === undefined) {
1029
1280
  //don't parse
1030
1281
  attrs[aName] = oldVal;
1031
- }else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
1282
+ } else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
1032
1283
  //overwrite
1033
1284
  attrs[aName] = newVal;
1034
- }else {
1285
+ } else {
1035
1286
  //parse
1036
1287
  attrs[aName] = parseValue(
1037
1288
  oldVal,
@@ -1056,46 +1307,52 @@ function buildAttributesMap(attrStr, jPath, tagName) {
1056
1307
  }
1057
1308
  }
1058
1309
 
1059
- const parseXml = function(xmlData) {
1310
+ const parseXml = function (xmlData) {
1060
1311
  xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
1061
- const xmlObj = new xmlNode('!xml');
1312
+ const xmlObj = new XmlNode('!xml');
1062
1313
  let currentNode = xmlObj;
1063
1314
  let textData = "";
1064
1315
  let jPath = "";
1065
- for(let i=0; i< xmlData.length; i++){//for each char in XML data
1316
+
1317
+ // Reset entity expansion counters for this document
1318
+ this.entityExpansionCount = 0;
1319
+ this.currentExpandedLength = 0;
1320
+
1321
+ const docTypeReader = new DocTypeReader(this.options.processEntities);
1322
+ for (let i = 0; i < xmlData.length; i++) {//for each char in XML data
1066
1323
  const ch = xmlData[i];
1067
- if(ch === '<'){
1324
+ if (ch === '<') {
1068
1325
  // const nextIndex = i+1;
1069
1326
  // const _2ndChar = xmlData[nextIndex];
1070
- if( xmlData[i+1] === '/') {//Closing Tag
1327
+ if (xmlData[i + 1] === '/') {//Closing Tag
1071
1328
  const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.");
1072
- let tagName = xmlData.substring(i+2,closeIndex).trim();
1329
+ let tagName = xmlData.substring(i + 2, closeIndex).trim();
1073
1330
 
1074
- if(this.options.removeNSPrefix){
1331
+ if (this.options.removeNSPrefix) {
1075
1332
  const colonIndex = tagName.indexOf(":");
1076
- if(colonIndex !== -1){
1077
- tagName = tagName.substr(colonIndex+1);
1333
+ if (colonIndex !== -1) {
1334
+ tagName = tagName.substr(colonIndex + 1);
1078
1335
  }
1079
1336
  }
1080
1337
 
1081
- if(this.options.transformTagName) {
1338
+ if (this.options.transformTagName) {
1082
1339
  tagName = this.options.transformTagName(tagName);
1083
1340
  }
1084
1341
 
1085
- if(currentNode){
1342
+ if (currentNode) {
1086
1343
  textData = this.saveTextToParentTag(textData, currentNode, jPath);
1087
1344
  }
1088
1345
 
1089
1346
  //check if last tag of nested tag was unpaired tag
1090
- const lastTagName = jPath.substring(jPath.lastIndexOf(".")+1);
1091
- if(tagName && this.options.unpairedTags.indexOf(tagName) !== -1 ){
1347
+ const lastTagName = jPath.substring(jPath.lastIndexOf(".") + 1);
1348
+ if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) {
1092
1349
  throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
1093
1350
  }
1094
1351
  let propIndex = 0;
1095
- if(lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1 ){
1096
- propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.')-1);
1352
+ if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
1353
+ propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.') - 1);
1097
1354
  this.tagsNodeStack.pop();
1098
- }else {
1355
+ } else {
1099
1356
  propIndex = jPath.lastIndexOf(".");
1100
1357
  }
1101
1358
  jPath = jPath.substring(0, propIndex);
@@ -1103,72 +1360,76 @@ const parseXml = function(xmlData) {
1103
1360
  currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
1104
1361
  textData = "";
1105
1362
  i = closeIndex;
1106
- } else if( xmlData[i+1] === '?') {
1363
+ } else if (xmlData[i + 1] === '?') {
1107
1364
 
1108
- let tagData = readTagExp(xmlData,i, false, "?>");
1109
- if(!tagData) throw new Error("Pi Tag is not closed.");
1365
+ let tagData = readTagExp(xmlData, i, false, "?>");
1366
+ if (!tagData) throw new Error("Pi Tag is not closed.");
1110
1367
 
1111
1368
  textData = this.saveTextToParentTag(textData, currentNode, jPath);
1112
- if( (this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags);else {
1113
-
1114
- const childNode = new xmlNode(tagData.tagName);
1369
+ if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) ; else {
1370
+
1371
+ const childNode = new XmlNode(tagData.tagName);
1115
1372
  childNode.add(this.options.textNodeName, "");
1116
-
1117
- if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
1373
+
1374
+ if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
1118
1375
  childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
1119
1376
  }
1120
- this.addChild(currentNode, childNode, jPath);
1121
-
1377
+ this.addChild(currentNode, childNode, jPath, i);
1122
1378
  }
1123
1379
 
1124
1380
 
1125
1381
  i = tagData.closeIndex + 1;
1126
- } else if(xmlData.substr(i + 1, 3) === '!--') {
1127
- const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.");
1128
- if(this.options.commentPropName){
1382
+ } else if (xmlData.substr(i + 1, 3) === '!--') {
1383
+ const endIndex = findClosingIndex(xmlData, "-->", i + 4, "Comment is not closed.");
1384
+ if (this.options.commentPropName) {
1129
1385
  const comment = xmlData.substring(i + 4, endIndex - 2);
1130
1386
 
1131
1387
  textData = this.saveTextToParentTag(textData, currentNode, jPath);
1132
1388
 
1133
- currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
1389
+ currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
1134
1390
  }
1135
1391
  i = endIndex;
1136
- } else if( xmlData.substr(i + 1, 2) === '!D') {
1137
- const result = readDocType(xmlData, i);
1392
+ } else if (xmlData.substr(i + 1, 2) === '!D') {
1393
+ const result = docTypeReader.readDocType(xmlData, i);
1138
1394
  this.docTypeEntities = result.entities;
1139
1395
  i = result.i;
1140
- }else if(xmlData.substr(i + 1, 2) === '![') {
1396
+ } else if (xmlData.substr(i + 1, 2) === '![') {
1141
1397
  const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
1142
- const tagExp = xmlData.substring(i + 9,closeIndex);
1398
+ const tagExp = xmlData.substring(i + 9, closeIndex);
1143
1399
 
1144
1400
  textData = this.saveTextToParentTag(textData, currentNode, jPath);
1145
1401
 
1146
1402
  let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
1147
- if(val == undefined) val = "";
1403
+ if (val == undefined) val = "";
1148
1404
 
1149
1405
  //cdata should be set even if it is 0 length string
1150
- if(this.options.cdataPropName){
1151
- currentNode.add(this.options.cdataPropName, [ { [this.options.textNodeName] : tagExp } ]);
1152
- }else {
1406
+ if (this.options.cdataPropName) {
1407
+ currentNode.add(this.options.cdataPropName, [{ [this.options.textNodeName]: tagExp }]);
1408
+ } else {
1153
1409
  currentNode.add(this.options.textNodeName, val);
1154
1410
  }
1155
-
1411
+
1156
1412
  i = closeIndex + 2;
1157
- }else {//Opening tag
1158
- let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
1159
- let tagName= result.tagName;
1413
+ } else {//Opening tag
1414
+ let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
1415
+ let tagName = result.tagName;
1160
1416
  const rawTagName = result.rawTagName;
1161
1417
  let tagExp = result.tagExp;
1162
1418
  let attrExpPresent = result.attrExpPresent;
1163
1419
  let closeIndex = result.closeIndex;
1164
1420
 
1165
1421
  if (this.options.transformTagName) {
1166
- tagName = this.options.transformTagName(tagName);
1422
+ //console.log(tagExp, tagName)
1423
+ const newTagName = this.options.transformTagName(tagName);
1424
+ if (tagExp === tagName) {
1425
+ tagExp = newTagName;
1426
+ }
1427
+ tagName = newTagName;
1167
1428
  }
1168
-
1429
+
1169
1430
  //save text as child node
1170
1431
  if (currentNode && textData) {
1171
- if(currentNode.tagname !== '!xml'){
1432
+ if (currentNode.tagname !== '!xml') {
1172
1433
  //when nested tag is found
1173
1434
  textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
1174
1435
  }
@@ -1176,131 +1437,200 @@ const parseXml = function(xmlData) {
1176
1437
 
1177
1438
  //check if last tag was unpaired tag
1178
1439
  const lastTag = currentNode;
1179
- if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){
1440
+ if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
1180
1441
  currentNode = this.tagsNodeStack.pop();
1181
1442
  jPath = jPath.substring(0, jPath.lastIndexOf("."));
1182
1443
  }
1183
- if(tagName !== xmlObj.tagname){
1444
+ if (tagName !== xmlObj.tagname) {
1184
1445
  jPath += jPath ? "." + tagName : tagName;
1185
1446
  }
1186
- if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) {
1447
+ const startIndex = i;
1448
+ if (this.isItStopNode(this.stopNodesExact, this.stopNodesWildcard, jPath, tagName)) {
1187
1449
  let tagContent = "";
1188
1450
  //self-closing tag
1189
- if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
1190
- if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
1451
+ if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
1452
+ if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
1191
1453
  tagName = tagName.substr(0, tagName.length - 1);
1192
1454
  jPath = jPath.substr(0, jPath.length - 1);
1193
1455
  tagExp = tagName;
1194
- }else {
1456
+ } else {
1195
1457
  tagExp = tagExp.substr(0, tagExp.length - 1);
1196
1458
  }
1197
1459
  i = result.closeIndex;
1198
1460
  }
1199
1461
  //unpaired tag
1200
- else if(this.options.unpairedTags.indexOf(tagName) !== -1){
1201
-
1462
+ else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
1463
+
1202
1464
  i = result.closeIndex;
1203
1465
  }
1204
1466
  //normal tag
1205
1467
  else {
1206
1468
  //read until closing tag is found
1207
1469
  const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
1208
- if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
1470
+ if (!result) throw new Error(`Unexpected end of ${rawTagName}`);
1209
1471
  i = result.i;
1210
1472
  tagContent = result.tagContent;
1211
1473
  }
1212
1474
 
1213
- const childNode = new xmlNode(tagName);
1214
- if(tagName !== tagExp && attrExpPresent){
1475
+ const childNode = new XmlNode(tagName);
1476
+
1477
+ if (tagName !== tagExp && attrExpPresent) {
1215
1478
  childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
1216
1479
  }
1217
- if(tagContent) {
1480
+ if (tagContent) {
1218
1481
  tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
1219
1482
  }
1220
-
1483
+
1221
1484
  jPath = jPath.substr(0, jPath.lastIndexOf("."));
1222
1485
  childNode.add(this.options.textNodeName, tagContent);
1223
-
1224
- this.addChild(currentNode, childNode, jPath);
1225
- }else {
1226
- //selfClosing tag
1227
- if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
1228
- if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
1486
+
1487
+ this.addChild(currentNode, childNode, jPath, startIndex);
1488
+ } else {
1489
+ //selfClosing tag
1490
+ if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
1491
+ if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
1229
1492
  tagName = tagName.substr(0, tagName.length - 1);
1230
1493
  jPath = jPath.substr(0, jPath.length - 1);
1231
1494
  tagExp = tagName;
1232
- }else {
1495
+ } else {
1233
1496
  tagExp = tagExp.substr(0, tagExp.length - 1);
1234
1497
  }
1235
-
1236
- if(this.options.transformTagName) {
1237
- tagName = this.options.transformTagName(tagName);
1498
+
1499
+ if (this.options.transformTagName) {
1500
+ const newTagName = this.options.transformTagName(tagName);
1501
+ if (tagExp === tagName) {
1502
+ tagExp = newTagName;
1503
+ }
1504
+ tagName = newTagName;
1238
1505
  }
1239
1506
 
1240
- const childNode = new xmlNode(tagName);
1241
- if(tagName !== tagExp && attrExpPresent){
1507
+ const childNode = new XmlNode(tagName);
1508
+ if (tagName !== tagExp && attrExpPresent) {
1242
1509
  childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
1243
1510
  }
1244
- this.addChild(currentNode, childNode, jPath);
1511
+ this.addChild(currentNode, childNode, jPath, startIndex);
1245
1512
  jPath = jPath.substr(0, jPath.lastIndexOf("."));
1246
1513
  }
1247
- //opening tag
1514
+ //opening tag
1248
1515
  else {
1249
- const childNode = new xmlNode( tagName);
1516
+ const childNode = new XmlNode(tagName);
1250
1517
  this.tagsNodeStack.push(currentNode);
1251
-
1252
- if(tagName !== tagExp && attrExpPresent){
1518
+
1519
+ if (tagName !== tagExp && attrExpPresent) {
1253
1520
  childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
1254
1521
  }
1255
- this.addChild(currentNode, childNode, jPath);
1522
+ this.addChild(currentNode, childNode, jPath, startIndex);
1256
1523
  currentNode = childNode;
1257
1524
  }
1258
1525
  textData = "";
1259
1526
  i = closeIndex;
1260
1527
  }
1261
1528
  }
1262
- }else {
1529
+ } else {
1263
1530
  textData += xmlData[i];
1264
1531
  }
1265
1532
  }
1266
1533
  return xmlObj.child;
1267
1534
  };
1268
1535
 
1269
- function addChild(currentNode, childNode, jPath){
1536
+ function addChild(currentNode, childNode, jPath, startIndex) {
1537
+ // unset startIndex if not requested
1538
+ if (!this.options.captureMetaData) startIndex = undefined;
1270
1539
  const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"]);
1271
- if(result === false);else if(typeof result === "string"){
1540
+ if (result === false) ; else if (typeof result === "string") {
1272
1541
  childNode.tagname = result;
1273
- currentNode.addChild(childNode);
1274
- }else {
1275
- currentNode.addChild(childNode);
1542
+ currentNode.addChild(childNode, startIndex);
1543
+ } else {
1544
+ currentNode.addChild(childNode, startIndex);
1276
1545
  }
1277
1546
  }
1278
1547
 
1279
- const replaceEntitiesValue$1 = function(val){
1548
+ const replaceEntitiesValue$1 = function (val, tagName, jPath) {
1549
+ // Performance optimization: Early return if no entities to replace
1550
+ if (val.indexOf('&') === -1) {
1551
+ return val;
1552
+ }
1553
+
1554
+ const entityConfig = this.options.processEntities;
1555
+
1556
+ if (!entityConfig.enabled) {
1557
+ return val;
1558
+ }
1280
1559
 
1281
- if(this.options.processEntities){
1282
- for(let entityName in this.docTypeEntities){
1283
- const entity = this.docTypeEntities[entityName];
1284
- val = val.replace( entity.regx, entity.val);
1560
+ // Check tag-specific filtering
1561
+ if (entityConfig.allowedTags) {
1562
+ if (!entityConfig.allowedTags.includes(tagName)) {
1563
+ return val; // Skip entity replacement for current tag as not set
1285
1564
  }
1286
- for(let entityName in this.lastEntities){
1287
- const entity = this.lastEntities[entityName];
1288
- val = val.replace( entity.regex, entity.val);
1565
+ }
1566
+
1567
+ if (entityConfig.tagFilter) {
1568
+ if (!entityConfig.tagFilter(tagName, jPath)) {
1569
+ return val; // Skip based on custom filter
1289
1570
  }
1290
- if(this.options.htmlEntities){
1291
- for(let entityName in this.htmlEntities){
1292
- const entity = this.htmlEntities[entityName];
1293
- val = val.replace( entity.regex, entity.val);
1571
+ }
1572
+
1573
+ // Replace DOCTYPE entities
1574
+ for (let entityName in this.docTypeEntities) {
1575
+ const entity = this.docTypeEntities[entityName];
1576
+ const matches = val.match(entity.regx);
1577
+
1578
+ if (matches) {
1579
+ // Track expansions
1580
+ this.entityExpansionCount += matches.length;
1581
+
1582
+ // Check expansion limit
1583
+ if (entityConfig.maxTotalExpansions &&
1584
+ this.entityExpansionCount > entityConfig.maxTotalExpansions) {
1585
+ throw new Error(
1586
+ `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
1587
+ );
1588
+ }
1589
+
1590
+ // Store length before replacement
1591
+ const lengthBefore = val.length;
1592
+ val = val.replace(entity.regx, entity.val);
1593
+
1594
+ // Check expanded length immediately after replacement
1595
+ if (entityConfig.maxExpandedLength) {
1596
+ this.currentExpandedLength += (val.length - lengthBefore);
1597
+
1598
+ if (this.currentExpandedLength > entityConfig.maxExpandedLength) {
1599
+ throw new Error(
1600
+ `Total expanded content size exceeded: ${this.currentExpandedLength} > ${entityConfig.maxExpandedLength}`
1601
+ );
1602
+ }
1294
1603
  }
1295
1604
  }
1296
- val = val.replace( this.ampEntity.regex, this.ampEntity.val);
1297
1605
  }
1606
+ if (val.indexOf('&') === -1) return val; // Early exit
1607
+
1608
+ // Replace standard entities
1609
+ for (let entityName in this.lastEntities) {
1610
+ const entity = this.lastEntities[entityName];
1611
+ val = val.replace(entity.regex, entity.val);
1612
+ }
1613
+ if (val.indexOf('&') === -1) return val; // Early exit
1614
+
1615
+ // Replace HTML entities if enabled
1616
+ if (this.options.htmlEntities) {
1617
+ for (let entityName in this.htmlEntities) {
1618
+ const entity = this.htmlEntities[entityName];
1619
+ val = val.replace(entity.regex, entity.val);
1620
+ }
1621
+ }
1622
+
1623
+ // Replace ampersand entity last
1624
+ val = val.replace(this.ampEntity.regex, this.ampEntity.val);
1625
+
1298
1626
  return val;
1299
1627
  };
1628
+
1629
+
1300
1630
  function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
1301
1631
  if (textData) { //store previously collected data as textNode
1302
- if(isLeafNode === undefined) isLeafNode = Object.keys(currentNode.child).length === 0;
1303
-
1632
+ if (isLeafNode === undefined) isLeafNode = currentNode.child.length === 0;
1633
+
1304
1634
  textData = this.parseTextData(textData,
1305
1635
  currentNode.tagname,
1306
1636
  jPath,
@@ -1317,17 +1647,14 @@ function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
1317
1647
 
1318
1648
  //TODO: use jPath to simplify the logic
1319
1649
  /**
1320
- *
1321
- * @param {string[]} stopNodes
1650
+ * @param {Set} stopNodesExact
1651
+ * @param {Set} stopNodesWildcard
1322
1652
  * @param {string} jPath
1323
- * @param {string} currentTagName
1653
+ * @param {string} currentTagName
1324
1654
  */
1325
- function isItStopNode(stopNodes, jPath, currentTagName){
1326
- const allNodesExp = "*." + currentTagName;
1327
- for (const stopNodePath in stopNodes) {
1328
- const stopNodeExp = stopNodes[stopNodePath];
1329
- if( allNodesExp === stopNodeExp || jPath === stopNodeExp ) return true;
1330
- }
1655
+ function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName) {
1656
+ if (stopNodesWildcard && stopNodesWildcard.has(currentTagName)) return true;
1657
+ if (stopNodesExact && stopNodesExact.has(jPath)) return true;
1331
1658
  return false;
1332
1659
  }
1333
1660
 
@@ -1337,24 +1664,24 @@ function isItStopNode(stopNodes, jPath, currentTagName){
1337
1664
  * @param {number} i starting index
1338
1665
  * @returns
1339
1666
  */
1340
- function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
1667
+ function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
1341
1668
  let attrBoundary;
1342
1669
  let tagExp = "";
1343
1670
  for (let index = i; index < xmlData.length; index++) {
1344
1671
  let ch = xmlData[index];
1345
1672
  if (attrBoundary) {
1346
- if (ch === attrBoundary) attrBoundary = "";//reset
1673
+ if (ch === attrBoundary) attrBoundary = "";//reset
1347
1674
  } else if (ch === '"' || ch === "'") {
1348
- attrBoundary = ch;
1675
+ attrBoundary = ch;
1349
1676
  } else if (ch === closingChar[0]) {
1350
- if(closingChar[1]){
1351
- if(xmlData[index + 1] === closingChar[1]){
1677
+ if (closingChar[1]) {
1678
+ if (xmlData[index + 1] === closingChar[1]) {
1352
1679
  return {
1353
1680
  data: tagExp,
1354
1681
  index: index
1355
1682
  }
1356
1683
  }
1357
- }else {
1684
+ } else {
1358
1685
  return {
1359
1686
  data: tagExp,
1360
1687
  index: index
@@ -1367,33 +1694,33 @@ function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
1367
1694
  }
1368
1695
  }
1369
1696
 
1370
- function findClosingIndex(xmlData, str, i, errMsg){
1697
+ function findClosingIndex(xmlData, str, i, errMsg) {
1371
1698
  const closingIndex = xmlData.indexOf(str, i);
1372
- if(closingIndex === -1){
1699
+ if (closingIndex === -1) {
1373
1700
  throw new Error(errMsg)
1374
- }else {
1701
+ } else {
1375
1702
  return closingIndex + str.length - 1;
1376
1703
  }
1377
1704
  }
1378
1705
 
1379
- function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
1380
- const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
1381
- if(!result) return;
1706
+ function readTagExp(xmlData, i, removeNSPrefix, closingChar = ">") {
1707
+ const result = tagExpWithClosingIndex(xmlData, i + 1, closingChar);
1708
+ if (!result) return;
1382
1709
  let tagExp = result.data;
1383
1710
  const closeIndex = result.index;
1384
1711
  const separatorIndex = tagExp.search(/\s/);
1385
1712
  let tagName = tagExp;
1386
1713
  let attrExpPresent = true;
1387
- if(separatorIndex !== -1){//separate tag name and attributes expression
1714
+ if (separatorIndex !== -1) {//separate tag name and attributes expression
1388
1715
  tagName = tagExp.substring(0, separatorIndex);
1389
1716
  tagExp = tagExp.substring(separatorIndex + 1).trimStart();
1390
1717
  }
1391
1718
 
1392
1719
  const rawTagName = tagName;
1393
- if(removeNSPrefix){
1720
+ if (removeNSPrefix) {
1394
1721
  const colonIndex = tagName.indexOf(":");
1395
- if(colonIndex !== -1){
1396
- tagName = tagName.substr(colonIndex+1);
1722
+ if (colonIndex !== -1) {
1723
+ tagName = tagName.substr(colonIndex + 1);
1397
1724
  attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
1398
1725
  }
1399
1726
  }
@@ -1412,47 +1739,47 @@ function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
1412
1739
  * @param {string} tagName
1413
1740
  * @param {number} i
1414
1741
  */
1415
- function readStopNodeData(xmlData, tagName, i){
1742
+ function readStopNodeData(xmlData, tagName, i) {
1416
1743
  const startIndex = i;
1417
1744
  // Starting at 1 since we already have an open tag
1418
1745
  let openTagCount = 1;
1419
1746
 
1420
1747
  for (; i < xmlData.length; i++) {
1421
- if( xmlData[i] === "<"){
1422
- if (xmlData[i+1] === "/") {//close tag
1423
- const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
1424
- let closeTagName = xmlData.substring(i+2,closeIndex).trim();
1425
- if(closeTagName === tagName){
1426
- openTagCount--;
1427
- if (openTagCount === 0) {
1428
- return {
1429
- tagContent: xmlData.substring(startIndex, i),
1430
- i : closeIndex
1431
- }
1748
+ if (xmlData[i] === "<") {
1749
+ if (xmlData[i + 1] === "/") {//close tag
1750
+ const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
1751
+ let closeTagName = xmlData.substring(i + 2, closeIndex).trim();
1752
+ if (closeTagName === tagName) {
1753
+ openTagCount--;
1754
+ if (openTagCount === 0) {
1755
+ return {
1756
+ tagContent: xmlData.substring(startIndex, i),
1757
+ i: closeIndex
1432
1758
  }
1433
1759
  }
1434
- i=closeIndex;
1435
- } else if(xmlData[i+1] === '?') {
1436
- const closeIndex = findClosingIndex(xmlData, "?>", i+1, "StopNode is not closed.");
1437
- i=closeIndex;
1438
- } else if(xmlData.substr(i + 1, 3) === '!--') {
1439
- const closeIndex = findClosingIndex(xmlData, "-->", i+3, "StopNode is not closed.");
1440
- i=closeIndex;
1441
- } else if(xmlData.substr(i + 1, 2) === '![') {
1442
- const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
1443
- i=closeIndex;
1444
- } else {
1445
- const tagData = readTagExp(xmlData, i, '>');
1760
+ }
1761
+ i = closeIndex;
1762
+ } else if (xmlData[i + 1] === '?') {
1763
+ const closeIndex = findClosingIndex(xmlData, "?>", i + 1, "StopNode is not closed.");
1764
+ i = closeIndex;
1765
+ } else if (xmlData.substr(i + 1, 3) === '!--') {
1766
+ const closeIndex = findClosingIndex(xmlData, "-->", i + 3, "StopNode is not closed.");
1767
+ i = closeIndex;
1768
+ } else if (xmlData.substr(i + 1, 2) === '![') {
1769
+ const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
1770
+ i = closeIndex;
1771
+ } else {
1772
+ const tagData = readTagExp(xmlData, i, '>');
1446
1773
 
1447
- if (tagData) {
1448
- const openTagName = tagData && tagData.tagName;
1449
- if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length-1] !== "/") {
1450
- openTagCount++;
1451
- }
1452
- i=tagData.closeIndex;
1774
+ if (tagData) {
1775
+ const openTagName = tagData && tagData.tagName;
1776
+ if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length - 1] !== "/") {
1777
+ openTagCount++;
1453
1778
  }
1779
+ i = tagData.closeIndex;
1454
1780
  }
1455
1781
  }
1782
+ }
1456
1783
  }//end for loop
1457
1784
  }
1458
1785
 
@@ -1460,11 +1787,11 @@ function parseValue(val, shouldParse, options) {
1460
1787
  if (shouldParse && typeof val === 'string') {
1461
1788
  //console.log(options)
1462
1789
  const newval = val.trim();
1463
- if(newval === 'true' ) return true;
1464
- else if(newval === 'false' ) return false;
1790
+ if (newval === 'true') return true;
1791
+ else if (newval === 'false') return false;
1465
1792
  else return toNumber(val, options);
1466
1793
  } else {
1467
- if (util.isExist(val)) {
1794
+ if (isExist(val)) {
1468
1795
  return val;
1469
1796
  } else {
1470
1797
  return '';
@@ -1472,10 +1799,17 @@ function parseValue(val, shouldParse, options) {
1472
1799
  }
1473
1800
  }
1474
1801
 
1802
+ function fromCodePoint(str, base, prefix) {
1803
+ const codePoint = Number.parseInt(str, base);
1475
1804
 
1476
- var OrderedObjParser_1 = OrderedObjParser$1;
1805
+ if (codePoint >= 0 && codePoint <= 0x10FFFF) {
1806
+ return String.fromCodePoint(codePoint);
1807
+ } else {
1808
+ return prefix + str + ";";
1809
+ }
1810
+ }
1477
1811
 
1478
- var node2json = {};
1812
+ const METADATA_SYMBOL = XmlNode.getMetaDataSymbol();
1479
1813
 
1480
1814
  /**
1481
1815
  *
@@ -1483,7 +1817,7 @@ var node2json = {};
1483
1817
  * @param {any} options
1484
1818
  * @returns
1485
1819
  */
1486
- function prettify$1(node, options){
1820
+ function prettify(node, options){
1487
1821
  return compress( node, options);
1488
1822
  }
1489
1823
 
@@ -1513,6 +1847,9 @@ function compress(arr, options, jPath){
1513
1847
 
1514
1848
  let val = compress(tagObj[property], options, newJpath);
1515
1849
  const isLeaf = isLeafTag(val, options);
1850
+ if (tagObj[METADATA_SYMBOL] !== undefined) {
1851
+ val[METADATA_SYMBOL] = tagObj[METADATA_SYMBOL]; // copy over metadata
1852
+ }
1516
1853
 
1517
1854
  if(tagObj[":@"]){
1518
1855
  assignAttributes( val, tagObj[":@"], newJpath, options);
@@ -1587,14 +1924,8 @@ function isLeafTag(obj, options){
1587
1924
 
1588
1925
  return false;
1589
1926
  }
1590
- node2json.prettify = prettify$1;
1591
1927
 
1592
- const { buildOptions} = OptionsBuilder;
1593
- const OrderedObjParser = OrderedObjParser_1;
1594
- const { prettify} = node2json;
1595
- const validator$1 = validator$2;
1596
-
1597
- let XMLParser$1 = class XMLParser{
1928
+ class XMLParser{
1598
1929
 
1599
1930
  constructor(options){
1600
1931
  this.externalEntities = {};
@@ -1603,19 +1934,20 @@ let XMLParser$1 = class XMLParser{
1603
1934
  }
1604
1935
  /**
1605
1936
  * Parse XML dats to JS object
1606
- * @param {string|Buffer} xmlData
1937
+ * @param {string|Uint8Array} xmlData
1607
1938
  * @param {boolean|Object} validationOption
1608
1939
  */
1609
1940
  parse(xmlData,validationOption){
1610
- if(typeof xmlData === "string");else if( xmlData.toString){
1941
+ if(typeof xmlData !== "string" && xmlData.toString){
1611
1942
  xmlData = xmlData.toString();
1612
- }else {
1943
+ }else if(typeof xmlData !== "string"){
1613
1944
  throw new Error("XML data is accepted in String or Bytes[] form.")
1614
1945
  }
1946
+
1615
1947
  if( validationOption){
1616
1948
  if(validationOption === true) validationOption = {}; //validate with default options
1617
1949
 
1618
- const result = validator$1.validate(xmlData, validationOption);
1950
+ const result = validate(xmlData, validationOption);
1619
1951
  if (result !== true) {
1620
1952
  throw Error( `${result.err.msg}:${result.err.line}:${result.err.col}` )
1621
1953
  }
@@ -1643,9 +1975,21 @@ let XMLParser$1 = class XMLParser{
1643
1975
  this.externalEntities[key] = value;
1644
1976
  }
1645
1977
  }
1646
- };
1647
1978
 
1648
- var XMLParser_1 = XMLParser$1;
1979
+ /**
1980
+ * Returns a Symbol that can be used to access the metadata
1981
+ * property on a node.
1982
+ *
1983
+ * If Symbol is not available in the environment, an ordinary property is used
1984
+ * and the name of the property is here returned.
1985
+ *
1986
+ * The XMLMetaData property is only present when `captureMetaData`
1987
+ * is true in the options.
1988
+ */
1989
+ static getMetaDataSymbol() {
1990
+ return XmlNode.getMetaDataSymbol();
1991
+ }
1992
+ }
1649
1993
 
1650
1994
  const EOL = "\n";
1651
1995
 
@@ -1781,11 +2125,6 @@ function replaceEntitiesValue(textValue, options) {
1781
2125
  }
1782
2126
  return textValue;
1783
2127
  }
1784
- var orderedJs2Xml = toXml;
1785
-
1786
- //parse Empty Node as self closing node
1787
- const buildFromOrderedJs = orderedJs2Xml;
1788
- const getIgnoreAttributesFn = ignoreAttributes;
1789
2128
 
1790
2129
  const defaultOptions = {
1791
2130
  attributeNamePrefix: '@_',
@@ -1850,7 +2189,7 @@ function Builder(options) {
1850
2189
 
1851
2190
  Builder.prototype.build = function(jObj) {
1852
2191
  if(this.options.preserveOrder){
1853
- return buildFromOrderedJs(jObj, this.options);
2192
+ return toXml(jObj, this.options);
1854
2193
  }else {
1855
2194
  if(Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1){
1856
2195
  jObj = {
@@ -1876,6 +2215,8 @@ Builder.prototype.j2x = function(jObj, level, ajPath) {
1876
2215
  // null attribute should be ignored by the attribute list, but should not cause the tag closing
1877
2216
  if (this.isAttribute(key)) {
1878
2217
  val += '';
2218
+ } else if (key === this.options.cdataPropName) {
2219
+ val += '';
1879
2220
  } else if (key[0] === '?') {
1880
2221
  val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
1881
2222
  } else {
@@ -2051,17 +2392,54 @@ function isAttribute(name /*, options*/) {
2051
2392
  }
2052
2393
  }
2053
2394
 
2054
- var json2xml = Builder;
2055
-
2056
- const validator = validator$2;
2057
- const XMLParser = XMLParser_1;
2058
- const XMLBuilder = json2xml;
2059
-
2060
- var fxp = {
2061
- XMLParser: XMLParser,
2062
- XMLValidator: validator,
2063
- XMLBuilder: XMLBuilder
2395
+ const ISO20022Messages = {
2396
+ CAMT_003: 'CAMT.003',
2397
+ CAMT_004: 'CAMT.004',
2398
+ CAMT_005: 'CAMT.005',
2399
+ CAMT_006: 'CAMT.006',
2400
+ CAMT_053: 'CAMT.053',
2401
+ PAIN_001: 'PAIN.001',
2402
+ PAIN_002: 'PAIN.002',
2064
2403
  };
2404
+ const ISO20022Implementations = new Map();
2405
+ function registerISO20022Implementation(cl) {
2406
+ cl.supportedMessages().forEach(msg => {
2407
+ ISO20022Implementations.set(msg, cl);
2408
+ });
2409
+ }
2410
+ function getISO20022Implementation(type) {
2411
+ return ISO20022Implementations.get(type);
2412
+ }
2413
+ class XML {
2414
+ /**
2415
+ * Creates and configures the XML Parser
2416
+ *
2417
+ * @returns {XMLParser} A configured instance of XMLParser
2418
+ */
2419
+ static getParser() {
2420
+ return new XMLParser({
2421
+ ignoreAttributes: false,
2422
+ attributeNamePrefix: '@_',
2423
+ textNodeName: '#text',
2424
+ /**
2425
+ * Disable automatic numeric parsing. ISO 20022 fields are semantically
2426
+ * strings (Max35Text, etc.). Numeric-looking values like AcctSvcrRef,
2427
+ * EndToEndId, NtryRef, and Cd must stay as strings to preserve leading
2428
+ * zeros and avoid precision loss on large numbers. Amounts are explicitly
2429
+ * converted to numbers downstream via parseAmountToMinorUnits.
2430
+ */
2431
+ parseTagValue: false,
2432
+ });
2433
+ }
2434
+ static getBuilder() {
2435
+ return new Builder({
2436
+ ignoreAttributes: false,
2437
+ attributeNamePrefix: '@_',
2438
+ textNodeName: '#text',
2439
+ format: true,
2440
+ });
2441
+ }
2442
+ }
2065
2443
 
2066
2444
  /**
2067
2445
  * Base error class for all ISO 20022 related errors in the library.
@@ -7407,7 +7785,7 @@ class PaymentInitiation {
7407
7785
  return this.serialize();
7408
7786
  }
7409
7787
  static getBuilder() {
7410
- return new fxp.XMLBuilder({
7788
+ return new Builder({
7411
7789
  ignoreAttributes: false,
7412
7790
  attributeNamePrefix: '@',
7413
7791
  textNodeName: '#',
@@ -7515,7 +7893,7 @@ class SWIFTCreditPaymentInitiation extends PaymentInitiation {
7515
7893
  * @returns {string} The XML representation of the payment initiation.
7516
7894
  */
7517
7895
  static fromXML(rawXml) {
7518
- const parser = new fxp.XMLParser({ ignoreAttributes: false });
7896
+ const parser = XML.getParser();
7519
7897
  const xml = parser.parse(rawXml);
7520
7898
  if (!xml.Document) {
7521
7899
  throw new InvalidXmlError('Invalid XML format');
@@ -7810,7 +8188,7 @@ class SEPACreditPaymentInitiation extends PaymentInitiation {
7810
8188
  return builder.build(xml);
7811
8189
  }
7812
8190
  static fromXML(rawXml) {
7813
- const parser = new fxp.XMLParser({ ignoreAttributes: false });
8191
+ const parser = XML.getParser();
7814
8192
  const xml = parser.parse(rawXml);
7815
8193
  if (!xml.Document) {
7816
8194
  throw new InvalidXmlError('Invalid XML format');
@@ -8123,7 +8501,7 @@ class SEPAMultiCreditPaymentInitiation extends PaymentInitiation {
8123
8501
  * @throws {InvalidXmlNamespaceError} If the namespace is not pain.001.001.03.
8124
8502
  */
8125
8503
  static fromXML(rawXml) {
8126
- const parser = new fxp.XMLParser({ ignoreAttributes: false });
8504
+ const parser = XML.getParser();
8127
8505
  const xml = parser.parse(rawXml);
8128
8506
  // Validate XML structure
8129
8507
  if (!xml.Document) {
@@ -8402,7 +8780,7 @@ class RTPCreditPaymentInitiation extends PaymentInitiation {
8402
8780
  return builder.build(xml);
8403
8781
  }
8404
8782
  static fromXML(rawXml) {
8405
- const parser = new fxp.XMLParser({ ignoreAttributes: false });
8783
+ const parser = XML.getParser();
8406
8784
  const xml = parser.parse(rawXml);
8407
8785
  if (!xml.Document) {
8408
8786
  throw new InvalidXmlError('Invalid XML format');
@@ -8739,11 +9117,7 @@ class ACHCreditPaymentInitiation extends PaymentInitiation {
8739
9117
  * @throws {Error} If multiple payment information blocks are found.
8740
9118
  */
8741
9119
  static fromXML(rawXml) {
8742
- const parser = new fxp.XMLParser({
8743
- ignoreAttributes: false,
8744
- attributeNamePrefix: '@_',
8745
- textNodeName: '#text',
8746
- });
9120
+ const parser = XML.getParser();
8747
9121
  const xml = parser.parse(rawXml);
8748
9122
  if (!xml.Document) {
8749
9123
  throw new InvalidXmlError('Invalid XML format');
@@ -9105,7 +9479,7 @@ class SEPADirectDebitPaymentInitiation extends PaymentInitiation {
9105
9479
  * @throws {InvalidXmlNamespaceError} If the namespace is not pain.008.
9106
9480
  */
9107
9481
  static fromXML(rawXml) {
9108
- const parser = new fxp.XMLParser({ ignoreAttributes: false });
9482
+ const parser = XML.getParser();
9109
9483
  const xml = parser.parse(rawXml);
9110
9484
  // Validate XML structure
9111
9485
  if (!xml.Document) {
@@ -9229,59 +9603,6 @@ class SEPADirectDebitPaymentInitiation extends PaymentInitiation {
9229
9603
  }
9230
9604
  }
9231
9605
 
9232
- const ISO20022Messages = {
9233
- CAMT_003: 'CAMT.003',
9234
- CAMT_004: 'CAMT.004',
9235
- CAMT_005: 'CAMT.005',
9236
- CAMT_006: 'CAMT.006',
9237
- CAMT_053: 'CAMT.053',
9238
- PAIN_001: 'PAIN.001',
9239
- PAIN_002: 'PAIN.002',
9240
- };
9241
- const ISO20022Implementations = new Map();
9242
- function registerISO20022Implementation(cl) {
9243
- cl.supportedMessages().forEach(msg => {
9244
- ISO20022Implementations.set(msg, cl);
9245
- });
9246
- }
9247
- function getISO20022Implementation(type) {
9248
- return ISO20022Implementations.get(type);
9249
- }
9250
- class XML {
9251
- /**
9252
- * Creates and configures the XML Parser
9253
- *
9254
- * @returns {XMLParser} A configured instance of XMLParser
9255
- */
9256
- static getParser() {
9257
- return new fxp.XMLParser({
9258
- ignoreAttributes: false,
9259
- attributeNamePrefix: '@_',
9260
- textNodeName: '#text',
9261
- tagValueProcessor: (tagName, tagValue, _jPath, _hasAttributes, isLeafNode) => {
9262
- /**
9263
- * Codes and Entry References can look like numbers and get parsed
9264
- * appropriately. We don't want this to happen, as they contain leading
9265
- * zeros or are too long and overflow.
9266
- *
9267
- * Ex. <Cd>0001234<Cd> Should resolve to "0001234"
9268
- */
9269
- if (isLeafNode && ['Cd', 'NtryRef'].includes(tagName))
9270
- return undefined;
9271
- return tagValue;
9272
- },
9273
- });
9274
- }
9275
- static getBuilder() {
9276
- return new fxp.XMLBuilder({
9277
- ignoreAttributes: false,
9278
- attributeNamePrefix: '@_',
9279
- textNodeName: '#text',
9280
- format: true,
9281
- });
9282
- }
9283
- }
9284
-
9285
9606
  class CashManagementGetAccount {
9286
9607
  _data;
9287
9608
  constructor(data) {
@@ -9463,8 +9784,8 @@ registerISO20022Implementation(CashManagementGetAccount);
9463
9784
 
9464
9785
  const parseStatement = (stmt) => {
9465
9786
  const id = stmt.Id.toString();
9466
- const electronicSequenceNumber = stmt.ElctrncSeqNb;
9467
- const legalSequenceNumber = stmt.LglSeqNb;
9787
+ const electronicSequenceNumber = stmt.ElctrncSeqNb ? Number(stmt.ElctrncSeqNb) : undefined;
9788
+ const legalSequenceNumber = stmt.LglSeqNb ? Number(stmt.LglSeqNb) : undefined;
9468
9789
  const creationDate = new Date(stmt.CreDtTm);
9469
9790
  let fromDate;
9470
9791
  let toDate;
@@ -9473,18 +9794,18 @@ const parseStatement = (stmt) => {
9473
9794
  toDate = new Date(stmt.FrToDt.ToDtTm);
9474
9795
  }
9475
9796
  // Txn Summaries
9476
- const numOfEntries = stmt.TxsSummry?.TtlNtries.NbOfNtries;
9477
- const sumOfEntries = stmt.TxsSummry?.TtlNtries.Sum;
9797
+ const numOfEntries = stmt.TxsSummry?.TtlNtries.NbOfNtries != null ? Number(stmt.TxsSummry.TtlNtries.NbOfNtries) : undefined;
9798
+ const sumOfEntries = stmt.TxsSummry?.TtlNtries.Sum != null ? Number(stmt.TxsSummry.TtlNtries.Sum) : undefined;
9478
9799
  const rawNetAmountOfEntries = stmt.TxsSummry?.TtlNtries.TtlNetNtryAmt;
9479
9800
  let netAmountOfEntries;
9480
9801
  // No currency information, default to USD
9481
9802
  if (rawNetAmountOfEntries) {
9482
9803
  netAmountOfEntries = parseAmountToMinorUnits(rawNetAmountOfEntries);
9483
9804
  }
9484
- const numOfCreditEntries = stmt.TxsSummry?.TtlCdtNtries.NbOfNtries;
9485
- const sumOfCreditEntries = stmt.TxsSummry?.TtlCdtNtries.Sum;
9486
- const numOfDebitEntries = stmt.TxsSummry?.TtlDbtNtries.NbOfNtries;
9487
- const sumOfDebitEntries = stmt.TxsSummry?.TtlDbtNtries.Sum;
9805
+ const numOfCreditEntries = stmt.TxsSummry?.TtlCdtNtries.NbOfNtries != null ? Number(stmt.TxsSummry.TtlCdtNtries.NbOfNtries) : undefined;
9806
+ const sumOfCreditEntries = stmt.TxsSummry?.TtlCdtNtries.Sum != null ? Number(stmt.TxsSummry.TtlCdtNtries.Sum) : undefined;
9807
+ const numOfDebitEntries = stmt.TxsSummry?.TtlDbtNtries.NbOfNtries != null ? Number(stmt.TxsSummry.TtlDbtNtries.NbOfNtries) : undefined;
9808
+ const sumOfDebitEntries = stmt.TxsSummry?.TtlDbtNtries.Sum != null ? Number(stmt.TxsSummry.TtlDbtNtries.Sum) : undefined;
9488
9809
  // Get account information
9489
9810
  // TODO: Save account types here
9490
9811
  const account = parseAccount(stmt.Acct);
@@ -9629,7 +9950,7 @@ const parseEntry = (entry) => {
9629
9950
  const referenceId = entry.NtryRef;
9630
9951
  const creditDebitIndicator = entry.CdtDbtInd === 'CRDT' ? 'credit' : 'debit';
9631
9952
  const bookingDate = parseDate(entry.BookgDt);
9632
- const reversal = entry.RvslInd === true;
9953
+ const reversal = entry.RvslInd === true || entry.RvslInd === 'true';
9633
9954
  const rawAmount = entry.Amt['#text'];
9634
9955
  const currency = entry.Amt['@_Ccy'];
9635
9956
  const amount = parseAmountToMinorUnits(rawAmount, currency);
@@ -10792,7 +11113,7 @@ class PaymentStatusReport {
10792
11113
  * @returns {PaymentStatusReport} A new PaymentStatusReport instance.
10793
11114
  */
10794
11115
  static fromXML(rawXml) {
10795
- const parser = new fxp.XMLParser({ ignoreAttributes: false });
11116
+ const parser = XML.getParser();
10796
11117
  const xml = parser.parse(rawXml);
10797
11118
  const customerPaymentStatusReport = xml.Document.CstmrPmtStsRpt;
10798
11119
  const rawCreationDate = customerPaymentStatusReport.GrpHdr.CreDtTm;