fast-xml-parser 5.5.7 → 5.5.8
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/CHANGELOG.md +3 -0
- package/lib/fxbuilder.min.js +1 -1
- package/lib/fxbuilder.min.js.map +1 -1
- package/lib/fxp.cjs +1 -1
- package/lib/fxp.d.cts +20 -12
- package/lib/fxp.min.js +1 -1
- package/lib/fxp.min.js.map +1 -1
- package/lib/fxparser.min.js +1 -1
- package/lib/fxparser.min.js.map +1 -1
- package/lib/pem.d.cts +148 -0
- package/package.json +2 -2
- package/src/fxp.d.ts +13 -11
- package/src/pem.d.ts +135 -0
- package/src/xmlparser/OrderedObjParser.js +20 -16
- package/src/xmlparser/XMLParser.js +2 -2
- package/src/xmlparser/node2json.js +10 -11
package/lib/pem.d.cts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-xml-parser",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.8",
|
|
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.
|
|
91
|
+
"path-expression-matcher": "^1.2.0",
|
|
92
92
|
"strnum": "^2.2.0"
|
|
93
93
|
}
|
|
94
94
|
}
|
package/src/fxp.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Expression, ReadonlyMatcher } from './pem';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// jPath: true → string
|
|
4
|
+
// jPath: false → ReadonlyMatcher
|
|
5
|
+
type JPathOrMatcher = string | ReadonlyMatcher;
|
|
6
|
+
type JPathOrExpression = string | Expression;
|
|
5
7
|
|
|
6
8
|
export type ProcessEntitiesOptions = {
|
|
7
9
|
/**
|
|
@@ -63,7 +65,7 @@ export type ProcessEntitiesOptions = {
|
|
|
63
65
|
*
|
|
64
66
|
* Defaults to `null`
|
|
65
67
|
*/
|
|
66
|
-
tagFilter?: ((tagName: string, jPathOrMatcher:
|
|
68
|
+
tagFilter?: ((tagName: string, jPathOrMatcher: JPathOrMatcher) => boolean) | null;
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
export type X2jOptions = {
|
|
@@ -108,7 +110,7 @@ export type X2jOptions = {
|
|
|
108
110
|
*
|
|
109
111
|
* Defaults to `true`
|
|
110
112
|
*/
|
|
111
|
-
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPathOrMatcher:
|
|
113
|
+
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPathOrMatcher: JPathOrMatcher) => boolean);
|
|
112
114
|
|
|
113
115
|
/**
|
|
114
116
|
* Whether to remove namespace string from tag and attribute names
|
|
@@ -175,7 +177,7 @@ export type X2jOptions = {
|
|
|
175
177
|
*
|
|
176
178
|
* Defaults to `(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode) => val`
|
|
177
179
|
*/
|
|
178
|
-
tagValueProcessor?: (tagName: string, tagValue: string, jPathOrMatcher:
|
|
180
|
+
tagValueProcessor?: (tagName: string, tagValue: string, jPathOrMatcher: JPathOrMatcher, hasAttributes: boolean, isLeafNode: boolean) => unknown;
|
|
179
181
|
|
|
180
182
|
/**
|
|
181
183
|
* Control how attribute value should be parsed
|
|
@@ -188,7 +190,7 @@ export type X2jOptions = {
|
|
|
188
190
|
*
|
|
189
191
|
* Defaults to `(attrName, val, jPathOrMatcher) => val`
|
|
190
192
|
*/
|
|
191
|
-
attributeValueProcessor?: (attrName: string, attrValue: string, jPathOrMatcher:
|
|
193
|
+
attributeValueProcessor?: (attrName: string, attrValue: string, jPathOrMatcher: JPathOrMatcher) => unknown;
|
|
192
194
|
|
|
193
195
|
/**
|
|
194
196
|
* Options to pass to `strnum` for parsing numbers
|
|
@@ -206,7 +208,7 @@ export type X2jOptions = {
|
|
|
206
208
|
*
|
|
207
209
|
* Defaults to `[]`
|
|
208
210
|
*/
|
|
209
|
-
stopNodes?:
|
|
211
|
+
stopNodes?: JPathOrExpression[];
|
|
210
212
|
|
|
211
213
|
/**
|
|
212
214
|
* List of tags without closing tags
|
|
@@ -233,7 +235,7 @@ export type X2jOptions = {
|
|
|
233
235
|
*
|
|
234
236
|
* Defaults to `() => false`
|
|
235
237
|
*/
|
|
236
|
-
isArray?: (tagName: string, jPathOrMatcher:
|
|
238
|
+
isArray?: (tagName: string, jPathOrMatcher: JPathOrMatcher, isLeafNode: boolean, isAttribute: boolean) => boolean;
|
|
237
239
|
|
|
238
240
|
/**
|
|
239
241
|
* Whether to process default and DOCTYPE entities
|
|
@@ -295,7 +297,7 @@ export type X2jOptions = {
|
|
|
295
297
|
*
|
|
296
298
|
* Defaults to `(tagName, jPathOrMatcher, attrs) => tagName`
|
|
297
299
|
*/
|
|
298
|
-
updateTag?: (tagName: string, jPathOrMatcher:
|
|
300
|
+
updateTag?: (tagName: string, jPathOrMatcher: JPathOrMatcher, attrs: { [k: string]: string }) => string | boolean;
|
|
299
301
|
|
|
300
302
|
/**
|
|
301
303
|
* If true, adds a Symbol to all object nodes, accessible by {@link XMLParser.getMetaDataSymbol} with
|
|
@@ -479,7 +481,7 @@ export type XmlBuilderOptions = {
|
|
|
479
481
|
*
|
|
480
482
|
* Defaults to `[]`
|
|
481
483
|
*/
|
|
482
|
-
stopNodes?:
|
|
484
|
+
stopNodes?: JPathOrExpression[];
|
|
483
485
|
|
|
484
486
|
/**
|
|
485
487
|
* Control how tag value should be parsed. Called only if tag value is not empty
|
package/src/pem.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
}
|
|
@@ -113,6 +113,10 @@ export default class OrderedObjParser {
|
|
|
113
113
|
// Initialize path matcher for path-expression-matcher
|
|
114
114
|
this.matcher = new Matcher();
|
|
115
115
|
|
|
116
|
+
// Live read-only proxy of matcher — PEM creates and caches this internally.
|
|
117
|
+
// All user callbacks receive this instead of the mutable matcher.
|
|
118
|
+
this.readonlyMatcher = this.matcher.readOnly();
|
|
119
|
+
|
|
116
120
|
// Flag to track if current node is a stop node (optimization)
|
|
117
121
|
this.isCurrentNodeStopNode = false;
|
|
118
122
|
|
|
@@ -225,7 +229,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
225
229
|
if (this.options.trimValues) {
|
|
226
230
|
parsedVal = parsedVal.trim();
|
|
227
231
|
}
|
|
228
|
-
parsedVal = this.replaceEntitiesValue(parsedVal, tagName,
|
|
232
|
+
parsedVal = this.replaceEntitiesValue(parsedVal, tagName, this.readonlyMatcher);
|
|
229
233
|
rawAttrsForMatcher[attrName] = parsedVal;
|
|
230
234
|
}
|
|
231
235
|
}
|
|
@@ -240,7 +244,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
240
244
|
const attrName = this.resolveNameSpace(matches[i][1]);
|
|
241
245
|
|
|
242
246
|
// Convert jPath to string if needed for ignoreAttributesFn
|
|
243
|
-
const jPathStr = this.options.jPath ? jPath.toString() :
|
|
247
|
+
const jPathStr = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
|
|
244
248
|
if (this.ignoreAttributesFn(attrName, jPathStr)) {
|
|
245
249
|
continue
|
|
246
250
|
}
|
|
@@ -259,10 +263,10 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
259
263
|
if (this.options.trimValues) {
|
|
260
264
|
oldVal = oldVal.trim();
|
|
261
265
|
}
|
|
262
|
-
oldVal = this.replaceEntitiesValue(oldVal, tagName,
|
|
266
|
+
oldVal = this.replaceEntitiesValue(oldVal, tagName, this.readonlyMatcher);
|
|
263
267
|
|
|
264
|
-
// Pass jPath string or
|
|
265
|
-
const jPathOrMatcher = this.options.jPath ? jPath.toString() :
|
|
268
|
+
// Pass jPath string or readonlyMatcher based on options.jPath setting
|
|
269
|
+
const jPathOrMatcher = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
|
|
266
270
|
const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher);
|
|
267
271
|
if (newVal === null || newVal === undefined) {
|
|
268
272
|
//don't parse
|
|
@@ -329,7 +333,7 @@ const parseXml = function (xmlData) {
|
|
|
329
333
|
tagName = transformTagName(this.options.transformTagName, tagName, "", this.options).tagName;
|
|
330
334
|
|
|
331
335
|
if (currentNode) {
|
|
332
|
-
textData = this.saveTextToParentTag(textData, currentNode, this.
|
|
336
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
333
337
|
}
|
|
334
338
|
|
|
335
339
|
//check if last tag of nested tag was unpaired tag
|
|
@@ -354,7 +358,7 @@ const parseXml = function (xmlData) {
|
|
|
354
358
|
let tagData = readTagExp(xmlData, i, false, "?>");
|
|
355
359
|
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
356
360
|
|
|
357
|
-
textData = this.saveTextToParentTag(textData, currentNode, this.
|
|
361
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
358
362
|
if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) {
|
|
359
363
|
//do nothing
|
|
360
364
|
} else {
|
|
@@ -365,7 +369,7 @@ const parseXml = function (xmlData) {
|
|
|
365
369
|
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
|
|
366
370
|
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName);
|
|
367
371
|
}
|
|
368
|
-
this.addChild(currentNode, childNode, this.
|
|
372
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, i);
|
|
369
373
|
}
|
|
370
374
|
|
|
371
375
|
|
|
@@ -375,7 +379,7 @@ const parseXml = function (xmlData) {
|
|
|
375
379
|
if (this.options.commentPropName) {
|
|
376
380
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
377
381
|
|
|
378
|
-
textData = this.saveTextToParentTag(textData, currentNode, this.
|
|
382
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
379
383
|
|
|
380
384
|
currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
|
|
381
385
|
}
|
|
@@ -388,9 +392,9 @@ const parseXml = function (xmlData) {
|
|
|
388
392
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
|
389
393
|
const tagExp = xmlData.substring(i + 9, closeIndex);
|
|
390
394
|
|
|
391
|
-
textData = this.saveTextToParentTag(textData, currentNode, this.
|
|
395
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
392
396
|
|
|
393
|
-
let val = this.parseTextData(tagExp, currentNode.tagname, this.
|
|
397
|
+
let val = this.parseTextData(tagExp, currentNode.tagname, this.readonlyMatcher, true, false, true, true);
|
|
394
398
|
if (val == undefined) val = "";
|
|
395
399
|
|
|
396
400
|
//cdata should be set even if it is 0 length string
|
|
@@ -432,7 +436,7 @@ const parseXml = function (xmlData) {
|
|
|
432
436
|
if (currentNode && textData) {
|
|
433
437
|
if (currentNode.tagname !== '!xml') {
|
|
434
438
|
//when nested tag is found
|
|
435
|
-
textData = this.saveTextToParentTag(textData, currentNode, this.
|
|
439
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher, false);
|
|
436
440
|
}
|
|
437
441
|
}
|
|
438
442
|
|
|
@@ -522,7 +526,7 @@ const parseXml = function (xmlData) {
|
|
|
522
526
|
this.matcher.pop(); // Pop the stop node tag
|
|
523
527
|
this.isCurrentNodeStopNode = false; // Reset flag
|
|
524
528
|
|
|
525
|
-
this.addChild(currentNode, childNode, this.
|
|
529
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
526
530
|
} else {
|
|
527
531
|
//selfClosing tag
|
|
528
532
|
if (isSelfClosing) {
|
|
@@ -532,7 +536,7 @@ const parseXml = function (xmlData) {
|
|
|
532
536
|
if (prefixedAttrs) {
|
|
533
537
|
childNode[":@"] = prefixedAttrs;
|
|
534
538
|
}
|
|
535
|
-
this.addChild(currentNode, childNode, this.
|
|
539
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
536
540
|
this.matcher.pop(); // Pop self-closing tag
|
|
537
541
|
this.isCurrentNodeStopNode = false; // Reset flag
|
|
538
542
|
}
|
|
@@ -541,7 +545,7 @@ const parseXml = function (xmlData) {
|
|
|
541
545
|
if (prefixedAttrs) {
|
|
542
546
|
childNode[":@"] = prefixedAttrs;
|
|
543
547
|
}
|
|
544
|
-
this.addChild(currentNode, childNode, this.
|
|
548
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
545
549
|
this.matcher.pop(); // Pop unpaired tag
|
|
546
550
|
this.isCurrentNodeStopNode = false; // Reset flag
|
|
547
551
|
i = result.closeIndex;
|
|
@@ -559,7 +563,7 @@ const parseXml = function (xmlData) {
|
|
|
559
563
|
if (prefixedAttrs) {
|
|
560
564
|
childNode[":@"] = prefixedAttrs;
|
|
561
565
|
}
|
|
562
|
-
this.addChild(currentNode, childNode, this.
|
|
566
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
563
567
|
currentNode = childNode;
|
|
564
568
|
}
|
|
565
569
|
textData = "";
|
|
@@ -35,7 +35,7 @@ export default class XMLParser {
|
|
|
35
35
|
orderedObjParser.addExternalEntities(this.externalEntities);
|
|
36
36
|
const orderedResult = orderedObjParser.parseXml(xmlData);
|
|
37
37
|
if (this.options.preserveOrder || orderedResult === undefined) return orderedResult;
|
|
38
|
-
else return prettify(orderedResult, this.options, orderedObjParser.matcher);
|
|
38
|
+
else return prettify(orderedResult, this.options, orderedObjParser.matcher, orderedObjParser.readonlyMatcher);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -68,4 +68,4 @@ export default class XMLParser {
|
|
|
68
68
|
static getMetaDataSymbol() {
|
|
69
69
|
return XmlNode.getMetaDataSymbol();
|
|
70
70
|
}
|
|
71
|
-
}
|
|
71
|
+
}
|
|
@@ -35,18 +35,17 @@ function stripAttributePrefix(attrs, prefix) {
|
|
|
35
35
|
* @param {Matcher} matcher - Path matcher instance
|
|
36
36
|
* @returns
|
|
37
37
|
*/
|
|
38
|
-
export default function prettify(node, options, matcher) {
|
|
39
|
-
return compress(node, options, matcher);
|
|
38
|
+
export default function prettify(node, options, matcher, readonlyMatcher) {
|
|
39
|
+
return compress(node, options, matcher, readonlyMatcher);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
43
|
* @param {array} arr
|
|
45
44
|
* @param {object} options
|
|
46
45
|
* @param {Matcher} matcher - Path matcher instance
|
|
47
46
|
* @returns object
|
|
48
47
|
*/
|
|
49
|
-
function compress(arr, options, matcher) {
|
|
48
|
+
function compress(arr, options, matcher, readonlyMatcher) {
|
|
50
49
|
let text;
|
|
51
50
|
const compressedObj = {}; //This is intended to be a plain object
|
|
52
51
|
for (let i = 0; i < arr.length; i++) {
|
|
@@ -69,11 +68,11 @@ function compress(arr, options, matcher) {
|
|
|
69
68
|
continue;
|
|
70
69
|
} else if (tagObj[property]) {
|
|
71
70
|
|
|
72
|
-
let val = compress(tagObj[property], options, matcher);
|
|
71
|
+
let val = compress(tagObj[property], options, matcher, readonlyMatcher);
|
|
73
72
|
const isLeaf = isLeafTag(val, options);
|
|
74
73
|
|
|
75
74
|
if (tagObj[":@"]) {
|
|
76
|
-
assignAttributes(val, tagObj[":@"],
|
|
75
|
+
assignAttributes(val, tagObj[":@"], readonlyMatcher, options);
|
|
77
76
|
} else if (Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode) {
|
|
78
77
|
val = val[options.textNodeName];
|
|
79
78
|
} else if (Object.keys(val).length === 0) {
|
|
@@ -95,8 +94,8 @@ function compress(arr, options, matcher) {
|
|
|
95
94
|
//TODO: if a node is not an array, then check if it should be an array
|
|
96
95
|
//also determine if it is a leaf node
|
|
97
96
|
|
|
98
|
-
// Pass jPath string or
|
|
99
|
-
const jPathOrMatcher = options.jPath ?
|
|
97
|
+
// Pass jPath string or readonlyMatcher based on options.jPath setting
|
|
98
|
+
const jPathOrMatcher = options.jPath ? readonlyMatcher.toString() : readonlyMatcher;
|
|
100
99
|
if (options.isArray(property, jPathOrMatcher, isLeaf)) {
|
|
101
100
|
compressedObj[property] = [val];
|
|
102
101
|
} else {
|
|
@@ -128,7 +127,7 @@ function propName(obj) {
|
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
function assignAttributes(obj, attrMap,
|
|
130
|
+
function assignAttributes(obj, attrMap, readonlyMatcher, options) {
|
|
132
131
|
if (attrMap) {
|
|
133
132
|
const keys = Object.keys(attrMap);
|
|
134
133
|
const len = keys.length; //don't make it inline
|
|
@@ -143,8 +142,8 @@ function assignAttributes(obj, attrMap, matcher, options) {
|
|
|
143
142
|
// For attributes, we need to create a temporary path
|
|
144
143
|
// Pass jPath string or matcher based on options.jPath setting
|
|
145
144
|
const jPathOrMatcher = options.jPath
|
|
146
|
-
?
|
|
147
|
-
:
|
|
145
|
+
? readonlyMatcher.toString() + "." + rawAttrName
|
|
146
|
+
: readonlyMatcher;
|
|
148
147
|
|
|
149
148
|
if (options.isArray(atrrName, jPathOrMatcher, true, true)) {
|
|
150
149
|
obj[atrrName] = [attrMap[atrrName]];
|