fast-xml-parser 5.5.11 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "5.5.11",
3
+ "version": "5.6.0",
4
4
  "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
5
5
  "main": "./lib/fxp.cjs",
6
6
  "type": "module",
@@ -87,8 +87,9 @@
87
87
  }
88
88
  ],
89
89
  "dependencies": {
90
+ "@nodable/entities": "^1.1.0",
90
91
  "fast-xml-builder": "^1.1.4",
91
- "path-expression-matcher": "^1.4.0",
92
+ "path-expression-matcher": "^1.5.0",
92
93
  "strnum": "^2.2.3"
93
94
  }
94
95
  }
Binary file
package/src/fxp.d.ts CHANGED
@@ -66,20 +66,23 @@ export class Expression {
66
66
  }
67
67
 
68
68
  // ---------------------------------------------------------------------------
69
- // ReadonlyMatcher
69
+ // MatcherView
70
70
  // ---------------------------------------------------------------------------
71
71
 
72
72
  /**
73
- * A live read-only view of a Matcher instance, returned by Matcher.readOnly.
73
+ * A lightweight, live read-only view of a Matcher instance.
74
74
  *
75
- * All query and inspection methods work normally and always reflect the current
76
- * state of the underlying matcher. State-mutating methods (`push`, `pop`,
77
- * `reset`, `updateCurrent`, `restore`) are not present calling them on the
78
- * underlying Proxy throws a `TypeError` at runtime.
75
+ * Returned by `Matcher.readOnly()`. The same instance is reused across every
76
+ * callback invocation no allocation overhead per call. Reads directly from
77
+ * the parent Matcher's internal state so it always reflects the current parser
78
+ * position with no copying or freezing.
79
+ *
80
+ * Mutation methods (`push`, `pop`, `reset`, `updateCurrent`, `restore`) are
81
+ * simply absent — misuse is caught at compile time by TypeScript.
79
82
  *
80
83
  * This is the type received by all FXP user callbacks when `jPath: false`.
81
84
  */
82
- export interface ReadonlyMatcher {
85
+ export class MatcherView {
83
86
  readonly separator: string;
84
87
 
85
88
  /** Check if current path matches an Expression. */
@@ -111,14 +114,14 @@ export interface ReadonlyMatcher {
111
114
 
112
115
  /** Current path as an array of tag names. */
113
116
  toArray(): string[];
114
-
115
- /**
116
- * Create a snapshot of the current state.
117
- * The snapshot can be passed to the real Matcher.restore if needed.
118
- */
119
- snapshot(): MatcherSnapshot;
120
117
  }
121
118
 
119
+ /**
120
+ * @deprecated Use {@link MatcherView} instead.
121
+ * Alias kept for backward compatibility.
122
+ */
123
+ export type ReadonlyMatcher = MatcherView;
124
+
122
125
  /** Internal node structure — exposed via snapshot only. */
123
126
  export interface PathNode {
124
127
  tag: string;
@@ -141,8 +144,8 @@ export interface MatcherSnapshot {
141
144
  **********************************************************************/
142
145
 
143
146
  // jPath: true → string
144
- // jPath: false → ReadonlyMatcher
145
- type JPathOrMatcher = string | ReadonlyMatcher;
147
+ // jPath: false → MatcherView
148
+ type JPathOrMatcher = string | MatcherView;
146
149
  type JPathOrExpression = string | Expression;
147
150
 
148
151
  export type ProcessEntitiesOptions = {
@@ -8,6 +8,7 @@ import toNumber from "strnum";
8
8
  import getIgnoreAttributesFn from "../ignoreAttributes.js";
9
9
  import { Expression, Matcher } from 'path-expression-matcher';
10
10
  import { ExpressionSet } from 'path-expression-matcher';
11
+ import EntityReplacer, { COMMON_HTML, NUMERIC_ENTITIES, CURRENCY_ENTITIES } from '@nodable/entities';
11
12
 
12
13
  // const regx =
13
14
  // '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
@@ -72,32 +73,6 @@ export default class OrderedObjParser {
72
73
  this.options = options;
73
74
  this.currentNode = null;
74
75
  this.tagsNodeStack = [];
75
- this.docTypeEntities = {};
76
- this.lastEntities = {
77
- "apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
78
- "gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
79
- "lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
80
- "quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
81
- };
82
- this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
83
- this.htmlEntities = {
84
- "space": { regex: /&(nbsp|#160);/g, val: " " },
85
- // "lt" : { regex: /&(lt|#60);/g, val: "<" },
86
- // "gt" : { regex: /&(gt|#62);/g, val: ">" },
87
- // "amp" : { regex: /&(amp|#38);/g, val: "&" },
88
- // "quot" : { regex: /&(quot|#34);/g, val: "\"" },
89
- // "apos" : { regex: /&(apos|#39);/g, val: "'" },
90
- "cent": { regex: /&(cent|#162);/g, val: "¢" },
91
- "pound": { regex: /&(pound|#163);/g, val: "£" },
92
- "yen": { regex: /&(yen|#165);/g, val: "¥" },
93
- "euro": { regex: /&(euro|#8364);/g, val: "€" },
94
- "copyright": { regex: /&(copy|#169);/g, val: "©" },
95
- "reg": { regex: /&(reg|#174);/g, val: "®" },
96
- "inr": { regex: /&(inr|#8377);/g, val: "₹" },
97
- "num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => fromCodePoint(str, 10, "&#") },
98
- "num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => fromCodePoint(str, 16, "&#x") },
99
- };
100
- this.addExternalEntities = addExternalEntities;
101
76
  this.parseXml = parseXml;
102
77
  this.parseTextData = parseTextData;
103
78
  this.resolveNameSpace = resolveNameSpace;
@@ -111,6 +86,16 @@ export default class OrderedObjParser {
111
86
  this.entityExpansionCount = 0;
112
87
  this.currentExpandedLength = 0;
113
88
 
89
+ this.entityReplacer = new EntityReplacer({
90
+ default: true,
91
+ // amp: true,
92
+ system: this.options.htmlEntities ? { ...COMMON_HTML, ...NUMERIC_ENTITIES, ...CURRENCY_ENTITIES } : {},
93
+ maxTotalExpansions: this.options.processEntities.maxTotalExpansions,
94
+ maxExpandedLength: this.options.processEntities.maxExpandedLength,
95
+ applyLimitsTo: "all",
96
+ //postCheck: resolved => resolved
97
+ });
98
+
114
99
  // Initialize path matcher for path-expression-matcher
115
100
  this.matcher = new Matcher();
116
101
 
@@ -141,17 +126,6 @@ export default class OrderedObjParser {
141
126
 
142
127
  }
143
128
 
144
- function addExternalEntities(externalEntities) {
145
- const entKeys = Object.keys(externalEntities);
146
- for (let i = 0; i < entKeys.length; i++) {
147
- const ent = entKeys[i];
148
- const escaped = ent.replace(/[.\-+*:]/g, '\\.');
149
- this.lastEntities[ent] = {
150
- regex: new RegExp("&" + escaped + ";", "g"),
151
- val: externalEntities[ent]
152
- }
153
- }
154
- }
155
129
 
156
130
  /**
157
131
  * @param {string} val
@@ -308,9 +282,6 @@ const parseXml = function (xmlData) {
308
282
  // Reset entity expansion counters for this document
309
283
  this.entityExpansionCount = 0;
310
284
  this.currentExpandedLength = 0;
311
- this.docTypeEntitiesKeys = [];
312
- this.lastEntitiesKeys = Object.keys(this.lastEntities);
313
- this.htmlEntitiesKeys = this.options.htmlEntities ? Object.keys(this.htmlEntities) : [];
314
285
  const options = this.options;
315
286
  const docTypeReader = new DocTypeReader(options.processEntities);
316
287
  const xmlLen = xmlData.length;
@@ -390,8 +361,7 @@ const parseXml = function (xmlData) {
390
361
  } else if (c1 === 33
391
362
  && xmlData.charCodeAt(i + 2) === 68) { //'!D'
392
363
  const result = docTypeReader.readDocType(xmlData, i);
393
- this.docTypeEntities = result.entities;
394
- this.docTypeEntitiesKeys = Object.keys(this.docTypeEntities) || []
364
+ this.entityReplacer.addInputEntities(result.entities);
395
365
  i = result.i;
396
366
  } else if (c1 === 33
397
367
  && xmlData.charCodeAt(i + 2) === 91) { // '!['
@@ -632,78 +602,7 @@ function replaceEntitiesValue(val, tagName, jPath) {
632
602
  }
633
603
  }
634
604
 
635
- // Replace DOCTYPE entities
636
- for (const entityName of this.docTypeEntitiesKeys) {
637
- const entity = this.docTypeEntities[entityName];
638
- const matches = val.match(entity.regx);
639
-
640
- if (matches) {
641
- // Track expansions
642
- this.entityExpansionCount += matches.length;
643
-
644
- // Check expansion limit
645
- if (entityConfig.maxTotalExpansions &&
646
- this.entityExpansionCount > entityConfig.maxTotalExpansions) {
647
- throw new Error(
648
- `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
649
- );
650
- }
651
-
652
- // Store length before replacement
653
- const lengthBefore = val.length;
654
- val = val.replace(entity.regx, entity.val);
655
-
656
- // Check expanded length immediately after replacement
657
- if (entityConfig.maxExpandedLength) {
658
- this.currentExpandedLength += (val.length - lengthBefore);
659
-
660
- if (this.currentExpandedLength > entityConfig.maxExpandedLength) {
661
- throw new Error(
662
- `Total expanded content size exceeded: ${this.currentExpandedLength} > ${entityConfig.maxExpandedLength}`
663
- );
664
- }
665
- }
666
- }
667
- }
668
- if (val.indexOf('&') === -1) return val;
669
- // Replace standard entities
670
- for (const entityName of this.lastEntitiesKeys) {
671
- const entity = this.lastEntities[entityName];
672
- const matches = val.match(entity.regex);
673
- if (matches) {
674
- this.entityExpansionCount += matches.length;
675
- if (entityConfig.maxTotalExpansions &&
676
- this.entityExpansionCount > entityConfig.maxTotalExpansions) {
677
- throw new Error(
678
- `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
679
- );
680
- }
681
- }
682
- val = val.replace(entity.regex, entity.val);
683
- }
684
- if (val.indexOf('&') === -1) return val;
685
-
686
- // Replace HTML entities if enabled
687
- for (const entityName of this.htmlEntitiesKeys) {
688
- const entity = this.htmlEntities[entityName];
689
- const matches = val.match(entity.regex);
690
- if (matches) {
691
- //console.log(matches);
692
- this.entityExpansionCount += matches.length;
693
- if (entityConfig.maxTotalExpansions &&
694
- this.entityExpansionCount > entityConfig.maxTotalExpansions) {
695
- throw new Error(
696
- `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
697
- );
698
- }
699
- }
700
- val = val.replace(entity.regex, entity.val);
701
- }
702
-
703
- // Replace ampersand entity last
704
- val = val.replace(this.ampEntity.regex, this.ampEntity.val);
705
-
706
- return val;
605
+ return this.entityReplacer.replace(val);
707
606
  }
708
607
 
709
608
 
@@ -32,7 +32,7 @@ export default class XMLParser {
32
32
  }
33
33
  }
34
34
  const orderedObjParser = new OrderedObjParser(this.options);
35
- orderedObjParser.addExternalEntities(this.externalEntities);
35
+ orderedObjParser.entityReplacer.setExternalEntities(this.externalEntities);
36
36
  const orderedResult = orderedObjParser.parseXml(xmlData);
37
37
  if (this.options.preserveOrder || orderedResult === undefined) return orderedResult;
38
38
  else return prettify(orderedResult, this.options, orderedObjParser.matcher, orderedObjParser.readonlyMatcher);