fast-xml-parser 5.5.8 → 5.5.10

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.8",
3
+ "version": "5.5.10",
4
4
  "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
5
5
  "main": "./lib/fxp.cjs",
6
6
  "type": "module",
@@ -88,7 +88,7 @@
88
88
  ],
89
89
  "dependencies": {
90
90
  "fast-xml-builder": "^1.1.4",
91
- "path-expression-matcher": "^1.2.0",
92
- "strnum": "^2.2.0"
91
+ "path-expression-matcher": "^1.2.1",
92
+ "strnum": "^2.2.2"
93
93
  }
94
94
  }
package/src/fxp.d.ts CHANGED
@@ -1,4 +1,144 @@
1
- import type { Expression, ReadonlyMatcher } from './pem';
1
+ /**
2
+ * Types copied from path-expression-matcher
3
+ * @version <version>
4
+ * @updated <date>
5
+ *
6
+ * Update this file when path-expression-matcher releases a new version.
7
+ * Source: https://github.com/NaturalIntelligence/path-expression-matcher
8
+ */
9
+
10
+ /**
11
+ * Options for creating an Expression
12
+ */
13
+ export interface ExpressionOptions {
14
+ /**
15
+ * Path separator character
16
+ * @default '.'
17
+ */
18
+ separator?: string;
19
+ }
20
+
21
+ /**
22
+ * Parsed segment from an expression pattern
23
+ */
24
+ export interface Segment {
25
+ type: 'tag' | 'deep-wildcard';
26
+ tag?: string;
27
+ namespace?: string;
28
+ attrName?: string;
29
+ attrValue?: string;
30
+ position?: 'first' | 'last' | 'odd' | 'even' | 'nth';
31
+ positionValue?: number;
32
+ }
33
+
34
+ /**
35
+ * Expression - Parses and stores a tag pattern expression.
36
+ * Patterns are parsed once and stored in an optimized structure for fast matching.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const expr = new Expression("root.users.user");
41
+ * const expr2 = new Expression("..user[id]:first");
42
+ * const expr3 = new Expression("root/users/user", { separator: '/' });
43
+ * ```
44
+ *
45
+ * Pattern Syntax:
46
+ * - `root.users.user` — Match exact path
47
+ * - `..user` — Match "user" at any depth (deep wildcard)
48
+ * - `user[id]` — Match user tag with "id" attribute
49
+ * - `user[id=123]` — Match user tag where id="123"
50
+ * - `user:first` — Match first occurrence of user tag
51
+ * - `ns::user` — Match user tag with namespace "ns"
52
+ * - `ns::user[id]:first` — Combine namespace, attribute, and position
53
+ */
54
+ export class Expression {
55
+ readonly pattern: string;
56
+ readonly separator: string;
57
+ readonly segments: Segment[];
58
+
59
+ constructor(pattern: string, options?: ExpressionOptions);
60
+
61
+ get length(): number;
62
+ hasDeepWildcard(): boolean;
63
+ hasAttributeCondition(): boolean;
64
+ hasPositionSelector(): boolean;
65
+ toString(): string;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // ReadonlyMatcher
70
+ // ---------------------------------------------------------------------------
71
+
72
+ /**
73
+ * A live read-only view of a Matcher instance, returned by Matcher.readOnly.
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.
79
+ *
80
+ * This is the type received by all FXP user callbacks when `jPath: false`.
81
+ */
82
+ export interface ReadonlyMatcher {
83
+ readonly separator: string;
84
+
85
+ /** Check if current path matches an Expression. */
86
+ matches(expression: Expression): boolean;
87
+
88
+ /** Get current tag name, or `undefined` if path is empty. */
89
+ getCurrentTag(): string | undefined;
90
+
91
+ /** Get current namespace, or `undefined` if not present. */
92
+ getCurrentNamespace(): string | undefined;
93
+
94
+ /** Get attribute value of the current node. */
95
+ getAttrValue(attrName: string): any;
96
+
97
+ /** Check if the current node has a given attribute. */
98
+ hasAttr(attrName: string): boolean;
99
+
100
+ /** Sibling position of the current node (child index in parent). */
101
+ getPosition(): number;
102
+
103
+ /** Occurrence counter of the current tag name at this level. */
104
+ getCounter(): number;
105
+
106
+ /** Number of nodes in the current path. */
107
+ getDepth(): number;
108
+
109
+ /** Current path as a string (e.g. `"root.users.user"`). */
110
+ toString(separator?: string, includeNamespace?: boolean): string;
111
+
112
+ /** Current path as an array of tag names. */
113
+ 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
+ }
121
+
122
+ /** Internal node structure — exposed via snapshot only. */
123
+ export interface PathNode {
124
+ tag: string;
125
+ namespace?: string;
126
+ position: number;
127
+ counter: number;
128
+ values?: Record<string, any>;
129
+ }
130
+
131
+ /** Snapshot of matcher state returned by `snapshot()` and `readOnly().snapshot()`. */
132
+ export interface MatcherSnapshot {
133
+ path: PathNode[];
134
+ siblingStacks: Map<string, number>[];
135
+ }
136
+
137
+ /**********************************************************************
138
+ *
139
+ * END of path-expression-matcher relevant typings
140
+ *
141
+ **********************************************************************/
2
142
 
3
143
  // jPath: true → string
4
144
  // jPath: false → ReadonlyMatcher
@@ -105,10 +105,10 @@ function normalizeProcessEntities(value) {
105
105
  return {
106
106
  enabled: value.enabled !== false,
107
107
  maxEntitySize: Math.max(1, value.maxEntitySize ?? 10000),
108
- maxExpansionDepth: Math.max(1, value.maxExpansionDepth ?? 10),
109
- maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? 1000),
108
+ maxExpansionDepth: Math.max(1, value.maxExpansionDepth ?? 10000),
109
+ maxTotalExpansions: Math.max(1, value.maxTotalExpansions ?? Infinity),
110
110
  maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000),
111
- maxEntityCount: Math.max(1, value.maxEntityCount ?? 100),
111
+ maxEntityCount: Math.max(1, value.maxEntityCount ?? 1000),
112
112
  allowedTags: value.allowedTags ?? null,
113
113
  tagFilter: value.tagFilter ?? null
114
114
  };
@@ -217,89 +217,80 @@ function buildAttributesMap(attrStr, jPath, tagName) {
217
217
  const len = matches.length; //don't make it inline
218
218
  const attrs = {};
219
219
 
220
- // First pass: parse all attributes and update matcher with raw values
221
- // This ensures the matcher has all attribute values when processors run
220
+ // Pre-process values once: trim + entity replacement
221
+ // Reused in both matcher update and second pass
222
+ const processedVals = new Array(len);
223
+ let hasRawAttrs = false;
222
224
  const rawAttrsForMatcher = {};
225
+
223
226
  for (let i = 0; i < len; i++) {
224
227
  const attrName = this.resolveNameSpace(matches[i][1]);
225
228
  const oldVal = matches[i][4];
226
229
 
227
230
  if (attrName.length && oldVal !== undefined) {
228
- let parsedVal = oldVal;
229
- if (this.options.trimValues) {
230
- parsedVal = parsedVal.trim();
231
- }
232
- parsedVal = this.replaceEntitiesValue(parsedVal, tagName, this.readonlyMatcher);
233
- rawAttrsForMatcher[attrName] = parsedVal;
231
+ let val = oldVal;
232
+ if (this.options.trimValues) val = val.trim();
233
+ val = this.replaceEntitiesValue(val, tagName, this.readonlyMatcher);
234
+ processedVals[i] = val;
235
+
236
+ rawAttrsForMatcher[attrName] = val;
237
+ hasRawAttrs = true;
234
238
  }
235
239
  }
236
240
 
237
- // Update matcher with raw attribute values BEFORE running processors
238
- if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) {
241
+ // Update matcher ONCE before second pass, if applicable
242
+ if (hasRawAttrs && typeof jPath === 'object' && jPath.updateCurrent) {
239
243
  jPath.updateCurrent(rawAttrsForMatcher);
240
244
  }
241
245
 
242
- // Second pass: now process attributes with matcher having full attribute context
246
+ // Hoist toString() once path doesn't change during attribute processing
247
+ const jPathStr = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
248
+
249
+ // Second pass: apply processors, build final attrs
250
+ let hasAttrs = false;
243
251
  for (let i = 0; i < len; i++) {
244
252
  const attrName = this.resolveNameSpace(matches[i][1]);
245
253
 
246
- // Convert jPath to string if needed for ignoreAttributesFn
247
- const jPathStr = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
248
- if (this.ignoreAttributesFn(attrName, jPathStr)) {
249
- continue
250
- }
254
+ if (this.ignoreAttributesFn(attrName, jPathStr)) continue;
251
255
 
252
- let oldVal = matches[i][4];
253
256
  let aName = this.options.attributeNamePrefix + attrName;
254
257
 
255
258
  if (attrName.length) {
256
259
  if (this.options.transformAttributeName) {
257
260
  aName = this.options.transformAttributeName(aName);
258
261
  }
259
- //if (aName === "__proto__") aName = "#__proto__";
260
262
  aName = sanitizeName(aName, this.options);
261
263
 
262
- if (oldVal !== undefined) {
263
- if (this.options.trimValues) {
264
- oldVal = oldVal.trim();
265
- }
266
- oldVal = this.replaceEntitiesValue(oldVal, tagName, this.readonlyMatcher);
264
+ if (matches[i][4] !== undefined) {
265
+ // Reuse already-processed value — no double entity replacement
266
+ const oldVal = processedVals[i];
267
267
 
268
- // Pass jPath string or readonlyMatcher based on options.jPath setting
269
- const jPathOrMatcher = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
270
- const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher);
268
+ const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathStr);
271
269
  if (newVal === null || newVal === undefined) {
272
- //don't parse
273
270
  attrs[aName] = oldVal;
274
271
  } else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
275
- //overwrite
276
272
  attrs[aName] = newVal;
277
273
  } else {
278
- //parse
279
- attrs[aName] = parseValue(
280
- oldVal,
281
- this.options.parseAttributeValue,
282
- this.options.numberParseOptions
283
- );
274
+ attrs[aName] = parseValue(oldVal, this.options.parseAttributeValue, this.options.numberParseOptions);
284
275
  }
276
+ hasAttrs = true;
285
277
  } else if (this.options.allowBooleanAttributes) {
286
278
  attrs[aName] = true;
279
+ hasAttrs = true;
287
280
  }
288
281
  }
289
282
  }
290
283
 
291
- if (!Object.keys(attrs).length) {
292
- return;
293
- }
284
+ if (!hasAttrs) return;
285
+
294
286
  if (this.options.attributesGroupName) {
295
287
  const attrCollection = {};
296
288
  attrCollection[this.options.attributesGroupName] = attrs;
297
289
  return attrCollection;
298
290
  }
299
- return attrs
291
+ return attrs;
300
292
  }
301
293
  }
302
-
303
294
  const parseXml = function (xmlData) {
304
295
  xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
305
296
  const xmlObj = new xmlNode('!xml');
@@ -659,6 +650,7 @@ function replaceEntitiesValue(val, tagName, jPath) {
659
650
  }
660
651
  }
661
652
  }
653
+ if (val.indexOf('&') === -1) return val;
662
654
  // Replace standard entities
663
655
  for (const entityName of Object.keys(this.lastEntities)) {
664
656
  const entity = this.lastEntities[entityName];
package/lib/pem.d.cts DELETED
@@ -1,148 +0,0 @@
1
- /**
2
- * Types copied from path-expression-matcher
3
- * @version <version>
4
- * @updated <date>
5
- *
6
- * Update this file when path-expression-matcher releases a new version.
7
- * Source: https://github.com/NaturalIntelligence/path-expression-matcher
8
- */
9
-
10
- /**
11
- * Options for creating an Expression
12
- */
13
- interface ExpressionOptions {
14
- /**
15
- * Path separator character
16
- * @default '.'
17
- */
18
- separator?: string;
19
- }
20
-
21
- /**
22
- * Parsed segment from an expression pattern
23
- */
24
- interface Segment {
25
- type: 'tag' | 'deep-wildcard';
26
- tag?: string;
27
- namespace?: string;
28
- attrName?: string;
29
- attrValue?: string;
30
- position?: 'first' | 'last' | 'odd' | 'even' | 'nth';
31
- positionValue?: number;
32
- }
33
-
34
- /**
35
- * Expression - Parses and stores a tag pattern expression.
36
- * Patterns are parsed once and stored in an optimized structure for fast matching.
37
- *
38
- * @example
39
- * ```typescript
40
- * const expr = new Expression("root.users.user");
41
- * const expr2 = new Expression("..user[id]:first");
42
- * const expr3 = new Expression("root/users/user", { separator: '/' });
43
- * ```
44
- *
45
- * Pattern Syntax:
46
- * - `root.users.user` — Match exact path
47
- * - `..user` — Match "user" at any depth (deep wildcard)
48
- * - `user[id]` — Match user tag with "id" attribute
49
- * - `user[id=123]` — Match user tag where id="123"
50
- * - `user:first` — Match first occurrence of user tag
51
- * - `ns::user` — Match user tag with namespace "ns"
52
- * - `ns::user[id]:first` — Combine namespace, attribute, and position
53
- */
54
- declare class Expression {
55
- readonly pattern: string;
56
- readonly separator: string;
57
- readonly segments: Segment[];
58
-
59
- constructor(pattern: string, options?: ExpressionOptions);
60
-
61
- get length(): number;
62
- hasDeepWildcard(): boolean;
63
- hasAttributeCondition(): boolean;
64
- hasPositionSelector(): boolean;
65
- toString(): string;
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // ReadonlyMatcher
70
- // ---------------------------------------------------------------------------
71
-
72
- /**
73
- * A live read-only view of a Matcher instance, returned by Matcher.readOnly().
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.
79
- *
80
- * This is the type received by all FXP user callbacks when `jPath: false`.
81
- */
82
- interface ReadonlyMatcher {
83
- readonly separator: string;
84
-
85
- /** Check if current path matches an Expression. */
86
- matches(expression: Expression): boolean;
87
-
88
- /** Get current tag name, or `undefined` if path is empty. */
89
- getCurrentTag(): string | undefined;
90
-
91
- /** Get current namespace, or `undefined` if not present. */
92
- getCurrentNamespace(): string | undefined;
93
-
94
- /** Get attribute value of the current node. */
95
- getAttrValue(attrName: string): any;
96
-
97
- /** Check if the current node has a given attribute. */
98
- hasAttr(attrName: string): boolean;
99
-
100
- /** Sibling position of the current node (child index in parent). */
101
- getPosition(): number;
102
-
103
- /** Occurrence counter of the current tag name at this level. */
104
- getCounter(): number;
105
-
106
- /** Number of nodes in the current path. */
107
- getDepth(): number;
108
-
109
- /** Current path as a string (e.g. `"root.users.user"`). */
110
- toString(separator?: string, includeNamespace?: boolean): string;
111
-
112
- /** Current path as an array of tag names. */
113
- 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
- }
121
-
122
- /** Internal node structure — exposed via snapshot only. */
123
- interface PathNode {
124
- tag: string;
125
- namespace?: string;
126
- position: number;
127
- counter: number;
128
- values?: Record<string, any>;
129
- }
130
-
131
- /** Snapshot of matcher state returned by `snapshot()` and `readOnly().snapshot()`. */
132
- interface MatcherSnapshot {
133
- path: PathNode[];
134
- siblingStacks: Map<string, number>[];
135
- }
136
-
137
- declare namespace pem {
138
- export {
139
- Expression,
140
- ExpressionOptions,
141
- Segment,
142
- ReadonlyMatcher,
143
- PathNode,
144
- MatcherSnapshot,
145
- }
146
- }
147
-
148
- export = pem;
package/src/pem.d.ts DELETED
@@ -1,135 +0,0 @@
1
- /**
2
- * Types copied from path-expression-matcher
3
- * @version <version>
4
- * @updated <date>
5
- *
6
- * Update this file when path-expression-matcher releases a new version.
7
- * Source: https://github.com/NaturalIntelligence/path-expression-matcher
8
- */
9
-
10
- /**
11
- * Options for creating an Expression
12
- */
13
- export interface ExpressionOptions {
14
- /**
15
- * Path separator character
16
- * @default '.'
17
- */
18
- separator?: string;
19
- }
20
-
21
- /**
22
- * Parsed segment from an expression pattern
23
- */
24
- export interface Segment {
25
- type: 'tag' | 'deep-wildcard';
26
- tag?: string;
27
- namespace?: string;
28
- attrName?: string;
29
- attrValue?: string;
30
- position?: 'first' | 'last' | 'odd' | 'even' | 'nth';
31
- positionValue?: number;
32
- }
33
-
34
- /**
35
- * Expression - Parses and stores a tag pattern expression.
36
- * Patterns are parsed once and stored in an optimized structure for fast matching.
37
- *
38
- * @example
39
- * ```typescript
40
- * const expr = new Expression("root.users.user");
41
- * const expr2 = new Expression("..user[id]:first");
42
- * const expr3 = new Expression("root/users/user", { separator: '/' });
43
- * ```
44
- *
45
- * Pattern Syntax:
46
- * - `root.users.user` — Match exact path
47
- * - `..user` — Match "user" at any depth (deep wildcard)
48
- * - `user[id]` — Match user tag with "id" attribute
49
- * - `user[id=123]` — Match user tag where id="123"
50
- * - `user:first` — Match first occurrence of user tag
51
- * - `ns::user` — Match user tag with namespace "ns"
52
- * - `ns::user[id]:first` — Combine namespace, attribute, and position
53
- */
54
- export class Expression {
55
- readonly pattern: string;
56
- readonly separator: string;
57
- readonly segments: Segment[];
58
-
59
- constructor(pattern: string, options?: ExpressionOptions);
60
-
61
- get length(): number;
62
- hasDeepWildcard(): boolean;
63
- hasAttributeCondition(): boolean;
64
- hasPositionSelector(): boolean;
65
- toString(): string;
66
- }
67
-
68
- // ---------------------------------------------------------------------------
69
- // ReadonlyMatcher
70
- // ---------------------------------------------------------------------------
71
-
72
- /**
73
- * A live read-only view of a {@link Matcher} instance, returned by {@link Matcher.readOnly}.
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.
79
- *
80
- * This is the type received by all FXP user callbacks when `jPath: false`.
81
- */
82
- export interface ReadonlyMatcher {
83
- readonly separator: string;
84
-
85
- /** Check if current path matches an Expression. */
86
- matches(expression: Expression): boolean;
87
-
88
- /** Get current tag name, or `undefined` if path is empty. */
89
- getCurrentTag(): string | undefined;
90
-
91
- /** Get current namespace, or `undefined` if not present. */
92
- getCurrentNamespace(): string | undefined;
93
-
94
- /** Get attribute value of the current node. */
95
- getAttrValue(attrName: string): any;
96
-
97
- /** Check if the current node has a given attribute. */
98
- hasAttr(attrName: string): boolean;
99
-
100
- /** Sibling position of the current node (child index in parent). */
101
- getPosition(): number;
102
-
103
- /** Occurrence counter of the current tag name at this level. */
104
- getCounter(): number;
105
-
106
- /** Number of nodes in the current path. */
107
- getDepth(): number;
108
-
109
- /** Current path as a string (e.g. `"root.users.user"`). */
110
- toString(separator?: string, includeNamespace?: boolean): string;
111
-
112
- /** Current path as an array of tag names. */
113
- toArray(): string[];
114
-
115
- /**
116
- * Create a snapshot of the current state.
117
- * The snapshot can be passed to the real {@link Matcher.restore} if needed.
118
- */
119
- snapshot(): MatcherSnapshot;
120
- }
121
-
122
- /** Internal node structure — exposed via snapshot only. */
123
- export interface PathNode {
124
- tag: string;
125
- namespace?: string;
126
- position: number;
127
- counter: number;
128
- values?: Record<string, any>;
129
- }
130
-
131
- /** Snapshot of matcher state returned by `snapshot()` and `readOnly().snapshot()`. */
132
- export interface MatcherSnapshot {
133
- path: PathNode[];
134
- siblingStacks: Map<string, number>[];
135
- }