Skip to content

Commit

Permalink
fix stop nodes and unpaired tags
Browse files Browse the repository at this point in the history
  • Loading branch information
amitguptagwl committed Nov 30, 2021
1 parent 5df0672 commit 03e1302
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 125 deletions.
134 changes: 79 additions & 55 deletions docs/v4/2.XMLparseOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,68 @@ Output

## alwaysCreateTextNode

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
```js
const xmlData = `
<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`
```json
{
"root": {
"a": [
"wow",
{
"c": "unlimited",
"#text": "wow again"
}
],
"b": "wow phir se",
"@_a": "nice"
}
}
```
Output when `alwaysCreateTextNode: true`
```json
{
"root": {
"a": [
{
"#text": "wow"
},
{
"c": {
"#text": "unlimited"
},
"#text": "wow again"
}
],
"b": {
"#text": "wow phir se"
},
"@_a": "nice"
}
}
```

## attributesGroupName

To group all the attributes of a tag under given property name.
Expand Down Expand Up @@ -590,66 +652,26 @@ Output
}
```

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.
You can also mention a tag which should not be processed irrespective of their path. Eg. `<pre>` or `<script>` in an HTML document.

Eg
```js
const xmlData = `
<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
stopNodes: ["*.pre", "*.script"]
};
const parser = new XMLParser(options);
const output = parser.parse(xmlDataStr);
```
Output when `alwaysCreateTextNode: false`
```json
{
"root": {
"a": [
"wow",
{
"c": "unlimited",
"#text": "wow again"
}
],
"b": "wow phir se",
"@_a": "nice"
}
}

Note that a stop node should not have same closing node in contents. Eg

```xml
<stop>
invalid </stop>
</stop>
```
Output when `alwaysCreateTextNode: true`
```json
{
"root": {
"a": [
{
"#text": "wow"
},
{
"c": {
"#text": "unlimited"
},
"#text": "wow again"
}
],
"b": {
"#text": "wow phir se"
},
"@_a": "nice"
}
}
nested stop notes are also not allowed
```xml
<stop>
<stop> invalid </stop>
</stop>
```

## tagValueProcessor
Expand Down Expand Up @@ -794,6 +816,8 @@ const xmlData = `
<tag>value</tag>
<empty />
<unpaired>
<unpaired />
<unpaired></unpaired>
</rootNode>`;

const options = {
Expand All @@ -808,7 +832,7 @@ Output
"rootNode": {
"tag": "value",
"empty": "",
"unpaired": ""
"unpaired": [ "", "", ""]
}
}
```
Expand Down
4 changes: 4 additions & 0 deletions docs/v4/3.XMLBuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ const xmlData = `
<tag>value</tag>
<empty />
<unpaired>
<unpaired />
<unpaired></unpaired>
</rootNode>`;

const options = {
Expand All @@ -132,6 +134,8 @@ Output
<tag>value</tag>
<empty></empty>
<unpaired></unpaired>
<unpaired></unpaired>
<unpaired></unpaired>
</rootNode>
```

Expand Down
78 changes: 20 additions & 58 deletions spec/stopNodes_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,67 +111,29 @@ describe("XMLParser StopNodes", function() {
expect(result).toBe(true);
});

it("2b. stop node is self-closing", function() {
const xmlData = `<issue><title>test 1</title><fix1/></issue>`;
const expected = {
"issue": {
"title": "test 1",
"fix1": ""
}
};

const options = {
attributeNamePrefix: "",
ignoreAttributes: false,
parseAttributeValue: true,
stopNodes: ["issue.fix1", "issue.fix2"]
};
const parser = new XMLParser(options);
let result = parser.parse(xmlData);

//console.log(JSON.stringify(result,null,4));
expect(result).toEqual(expected);

result = XMLValidator.validate(xmlData);
expect(result).toBe(true);
});

xit("4. cdata", function() {
const xmlData = `<?xml version='1.0'?>
<issue>
<fix1>
<phone>+122233344550</phone>
<fix2><![CDATA[<fix1>Jack</fix1>]]><![CDATA[Jack]]></fix2>
<name><![CDATA[<some>Mohan</some>]]></name>
<blank><![CDATA[]]></blank>
<regx><![CDATA[^[ ].*$]]></regx>
</fix1>
<fix2>
<![CDATA[<some>Mohan</some>]]>
</fix2>
</issue>`;
const expected = {
"issue": {
"fix1": "\n <phone>+122233344550</phone>\n <fix2><![CDATA[<fix1>Jack</fix1>]]><![CDATA[Jack]]></fix2>\n <name><![CDATA[<some>Mohan</some>]]></name>\n <blank><![CDATA[]]></blank>\n <regx><![CDATA[^[ ].*$]]></regx>\n ",
"fix2": "\n\t\t<![CDATA[<some>Mohan</some>]]>\n\t"
}
};
const options = {
attributeNamePrefix: "",
ignoreAttributes: false,
parseAttributeValue: true,
stopNodes: ["issue.fix1", "issue.fix2"]
it("2b. stop node is self-closing", function() {
const xmlData = `<issue><title>test 1</title><fix1/></issue>`;
const expected = {
"issue": {
"title": "test 1",
"fix1": ""
}
};
const parser = new XMLParser(options);
let result = parser.parse(xmlData);

// console.log(JSON.st ringify(result,null,4));
expect(result).toEqual(expected);
const options = {
attributeNamePrefix: "",
ignoreAttributes: false,
parseAttributeValue: true,
stopNodes: ["issue.fix1", "issue.fix2"]
};
const parser = new XMLParser(options);
let result = parser.parse(xmlData);

result = XMLValidator.validate(xmlData, {
allowBooleanAttributes: true
});
expect(result).toBe(true);
//console.log(JSON.stringify(result,null,4));
expect(result).toEqual(expected);

result = XMLValidator.validate(xmlData);
expect(result).toBe(true);
});

it("5. stopNode at root level", function() {
Expand Down
65 changes: 65 additions & 0 deletions spec/unpairedTags_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,69 @@ describe("unpaired and empty tags", function() {
// console.log(output);
expect(output.replace(/\s+/g, "")).toEqual(xmlData.replace(/\s+/g, ""));
});

it("should be parsed when unpaired tag is self-closing or paired closing tag", function() {
const xmlData = `<rootNode>
<unpaired>
<self />
<unpaired>
<unpaired />
<unpaired>
<unpaired></unpaired>
<unpaired>
</rootNode>`;

const expectedXml = `<rootNode>
<unpaired>
<self/>
<unpaired>
<unpaired>
<unpaired>
<unpaired>
<unpaired>
</rootNode>`;
const options = {
// format: true,
preserveOrder: true,
suppressEmptyNode: true,
unpairedTags: ["unpaired"]
};
const parser = new XMLParser(options);
let result = parser.parse(xmlData);
// console.log(JSON.stringify(result, null,4));

const builder = new XMLBuilder(options);
const output = builder.build(result);
// console.log(output);
expect(output.replace(/\s+/g, "")).toEqual(expectedXml.replace(/\s+/g, ""));
});

it("should parsed unpaired tag before stop nodes", function() {
const xmlData = `<rootNode>
<unpaired>
<stop>here</stop>
<unpaired>
</rootNode>`;

const expectedXml = `<rootNode>
<unpaired>
<stop>here</stop>
<unpaired>
</rootNode>`;
const options = {
// format: true,
preserveOrder: true,
suppressEmptyNode: true,
unpairedTags: ["unpaired"],
stopNodes: ["*.stop"]
};
const parser = new XMLParser(options);
let result = parser.parse(xmlData);
// console.log(JSON.stringify(result, null,4));

const builder = new XMLBuilder(options);
const output = builder.build(result);
// console.log(output);
expect(output.replace(/\s+/g, "")).toEqual(expectedXml.replace(/\s+/g, ""));
});
});
19 changes: 7 additions & 12 deletions src/xmlparser/OrderedObjParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const parseXml = function(xmlData) {
tagName = tagName.substr(colonIndex+1);
}
}

if(currentNode){
textData = this.parseTextData(textData
, currentNode.tagname
Expand Down Expand Up @@ -276,6 +276,12 @@ const parseXml = function(xmlData) {
jPath += jPath ? "." + tagName : tagName;
}

//check if last tag was unpaired tag
const lastTag = currentNode;
if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){
currentNode = this.tagsNodeStack.pop();
}

if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) { //TODO: namespace
let tagContent = "";
//self-closing tag
Expand Down Expand Up @@ -310,17 +316,6 @@ const parseXml = function(xmlData) {
tagExp = tagExp.substr(0, tagExp.length - 1);
}

const childNode = new xmlNode(tagName);
if(tagName !== tagExp && attrExpPresent){
childNode.attributes = this.buildAttributesMap(tagExp, jPath);
}
jPath = jPath.substr(0, jPath.lastIndexOf("."));
currentNode.addChild(childNode);
}
//boolean tags
else if(this.options.unpairedTags.indexOf(tagName) !== -1){
// tagExp = tagExp.substr(0, tagExp.length - 1);

const childNode = new xmlNode(tagName);
if(tagName !== tagExp && attrExpPresent){
childNode.attributes = this.buildAttributesMap(tagExp, jPath);
Expand Down

0 comments on commit 03e1302

Please sign in to comment.