diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f3524a..ddaf6b1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library. +**4.2.3 / 2023-05-23** +* fix: Unopened closing tags should be ignored by XMLParser instead of throwing "*Cannot read properties of undefined*" exception. + **4.2.2 / 2023-04-18** * fix #562: fix unpaired tag when it comes in last of a nested tag. Also throw error when unpaired tag is used as closing tag diff --git a/package.json b/package.json index 90b11f7b..37175c90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fast-xml-parser", - "version": "4.2.2", + "version": "4.2.3", "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries", "main": "./src/fxp.js", "scripts": { diff --git a/spec/xmlParser_spec.js b/spec/xmlParser_spec.js index ad6f05eb..730501ab 100644 --- a/spec/xmlParser_spec.js +++ b/spec/xmlParser_spec.js @@ -1066,4 +1066,45 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); + + it("should ignore closing tags without an opening tag", function() { + const xmlData = ` + + Hello + + + + + + + + World + + `; + const options = { + ignoreAttributes: false, + preserveOrder: false, + alwaysCreateTextNode: false + }; + const expected = { + "rootNode": { + "parentTag": [ + { + "childTag": "Hello", + "@_attr": "my attr" + }, + { + "@_attr": "my attr" + }, + { + "childTag": "World", + "@_attr": "my attr" + } + ] + } + }; + const parser = new XMLParser(options); + let result = parser.parse(xmlData); + expect(result).toEqual(expected); + }); }); diff --git a/src/xmlparser/OrderedObjParser.js b/src/xmlparser/OrderedObjParser.js index db11a8c0..cc4af585 100644 --- a/src/xmlparser/OrderedObjParser.js +++ b/src/xmlparser/OrderedObjParser.js @@ -207,20 +207,23 @@ const parseXml = function(xmlData) { } //check if last tag of nested tag was unpaired tag - const lastTagName = jPath.substring(jPath.lastIndexOf(".")+1); + const lastTagName = jPath.substring(jPath.lastIndexOf("|")+1); if(tagName && this.options.unpairedTags.indexOf(tagName) !== -1 ){ throw new Error(`Unpaired tag can not be used as closing tag: `); } let propIndex = 0 if(lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1 ){ - propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.')-1) + propIndex = jPath.lastIndexOf('|', jPath.lastIndexOf('|')-1) this.tagsNodeStack.pop(); }else{ - propIndex = jPath.lastIndexOf("."); - } - jPath = jPath.substring(0, propIndex); + propIndex = jPath.lastIndexOf("|"); + } + if(lastTagName === tagName) { + //update path and pop out node only when the closing tag matches with opening tag. This condition will thus ignore unopened closing tags. + jPath = jPath.substring(0, propIndex); - currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope + currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope + } textData = ""; i = closeIndex; } else if( xmlData[i+1] === '?') { @@ -300,10 +303,10 @@ const parseXml = function(xmlData) { const lastTag = currentNode; if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1 ){ currentNode = this.tagsNodeStack.pop(); - jPath = jPath.substring(0, jPath.lastIndexOf(".")); + jPath = jPath.substring(0, jPath.lastIndexOf("|")); } if(tagName !== xmlObj.tagname){ - jPath += jPath ? "." + tagName : tagName; + jPath += jPath ? "|" + tagName : tagName; } if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) { //TODO: namespace let tagContent = ""; @@ -332,7 +335,7 @@ const parseXml = function(xmlData) { tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true); } - jPath = jPath.substr(0, jPath.lastIndexOf(".")); + jPath = jPath.substr(0, jPath.lastIndexOf("|")); childNode.add(this.options.textNodeName, tagContent); this.addChild(currentNode, childNode, jPath) @@ -355,7 +358,7 @@ const parseXml = function(xmlData) { childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName); } this.addChild(currentNode, childNode, jPath) - jPath = jPath.substr(0, jPath.lastIndexOf(".")); + jPath = jPath.substr(0, jPath.lastIndexOf("|")); } //opening tag else{