We can customize parsing by providing different options to the parser;
const {XMLParser} = require('fast-xml-parser');
const options = {
ignoreAttributes : false
};
const parser = new XMLParser(options);
let jsonObj = parser.parse(xmlDataStr);
⚠️ Security Note: If accepting untrusted JSON input, sanitize/validate property names before passing to the builder. Reserved property names like your configured commentPropertyName, cdataPropertyName , or attribute name after attributeNamePrefix should be blocked or escaped in untrusted input.
Let's understand each option in detail with necessary examples
To allow attributes without value.
By default boolean attributes are ignored
const xmlDataStr = `<root a="nice" checked><a>wow</a></root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : "@_"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_a": "nice",
"a": "wow"
}
}Once you allow boolean attributes, they are parsed and set to true.
const xmlDataStr = `<root a="nice" checked><a>wow</a></root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : "@_",
allowBooleanAttributes: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_a": "nice",
"@_checked": true,
"a": "wow"
}
}FXP creates text node always when preserveOrder: true. Otherwise, it creates a property with the tag name and assign the value directly.
You can force FXP to render a tag with textnode using this option.
Eg
const xmlDataStr = `
<root a="nice" checked>
<a>wow</a>
<a>
wow again
<c> unlimited </c>
</a>
<b>wow phir se</b>
</root>`;
const options = {
ignoreAttributes: false,
// alwaysCreateTextNode: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output when alwaysCreateTextNode: false
{
"root": {
"a": [
"wow",
{
"c": "unlimited",
"#text": "wow again"
}
],
"b": "wow phir se",
"@_a": "nice"
}
}Output when alwaysCreateTextNode: true
{
"root": {
"a": [
{
"#text": "wow"
},
{
"c": {
"#text": "unlimited"
},
"#text": "wow again"
}
],
"b": {
"#text": "wow phir se"
},
"@_a": "nice"
}
}To group all the attributes of a tag under given property name.
const xmlDataStr = `<root a="nice" b="very nice" ><a>wow</a></root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : "@_",
attributesGroupName : "@_"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_": {
"@_a": "nice",
"@_b": "very nice"
},
"a": "wow"
}
}If preserveOrder is set to true, attributesGroupName is ignored (because attributes are already preserved in their original order within the node).
To recognize attributes in the JS object separately. You can prepend some string with each attribute name.
const xmlDataStr = `<root a="nice" ><a>wow</a></root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : "@_"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_a": "nice",
"a": "wow"
}
}Similar to tagValueProcessor but applied to attributes.
Important Notes:
-
Pattern Matching on Original Values: When using
attributeValueProcessorwith pattern matching (Expression patterns ormatcher.getAttrValue()), patterns are matched against the original attribute values from the XML, not the transformed values returned by the processor. -
jPath Parameter: The third parameter is either a string (when
jPath: true, default) or a Matcher instance (whenjPath: false).
Signature:
attributeValueProcessor: (attrName, attrValue, jPathOrMatcher) => {
// Return modified value, or original value to keep unchanged
}Example with Original Value Matching:
const xmlData = `
<root>
<user id="123" role="admin">Alice</user>
</root>`;
const options = {
ignoreAttributes: false,
jPath: false,
attributeValueProcessor: (name, val, matcher) => {
// matcher.getAttrValue('role') returns "admin" (original value)
// Even if we modify 'role' attribute, matcher still sees "admin"
if (name === 'id' && matcher.getAttrValue('role') === 'admin') {
return 'ADMIN-' + val; // Transforms to "ADMIN-123"
}
if (name === 'role') {
return val.toUpperCase(); // Transforms to "ADMIN"
}
return val;
}
};
const parser = new XMLParser(options);
const result = parser.parse(xmlData);
// Result:
// {
// "root": {
// "user": {
// "@_id": "ADMIN-123", // Modified value in output
// "@_role": "ADMIN", // Modified value in output
// "#text": "Alice"
// }
// }
// }
// But during processing:
// - matcher.getAttrValue('role') always returned "admin" (original)
// - Pattern matching uses original valuesWith jPath: true (default):
const options = {
ignoreAttributes: false,
attributeValueProcessor: (name, val, jPath) => {
console.log(jPath); // String: "root.user"
return val;
}
};With jPath: false:
const options = {
ignoreAttributes: false,
jPath: false,
attributeValueProcessor: (name, val, matcher) => {
console.log(matcher.toString()); // String: "root.user"
console.log(matcher.getCurrentTag()); // "user"
console.log(matcher.getAttrValue('id')); // Access other attributes
return val;
}
};If cdataPropName is not set to some property name, then CDATA values are merged to tag's text value.
Eg
const xmlDataStr = `
<a>
<name>name:<![CDATA[<some>Jack</some>]]><![CDATA[Jack]]></name>
</a>`;
const parser = new XMLParser();
const output = parser.parse(xmlDataStr);Output
{
"a": {
"name": "name:<some>Jack</some>Jack"
}
}When cdataPropName is set to some property then text value and CDATA are parsed to different properties.
Example 2
const options = {
cdataPropName: "__cdata"
}Output
{
"a": {
"name": {
"__cdata": [
"<some>Jack</some>",
"Jack"
],
"#text": "name:"
}
}
}If commentPropName is set to some property name, then comments are parsed from XML.
Eg
const xmlDataStr = `
<!--Students grades are uploaded by months-->
<class_list>
<student>
<!--Student details-->
<!--A second comment-->
<name>Tanmay</name>
<grade>A</grade>
</student>
</class_list>`;
const options = {
commentPropName: "#comment"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"#comment": "Students grades are uploaded by months",
"class_list": {
"student": {
"#comment": [
"Student details",
"A second comment"
],
"name": "Tanmay",
"grade": "A"
}
}
}FXP by default parse XML entities if processEntities: true. You can set htmlEntities to parse HTML entities. Check entities section for more information.
entityDecoder option is supported v5.7.0 onward. It allows you to set your own entity decoder which supports following methods.
type EntityDecoderOptions = {
setExternalEntities: (entities: Record<string, string>) => void;
addInputEntities: (entities: Record<string, string>) => void;
reset: () => void;
decode: (text: string) => string;
setXmlVersion: (version: number) => void;
}
We're not explicitely checking the type and required methods but parser may throw error if the provided object doesn't have the required methods.
If XML document has version attribute in xml declaration then it will be passed to setXmlVersion method of the decoder to handle NCR gracefully. You dont have to set ignoreAttributes to false to read attributes from xml decalaration.
When processEntities is set to false then entityDecoder will not be used. But if 'true' and entityDecoder is not provided then default entity decoder (@nodable/entities) will be used.
When entityDecoder is provided then following options which are set to parser directly will be ignored. User is expected them to set right config in decoder's config.
- htmlEntities
- maxTotalExpansions
- maxExpandedLength
If your XML has NCR(Numeric Character Reference) in XML document then the response can be changed baced on XML version read from declaration tag. If not set then '1.0' is considered as default
You can read more detail for advance handling in @nodable/entities configuration.
⚠️ It is advisable to use thepostCheckfunction to safely handle malicious data.- FXP overrides
onInputEntityto check entity value if marked unsafe by is-unsafe. You may override for better protection of your project specific security.
onInputEntity: (name, value) =>
isUnsafe(value, [VALID_CONTEXTS.HTML, VALID_CONTEXTS.XML])
? ENTITY_ACTION.BLOCK : ENTITY_ACTION.ALLOW- 2 separate entities togehter can form dangerous property name.
By default, ignoreAttributes is set to true. This means that attributes are ignored by the parser. If you set any configuration related to attributes without setting ignoreAttributes: false, it will not have any effect.
You can specify an array of strings, regular expressions, or a callback function to selectively ignore specific attributes during parsing or building.
<tag
ns:attr1="a1-value"
ns:attr2="a2-value"
ns2:attr3="a3-value"
ns2:attr4="a4-value">
value
</tag>You can use the ignoreAttributes option in three different ways:
- Array of Strings: Ignore specific attributes by name.
- Array of Regular Expressions: Ignore attributes that match a pattern.
- Callback Function: Ignore attributes based on custom logic.
const options = {
attributeNamePrefix: "$",
ignoreAttributes: ['ns:attr1', 'ns:attr2'],
parseAttributeValue: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlData);Result:
{
"tag": {
"#text": "value",
"$ns2:attr3": "a3-value",
"$ns2:attr4": "a4-value"
}
}const options = {
attributeNamePrefix: "$",
ignoreAttributes: [/^ns2:/],
parseAttributeValue: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlData);Result:
{
"tag": {
"#text": "value",
"$ns:attr1": "a1-value",
"$ns:attr2": "a2-value"
}
}const options = {
attributeNamePrefix: "$",
ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2',
parseAttributeValue: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlData);Result:
{
"tag": {
"$ns2:attr3": "a3-value",
"$ns2:attr4": "a4-value",
"tag2": "value"
}
}const { Expression } = require('path-expression-matcher');
const xmlData = `
<root>
<user id="1" internal="secret" name="Alice">Alice</user>
<user id="2" internal="topsecret" name="Bob">Bob</user>
</root>`;
const userExpr = new Expression("root.user");
const options = {
ignoreAttributes: false,
jPath: false,
ignoreAttributes: (attrName, matcher) => {
// Ignore 'internal' attribute only for user tags
if (matcher.matches(userExpr) && attrName === 'internal') {
return true;
}
// Can also check current attributes
if (attrName === 'temp' && matcher.hasAttr('deprecated')) {
return true;
}
return false;
}
};
const parser = new XMLParser(options);
const result = parser.parse(xmlData);
// Result: 'internal' attribute is ignored
// {
// "root": {
// "user": [
// { "@_id": "1", "@_name": "Alice", "#text": "Alice" },
// { "@_id": "2", "@_name": "Bob", "#text": "Bob" }
// ]
// }
// }As many users want to ignore XML declaration tag to be ignored from parsing output, they can use ignoreDeclaration: true.
Eg
const xmlDataStr = `<?xml version="1.0"?>
<?elementnames <fred>, <bert>, <harry> ?>
<h1></h1>`;
const options = {
ignoreDeclaration: true,
attributeNamePrefix : "@_"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"?elementnames": "",
"h1": ""
}As many users want to ignore PI tags to be ignored from parsing output, they can use ignorePiTags: true.
Eg
const xmlDataStr = `<?xml version="1.0"?>
<?elementnames <fred>, <bert>, <harry> ?>
<h1></h1>`;
const options = {
ignoreDeclaration: true,
ignorePiTags: true,
attributeNamePrefix : "@_"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"h1": ""
}Whether a single tag should be parsed as an array or an object, it can't be decided by FXP. Hence isArray method can help users to take the decision if a tag should be parsed as an array.
Signature:
isArray: (tagName, jPathOrMatcher, isLeafNode, isAttribute) => {
// Return true to force array, false/undefined for default behavior
}Eg
const xmlDataStr = `
<root a="nice" checked>
<a>wow</a>
<a>
wow again
<c> unlimited </c>
</a>
<b>wow phir se</b>
</root>`;
const alwaysArray = [
"root.a.c",
"root.b"
];
const options = {
ignoreAttributes: false,
//name: is either tagname, or attribute name
//jPath: upto the tag name
isArray: (name, jpath, isLeafNode, isAttribute) => {
if( alwaysArray.indexOf(jpath) !== -1) return true;
}
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_a": "nice",
"a": [
"wow",
{
"#text": "wow again",
"c": [
"unlimited"
]
}
],
"b": [
"wow phir se"
]
}
}Type: boolean
Default: true
Controls whether callbacks receive a jPath string or a Matcher instance.
- When
jPath: true(default): Callbacks receive the path as a string (e.g.,"root.user") - When
jPath: false: Callbacks receive aMatcherinstance with advanced querying capabilities
Example:
const { XMLParser } = require('fast-xml-parser');
const { Expression } = require('path-expression-matcher');
const xmlData = `
<root>
<user id="1" type="admin">Alice</user>
<user id="2" type="regular">Bob</user>
</root>`;
const adminExpr = new Expression("..user[type=admin]");
const options = {
ignoreAttributes: false,
jPath: false, // Receive Matcher instead of string
attributeValueProcessor: (attrName, val, matcher) => {
// matcher is a Matcher instance, not a string
console.log('Current path:', matcher.toString()); // "root.user"
console.log('Current tag:', matcher.getCurrentTag()); // "user"
console.log('Has type attr:', matcher.hasAttr('type')); // true
console.log('Type value:', matcher.getAttrValue('type')); // "admin" or "regular"
// Pattern matching
if (attrName === 'id' && matcher.matches(adminExpr)) {
return 'ADMIN-' + val; // Prefix admin user IDs
}
return val;
}
};
const parser = new XMLParser(options);
const result = parser.parse(xmlData);Result:
{
"root": {
"user": [
{ "@_id": "ADMIN-1", "@_type": "admin", "#text": "Alice" },
{ "@_id": "2", "@_type": "regular", "#text": "Bob" }
]
}
}Please refer path-expression-matcher to know what methods are supported by Matcher.
Though it is not recommended to use deeply nested XML, but if you are using it then you can set maxNestedTags option to limit the nesting depth.
Eg
const xmlDataStr = `
<root>
<a>
<b>
<c>
<d>
<e>
<f>
<g>
<h>
<i>
<j>
<k>
<l>
<m>
<n>
<o>
<p>
<q>
<r>
<s/>
</r>
</q>
</p>
</o>
</n>
</m>
</l>
</k>
</j>
</i>
</h>
</g>
</f>
</e>
</d>
</c>
</b>
</a>
</root>`;
const options = {
maxNestedTags: 10 //default is 100
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
Error: Maximum nested tags exceededFXP uses strnum library to parse string into numbers. This property allows you to set configuration for strnum package.
Eg
const xmlDataStr = `
<root>
<a>-0x2f</a>
<a>006</a>
<a>6.00</a>
<a>-01.0E2</a>
<a>+1212121212</a>
</root>`;
const options = {
numberParseOptions: {
leadingZeros: true,
hex: true,
skipLike: /\+[0-9]{10}/,
// eNotation: false
}
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"a": [ -47, 6, 6, -100, "+1212121212" ],
}
}This option allows you to handle dangerous tag names or attribute names.
Eg
const xmlDataStr = `
<root>
<constructor>1</constructor>
<toString>2</toString>
</root>`;
const options = {
onDangerousProperty: (name) => {
return "__" + name;
}
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"constructor": 1,
"toString": 2
}
}By default, it throws an error for critical properties in tag names or attribute names: "__proto__", "constructor", "prototype". And append __ to other dangerous properties: "toString", "valueOf", "hasOwnProperty" ,"__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__".
parseAttributeValue: true option can be set to parse attributes value. This option uses strnum package to parse numeric values. For more controlled parsing check numberParseOptions option.
Eg
const xmlDataStr = `
<root a="nice" checked enabled="true" int="32" int="34">
<a>wow</a>
</root>`;
const options = {
ignoreAttributes: false,
// parseAttributeValue: true,
allowBooleanAttributes: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"a": "wow",
"@_a": "nice",
"@_checked": true,
"@_enabled": "true",
"@_int": "34"
}
}Boolean attributes are always parsed to true. XML Parser doesn't give error if validation is kept off so it'll overwrite the value of repeated attributes.
When parseAttributeValue: true
{
"root": {
"a": "wow",
"@_a": "nice",
"@_checked": true,
"@_enabled": true,
"@_int": 34
}
}When validation options are set or true.
//..
const output = parser.parse(xmlDataStr, {
allowBooleanAttributes: true
});
//..Output
Error: Attribute 'int' is repeated.:1:48parseTagValue: true option can be set to parse tags value. This option uses strnum package to parse numeric values. For more controlled parsing check numberParseOptions option.
Eg
const xmlDataStr = `
<root>
35<nested>34</nested>
</root>`;
const options = {
parseTagValue: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": 35
}Example 2 Input
<root>
35<nested>34</nested>
</root>Output
{
"root": {
"nested": 34,
"#text": 35
}
}Example 3 Input
<root>
35<nested>34</nested>46
</root>Output
{
"root": {
"nested": 34,
"#text": "3546"
}
}When preserveOrder: true , you get some long ugly result. That's because this option is used to keep the order of tags in result js Object. It also helps XML builder to build the similar kind of XML from the js object without losing information.
const XMLdata = `
<!--Students grades are uploaded by months-->
<class_list standard="3">
<student>
<!--Student details-->
<!--A second comment-->
<name>Tanmay</name>
<grade>A</grade>
</student>
</class_list>`;
const options = {
commentPropName: "#comment",
preserveOrder: true
};
const parser = new XMLParser(options);
let result = parser.parse(XMLdata);[
{
"#comment": [
{ "#text": "Students grades are uploaded by months" }
]
},
{
"class_list": [
{
"student": [
{
"#comment": [
{ "#text": "Student details" }
]
},
{
"#comment": [
{ "#text": "A second comment" }
]
},
{
"name": [
{ "#text": "Tanmay" }
]
},
{
"grade": [
{ "#text": "A" }
]
}
]
}
],
":@": {
"standard" : "3"
}
}
]-
false (Recommended): The parser will recognize the
<!DOCTYPE>and<!ENTITY>tags but will not perform any string substitution. This prevents Entity Expansion (DoS) attacks while allowing the rest of the XML to be parsed normally, even if the DOCTYPE internal subset is complex. -
true: The parser will actively replace all
&entity;references with their defined values. Use with caution: only enable this for trusted XML sources to avoid resource exhaustion.
heck Entities section for more detail.
Remove namespace string from tag and attribute names.
Default is removeNSPrefix: false
const xmlDataStr = `<root some:a="nice" ><any:a>wow</any:a></root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : "@_"
//removeNSPrefix: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_some:a": "nice",
"any:a": "wow"
}
}Setting removeNSPrefix: true
const xmlDataStr = `<root some:a="nice" ><any:a>wow</any:a></root>`;
const options = {
ignoreAttributes: false,
attributeNamePrefix : "@_",
removeNSPrefix: true
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"@_a": "nice",
"a": "wow"
}
}Stop nodes are tags whose content should be parsed as raw text instead of being processed as XML. This is useful for tags like <script>, <style>, or <pre> where you want to preserve the exact content.
Type: Array<string | Expression>
Default: []
You can specify stop nodes in two ways:
const options = {
stopNodes: [
"root.a", // Exact path
"*.pre", // Any 'pre' tag
"*.script" // Any 'script' tag
]
};Note: The *.tagname syntax has been converted internally to ..tagname for compatibility with the Expression matcher. Both work the same way.
Using Expression from path-expression-matcher gives you powerful pattern matching capabilities including attribute-based and namespace-based matching.
const { XMLParser } = require('fast-xml-parser');
const { Expression } = require('path-expression-matcher');
const xmlData = `
<root>
<div class="normal">
<span>This will be parsed</span>
</div>
<div class="code">
<span>This will be raw text</span>
</div>
<script src="app.js">
console.log("This will be raw text");
</script>
</root>`;
const options = {
ignoreAttributes: false,
stopNodes: [
new Expression('..script'), // Any script tag
new Expression('..style'), // Any style tag
new Expression('..div[class=code]'), // div with class="code"
new Expression('..pre[lang=xml]'), // pre with lang="xml"
new Expression('root.data.raw') // Exact path
]
};
const parser = new XMLParser(options);
const result = parser.parse(xmlData);Result:
{
"root": {
"div": [
{
"@_class": "normal",
"span": "This will be parsed"
},
{
"@_class": "code",
"#text": "\n <span>This will be raw text</span>\n "
}
],
"script": {
"@_src": "app.js",
"#text": "\n console.log(\"This will be raw text\");\n "
}
}
}Basic Patterns:
"root.users.user"- Exact path match"*.user"- Any parent (single wildcard)"..user"- Anywhere in tree (deep wildcard)
Attribute Matching:
"user[id]"- user with "id" attribute (any value)"user[type=admin]"- user with type="admin""..div[class=code]"- div with class="code" anywhere
Namespace Matching:
"soap::Envelope"- Envelope with namespace "soap""..ns::UserId"- UserId with namespace "ns" anywhere"*::user"- user with any namespace
Position Selectors:
"user:first"- First occurrence of user"user:nth(2)"- Third user (zero-based)"item:odd"- Odd-numbered items
Combined Patterns:
"..div[class=code]:first"- First div with class="code""soap::Envelope.soap::Body..ns::UserId"- Complex path with namespaces
Important Notes:
-
Attribute Values for Matching: Pattern matching uses the original attribute values from the XML, not values transformed by
attributeValueProcessor. -
Nested Stop Nodes: Stop nodes should not contain the same closing tag in their content:
<!-- Invalid -->
<stop>
invalid </stop>
</stop>- Self-Closing Stop Nodes: Fully supported:
<script src="app.js" />
<!-- Works correctly, no content to preserve -->For more details on Expression patterns, see the path-expression-matcher documentation.
Note that a stop node should not have same closing node in contents. Eg
<stop>
invalid </stop>
</stop>nested stop notes are also not allowed
<stop>
<stop> invalid </stop>
</stop>By default, FXP will throw an error if it encounters a tag or attribute name that matches a reserved property name set by user or default(e.g., __comment, __cdata).
Setting strictReservedNames: false allows the parser to continue processing even if such names are found, treating them as regular tag or attribute names.
Example:
const xmlDataStr = `
<root>
<__comment>1</__comment>
</root>`;
const options = {
commentPropName: "__comment",
strictReservedNames: false
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output:
{
"root": {
"__comment": 1
}
}With tagValueProcessor you can control how and which tag value should be parsed.
- If
tagValueProcessorreturnsundefinedornullthen original value would be set without parsing. - If it returns different value or value with different data type then new value would be set without parsing.
- Otherwise original value would be set after parsing (if
parseTagValue: true) - if tag value is empty then
tagValueProcessorwill not be called.
Signature:
tagValueProcessor: (tagName, tagValue, jPathOrMatcher, hasAttributes, isLeafNode) => {
// Return modified value
}With jPath: true (default):
const options = {
ignoreAttributes: false,
tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => {
console.log(jPath); // String: "root.a"
if (isLeafNode) return tagValue;
return "";
}
};With jPath: false:
const { Expression } = require('path-expression-matcher');
const options = {
ignoreAttributes: false,
jPath: false,
tagValueProcessor: (tagName, tagValue, matcher, hasAttributes, isLeafNode) => {
console.log(matcher.toString()); // String: "root.a"
console.log(matcher.getCurrentTag()); // "a"
// Pattern matching
const expr = new Expression("root.a");
if (matcher.matches(expr)) {
return tagValue.toUpperCase();
}
return tagValue;
}
};Eg
const xmlDataStr = `
<root a="nice" checked>
<a>wow</a>
<a>
wow again
<c> unlimited </c>
</a>
<b>wow phir se</b>
</root>`;
const options = {
ignoreAttributes: false,
tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => {
if(isLeafNode) return tagValue;
return "";
}
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"a": [
{
"#text": "wow",
"@_a": "2"
},
{
"c": {
"#text": "unlimited",
"@_a": "2"
},
"@_a": "2"
}
],
"b": {
"#text": "wow phir se",
"@_a": "2"
},
"@_a": "nice"
}
}Text value of a tag is parsed to #text property by default. You can always change this.
Eg
const xmlDataStr = `
<a>
text<b>alpha</b>
</a>`;
const options = {
ignoreAttributes: false,
textNodeName: "$text"
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"a": {
"b": "alpha",
"$text": "text"
}
}transformTagName property function let you change the name of a tag as per your need. Eg 'red-code' can be transformed to 'redCode' or 'redCode' can be transformed to 'redcode'.
transformTagName: (tagName) => tagName.toLowerCase()transformAttributeName property function let you modify the name of attribute
transformAttributeName: (attributeName) => attributeName.toLowerCase()Remove surrounding whitespace from tag or attribute value.
Eg
const xmlDataStr = `
<root attri=" ibu te ">
35 <nested> 34</nested>
</root>`;
const options = {
ignoreAttributes: false,
parseTagValue: true, //default
trimValues: false
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"root": {
"nested": 34,
"#text": "\n 35 \n ",
"@_attri": " ibu te "
}
}If the tag value consists of whitespace only and trimValues: false then value will not be parsed even if parseTagValue:true. Similarly, if trimValues: true and parseTagValue:false then surrounding whitespace will be removed.
const options = {
ignoreAttributes: false,
parseTagValue: false,
trimValues: true //default
};Output
{
"root": {
"nested": "34",
"#text": "35",
"@_attri": "ibu te"
}
}Unpaired Tags are the tags which don't have matching closing tag. Eg <br> in HTML. You can parse unpaired tags by providing their list to the parser, validator and builder.
Eg
const xmlDataStr = `
<rootNode>
<tag>value</tag>
<empty />
<unpaired>
<unpaired />
<unpaired>
</rootNode>`;
const options = {
unpairedTags: ["unpaired"]
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);Output
{
"rootNode": {
"tag": "value",
"empty": "",
"unpaired": [ "", "", ""]
}
}Note: Unpaired tag can't be used as closing tag e.g. </unpaired> is not allowed.
This property allow you to change tag name when you send different name, skip a tag from the parsing result when you return false, or to change attributes.
Signature:
updateTag: (tagName, jPathOrMatcher, attrs) => {
// Return new tag name (string), false to skip, or undefined to keep original
}
⚠️ You should ensure that any change in tagname or attributes don't form a dangerous property.
With jPath: true (default):
const options = {
attributeNamePrefix: "",
ignoreAttributes: false,
updateTag(tagName, jPath, attrs) {
attrs["At"] = "Home";
delete attrs["at"];
if (tagName === "a") return "A";
else if (tagName === "b") return false;
}
};With jPath: false:
const { Expression } = require('path-expression-matcher');
const options = {
ignoreAttributes: false,
jPath: false,
updateTag(tagName, matcher, attrs) {
// Pattern matching
const expr = new Expression("root.oldName");
if (matcher.matches(expr)) {
return "newName";
}
// Access current path
console.log(matcher.toString());
return tagName; // Keep original
}
};If captureMetaData is true, then a Symbol is added to nodes with metadata about that node.
XMLParser.getMetaDataSymbol() will return the symbol. Being a Symbol, this property
will not normally appear in, for example, Object.keys.
The MetaData object is not available for nodes that resolve as strings or arrays.
const parser = new XMLParser({ignoreAttributes: false, captureMetaData: true});
const xml = `<root><thing name="zero"/><thing name="one"/></root>`;
const jsonObj = parser.parse(xml);
const META_DATA_SYMBOL = XMLParser.getMetaDataSymbol();
// get the char offsets of the tag <thing name="zero"/>
const thingZero = jsonObj.root.thing[0];
const thingZeroMetaData = thingZero[META_DATA_SYMBOL];
const thingZeroStartIndex = thingZeroMetaData.startIndex; // 6
const thingZeroEndIndex = thingZeroMetaData.endIndex; // 26
xml.slice(thingZeroStartIndex, thingZeroEndIndex); // '<thing name="zero"/>'