Skip to content

Commit 98d8f47

Browse files
authored
feat #666: add selective ignoreAttributes by pattern or callback (#668)
* feat #666: add selective ignoreAttributes by pattern or callback * chore: resolve codeclimate issues
1 parent d40e29c commit 98d8f47

File tree

7 files changed

+362
-24
lines changed

7 files changed

+362
-24
lines changed

docs/v4/2.XMLparseOptions.md

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -280,24 +280,95 @@ FXP by default parse XMl entities if `processEntities: true`. You can set `htmlE
280280

281281
## ignoreAttributes
282282

283-
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.
283+
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.
284+
285+
### Selective Attribute Ignoring
286+
287+
You can specify an array of strings, regular expressions, or a callback function to selectively ignore specific attributes during parsing or building.
288+
289+
### Example Input XML
290+
291+
```xml
292+
<tag
293+
ns:attr1="a1-value"
294+
ns:attr2="a2-value"
295+
ns2:attr3="a3-value"
296+
ns2:attr4="a4-value">
297+
value
298+
</tag>
299+
```
300+
301+
You can use the `ignoreAttributes` option in three different ways:
302+
303+
1. **Array of Strings**: Ignore specific attributes by name.
304+
2. **Array of Regular Expressions**: Ignore attributes that match a pattern.
305+
3. **Callback Function**: Ignore attributes based on custom logic.
306+
307+
### Example: Ignoring Attributes by Array of Strings
284308

285-
Eg
286309
```js
287-
const xmlDataStr = `<root a="nice" ><a>wow</a></root>`;
310+
const options = {
311+
attributeNamePrefix: "$",
312+
ignoreAttributes: ['ns:attr1', 'ns:attr2'],
313+
parseAttributeValue: true
314+
};
315+
const parser = new XMLParser(options);
316+
const output = parser.parse(xmlData);
317+
```
318+
319+
Result:
320+
```json
321+
{
322+
"tag": {
323+
"#text": "value",
324+
"$ns2:attr3": "a3-value",
325+
"$ns2:attr4": "a4-value"
326+
}
327+
}
328+
```
288329

330+
### Example: Ignoring Attributes by Regular Expressions
331+
332+
```js
289333
const options = {
290-
// ignoreAttributes: false,
291-
attributeNamePrefix : "@_"
334+
attributeNamePrefix: "$",
335+
ignoreAttributes: [/^ns2:/],
336+
parseAttributeValue: true
292337
};
293338
const parser = new XMLParser(options);
294-
const output = parser.parse(xmlDataStr);
339+
const output = parser.parse(xmlData);
295340
```
296-
Output
341+
342+
Result:
297343
```json
298344
{
299-
"root": {
300-
"a": "wow"
345+
"tag": {
346+
"#text": "value",
347+
"$ns:attr1": "a1-value",
348+
"$ns:attr2": "a2-value"
349+
}
350+
}
351+
```
352+
353+
### Example: Ignoring Attributes via Callback Function
354+
355+
```js
356+
const options = {
357+
attributeNamePrefix: "$",
358+
ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2',
359+
parseAttributeValue: true
360+
};
361+
const parser = new XMLParser(options);
362+
const output = parser.parse(xmlData);
363+
```
364+
365+
Result:
366+
```json
367+
{
368+
"tag": {
369+
"$ns2:attr3": "a3-value",
370+
"$ns2:attr4": "a4-value",
371+
"tag2": "value"
301372
}
302373
}
303374
```

docs/v4/3.XMLBuilder.md

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,89 @@ It is recommended to use `preserveOrder: true` when you're parsing XML to js obj
170170
By default, parsed XML is single line XML string. By `format: true`, you can format it for better view.
171171

172172
## ignoreAttributes
173-
Don't consider attributes while building XML. Other attributes related properties should be set to correctly identifying an attribute property.
173+
174+
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.
175+
176+
### Selective Attribute Ignoring
177+
178+
The `ignoreAttributes` option supports:
179+
180+
1. **Array of Strings**: Ignore specific attributes by name while building XML.
181+
2. **Array of Regular Expressions**: Ignore attributes that match a pattern while building XML.
182+
3. **Callback Function**: Ignore attributes based on custom logic during the building process.
183+
184+
### Example Input JSON
185+
186+
```json
187+
{
188+
"tag": {
189+
"$ns:attr1": "a1-value",
190+
"$ns:attr2": "a2-value",
191+
"$ns2:attr3": "a3-value",
192+
"$ns2:attr4": "a4-value",
193+
"tag2": {
194+
"$ns:attr1": "a1-value",
195+
"$ns:attr2": "a2-value",
196+
"$ns2:attr3": "a3-value",
197+
"$ns2:attr4": "a4-value"
198+
}
199+
}
200+
}
201+
```
202+
203+
### Example: Ignoring Attributes by Array of Strings
204+
205+
```js
206+
const options = {
207+
attributeNamePrefix: "$",
208+
ignoreAttributes: ['ns:attr1', 'ns:attr2']
209+
};
210+
const builder = new XMLBuilder(options);
211+
const xmlOutput = builder.build(jsonData);
212+
```
213+
214+
Result:
215+
```xml
216+
<tag ns2:attr3="a3-value" ns2:attr4="a4-value">
217+
<tag2 ns2:attr3="a3-value" ns2:attr4="a4-value"></tag2>
218+
</tag>
219+
```
220+
221+
### Example: Ignoring Attributes by Regular Expressions
222+
223+
```js
224+
const options = {
225+
attributeNamePrefix: "$",
226+
ignoreAttributes: [/^ns2:/]
227+
};
228+
const builder = new XMLBuilder(options);
229+
const xmlOutput = builder.build(jsonData);
230+
```
231+
232+
Result:
233+
```xml
234+
<tag ns:attr1="a1-value" ns:attr2="a2-value">
235+
<tag2 ns:attr1="a1-value" ns:attr2="a2-value"></tag2>
236+
</tag>
237+
```
238+
239+
### Example: Ignoring Attributes via Callback Function
240+
241+
```js
242+
const options = {
243+
attributeNamePrefix: "$",
244+
ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2'
245+
};
246+
const builder = new XMLBuilder(options);
247+
const xmlOutput = builder.build(jsonData);
248+
```
249+
250+
Result:
251+
```xml
252+
<tag ns2:attr3="a3-value" ns2:attr4="a4-value">
253+
<tag2></tag2>
254+
</tag>
255+
```
174256

175257
## indentBy
176258
Applicable only if `format:true` is set.

spec/attrIgnore_spec.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"use strict";
2+
3+
const { XMLParser, XMLBuilder, XMLValidator } = require("../src/fxp");
4+
5+
const xmlData = `
6+
<tag
7+
ns:attr1="a1-value"
8+
ns:attr2="a2-value"
9+
ns2:attr3="a3-value"
10+
ns2:attr4="a4-value">
11+
value
12+
</tag>`;
13+
14+
const jsonData = {
15+
tag: {
16+
'$ns:attr1': 'a1-value',
17+
'$ns:attr2': 'a2-value',
18+
'$ns2:attr3': 'a3-value',
19+
'$ns2:attr4': 'a4-value',
20+
tag2: {
21+
'$ns:attr1': 'a1-value',
22+
'$ns:attr2': 'a2-value',
23+
'$ns2:attr3': 'a3-value',
24+
'$ns2:attr4': 'a4-value',
25+
}
26+
}
27+
}
28+
29+
describe("XMLParser", function () {
30+
31+
it('must ignore parsing attributes by array of strings', () => {
32+
33+
const options = {
34+
attributeNamePrefix: "$",
35+
ignoreAttributes: ['ns:attr1', 'ns:attr2'],
36+
parseAttributeValue: true
37+
};
38+
const parser = new XMLParser(options);
39+
expect(parser.parse(xmlData)).toEqual({
40+
tag: {
41+
'#text': 'value',
42+
'$ns2:attr3': 'a3-value',
43+
'$ns2:attr4': 'a4-value',
44+
},
45+
})
46+
47+
expect(XMLValidator.validate(xmlData)).toBe(true);
48+
})
49+
50+
it('must ignore parsing attributes by array of RegExp', () => {
51+
52+
const options = {
53+
attributeNamePrefix: "$",
54+
ignoreAttributes: [/^ns2:/],
55+
parseAttributeValue: true
56+
};
57+
const parser = new XMLParser(options);
58+
expect(parser.parse(xmlData)).toEqual({
59+
tag: {
60+
'#text': 'value',
61+
'$ns:attr1': 'a1-value',
62+
'$ns:attr2': 'a2-value',
63+
},
64+
})
65+
66+
expect(XMLValidator.validate(xmlData)).toBe(true);
67+
})
68+
69+
it('must ignore parsing attributes via callback fn', () => {
70+
const xmlData = `
71+
<tag
72+
ns:attr1="a1-value"
73+
ns:attr2="a2-value"
74+
ns2:attr3="a3-value"
75+
ns2:attr4="a4-value">
76+
<tag2
77+
ns:attr1="a1-value"
78+
ns:attr2="a2-value"
79+
ns2:attr3="a3-value"
80+
ns2:attr4="a4-value">
81+
value
82+
</tag2>
83+
</tag>`;
84+
85+
const options = {
86+
attributeNamePrefix: "$",
87+
ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2',
88+
parseAttributeValue: true
89+
};
90+
const parser = new XMLParser(options);
91+
expect(parser.parse(xmlData)).toEqual({
92+
tag: {
93+
'$ns2:attr3': 'a3-value',
94+
'$ns2:attr4': 'a4-value',
95+
tag2: 'value',
96+
},
97+
})
98+
99+
expect(XMLValidator.validate(xmlData)).toBe(true);
100+
})
101+
102+
103+
it('must ignore building attributes by array of strings', () => {
104+
105+
const options = {
106+
attributeNamePrefix: "$",
107+
ignoreAttributes: ['ns:attr1', 'ns:attr2'],
108+
parseAttributeValue: true
109+
};
110+
const builder = new XMLBuilder(options);
111+
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>')
112+
113+
expect(XMLValidator.validate(xmlData)).toBe(true);
114+
})
115+
116+
it('must ignore building attributes by array of RegExp', () => {
117+
118+
const options = {
119+
attributeNamePrefix: "$",
120+
ignoreAttributes: [/^ns2:/],
121+
parseAttributeValue: true
122+
};
123+
const builder = new XMLBuilder(options);
124+
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>')
125+
126+
expect(XMLValidator.validate(xmlData)).toBe(true);
127+
})
128+
129+
it('must ignore building attributes via callback fn', () => {
130+
131+
const options = {
132+
attributeNamePrefix: "$",
133+
ignoreAttributes: (aName, jPath) => aName.startsWith('ns:') || jPath === 'tag.tag2',
134+
parseAttributeValue: true
135+
};
136+
const builder = new XMLBuilder(options);
137+
expect(builder.build(jsonData)).toEqual('<tag ns2:attr3="a3-value" ns2:attr4="a4-value"><tag2></tag2></tag>')
138+
139+
expect(XMLValidator.validate(xmlData)).toBe(true);
140+
})
141+
})

src/fxp.d.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,17 @@ type X2jOptions = {
3030
/**
3131
* Whether to ignore attributes when parsing
3232
*
33+
* When `true` - ignores all the attributes
34+
*
35+
* When `false` - parses all the attributes
36+
*
37+
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
38+
*
39+
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
40+
*
3341
* Defaults to `true`
3442
*/
35-
ignoreAttributes?: boolean;
43+
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
3644

3745
/**
3846
* Whether to remove namespace string from tag and attribute names
@@ -250,11 +258,19 @@ type XmlBuilderOptions = {
250258
textNodeName?: string;
251259

252260
/**
253-
* Whether to ignore attributes when parsing
261+
* Whether to ignore attributes when building
262+
*
263+
* When `true` - ignores all the attributes
264+
*
265+
* When `false` - builds all the attributes
266+
*
267+
* When `Array<string | RegExp>` - filters out attributes that match provided patterns
268+
*
269+
* When `Function` - calls the function for each attribute and filters out those for which the function returned `true`
254270
*
255271
* Defaults to `true`
256272
*/
257-
ignoreAttributes?: boolean;
273+
ignoreAttributes?: boolean | (string | RegExp)[] | ((attrName: string, jPath: string) => boolean);
258274

259275
/**
260276
* Give a property name to set CDATA values to instead of merging to tag's text value

0 commit comments

Comments
 (0)