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