Skip to content

Commit

Permalink
feat #666: add selective ignoreAttributes by pattern or callback (#668)
Browse files Browse the repository at this point in the history
* feat #666: add selective ignoreAttributes by pattern or callback

* chore: resolve codeclimate issues
  • Loading branch information
mav-rik authored Sep 1, 2024
1 parent d40e29c commit 98d8f47
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 24 deletions.
89 changes: 80 additions & 9 deletions docs/v4/2.XMLparseOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<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:

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 = `<root a="nice" ><a>wow</a></root>`;
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"
}
}
```
Expand Down
84 changes: 83 additions & 1 deletion docs/v4/3.XMLBuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<tag ns2:attr3="a3-value" ns2:attr4="a4-value">
<tag2 ns2:attr3="a3-value" ns2:attr4="a4-value"></tag2>
</tag>
```

### Example: Ignoring Attributes by Regular Expressions

```js
const options = {
attributeNamePrefix: "$",
ignoreAttributes: [/^ns2:/]
};
const builder = new XMLBuilder(options);
const xmlOutput = builder.build(jsonData);
```

Result:
```xml
<tag ns:attr1="a1-value" ns:attr2="a2-value">
<tag2 ns:attr1="a1-value" ns:attr2="a2-value"></tag2>
</tag>
```

### 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
<tag ns2:attr3="a3-value" ns2:attr4="a4-value">
<tag2></tag2>
</tag>
```

## indentBy
Applicable only if `format:true` is set.
Expand Down
141 changes: 141 additions & 0 deletions spec/attrIgnore_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"use strict";

const { XMLParser, XMLBuilder, XMLValidator } = require("../src/fxp");

const xmlData = `
<tag
ns:attr1="a1-value"
ns:attr2="a2-value"
ns2:attr3="a3-value"
ns2:attr4="a4-value">
value
</tag>`;

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 = `
<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">
value
</tag2>
</tag>`;

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('<tag ns2:attr3="a3-value" ns2:attr4="a4-value"><tag2 ns2:attr3="a3-value" ns2:attr4="a4-value"></tag2></tag>')

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('<tag ns:attr1="a1-value" ns:attr2="a2-value"><tag2 ns:attr1="a1-value" ns:attr2="a2-value"></tag2></tag>')

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('<tag ns2:attr3="a3-value" ns2:attr4="a4-value"><tag2></tag2></tag>')

expect(XMLValidator.validate(xmlData)).toBe(true);
})
})
22 changes: 19 additions & 3 deletions src/fxp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | RegExp>` - 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
Expand Down Expand Up @@ -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<string | RegExp>` - 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
Expand Down
Loading

0 comments on commit 98d8f47

Please sign in to comment.