Skip to content

Commit 71dc2d3

Browse files
committed
update path-expression-matcher for performance
1 parent e868ac5 commit 71dc2d3

File tree

4 files changed

+48
-60
lines changed

4 files changed

+48
-60
lines changed

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
],
8989
"dependencies": {
9090
"fast-xml-builder": "^1.1.4",
91-
"path-expression-matcher": "^1.2.0",
91+
"path-expression-matcher": "^1.2.1",
9292
"strnum": "^2.2.2"
9393
}
9494
}

src/xmlparser/OrderedObjParser.js

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -217,92 +217,80 @@ function buildAttributesMap(attrStr, jPath, tagName) {
217217
const len = matches.length; //don't make it inline
218218
const attrs = {};
219219

220-
if (this.options.jPath === false) {
221-
// First pass: parse all attributes and update matcher with raw values
222-
// This ensures the matcher has all attribute values when processors run
223-
const rawAttrsForMatcher = {};
224-
for (let i = 0; i < len; i++) {
225-
const attrName = this.resolveNameSpace(matches[i][1]);
226-
const oldVal = matches[i][4];
227-
228-
if (attrName.length && oldVal !== undefined) {
229-
let parsedVal = oldVal;
230-
if (this.options.trimValues) {
231-
parsedVal = parsedVal.trim();
232-
}
233-
parsedVal = this.replaceEntitiesValue(parsedVal, tagName, this.readonlyMatcher);
234-
rawAttrsForMatcher[attrName] = parsedVal;
235-
}
236-
}
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;
224+
const rawAttrsForMatcher = {};
225+
226+
for (let i = 0; i < len; i++) {
227+
const attrName = this.resolveNameSpace(matches[i][1]);
228+
const oldVal = matches[i][4];
229+
230+
if (attrName.length && oldVal !== undefined) {
231+
let val = oldVal;
232+
if (this.options.trimValues) val = val.trim();
233+
val = this.replaceEntitiesValue(val, tagName, this.readonlyMatcher);
234+
processedVals[i] = val;
237235

238-
// Update matcher with raw attribute values BEFORE running processors
239-
if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) {
240-
jPath.updateCurrent(rawAttrsForMatcher);
236+
rawAttrsForMatcher[attrName] = val;
237+
hasRawAttrs = true;
241238
}
242239
}
243240

244-
// Second pass: now process attributes with matcher having full attribute context
241+
// Update matcher ONCE before second pass, if applicable
242+
if (hasRawAttrs && typeof jPath === 'object' && jPath.updateCurrent) {
243+
jPath.updateCurrent(rawAttrsForMatcher);
244+
}
245+
246+
// Hoist toString() once — path doesn't change during attribute processing
245247
const jPathStr = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
246248

249+
// Second pass: apply processors, build final attrs
250+
let hasAttrs = false;
247251
for (let i = 0; i < len; i++) {
248252
const attrName = this.resolveNameSpace(matches[i][1]);
249253

250-
// Convert jPath to string if needed for ignoreAttributesFn
251-
if (this.ignoreAttributesFn(attrName, jPathStr)) {
252-
continue
253-
}
254+
if (this.ignoreAttributesFn(attrName, jPathStr)) continue;
254255

255-
let oldVal = matches[i][4];
256256
let aName = this.options.attributeNamePrefix + attrName;
257257

258258
if (attrName.length) {
259259
if (this.options.transformAttributeName) {
260260
aName = this.options.transformAttributeName(aName);
261261
}
262-
//if (aName === "__proto__") aName = "#__proto__";
263262
aName = sanitizeName(aName, this.options);
264263

265-
if (oldVal !== undefined) {
266-
if (this.options.trimValues) {
267-
oldVal = oldVal.trim();
268-
}
269-
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];
270267

271-
// Pass jPath string or readonlyMatcher based on options.jPath setting
272-
const jPathOrMatcher = this.options.jPath ? jPath.toString() : this.readonlyMatcher;
273-
const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher);
268+
const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathStr);
274269
if (newVal === null || newVal === undefined) {
275-
//don't parse
276270
attrs[aName] = oldVal;
277271
} else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
278-
//overwrite
279272
attrs[aName] = newVal;
280273
} else {
281-
//parse
282-
attrs[aName] = parseValue(
283-
oldVal,
284-
this.options.parseAttributeValue,
285-
this.options.numberParseOptions
286-
);
274+
attrs[aName] = parseValue(oldVal, this.options.parseAttributeValue, this.options.numberParseOptions);
287275
}
276+
hasAttrs = true;
288277
} else if (this.options.allowBooleanAttributes) {
289278
attrs[aName] = true;
279+
hasAttrs = true;
290280
}
291281
}
292282
}
293283

294-
if (!Object.keys(attrs).length) {
295-
return;
296-
}
284+
if (!hasAttrs) return;
285+
297286
if (this.options.attributesGroupName) {
298287
const attrCollection = {};
299288
attrCollection[this.options.attributesGroupName] = attrs;
300289
return attrCollection;
301290
}
302-
return attrs
291+
return attrs;
303292
}
304293
}
305-
306294
const parseXml = function (xmlData) {
307295
xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
308296
const xmlObj = new xmlNode('!xml');

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3149,10 +3149,10 @@ path-exists@^4.0.0:
31493149
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
31503150
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
31513151

3152-
path-expression-matcher@^1.1.3, path-expression-matcher@^1.2.0:
3153-
version "1.2.0"
3154-
resolved "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz"
3155-
integrity sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==
3152+
path-expression-matcher@^1.1.3, path-expression-matcher@^1.2.1:
3153+
version "1.2.1"
3154+
resolved "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.1.tgz"
3155+
integrity sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw==
31563156

31573157
path-is-absolute@^1.0.0:
31583158
version "1.0.1"

0 commit comments

Comments
 (0)