@btc-embedded/cdk-extensions 0.23.4 → 0.23.5
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/.jsii +5 -5
- package/CHANGELOG.md +7 -0
- package/assets/cli/catnip.js +154 -166
- package/lib/constructs/EventPipe.js +1 -1
- package/lib/constructs/ExportedService.js +1 -1
- package/lib/constructs/S3Bucket.js +1 -1
- package/lib/constructs/SecureRestApi.js +1 -1
- package/lib/constructs/SecureRestApiV2.js +1 -1
- package/lib/constructs/api-keys/ApiKeyClientAuthorization.js +1 -1
- package/lib/constructs/api-keys/ApiKeyManagement.js +1 -1
- package/lib/constructs/api-keys/ApiKeyPreTokenHandler.js +1 -1
- package/lib/constructs/api-keys/ApiKeyStore.js +1 -1
- package/lib/extensions/ApiGatewayExtension.js +1 -1
- package/lib/extensions/ApplicationContainer.js +1 -1
- package/lib/extensions/ApplicationLoadBalancerExtension.js +1 -1
- package/lib/extensions/ApplicationLoadBalancerExtensionV2.js +1 -1
- package/lib/extensions/CloudMapExtension.js +1 -1
- package/lib/extensions/DeactivatableServiceExtension.js +1 -1
- package/lib/extensions/DeploymentConfigExtension.js +1 -1
- package/lib/extensions/DocumentDbAccessExtension.js +1 -1
- package/lib/extensions/DomainEventMessagingExtension.js +1 -1
- package/lib/extensions/EfsMountExtension.js +1 -1
- package/lib/extensions/ExtraContainerExtension.js +1 -1
- package/lib/extensions/HTTPApiExtension.js +1 -1
- package/lib/extensions/LogExtension.js +1 -1
- package/lib/extensions/ModifyContainerDefinitionExtension.js +1 -1
- package/lib/extensions/ModifyTaskDefinitionExtension.js +1 -1
- package/lib/extensions/OpenIdExtension.js +1 -1
- package/lib/extensions/OpenTelemetryExtension.js +1 -1
- package/lib/extensions/PostgresDbAccessExtension.js +1 -1
- package/lib/extensions/SharedVolumeExtension.js +1 -1
- package/lib/extensions/TcpKeepAliveExtension.js +1 -1
- package/lib/platform/ApiGateway.js +1 -1
- package/lib/platform/ApiGatewayVpcLink.js +2 -2
- package/lib/platform/ApplicationLoadBalancer.js +1 -1
- package/lib/platform/ApplicationLoadBalancerV2.d.ts +1 -0
- package/lib/platform/ApplicationLoadBalancerV2.js +10 -3
- package/lib/platform/AuroraPostgresDB.js +2 -2
- package/lib/platform/BTCLogGroup.js +1 -1
- package/lib/platform/CognitoUserPool.js +2 -2
- package/lib/platform/DefaultUserPoolClients.js +1 -1
- package/lib/platform/DocumentDB.js +2 -2
- package/lib/platform/EcsCluster.js +1 -1
- package/lib/platform/EfsFileSystem.js +1 -1
- package/lib/platform/HostedZone.js +1 -1
- package/lib/platform/PrivateDnsNamespace.js +1 -1
- package/lib/platform/ResourceServer.js +1 -1
- package/lib/platform/Vpc.js +1 -1
- package/lib/platform/VpcV2.js +1 -1
- package/lib/stacks/ApplicationStack.js +1 -1
- package/lib/utils/BasePlatformStackResolver.js +1 -1
- package/lib/utils/StackParameter.js +1 -1
- package/node_modules/@nodable/entities/README.md +41 -0
- package/node_modules/@nodable/entities/package.json +54 -0
- package/node_modules/@nodable/entities/src/EntityDecoder.js +543 -0
- package/node_modules/@nodable/entities/src/EntityEncoder.js +194 -0
- package/node_modules/@nodable/entities/src/entities.js +1177 -0
- package/node_modules/@nodable/entities/src/entityTries.js +49 -0
- package/node_modules/@nodable/entities/src/index.d.ts +264 -0
- package/node_modules/@nodable/entities/src/index.js +29 -0
- package/node_modules/fast-xml-builder/CHANGELOG.md +40 -0
- package/node_modules/fast-xml-builder/LICENSE +21 -0
- package/node_modules/fast-xml-builder/README.md +74 -0
- package/node_modules/fast-xml-builder/lib/fxb.cjs +1 -0
- package/node_modules/fast-xml-builder/lib/fxb.d.cts +270 -0
- package/node_modules/fast-xml-builder/lib/fxb.min.js +2 -0
- package/node_modules/fast-xml-builder/lib/fxb.min.js.map +1 -0
- package/node_modules/fast-xml-builder/package.json +81 -0
- package/node_modules/fast-xml-builder/src/fxb.d.ts +270 -0
- package/node_modules/fast-xml-builder/src/fxb.js +599 -0
- package/node_modules/fast-xml-builder/src/ignoreAttributes.js +18 -0
- package/node_modules/fast-xml-builder/src/orderedJs2Xml.js +359 -0
- package/node_modules/fast-xml-builder/src/util.js +16 -0
- package/node_modules/fast-xml-parser/CHANGELOG.md +165 -0
- package/node_modules/fast-xml-parser/README.md +21 -44
- package/node_modules/fast-xml-parser/lib/fxbuilder.min.js +1 -1
- package/node_modules/fast-xml-parser/lib/fxbuilder.min.js.map +1 -1
- package/node_modules/fast-xml-parser/lib/fxp.cjs +1 -1
- package/node_modules/fast-xml-parser/lib/fxp.d.cts +343 -31
- package/node_modules/fast-xml-parser/lib/fxp.min.js +1 -1
- package/node_modules/fast-xml-parser/lib/fxp.min.js.map +1 -1
- package/node_modules/fast-xml-parser/lib/fxparser.min.js +1 -1
- package/node_modules/fast-xml-parser/lib/fxparser.min.js.map +1 -1
- package/node_modules/fast-xml-parser/lib/fxvalidator.min.js +1 -1
- package/node_modules/fast-xml-parser/lib/fxvalidator.min.js.map +1 -1
- package/node_modules/fast-xml-parser/package.json +13 -8
- package/node_modules/fast-xml-parser/src/fxp.d.ts +335 -30
- package/node_modules/fast-xml-parser/src/fxp.js +1 -1
- package/node_modules/fast-xml-parser/src/util.js +18 -25
- package/node_modules/fast-xml-parser/src/v6/EntitiesParser.js +89 -87
- package/node_modules/fast-xml-parser/src/v6/OptionsBuilder.js +10 -10
- package/node_modules/fast-xml-parser/src/v6/OutputBuilders/BaseOutputBuilder.js +23 -23
- package/node_modules/fast-xml-parser/src/v6/OutputBuilders/JsArrBuilder.js +29 -29
- package/node_modules/fast-xml-parser/src/v6/OutputBuilders/JsMinArrBuilder.js +1 -1
- package/node_modules/fast-xml-parser/src/v6/OutputBuilders/JsObjBuilder.js +39 -39
- package/node_modules/fast-xml-parser/src/v6/OutputBuilders/ParserOptionsBuilder.js +21 -21
- package/node_modules/fast-xml-parser/src/v6/XMLParser.js +22 -22
- package/node_modules/fast-xml-parser/src/v6/valueParsers/EntitiesParser.js +85 -85
- package/node_modules/fast-xml-parser/src/validator.js +34 -34
- package/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js +5 -284
- package/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js +335 -293
- package/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js +160 -43
- package/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +540 -308
- package/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js +26 -26
- package/node_modules/fast-xml-parser/src/xmlparser/node2json.js +99 -41
- package/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js +10 -10
- package/node_modules/path-expression-matcher/LICENSE +21 -0
- package/node_modules/path-expression-matcher/README.md +872 -0
- package/node_modules/path-expression-matcher/lib/pem.cjs +1 -0
- package/node_modules/path-expression-matcher/lib/pem.d.cts +634 -0
- package/node_modules/path-expression-matcher/lib/pem.min.js +2 -0
- package/node_modules/path-expression-matcher/lib/pem.min.js.map +1 -0
- package/node_modules/path-expression-matcher/package.json +78 -0
- package/node_modules/path-expression-matcher/src/Expression.js +232 -0
- package/node_modules/path-expression-matcher/src/ExpressionSet.js +209 -0
- package/node_modules/path-expression-matcher/src/Matcher.js +570 -0
- package/node_modules/path-expression-matcher/src/index.d.ts +523 -0
- package/node_modules/path-expression-matcher/src/index.js +29 -0
- package/node_modules/strnum/CHANGELOG.md +12 -2
- package/node_modules/strnum/README.md +1 -0
- package/node_modules/strnum/package.json +5 -4
- package/node_modules/strnum/strnum.js +99 -65
- package/node_modules/xml-naming/README.md +189 -0
- package/node_modules/xml-naming/package.json +54 -0
- package/node_modules/xml-naming/src/index.d.ts +74 -0
- package/node_modules/xml-naming/src/index.js +270 -0
- package/package.json +3 -2
- package/renovate.json5 +1 -0
- package/node_modules/fast-xml-parser/src/xmlbuilder/orderedJs2Xml.js +0 -134
- package/node_modules/strnum/.github/SECURITY.md +0 -5
- package/node_modules/strnum/.vscode/launch.json +0 -25
- package/node_modules/strnum/algo.stflow +0 -84
- package/node_modules/strnum/strnum.test.js +0 -173
- package/node_modules/strnum/test.js +0 -9
- /package/node_modules/{fast-xml-parser/src/xmlbuilder → fast-xml-builder/src}/prettifyJs2Xml.js +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
///@ts-check
|
|
3
3
|
|
|
4
|
-
import {getAllMatches, isExist} from '../util.js';
|
|
4
|
+
import { getAllMatches, isExist, DANGEROUS_PROPERTY_NAMES, criticalProperties } from '../util.js';
|
|
5
5
|
import xmlNode from './xmlNode.js';
|
|
6
|
-
import
|
|
6
|
+
import DocTypeReader from './DocTypeReader.js';
|
|
7
7
|
import toNumber from "strnum";
|
|
8
8
|
import getIgnoreAttributesFn from "../ignoreAttributes.js";
|
|
9
|
+
import { Expression, Matcher } from 'path-expression-matcher';
|
|
10
|
+
import { ExpressionSet } from 'path-expression-matcher';
|
|
11
|
+
import { EntityDecoder, XML, CURRENCY, COMMON_HTML } from '@nodable/entities';
|
|
9
12
|
|
|
10
13
|
// const regx =
|
|
11
14
|
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
|
|
@@ -14,37 +17,62 @@ import getIgnoreAttributesFn from "../ignoreAttributes.js";
|
|
|
14
17
|
//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
|
|
15
18
|
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
// Helper functions for attribute and namespace handling
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract raw attributes (without prefix) from prefixed attribute map
|
|
24
|
+
* @param {object} prefixedAttrs - Attributes with prefix from buildAttributesMap
|
|
25
|
+
* @param {object} options - Parser options containing attributeNamePrefix
|
|
26
|
+
* @returns {object} Raw attributes for matcher
|
|
27
|
+
*/
|
|
28
|
+
function extractRawAttributes(prefixedAttrs, options) {
|
|
29
|
+
if (!prefixedAttrs) return {};
|
|
30
|
+
|
|
31
|
+
// Handle attributesGroupName option
|
|
32
|
+
const attrs = options.attributesGroupName
|
|
33
|
+
? prefixedAttrs[options.attributesGroupName]
|
|
34
|
+
: prefixedAttrs;
|
|
35
|
+
|
|
36
|
+
if (!attrs) return {};
|
|
37
|
+
|
|
38
|
+
const rawAttrs = {};
|
|
39
|
+
for (const key in attrs) {
|
|
40
|
+
// Remove the attribute prefix to get raw name
|
|
41
|
+
if (key.startsWith(options.attributeNamePrefix)) {
|
|
42
|
+
const rawName = key.substring(options.attributeNamePrefix.length);
|
|
43
|
+
rawAttrs[rawName] = attrs[key];
|
|
44
|
+
} else {
|
|
45
|
+
// Attribute without prefix (shouldn't normally happen, but be safe)
|
|
46
|
+
rawAttrs[key] = attrs[key];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return rawAttrs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract namespace from raw tag name
|
|
54
|
+
* @param {string} rawTagName - Tag name possibly with namespace (e.g., "soap:Envelope")
|
|
55
|
+
* @returns {string|undefined} Namespace or undefined
|
|
56
|
+
*/
|
|
57
|
+
function extractNamespace(rawTagName) {
|
|
58
|
+
if (!rawTagName || typeof rawTagName !== 'string') return undefined;
|
|
59
|
+
|
|
60
|
+
const colonIndex = rawTagName.indexOf(':');
|
|
61
|
+
if (colonIndex !== -1 && colonIndex > 0) {
|
|
62
|
+
const ns = rawTagName.substring(0, colonIndex);
|
|
63
|
+
// Don't treat xmlns as a namespace
|
|
64
|
+
if (ns !== 'xmlns') {
|
|
65
|
+
return ns;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default class OrderedObjParser {
|
|
72
|
+
constructor(options, externalEntities) {
|
|
19
73
|
this.options = options;
|
|
20
74
|
this.currentNode = null;
|
|
21
75
|
this.tagsNodeStack = [];
|
|
22
|
-
this.docTypeEntities = {};
|
|
23
|
-
this.lastEntities = {
|
|
24
|
-
"apos" : { regex: /&(apos|#39|#x27);/g, val : "'"},
|
|
25
|
-
"gt" : { regex: /&(gt|#62|#x3E);/g, val : ">"},
|
|
26
|
-
"lt" : { regex: /&(lt|#60|#x3C);/g, val : "<"},
|
|
27
|
-
"quot" : { regex: /&(quot|#34|#x22);/g, val : "\""},
|
|
28
|
-
};
|
|
29
|
-
this.ampEntity = { regex: /&(amp|#38|#x26);/g, val : "&"};
|
|
30
|
-
this.htmlEntities = {
|
|
31
|
-
"space": { regex: /&(nbsp|#160);/g, val: " " },
|
|
32
|
-
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
|
|
33
|
-
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
|
|
34
|
-
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
|
|
35
|
-
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
|
|
36
|
-
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
|
|
37
|
-
"cent" : { regex: /&(cent|#162);/g, val: "¢" },
|
|
38
|
-
"pound" : { regex: /&(pound|#163);/g, val: "£" },
|
|
39
|
-
"yen" : { regex: /&(yen|#165);/g, val: "¥" },
|
|
40
|
-
"euro" : { regex: /&(euro|#8364);/g, val: "€" },
|
|
41
|
-
"copyright" : { regex: /&(copy|#169);/g, val: "©" },
|
|
42
|
-
"reg" : { regex: /&(reg|#174);/g, val: "®" },
|
|
43
|
-
"inr" : { regex: /&(inr|#8377);/g, val: "₹" },
|
|
44
|
-
"num_dec": { regex: /&#([0-9]{1,7});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
|
|
45
|
-
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val : (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) },
|
|
46
|
-
};
|
|
47
|
-
this.addExternalEntities = addExternalEntities;
|
|
48
76
|
this.parseXml = parseXml;
|
|
49
77
|
this.parseTextData = parseTextData;
|
|
50
78
|
this.resolveNameSpace = resolveNameSpace;
|
|
@@ -55,52 +83,88 @@ export default class OrderedObjParser{
|
|
|
55
83
|
this.saveTextToParentTag = saveTextToParentTag;
|
|
56
84
|
this.addChild = addChild;
|
|
57
85
|
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
86
|
+
this.entityExpansionCount = 0;
|
|
87
|
+
this.currentExpandedLength = 0;
|
|
88
|
+
let namedEntities = { ...XML };
|
|
89
|
+
if (this.options.entityDecoder) {
|
|
90
|
+
this.entityDecoder = this.options.entityDecoder
|
|
91
|
+
} else {
|
|
92
|
+
if (typeof this.options.htmlEntities === "object") namedEntities = this.options.htmlEntities;
|
|
93
|
+
else if (this.options.htmlEntities === true) namedEntities = { ...COMMON_HTML, ...CURRENCY };
|
|
94
|
+
this.entityDecoder = new EntityDecoder({
|
|
95
|
+
namedEntities: { ...namedEntities, ...externalEntities },
|
|
96
|
+
numericAllowed: this.options.htmlEntities,
|
|
97
|
+
limit: {
|
|
98
|
+
maxTotalExpansions: this.options.processEntities.maxTotalExpansions,
|
|
99
|
+
maxExpandedLength: this.options.processEntities.maxExpandedLength,
|
|
100
|
+
applyLimitsTo: this.options.processEntities.appliesTo,
|
|
101
|
+
}
|
|
102
|
+
//postCheck: resolved => resolved
|
|
103
|
+
});
|
|
104
|
+
}
|
|
61
105
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
// Initialize path matcher for path-expression-matcher
|
|
107
|
+
this.matcher = new Matcher();
|
|
108
|
+
this.readonlyMatcher = this.matcher.readOnly();
|
|
109
|
+
|
|
110
|
+
// Flag to track if current node is a stop node (optimization)
|
|
111
|
+
this.isCurrentNodeStopNode = false;
|
|
112
|
+
|
|
113
|
+
// Pre-compile stopNodes expressions
|
|
114
|
+
this.stopNodeExpressionsSet = new ExpressionSet();
|
|
115
|
+
const stopNodesOpts = this.options.stopNodes;
|
|
116
|
+
if (stopNodesOpts && stopNodesOpts.length > 0) {
|
|
117
|
+
for (let i = 0; i < stopNodesOpts.length; i++) {
|
|
118
|
+
const stopNodeExp = stopNodesOpts[i];
|
|
119
|
+
if (typeof stopNodeExp === 'string') {
|
|
120
|
+
// Convert string to Expression object
|
|
121
|
+
this.stopNodeExpressionsSet.add(new Expression(stopNodeExp));
|
|
122
|
+
} else if (stopNodeExp instanceof Expression) {
|
|
123
|
+
// Already an Expression object
|
|
124
|
+
this.stopNodeExpressionsSet.add(stopNodeExp);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.stopNodeExpressionsSet.seal();
|
|
69
128
|
}
|
|
70
129
|
}
|
|
130
|
+
|
|
71
131
|
}
|
|
72
132
|
|
|
133
|
+
|
|
73
134
|
/**
|
|
74
135
|
* @param {string} val
|
|
75
136
|
* @param {string} tagName
|
|
76
|
-
* @param {string} jPath
|
|
137
|
+
* @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
|
|
77
138
|
* @param {boolean} dontTrim
|
|
78
139
|
* @param {boolean} hasAttributes
|
|
79
140
|
* @param {boolean} isLeafNode
|
|
80
141
|
* @param {boolean} escapeEntities
|
|
81
142
|
*/
|
|
82
143
|
function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
|
|
144
|
+
const options = this.options;
|
|
83
145
|
if (val !== undefined) {
|
|
84
|
-
if (
|
|
146
|
+
if (options.trimValues && !dontTrim) {
|
|
85
147
|
val = val.trim();
|
|
86
148
|
}
|
|
87
|
-
if(val.length > 0){
|
|
88
|
-
if(!escapeEntities) val = this.replaceEntitiesValue(val);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
149
|
+
if (val.length > 0) {
|
|
150
|
+
if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
|
|
151
|
+
|
|
152
|
+
// Pass jPath string or matcher based on options.jPath setting
|
|
153
|
+
const jPathOrMatcher = options.jPath ? jPath.toString() : jPath;
|
|
154
|
+
const newval = options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode);
|
|
155
|
+
if (newval === null || newval === undefined) {
|
|
92
156
|
//don't parse
|
|
93
157
|
return val;
|
|
94
|
-
}else if(typeof newval !== typeof val || newval !== val){
|
|
158
|
+
} else if (typeof newval !== typeof val || newval !== val) {
|
|
95
159
|
//overwrite
|
|
96
160
|
return newval;
|
|
97
|
-
}else if(
|
|
98
|
-
return parseValue(val,
|
|
99
|
-
}else{
|
|
161
|
+
} else if (options.trimValues) {
|
|
162
|
+
return parseValue(val, options.parseTagValue, options.numberParseOptions);
|
|
163
|
+
} else {
|
|
100
164
|
const trimmedVal = val.trim();
|
|
101
|
-
if(trimmedVal === val){
|
|
102
|
-
return parseValue(val,
|
|
103
|
-
}else{
|
|
165
|
+
if (trimmedVal === val) {
|
|
166
|
+
return parseValue(val, options.parseTagValue, options.numberParseOptions);
|
|
167
|
+
} else {
|
|
104
168
|
return val;
|
|
105
169
|
}
|
|
106
170
|
}
|
|
@@ -126,342 +190,460 @@ function resolveNameSpace(tagname) {
|
|
|
126
190
|
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
|
|
127
191
|
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
|
|
128
192
|
|
|
129
|
-
function buildAttributesMap(attrStr, jPath, tagName) {
|
|
130
|
-
|
|
193
|
+
function buildAttributesMap(attrStr, jPath, tagName, force = false) {
|
|
194
|
+
const options = this.options;
|
|
195
|
+
if (force === true || (options.ignoreAttributes !== true && typeof attrStr === 'string')) {
|
|
131
196
|
// attrStr = attrStr.replace(/\r?\n/g, ' ');
|
|
132
197
|
//attrStr = attrStr || attrStr.trim();
|
|
133
198
|
|
|
134
199
|
const matches = getAllMatches(attrStr, attrsRegx);
|
|
135
200
|
const len = matches.length; //don't make it inline
|
|
136
201
|
const attrs = {};
|
|
202
|
+
|
|
203
|
+
// Pre-process values once: trim + entity replacement
|
|
204
|
+
// Reused in both matcher update and second pass
|
|
205
|
+
const processedVals = new Array(len);
|
|
206
|
+
let hasRawAttrs = false;
|
|
207
|
+
const rawAttrsForMatcher = {};
|
|
208
|
+
|
|
137
209
|
for (let i = 0; i < len; i++) {
|
|
138
210
|
const attrName = this.resolveNameSpace(matches[i][1]);
|
|
139
|
-
|
|
140
|
-
|
|
211
|
+
const oldVal = matches[i][4];
|
|
212
|
+
|
|
213
|
+
if (attrName.length && oldVal !== undefined) {
|
|
214
|
+
let val = oldVal;
|
|
215
|
+
if (options.trimValues) val = val.trim();
|
|
216
|
+
val = this.replaceEntitiesValue(val, tagName, this.readonlyMatcher);
|
|
217
|
+
processedVals[i] = val;
|
|
218
|
+
|
|
219
|
+
rawAttrsForMatcher[attrName] = val;
|
|
220
|
+
hasRawAttrs = true;
|
|
141
221
|
}
|
|
142
|
-
|
|
143
|
-
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Update matcher ONCE before second pass, if applicable
|
|
225
|
+
if (hasRawAttrs && typeof jPath === 'object' && jPath.updateCurrent) {
|
|
226
|
+
jPath.updateCurrent(rawAttrsForMatcher);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Hoist toString() once — path doesn't change during attribute processing
|
|
230
|
+
const jPathStr = options.jPath ? jPath.toString() : this.readonlyMatcher;
|
|
231
|
+
|
|
232
|
+
// Second pass: apply processors, build final attrs
|
|
233
|
+
let hasAttrs = false;
|
|
234
|
+
for (let i = 0; i < len; i++) {
|
|
235
|
+
const attrName = this.resolveNameSpace(matches[i][1]);
|
|
236
|
+
|
|
237
|
+
if (this.ignoreAttributesFn(attrName, jPathStr)) continue;
|
|
238
|
+
|
|
239
|
+
let aName = options.attributeNamePrefix + attrName;
|
|
240
|
+
|
|
144
241
|
if (attrName.length) {
|
|
145
|
-
if (
|
|
146
|
-
aName =
|
|
242
|
+
if (options.transformAttributeName) {
|
|
243
|
+
aName = options.transformAttributeName(aName);
|
|
147
244
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const newVal =
|
|
155
|
-
if(newVal === null || newVal === undefined){
|
|
156
|
-
//don't parse
|
|
245
|
+
aName = sanitizeName(aName, options);
|
|
246
|
+
|
|
247
|
+
if (matches[i][4] !== undefined) {
|
|
248
|
+
// Reuse already-processed value — no double entity replacement
|
|
249
|
+
const oldVal = processedVals[i];
|
|
250
|
+
|
|
251
|
+
const newVal = options.attributeValueProcessor(attrName, oldVal, jPathStr);
|
|
252
|
+
if (newVal === null || newVal === undefined) {
|
|
157
253
|
attrs[aName] = oldVal;
|
|
158
|
-
}else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
|
|
159
|
-
//overwrite
|
|
254
|
+
} else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
|
|
160
255
|
attrs[aName] = newVal;
|
|
161
|
-
}else{
|
|
162
|
-
|
|
163
|
-
attrs[aName] = parseValue(
|
|
164
|
-
oldVal,
|
|
165
|
-
this.options.parseAttributeValue,
|
|
166
|
-
this.options.numberParseOptions
|
|
167
|
-
);
|
|
256
|
+
} else {
|
|
257
|
+
attrs[aName] = parseValue(oldVal, options.parseAttributeValue, options.numberParseOptions);
|
|
168
258
|
}
|
|
169
|
-
|
|
259
|
+
hasAttrs = true;
|
|
260
|
+
} else if (options.allowBooleanAttributes) {
|
|
170
261
|
attrs[aName] = true;
|
|
262
|
+
hasAttrs = true;
|
|
171
263
|
}
|
|
172
264
|
}
|
|
173
265
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (
|
|
266
|
+
|
|
267
|
+
if (!hasAttrs) return;
|
|
268
|
+
|
|
269
|
+
if (options.attributesGroupName && !options.preserveOrder) {
|
|
178
270
|
const attrCollection = {};
|
|
179
|
-
attrCollection[
|
|
271
|
+
attrCollection[options.attributesGroupName] = attrs;
|
|
180
272
|
return attrCollection;
|
|
181
273
|
}
|
|
182
|
-
return attrs
|
|
274
|
+
return attrs;
|
|
183
275
|
}
|
|
184
276
|
}
|
|
185
|
-
|
|
186
|
-
const parseXml = function(xmlData) {
|
|
277
|
+
const parseXml = function (xmlData) {
|
|
187
278
|
xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
|
|
188
279
|
const xmlObj = new xmlNode('!xml');
|
|
189
280
|
let currentNode = xmlObj;
|
|
190
281
|
let textData = "";
|
|
191
|
-
|
|
192
|
-
|
|
282
|
+
|
|
283
|
+
// Reset matcher for new document
|
|
284
|
+
this.matcher.reset();
|
|
285
|
+
this.entityDecoder.reset();
|
|
286
|
+
|
|
287
|
+
// Reset entity expansion counters for this document
|
|
288
|
+
this.entityExpansionCount = 0;
|
|
289
|
+
this.currentExpandedLength = 0;
|
|
290
|
+
const options = this.options;
|
|
291
|
+
const docTypeReader = new DocTypeReader(options.processEntities);
|
|
292
|
+
const xmlLen = xmlData.length;
|
|
293
|
+
for (let i = 0; i < xmlLen; i++) {//for each char in XML data
|
|
193
294
|
const ch = xmlData[i];
|
|
194
|
-
if(ch === '<'){
|
|
295
|
+
if (ch === '<') {
|
|
195
296
|
// const nextIndex = i+1;
|
|
196
297
|
// const _2ndChar = xmlData[nextIndex];
|
|
197
|
-
|
|
298
|
+
const c1 = xmlData.charCodeAt(i + 1);
|
|
299
|
+
if (c1 === 47) {//Closing Tag '/'
|
|
198
300
|
const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
|
|
199
|
-
let tagName = xmlData.substring(i+2,closeIndex).trim();
|
|
301
|
+
let tagName = xmlData.substring(i + 2, closeIndex).trim();
|
|
200
302
|
|
|
201
|
-
if(
|
|
303
|
+
if (options.removeNSPrefix) {
|
|
202
304
|
const colonIndex = tagName.indexOf(":");
|
|
203
|
-
if(colonIndex !== -1){
|
|
204
|
-
tagName = tagName.substr(colonIndex+1);
|
|
305
|
+
if (colonIndex !== -1) {
|
|
306
|
+
tagName = tagName.substr(colonIndex + 1);
|
|
205
307
|
}
|
|
206
308
|
}
|
|
207
309
|
|
|
208
|
-
|
|
209
|
-
tagName = this.options.transformTagName(tagName);
|
|
210
|
-
}
|
|
310
|
+
tagName = transformTagName(options.transformTagName, tagName, "", options).tagName;
|
|
211
311
|
|
|
212
|
-
if(currentNode){
|
|
213
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
312
|
+
if (currentNode) {
|
|
313
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
214
314
|
}
|
|
215
315
|
|
|
216
316
|
//check if last tag of nested tag was unpaired tag
|
|
217
|
-
const lastTagName =
|
|
218
|
-
if(tagName &&
|
|
317
|
+
const lastTagName = this.matcher.getCurrentTag();
|
|
318
|
+
if (tagName && options.unpairedTagsSet.has(tagName)) {
|
|
219
319
|
throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
|
|
220
320
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
321
|
+
if (lastTagName && options.unpairedTagsSet.has(lastTagName)) {
|
|
322
|
+
// Pop the unpaired tag
|
|
323
|
+
this.matcher.pop();
|
|
224
324
|
this.tagsNodeStack.pop();
|
|
225
|
-
}else{
|
|
226
|
-
propIndex = jPath.lastIndexOf(".");
|
|
227
325
|
}
|
|
228
|
-
|
|
326
|
+
// Pop the closing tag
|
|
327
|
+
this.matcher.pop();
|
|
328
|
+
this.isCurrentNodeStopNode = false; // Reset flag when closing tag
|
|
229
329
|
|
|
230
330
|
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
|
|
231
331
|
textData = "";
|
|
232
332
|
i = closeIndex;
|
|
233
|
-
} else if(
|
|
333
|
+
} else if (c1 === 63) { //'?'
|
|
234
334
|
|
|
235
|
-
let tagData = readTagExp(xmlData,i, false, "?>");
|
|
236
|
-
if(!tagData) throw new Error("Pi Tag is not closed.");
|
|
335
|
+
let tagData = readTagExp(xmlData, i, false, "?>");
|
|
336
|
+
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
237
337
|
|
|
238
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
239
|
-
|
|
338
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
339
|
+
const attsMap = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName, true);
|
|
340
|
+
if (attsMap) {
|
|
341
|
+
const ver = attsMap[this.options.attributeNamePrefix + "version"];
|
|
342
|
+
this.entityDecoder.setXmlVersion(Number(ver) || 1.0);
|
|
343
|
+
docTypeReader.setXmlVersion(Number(ver) || 1.0);
|
|
344
|
+
}
|
|
345
|
+
if ((options.ignoreDeclaration && tagData.tagName === "?xml") || options.ignorePiTags) {
|
|
346
|
+
//do nothing
|
|
347
|
+
} else {
|
|
240
348
|
|
|
241
|
-
}else{
|
|
242
|
-
|
|
243
349
|
const childNode = new xmlNode(tagData.tagName);
|
|
244
|
-
childNode.add(
|
|
245
|
-
|
|
246
|
-
if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
|
|
247
|
-
childNode[":@"] =
|
|
350
|
+
childNode.add(options.textNodeName, "");
|
|
351
|
+
|
|
352
|
+
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent && options.ignoreAttributes !== true) {
|
|
353
|
+
childNode[":@"] = attsMap
|
|
248
354
|
}
|
|
249
|
-
this.addChild(currentNode, childNode,
|
|
355
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, i);
|
|
250
356
|
}
|
|
251
357
|
|
|
252
358
|
|
|
253
359
|
i = tagData.closeIndex + 1;
|
|
254
|
-
} else if(
|
|
255
|
-
|
|
256
|
-
|
|
360
|
+
} else if (c1 === 33
|
|
361
|
+
&& xmlData.charCodeAt(i + 2) === 45
|
|
362
|
+
&& xmlData.charCodeAt(i + 3) === 45) { //'!--'
|
|
363
|
+
const endIndex = findClosingIndex(xmlData, "-->", i + 4, "Comment is not closed.")
|
|
364
|
+
if (options.commentPropName) {
|
|
257
365
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
258
366
|
|
|
259
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
367
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
260
368
|
|
|
261
|
-
currentNode.add(
|
|
369
|
+
currentNode.add(options.commentPropName, [{ [options.textNodeName]: comment }]);
|
|
262
370
|
}
|
|
263
371
|
i = endIndex;
|
|
264
|
-
} else if
|
|
265
|
-
|
|
266
|
-
|
|
372
|
+
} else if (c1 === 33
|
|
373
|
+
&& xmlData.charCodeAt(i + 2) === 68) { //'!D'
|
|
374
|
+
const result = docTypeReader.readDocType(xmlData, i);
|
|
375
|
+
this.entityDecoder.addInputEntities(result.entities);
|
|
267
376
|
i = result.i;
|
|
268
|
-
}else if(
|
|
377
|
+
} else if (c1 === 33
|
|
378
|
+
&& xmlData.charCodeAt(i + 2) === 91) { // '!['
|
|
269
379
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
|
270
|
-
const tagExp = xmlData.substring(i + 9,closeIndex);
|
|
380
|
+
const tagExp = xmlData.substring(i + 9, closeIndex);
|
|
271
381
|
|
|
272
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
382
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
273
383
|
|
|
274
|
-
let val = this.parseTextData(tagExp, currentNode.tagname,
|
|
275
|
-
if(val == undefined) val = "";
|
|
384
|
+
let val = this.parseTextData(tagExp, currentNode.tagname, this.readonlyMatcher, true, false, true, true);
|
|
385
|
+
if (val == undefined) val = "";
|
|
276
386
|
|
|
277
387
|
//cdata should be set even if it is 0 length string
|
|
278
|
-
if(
|
|
279
|
-
currentNode.add(
|
|
280
|
-
}else{
|
|
281
|
-
currentNode.add(
|
|
388
|
+
if (options.cdataPropName) {
|
|
389
|
+
currentNode.add(options.cdataPropName, [{ [options.textNodeName]: tagExp }]);
|
|
390
|
+
} else {
|
|
391
|
+
currentNode.add(options.textNodeName, val);
|
|
282
392
|
}
|
|
283
|
-
|
|
393
|
+
|
|
284
394
|
i = closeIndex + 2;
|
|
285
|
-
}else {//Opening tag
|
|
286
|
-
let result = readTagExp(xmlData,i,
|
|
287
|
-
|
|
395
|
+
} else {//Opening tag
|
|
396
|
+
let result = readTagExp(xmlData, i, options.removeNSPrefix);
|
|
397
|
+
|
|
398
|
+
// Safety check: readTagExp can return undefined
|
|
399
|
+
if (!result) {
|
|
400
|
+
// Log context for debugging
|
|
401
|
+
const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlLen, i + 50));
|
|
402
|
+
throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
let tagName = result.tagName;
|
|
288
406
|
const rawTagName = result.rawTagName;
|
|
289
407
|
let tagExp = result.tagExp;
|
|
290
408
|
let attrExpPresent = result.attrExpPresent;
|
|
291
409
|
let closeIndex = result.closeIndex;
|
|
292
410
|
|
|
293
|
-
|
|
294
|
-
|
|
411
|
+
({ tagName, tagExp } = transformTagName(options.transformTagName, tagName, tagExp, options));
|
|
412
|
+
|
|
413
|
+
if (options.strictReservedNames &&
|
|
414
|
+
(tagName === options.commentPropName
|
|
415
|
+
|| tagName === options.cdataPropName
|
|
416
|
+
|| tagName === options.textNodeName
|
|
417
|
+
|| tagName === options.attributesGroupName
|
|
418
|
+
)) {
|
|
419
|
+
throw new Error(`Invalid tag name: ${tagName}`);
|
|
295
420
|
}
|
|
296
|
-
|
|
421
|
+
|
|
297
422
|
//save text as child node
|
|
298
423
|
if (currentNode && textData) {
|
|
299
|
-
if(currentNode.tagname !== '!xml'){
|
|
424
|
+
if (currentNode.tagname !== '!xml') {
|
|
300
425
|
//when nested tag is found
|
|
301
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
426
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher, false);
|
|
302
427
|
}
|
|
303
428
|
}
|
|
304
429
|
|
|
305
430
|
//check if last tag was unpaired tag
|
|
306
431
|
const lastTag = currentNode;
|
|
307
|
-
if(lastTag &&
|
|
432
|
+
if (lastTag && options.unpairedTagsSet.has(lastTag.tagname)) {
|
|
308
433
|
currentNode = this.tagsNodeStack.pop();
|
|
309
|
-
|
|
434
|
+
this.matcher.pop();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Clean up self-closing syntax BEFORE processing attributes
|
|
438
|
+
// This is where tagExp gets the trailing / removed
|
|
439
|
+
let isSelfClosing = false;
|
|
440
|
+
if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
|
|
441
|
+
isSelfClosing = true;
|
|
442
|
+
if (tagName[tagName.length - 1] === "/") {
|
|
443
|
+
tagName = tagName.substr(0, tagName.length - 1);
|
|
444
|
+
tagExp = tagName;
|
|
445
|
+
} else {
|
|
446
|
+
tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Re-check attrExpPresent after cleaning
|
|
450
|
+
attrExpPresent = (tagName !== tagExp);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Now process attributes with CLEAN tagExp (no trailing /)
|
|
454
|
+
let prefixedAttrs = null;
|
|
455
|
+
let rawAttrs = {};
|
|
456
|
+
let namespace = undefined;
|
|
457
|
+
|
|
458
|
+
// Extract namespace from rawTagName
|
|
459
|
+
namespace = extractNamespace(rawTagName);
|
|
460
|
+
|
|
461
|
+
// Push tag to matcher FIRST (with empty attrs for now) so callbacks see correct path
|
|
462
|
+
if (tagName !== xmlObj.tagname) {
|
|
463
|
+
this.matcher.push(tagName, {}, namespace);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Now build attributes - callbacks will see correct matcher state
|
|
467
|
+
if (tagName !== tagExp && attrExpPresent) {
|
|
468
|
+
// Build attributes (returns prefixed attributes for the tree)
|
|
469
|
+
// Note: buildAttributesMap now internally updates the matcher with raw attributes
|
|
470
|
+
prefixedAttrs = this.buildAttributesMap(tagExp, this.matcher, tagName);
|
|
471
|
+
|
|
472
|
+
if (prefixedAttrs) {
|
|
473
|
+
// Extract raw attributes (without prefix) for our use
|
|
474
|
+
//TODO: seems a performance overhead
|
|
475
|
+
rawAttrs = extractRawAttributes(prefixedAttrs, options);
|
|
476
|
+
}
|
|
310
477
|
}
|
|
311
|
-
|
|
312
|
-
|
|
478
|
+
|
|
479
|
+
// Now check if this is a stop node (after attributes are set)
|
|
480
|
+
if (tagName !== xmlObj.tagname) {
|
|
481
|
+
this.isCurrentNodeStopNode = this.isItStopNode();
|
|
313
482
|
}
|
|
483
|
+
|
|
314
484
|
const startIndex = i;
|
|
315
|
-
if (this.
|
|
485
|
+
if (this.isCurrentNodeStopNode) {
|
|
316
486
|
let tagContent = "";
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
tagName = tagName.substr(0, tagName.length - 1);
|
|
321
|
-
jPath = jPath.substr(0, jPath.length - 1);
|
|
322
|
-
tagExp = tagName;
|
|
323
|
-
}else{
|
|
324
|
-
tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
325
|
-
}
|
|
487
|
+
|
|
488
|
+
// For self-closing tags, content is empty
|
|
489
|
+
if (isSelfClosing) {
|
|
326
490
|
i = result.closeIndex;
|
|
327
491
|
}
|
|
328
492
|
//unpaired tag
|
|
329
|
-
else if(
|
|
330
|
-
|
|
493
|
+
else if (options.unpairedTagsSet.has(tagName)) {
|
|
331
494
|
i = result.closeIndex;
|
|
332
495
|
}
|
|
333
496
|
//normal tag
|
|
334
|
-
else{
|
|
497
|
+
else {
|
|
335
498
|
//read until closing tag is found
|
|
336
499
|
const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
|
|
337
|
-
if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
|
|
500
|
+
if (!result) throw new Error(`Unexpected end of ${rawTagName}`);
|
|
338
501
|
i = result.i;
|
|
339
502
|
tagContent = result.tagContent;
|
|
340
503
|
}
|
|
341
504
|
|
|
342
505
|
const childNode = new xmlNode(tagName);
|
|
343
506
|
|
|
344
|
-
if(
|
|
345
|
-
childNode[":@"] =
|
|
346
|
-
}
|
|
347
|
-
if(tagContent) {
|
|
348
|
-
tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
|
|
507
|
+
if (prefixedAttrs) {
|
|
508
|
+
childNode[":@"] = prefixedAttrs;
|
|
349
509
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
childNode.add(
|
|
353
|
-
|
|
354
|
-
this.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}else{
|
|
363
|
-
tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if(this.options.transformTagName) {
|
|
367
|
-
tagName = this.options.transformTagName(tagName);
|
|
368
|
-
}
|
|
510
|
+
|
|
511
|
+
// For stop nodes, store raw content as-is without any processing
|
|
512
|
+
childNode.add(options.textNodeName, tagContent);
|
|
513
|
+
|
|
514
|
+
this.matcher.pop(); // Pop the stop node tag
|
|
515
|
+
this.isCurrentNodeStopNode = false; // Reset flag
|
|
516
|
+
|
|
517
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
518
|
+
} else {
|
|
519
|
+
//selfClosing tag
|
|
520
|
+
if (isSelfClosing) {
|
|
521
|
+
({ tagName, tagExp } = transformTagName(options.transformTagName, tagName, tagExp, options));
|
|
369
522
|
|
|
370
523
|
const childNode = new xmlNode(tagName);
|
|
371
|
-
if(
|
|
372
|
-
childNode[":@"] =
|
|
524
|
+
if (prefixedAttrs) {
|
|
525
|
+
childNode[":@"] = prefixedAttrs;
|
|
373
526
|
}
|
|
374
|
-
this.addChild(currentNode, childNode,
|
|
375
|
-
|
|
527
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
528
|
+
this.matcher.pop(); // Pop self-closing tag
|
|
529
|
+
this.isCurrentNodeStopNode = false; // Reset flag
|
|
530
|
+
}
|
|
531
|
+
else if (options.unpairedTagsSet.has(tagName)) {//unpaired tag
|
|
532
|
+
const childNode = new xmlNode(tagName);
|
|
533
|
+
if (prefixedAttrs) {
|
|
534
|
+
childNode[":@"] = prefixedAttrs;
|
|
535
|
+
}
|
|
536
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
537
|
+
this.matcher.pop(); // Pop unpaired tag
|
|
538
|
+
this.isCurrentNodeStopNode = false; // Reset flag
|
|
539
|
+
i = result.closeIndex;
|
|
540
|
+
// Continue to next iteration without changing currentNode
|
|
541
|
+
continue;
|
|
376
542
|
}
|
|
377
|
-
|
|
378
|
-
else{
|
|
379
|
-
const childNode = new xmlNode(
|
|
543
|
+
//opening tag
|
|
544
|
+
else {
|
|
545
|
+
const childNode = new xmlNode(tagName);
|
|
546
|
+
if (this.tagsNodeStack.length > options.maxNestedTags) {
|
|
547
|
+
throw new Error("Maximum nested tags exceeded");
|
|
548
|
+
}
|
|
380
549
|
this.tagsNodeStack.push(currentNode);
|
|
381
|
-
|
|
382
|
-
if(
|
|
383
|
-
childNode[":@"] =
|
|
550
|
+
|
|
551
|
+
if (prefixedAttrs) {
|
|
552
|
+
childNode[":@"] = prefixedAttrs;
|
|
384
553
|
}
|
|
385
|
-
this.addChild(currentNode, childNode,
|
|
554
|
+
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
|
|
386
555
|
currentNode = childNode;
|
|
387
556
|
}
|
|
388
557
|
textData = "";
|
|
389
558
|
i = closeIndex;
|
|
390
559
|
}
|
|
391
560
|
}
|
|
392
|
-
}else{
|
|
561
|
+
} else {
|
|
393
562
|
textData += xmlData[i];
|
|
394
563
|
}
|
|
395
564
|
}
|
|
396
565
|
return xmlObj.child;
|
|
397
566
|
}
|
|
398
567
|
|
|
399
|
-
function addChild(currentNode, childNode,
|
|
568
|
+
function addChild(currentNode, childNode, matcher, startIndex) {
|
|
400
569
|
// unset startIndex if not requested
|
|
401
570
|
if (!this.options.captureMetaData) startIndex = undefined;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
571
|
+
|
|
572
|
+
// Pass jPath string or matcher based on options.jPath setting
|
|
573
|
+
const jPathOrMatcher = this.options.jPath ? matcher.toString() : matcher;
|
|
574
|
+
const result = this.options.updateTag(childNode.tagname, jPathOrMatcher, childNode[":@"])
|
|
575
|
+
if (result === false) {
|
|
576
|
+
//do nothing
|
|
577
|
+
} else if (typeof result === "string") {
|
|
405
578
|
childNode.tagname = result
|
|
406
579
|
currentNode.addChild(childNode, startIndex);
|
|
407
|
-
}else{
|
|
580
|
+
} else {
|
|
408
581
|
currentNode.addChild(childNode, startIndex);
|
|
409
582
|
}
|
|
410
583
|
}
|
|
411
584
|
|
|
412
|
-
|
|
585
|
+
/**
|
|
586
|
+
* @param {object} val - Entity object with regex and val properties
|
|
587
|
+
* @param {string} tagName - Tag name
|
|
588
|
+
* @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
|
|
589
|
+
*/
|
|
590
|
+
function replaceEntitiesValue(val, tagName, jPath) {
|
|
591
|
+
const entityConfig = this.options.processEntities;
|
|
413
592
|
|
|
414
|
-
if(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
593
|
+
if (!entityConfig || !entityConfig.enabled) {
|
|
594
|
+
return val;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Check if tag is allowed to contain entities
|
|
598
|
+
if (entityConfig.allowedTags) {
|
|
599
|
+
const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
|
|
600
|
+
const allowed = Array.isArray(entityConfig.allowedTags)
|
|
601
|
+
? entityConfig.allowedTags.includes(tagName)
|
|
602
|
+
: entityConfig.allowedTags(tagName, jPathOrMatcher);
|
|
603
|
+
|
|
604
|
+
if (!allowed) {
|
|
605
|
+
return val;
|
|
422
606
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Apply custom tag filter if provided
|
|
610
|
+
if (entityConfig.tagFilter) {
|
|
611
|
+
const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
|
|
612
|
+
if (!entityConfig.tagFilter(tagName, jPathOrMatcher)) {
|
|
613
|
+
return val; // Skip based on custom filter
|
|
428
614
|
}
|
|
429
|
-
val = val.replace( this.ampEntity.regex, this.ampEntity.val);
|
|
430
615
|
}
|
|
431
|
-
|
|
616
|
+
|
|
617
|
+
return this.entityDecoder.decode(val);
|
|
432
618
|
}
|
|
433
|
-
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
function saveTextToParentTag(textData, parentNode, matcher, isLeafNode) {
|
|
434
622
|
if (textData) { //store previously collected data as textNode
|
|
435
|
-
if(isLeafNode === undefined) isLeafNode =
|
|
436
|
-
|
|
623
|
+
if (isLeafNode === undefined) isLeafNode = parentNode.child.length === 0
|
|
624
|
+
|
|
437
625
|
textData = this.parseTextData(textData,
|
|
438
|
-
|
|
439
|
-
|
|
626
|
+
parentNode.tagname,
|
|
627
|
+
matcher,
|
|
440
628
|
false,
|
|
441
|
-
|
|
629
|
+
parentNode[":@"] ? Object.keys(parentNode[":@"]).length !== 0 : false,
|
|
442
630
|
isLeafNode);
|
|
443
631
|
|
|
444
632
|
if (textData !== undefined && textData !== "")
|
|
445
|
-
|
|
633
|
+
parentNode.add(this.options.textNodeName, textData);
|
|
446
634
|
textData = "";
|
|
447
635
|
}
|
|
448
636
|
return textData;
|
|
449
637
|
}
|
|
450
638
|
|
|
451
|
-
//TODO: use jPath to simplify the logic
|
|
452
639
|
/**
|
|
453
|
-
*
|
|
454
|
-
* @param {
|
|
455
|
-
* @param {string} jPath
|
|
456
|
-
* @param {string} currentTagName
|
|
640
|
+
* @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
|
|
641
|
+
* @param {Matcher} matcher - Current path matcher
|
|
457
642
|
*/
|
|
458
|
-
function isItStopNode(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if( allNodesExp === stopNodeExp || jPath === stopNodeExp ) return true;
|
|
463
|
-
}
|
|
464
|
-
return false;
|
|
643
|
+
function isItStopNode() {
|
|
644
|
+
if (this.stopNodeExpressionsSet.size === 0) return false;
|
|
645
|
+
|
|
646
|
+
return this.matcher.matchesAny(this.stopNodeExpressionsSet);
|
|
465
647
|
}
|
|
466
648
|
|
|
467
649
|
/**
|
|
@@ -470,63 +652,75 @@ function isItStopNode(stopNodes, jPath, currentTagName){
|
|
|
470
652
|
* @param {number} i starting index
|
|
471
653
|
* @returns
|
|
472
654
|
*/
|
|
473
|
-
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
655
|
+
function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
|
|
656
|
+
//TODO: ignore boolean attributes in tag expression
|
|
657
|
+
//TODO: if ignore attributes, dont read full attribute expression but the end. But read for xml declaration
|
|
658
|
+
let attrBoundary = 0;
|
|
659
|
+
const len = xmlData.length;
|
|
660
|
+
const closeCode0 = closingChar.charCodeAt(0);
|
|
661
|
+
const closeCode1 = closingChar.length > 1 ? closingChar.charCodeAt(1) : -1;
|
|
662
|
+
|
|
663
|
+
let result = '';
|
|
664
|
+
let segmentStart = i;
|
|
665
|
+
|
|
666
|
+
for (let index = i; index < len; index++) {
|
|
667
|
+
const code = xmlData.charCodeAt(index);
|
|
668
|
+
|
|
478
669
|
if (attrBoundary) {
|
|
479
|
-
|
|
480
|
-
} else if (
|
|
481
|
-
|
|
482
|
-
} else if (
|
|
483
|
-
if(
|
|
484
|
-
if(xmlData
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
index: index
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}else{
|
|
491
|
-
return {
|
|
492
|
-
data: tagExp,
|
|
493
|
-
index: index
|
|
670
|
+
if (code === attrBoundary) attrBoundary = 0;
|
|
671
|
+
} else if (code === 34 || code === 39) { // " or '
|
|
672
|
+
attrBoundary = code;
|
|
673
|
+
} else if (code === closeCode0) {
|
|
674
|
+
if (closeCode1 !== -1) {
|
|
675
|
+
if (xmlData.charCodeAt(index + 1) === closeCode1) {
|
|
676
|
+
result += xmlData.substring(segmentStart, index);
|
|
677
|
+
return { data: result, index };
|
|
494
678
|
}
|
|
679
|
+
} else {
|
|
680
|
+
result += xmlData.substring(segmentStart, index);
|
|
681
|
+
return { data: result, index };
|
|
495
682
|
}
|
|
496
|
-
} else if (
|
|
497
|
-
|
|
683
|
+
} else if (code === 9 && !attrBoundary) { // \t - only replace with space outside attribute values
|
|
684
|
+
// Flush accumulated segment, add space, start new segment
|
|
685
|
+
result += xmlData.substring(segmentStart, index) + ' ';
|
|
686
|
+
segmentStart = index + 1;
|
|
498
687
|
}
|
|
499
|
-
tagExp += ch;
|
|
500
688
|
}
|
|
501
689
|
}
|
|
502
690
|
|
|
503
|
-
function findClosingIndex(xmlData, str, i, errMsg){
|
|
691
|
+
function findClosingIndex(xmlData, str, i, errMsg) {
|
|
504
692
|
const closingIndex = xmlData.indexOf(str, i);
|
|
505
|
-
if(closingIndex === -1){
|
|
693
|
+
if (closingIndex === -1) {
|
|
506
694
|
throw new Error(errMsg)
|
|
507
|
-
}else{
|
|
695
|
+
} else {
|
|
508
696
|
return closingIndex + str.length - 1;
|
|
509
697
|
}
|
|
510
698
|
}
|
|
511
699
|
|
|
512
|
-
function
|
|
513
|
-
const
|
|
514
|
-
if(
|
|
700
|
+
function findClosingChar(xmlData, char, i, errMsg) {
|
|
701
|
+
const closingIndex = xmlData.indexOf(char, i);
|
|
702
|
+
if (closingIndex === -1) throw new Error(errMsg);
|
|
703
|
+
return closingIndex; // no offset needed
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function readTagExp(xmlData, i, removeNSPrefix, closingChar = ">") {
|
|
707
|
+
const result = tagExpWithClosingIndex(xmlData, i + 1, closingChar);
|
|
708
|
+
if (!result) return;
|
|
515
709
|
let tagExp = result.data;
|
|
516
710
|
const closeIndex = result.index;
|
|
517
711
|
const separatorIndex = tagExp.search(/\s/);
|
|
518
712
|
let tagName = tagExp;
|
|
519
713
|
let attrExpPresent = true;
|
|
520
|
-
if(separatorIndex !== -1){//separate tag name and attributes expression
|
|
714
|
+
if (separatorIndex !== -1) {//separate tag name and attributes expression
|
|
521
715
|
tagName = tagExp.substring(0, separatorIndex);
|
|
522
716
|
tagExp = tagExp.substring(separatorIndex + 1).trimStart();
|
|
523
717
|
}
|
|
524
718
|
|
|
525
719
|
const rawTagName = tagName;
|
|
526
|
-
if(removeNSPrefix){
|
|
720
|
+
if (removeNSPrefix) {
|
|
527
721
|
const colonIndex = tagName.indexOf(":");
|
|
528
|
-
if(colonIndex !== -1){
|
|
529
|
-
tagName = tagName.substr(colonIndex+1);
|
|
722
|
+
if (colonIndex !== -1) {
|
|
723
|
+
tagName = tagName.substr(colonIndex + 1);
|
|
530
724
|
attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
|
|
531
725
|
}
|
|
532
726
|
}
|
|
@@ -545,47 +739,52 @@ function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
|
|
|
545
739
|
* @param {string} tagName
|
|
546
740
|
* @param {number} i
|
|
547
741
|
*/
|
|
548
|
-
function readStopNodeData(xmlData, tagName, i){
|
|
742
|
+
function readStopNodeData(xmlData, tagName, i) {
|
|
549
743
|
const startIndex = i;
|
|
550
744
|
// Starting at 1 since we already have an open tag
|
|
551
745
|
let openTagCount = 1;
|
|
552
746
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
747
|
+
const xmllen = xmlData.length;
|
|
748
|
+
for (; i < xmllen; i++) {
|
|
749
|
+
if (xmlData[i] === "<") {
|
|
750
|
+
const c1 = xmlData.charCodeAt(i + 1);
|
|
751
|
+
if (c1 === 47) {//close tag '/'
|
|
752
|
+
const closeIndex = findClosingChar(xmlData, ">", i, `${tagName} is not closed`);
|
|
753
|
+
let closeTagName = xmlData.substring(i + 2, closeIndex).trim();
|
|
754
|
+
if (closeTagName === tagName) {
|
|
755
|
+
openTagCount--;
|
|
756
|
+
if (openTagCount === 0) {
|
|
757
|
+
return {
|
|
758
|
+
tagContent: xmlData.substring(startIndex, i),
|
|
759
|
+
i: closeIndex
|
|
565
760
|
}
|
|
566
761
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
762
|
+
}
|
|
763
|
+
i = closeIndex;
|
|
764
|
+
} else if (c1 === 63) { //?
|
|
765
|
+
const closeIndex = findClosingIndex(xmlData, "?>", i + 1, "StopNode is not closed.")
|
|
766
|
+
i = closeIndex;
|
|
767
|
+
} else if (c1 === 33
|
|
768
|
+
&& xmlData.charCodeAt(i + 2) === 45
|
|
769
|
+
&& xmlData.charCodeAt(i + 3) === 45) { // '!--'
|
|
770
|
+
const closeIndex = findClosingIndex(xmlData, "-->", i + 3, "StopNode is not closed.")
|
|
771
|
+
i = closeIndex;
|
|
772
|
+
} else if (c1 === 33
|
|
773
|
+
&& xmlData.charCodeAt(i + 2) === 91) { // '!['
|
|
774
|
+
const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
|
|
775
|
+
i = closeIndex;
|
|
776
|
+
} else {
|
|
777
|
+
const tagData = readTagExp(xmlData, i, false)
|
|
579
778
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
i=tagData.closeIndex;
|
|
779
|
+
if (tagData) {
|
|
780
|
+
const openTagName = tagData && tagData.tagName;
|
|
781
|
+
if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length - 1] !== "/") {
|
|
782
|
+
openTagCount++;
|
|
586
783
|
}
|
|
784
|
+
i = tagData.closeIndex;
|
|
587
785
|
}
|
|
588
786
|
}
|
|
787
|
+
}
|
|
589
788
|
}//end for loop
|
|
590
789
|
}
|
|
591
790
|
|
|
@@ -593,8 +792,8 @@ function parseValue(val, shouldParse, options) {
|
|
|
593
792
|
if (shouldParse && typeof val === 'string') {
|
|
594
793
|
//console.log(options)
|
|
595
794
|
const newval = val.trim();
|
|
596
|
-
if(newval === 'true'
|
|
597
|
-
else if(newval === 'false'
|
|
795
|
+
if (newval === 'true') return true;
|
|
796
|
+
else if (newval === 'false') return false;
|
|
598
797
|
else return toNumber(val, options);
|
|
599
798
|
} else {
|
|
600
799
|
if (isExist(val)) {
|
|
@@ -604,3 +803,36 @@ function parseValue(val, shouldParse, options) {
|
|
|
604
803
|
}
|
|
605
804
|
}
|
|
606
805
|
}
|
|
806
|
+
|
|
807
|
+
function fromCodePoint(str, base, prefix) {
|
|
808
|
+
const codePoint = Number.parseInt(str, base);
|
|
809
|
+
|
|
810
|
+
if (codePoint >= 0 && codePoint <= 0x10FFFF) {
|
|
811
|
+
return String.fromCodePoint(codePoint);
|
|
812
|
+
} else {
|
|
813
|
+
return prefix + str + ";";
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function transformTagName(fn, tagName, tagExp, options) {
|
|
818
|
+
if (fn) {
|
|
819
|
+
const newTagName = fn(tagName);
|
|
820
|
+
if (tagExp === tagName) {
|
|
821
|
+
tagExp = newTagName
|
|
822
|
+
}
|
|
823
|
+
tagName = newTagName;
|
|
824
|
+
}
|
|
825
|
+
tagName = sanitizeName(tagName, options);
|
|
826
|
+
return { tagName, tagExp };
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
function sanitizeName(name, options) {
|
|
832
|
+
if (criticalProperties.includes(name)) {
|
|
833
|
+
throw new Error(`[SECURITY] Invalid name: "${name}" is a reserved JavaScript keyword that could cause prototype pollution`);
|
|
834
|
+
} else if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
|
|
835
|
+
return options.onDangerousProperty(name);
|
|
836
|
+
}
|
|
837
|
+
return name;
|
|
838
|
+
}
|