diff --git a/docs/v4/2.XMLparseOptions.md b/docs/v4/2.XMLparseOptions.md
index b59f125c..1f097601 100644
--- a/docs/v4/2.XMLparseOptions.md
+++ b/docs/v4/2.XMLparseOptions.md
@@ -280,24 +280,95 @@ FXP by default parse XMl entities if `processEntities: true`. You can set `htmlE
## ignoreAttributes
-By default `ignoreAttributes` is set to `true`. It means, attributes are ignored by the parser. If you set any configuration related to attributes without setting `ignoreAttributes: false`, it is useless.
+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.
+
+### Selective Attribute Ignoring
+
+You can specify an array of strings, regular expressions, or a callback function to selectively ignore specific attributes during parsing or building.
+
+### Example Input XML
+
+```xml
+
+ value
+
+```
+
+You can use the `ignoreAttributes` option in three different ways:
+
+1. **Array of Strings**: Ignore specific attributes by name.
+2. **Array of Regular Expressions**: Ignore attributes that match a pattern.
+3. **Callback Function**: Ignore attributes based on custom logic.
+
+### Example: Ignoring Attributes by Array of Strings
-Eg
```js
-const xmlDataStr = `wow`;
+const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: ['ns:attr1', 'ns:attr2'],
+ parseAttributeValue: true
+};
+const parser = new XMLParser(options);
+const output = parser.parse(xmlData);
+```
+
+Result:
+```json
+{
+ "tag": {
+ "#text": "value",
+ "$ns2:attr3": "a3-value",
+ "$ns2:attr4": "a4-value"
+ }
+}
+```
+### Example: Ignoring Attributes by Regular Expressions
+
+```js
const options = {
- // ignoreAttributes: false,
- attributeNamePrefix : "@_"
+ attributeNamePrefix: "$",
+ ignoreAttributes: [/^ns2:/],
+ parseAttributeValue: true
};
const parser = new XMLParser(options);
-const output = parser.parse(xmlDataStr);
+const output = parser.parse(xmlData);
```
-Output
+
+Result:
```json
{
- "root": {
- "a": "wow"
+ "tag": {
+ "#text": "value",
+ "$ns:attr1": "a1-value",
+ "$ns:attr2": "a2-value"
+ }
+}
+```
+
+### Example: Ignoring Attributes via Callback Function
+
+```js
+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:
+```json
+{
+ "tag": {
+ "$ns2:attr3": "a3-value",
+ "$ns2:attr4": "a4-value",
+ "tag2": "value"
}
}
```
diff --git a/docs/v4/3.XMLBuilder.md b/docs/v4/3.XMLBuilder.md
index 713b1481..20032e6a 100644
--- a/docs/v4/3.XMLBuilder.md
+++ b/docs/v4/3.XMLBuilder.md
@@ -170,7 +170,89 @@ It is recommended to use `preserveOrder: true` when you're parsing XML to js obj
By default, parsed XML is single line XML string. By `format: true`, you can format it for better view.
## ignoreAttributes
-Don't consider attributes while building XML. Other attributes related properties should be set to correctly identifying an attribute property.
+
+By default, the `ignoreAttributes` option skips attributes while building XML. However, you can specify an array of strings, regular expressions, or a callback function to selectively ignore specific attributes during the building process.
+
+### Selective Attribute Ignoring
+
+The `ignoreAttributes` option supports:
+
+1. **Array of Strings**: Ignore specific attributes by name while building XML.
+2. **Array of Regular Expressions**: Ignore attributes that match a pattern while building XML.
+3. **Callback Function**: Ignore attributes based on custom logic during the building process.
+
+### Example Input JSON
+
+```json
+{
+ "tag": {
+ "$ns:attr1": "a1-value",
+ "$ns:attr2": "a2-value",
+ "$ns2:attr3": "a3-value",
+ "$ns2:attr4": "a4-value",
+ "tag2": {
+ "$ns:attr1": "a1-value",
+ "$ns:attr2": "a2-value",
+ "$ns2:attr3": "a3-value",
+ "$ns2:attr4": "a4-value"
+ }
+ }
+}
+```
+
+### Example: Ignoring Attributes by Array of Strings
+
+```js
+const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: ['ns:attr1', 'ns:attr2']
+};
+const builder = new XMLBuilder(options);
+const xmlOutput = builder.build(jsonData);
+```
+
+Result:
+```xml
+
+
+
+```
+
+### Example: Ignoring Attributes by Regular Expressions
+
+```js
+const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: [/^ns2:/]
+};
+const builder = new XMLBuilder(options);
+const xmlOutput = builder.build(jsonData);
+```
+
+Result:
+```xml
+
+
+
+```
+
+### Example: Ignoring Attributes via Callback Function
+
+```js
+const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2'
+};
+const builder = new XMLBuilder(options);
+const xmlOutput = builder.build(jsonData);
+```
+
+Result:
+```xml
+
+
+
+```
## indentBy
Applicable only if `format:true` is set.
diff --git a/spec/attrIgnore_spec.js b/spec/attrIgnore_spec.js
new file mode 100644
index 00000000..485a9ce4
--- /dev/null
+++ b/spec/attrIgnore_spec.js
@@ -0,0 +1,141 @@
+"use strict";
+
+const { XMLParser, XMLBuilder, XMLValidator } = require("../src/fxp");
+
+const xmlData = `
+
+ value
+`;
+
+const jsonData = {
+ tag: {
+ '$ns:attr1': 'a1-value',
+ '$ns:attr2': 'a2-value',
+ '$ns2:attr3': 'a3-value',
+ '$ns2:attr4': 'a4-value',
+ tag2: {
+ '$ns:attr1': 'a1-value',
+ '$ns:attr2': 'a2-value',
+ '$ns2:attr3': 'a3-value',
+ '$ns2:attr4': 'a4-value',
+ }
+ }
+}
+
+describe("XMLParser", function () {
+
+ it('must ignore parsing attributes by array of strings', () => {
+
+ const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: ['ns:attr1', 'ns:attr2'],
+ parseAttributeValue: true
+ };
+ const parser = new XMLParser(options);
+ expect(parser.parse(xmlData)).toEqual({
+ tag: {
+ '#text': 'value',
+ '$ns2:attr3': 'a3-value',
+ '$ns2:attr4': 'a4-value',
+ },
+ })
+
+ expect(XMLValidator.validate(xmlData)).toBe(true);
+ })
+
+ it('must ignore parsing attributes by array of RegExp', () => {
+
+ const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: [/^ns2:/],
+ parseAttributeValue: true
+ };
+ const parser = new XMLParser(options);
+ expect(parser.parse(xmlData)).toEqual({
+ tag: {
+ '#text': 'value',
+ '$ns:attr1': 'a1-value',
+ '$ns:attr2': 'a2-value',
+ },
+ })
+
+ expect(XMLValidator.validate(xmlData)).toBe(true);
+ })
+
+ it('must ignore parsing attributes via callback fn', () => {
+ const xmlData = `
+
+
+ value
+
+ `;
+
+ const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2',
+ parseAttributeValue: true
+ };
+ const parser = new XMLParser(options);
+ expect(parser.parse(xmlData)).toEqual({
+ tag: {
+ '$ns2:attr3': 'a3-value',
+ '$ns2:attr4': 'a4-value',
+ tag2: 'value',
+ },
+ })
+
+ expect(XMLValidator.validate(xmlData)).toBe(true);
+ })
+
+
+ it('must ignore building attributes by array of strings', () => {
+
+ const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: ['ns:attr1', 'ns:attr2'],
+ parseAttributeValue: true
+ };
+ const builder = new XMLBuilder(options);
+ expect(builder.build(jsonData)).toEqual('')
+
+ expect(XMLValidator.validate(xmlData)).toBe(true);
+ })
+
+ it('must ignore building attributes by array of RegExp', () => {
+
+ const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: [/^ns2:/],
+ parseAttributeValue: true
+ };
+ const builder = new XMLBuilder(options);
+ expect(builder.build(jsonData)).toEqual('')
+
+ expect(XMLValidator.validate(xmlData)).toBe(true);
+ })
+
+ it('must ignore building attributes via callback fn', () => {
+
+ const options = {
+ attributeNamePrefix: "$",
+ ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2',
+ parseAttributeValue: true
+ };
+ const builder = new XMLBuilder(options);
+ expect(builder.build(jsonData)).toEqual('')
+
+ expect(XMLValidator.validate(xmlData)).toBe(true);
+ })
+})
\ No newline at end of file
diff --git a/src/fxp.d.ts b/src/fxp.d.ts
index bddcfefe..7a48b9db 100644
--- a/src/fxp.d.ts
+++ b/src/fxp.d.ts
@@ -30,9 +30,17 @@ type X2jOptions = {
/**
* Whether to ignore attributes when parsing
*
+ * When `true` - ignores all the attributes
+ *
+ * When `false` - parses all the attributes
+ *
+ * When `Array` - filters out attributes that match provided patterns
+ *
+ * When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
+ *
* Defaults to `true`
*/
- ignoreAttributes?: boolean;
+ ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Whether to remove namespace string from tag and attribute names
@@ -250,11 +258,19 @@ type XmlBuilderOptions = {
textNodeName?: string;
/**
- * Whether to ignore attributes when parsing
+ * Whether to ignore attributes when building
+ *
+ * When `true` - ignores all the attributes
+ *
+ * When `false` - builds all the attributes
+ *
+ * When `Array` - filters out attributes that match provided patterns
+ *
+ * When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
*
* Defaults to `true`
*/
- ignoreAttributes?: boolean;
+ ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
/**
* Give a property name to set CDATA values to instead of merging to tag's text value
diff --git a/src/ignoreAttributes.js b/src/ignoreAttributes.js
new file mode 100644
index 00000000..9fb346bb
--- /dev/null
+++ b/src/ignoreAttributes.js
@@ -0,0 +1,20 @@
+function getIgnoreAttributesFn(ignoreAttributes) {
+ if (typeof ignoreAttributes === 'function') {
+ return ignoreAttributes
+ }
+ if (Array.isArray(ignoreAttributes)) {
+ return (attrName) => {
+ for (const pattern of ignoreAttributes) {
+ if (typeof pattern === 'string' && attrName === pattern) {
+ return true
+ }
+ if (pattern instanceof RegExp && pattern.test(attrName)) {
+ return true
+ }
+ }
+ }
+ }
+ return () => false
+}
+
+module.exports = getIgnoreAttributesFn
\ No newline at end of file
diff --git a/src/xmlbuilder/json2xml.js b/src/xmlbuilder/json2xml.js
index f30604a4..d35c6921 100644
--- a/src/xmlbuilder/json2xml.js
+++ b/src/xmlbuilder/json2xml.js
@@ -1,6 +1,7 @@
'use strict';
//parse Empty Node as self closing node
const buildFromOrderedJs = require('./orderedJs2Xml');
+const getIgnoreAttributesFn = require('../ignoreAttributes')
const defaultOptions = {
attributeNamePrefix: '@_',
@@ -38,11 +39,12 @@ const defaultOptions = {
function Builder(options) {
this.options = Object.assign({}, defaultOptions, options);
- if (this.options.ignoreAttributes || this.options.attributesGroupName) {
+ if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
this.isAttribute = function(/*a*/) {
return false;
};
} else {
+ this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
this.attrPrefixLen = this.options.attributeNamePrefix.length;
this.isAttribute = isAttribute;
}
@@ -71,13 +73,14 @@ Builder.prototype.build = function(jObj) {
[this.options.arrayNodeName] : jObj
}
}
- return this.j2x(jObj, 0).val;
+ return this.j2x(jObj, 0, []).val;
}
};
-Builder.prototype.j2x = function(jObj, level) {
+Builder.prototype.j2x = function(jObj, level, ajPath) {
let attrStr = '';
let val = '';
+ const jPath = ajPath.join('.')
for (let key in jObj) {
if(!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
if (typeof jObj[key] === 'undefined') {
@@ -100,9 +103,9 @@ Builder.prototype.j2x = function(jObj, level) {
} else if (typeof jObj[key] !== 'object') {
//premitive type
const attr = this.isAttribute(key);
- if (attr) {
+ if (attr && !this.ignoreAttributesFn(attr, jPath)) {
attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
- }else {
+ } else if (!attr) {
//tag value
if (key === this.options.textNodeName) {
let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
@@ -126,13 +129,13 @@ Builder.prototype.j2x = function(jObj, level) {
// val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
} else if (typeof item === 'object') {
if(this.options.oneListGroup){
- const result = this.j2x(item, level + 1);
+ const result = this.j2x(item, level + 1, ajPath.concat(key));
listTagVal += result.val;
if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
listTagAttr += result.attrStr
}
}else{
- listTagVal += this.processTextOrObjNode(item, key, level)
+ listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
}
} else {
if (this.options.oneListGroup) {
@@ -157,7 +160,7 @@ Builder.prototype.j2x = function(jObj, level) {
attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
}
} else {
- val += this.processTextOrObjNode(jObj[key], key, level)
+ val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
}
}
}
@@ -172,8 +175,8 @@ Builder.prototype.buildAttrPairStr = function(attrName, val){
} else return ' ' + attrName + '="' + val + '"';
}
-function processTextOrObjNode (object, key, level) {
- const result = this.j2x(object, level + 1);
+function processTextOrObjNode (object, key, level, ajPath) {
+ const result = this.j2x(object, level + 1, ajPath.concat(key));
if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
} else {
diff --git a/src/xmlparser/OrderedObjParser.js b/src/xmlparser/OrderedObjParser.js
index ffd3f24f..70db0557 100644
--- a/src/xmlparser/OrderedObjParser.js
+++ b/src/xmlparser/OrderedObjParser.js
@@ -5,6 +5,7 @@ const util = require('../util');
const xmlNode = require('./xmlNode');
const readDocType = require("./DocTypeReader");
const toNumber = require("strnum");
+const getIgnoreAttributesFn = require('../ignoreAttributes')
// const regx =
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
@@ -53,6 +54,7 @@ class OrderedObjParser{
this.readStopNodeData = readStopNodeData;
this.saveTextToParentTag = saveTextToParentTag;
this.addChild = addChild;
+ this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
}
}
@@ -125,7 +127,7 @@ function resolveNameSpace(tagname) {
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
function buildAttributesMap(attrStr, jPath, tagName) {
- if (!this.options.ignoreAttributes && typeof attrStr === 'string') {
+ if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
// attrStr = attrStr.replace(/\r?\n/g, ' ');
//attrStr = attrStr || attrStr.trim();
@@ -134,6 +136,9 @@ function buildAttributesMap(attrStr, jPath, tagName) {
const attrs = {};
for (let i = 0; i < len; i++) {
const attrName = this.resolveNameSpace(matches[i][1]);
+ if (this.ignoreAttributesFn(attrName, jPath)) {
+ continue
+ }
let oldVal = matches[i][4];
let aName = this.options.attributeNamePrefix + attrName;
if (attrName.length) {