Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat #666: add selective ignoreAttributes by pattern or callback #668

Merged
merged 2 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading