diff --git a/spec/attr_spec.js b/spec/attr_spec.js index 0d8b1e9a..f8bffe2e 100644 --- a/spec/attr_spec.js +++ b/spec/attr_spec.js @@ -359,104 +359,5 @@ id="7" data="foo bar" bug="true"/>`; // console.log(output); expect(output.replace(/\s+/g, "")).toEqual(XMLdata.replace(/\s+/g, "")); }); - it("should parse attributes with valid names", function() { - const xmlData = ` - - - - - - `; - const expected = { - "root": { - "a": [ - { - "keep": "me" - }, - "", - { - "need": "friend", - "friend": "me" - }, - { - "Camel": "case", - "makeme": "lower" - } - ], - "b": { - "change": "VAL" - } - } - }; - const options = { - attributeNamePrefix: "", - ignoreAttributes: false, - parseAttributeValue: true, - updateAttributes(tagName, attrs, jPath){ - if(attrs["skip"]) delete attrs["skip"] - if(attrs["camel"]) { - attrs["Camel"] = attrs["camel"]; - delete attrs["camel"]; - } - if(attrs["need"]) { - attrs["friend"] = "me"; - } - if(attrs["MakeMe"]) { - attrs["makeme"] = attrs["MakeMe"]; - delete attrs["MakeMe"]; - } - if(attrs["change"]) { - attrs["change"] = attrs["change"].toUpperCase(); - } - return attrs; - } - }; - - 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); - }); - it("should parse attributes with valid names", function() { - const xmlData = ` - - - - - - `; - const expected = { - "root": { - "a": [ - "", - "", - "", - "" - ], - "b": "" - } - }; - const options = { - attributeNamePrefix: "", - ignoreAttributes: false, - parseAttributeValue: true, - updateAttributes(tagName, attrs,jPath){ - // console.log("called") - return null; - } - }; - - 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); - }); }); + \ No newline at end of file diff --git a/spec/updateTag_spec.js b/spec/updateTag_spec.js new file mode 100644 index 00000000..dbfded3e --- /dev/null +++ b/spec/updateTag_spec.js @@ -0,0 +1,182 @@ +"use strict"; + +const {XMLParser, XMLBuilder, XMLValidator} = require("../src/fxp"); +const he = require("he"); + +describe("XMLParser updateTag ", function() { + it("should delete, join, update attribute name and value", function() { + const xmlData = ` + + + + + + `; + const expected = { + "root": { + "a": [ + { + "keep": "me" + }, + "", + { + "need": "friend", + "friend": "me" + }, + { + "Camel": "case", + "makeme": "lower" + } + ], + "b": { + "change": "VAL" + } + } + }; + const options = { + attributeNamePrefix: "", + ignoreAttributes: false, + parseAttributeValue: true, + updateTag(tagName, jPath, attrs){ + if(attrs["skip"]) delete attrs["skip"] + if(attrs["camel"]) { + attrs["Camel"] = attrs["camel"]; + delete attrs["camel"]; + } + if(attrs["need"]) { + attrs["friend"] = "me"; + } + if(attrs["MakeMe"]) { + attrs["makeme"] = attrs["MakeMe"]; + delete attrs["MakeMe"]; + } + if(attrs["change"]) { + attrs["change"] = attrs["change"].toUpperCase(); + } + return tagName; + } + }; + + 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); + }); + it("should delete all the attributes", function() { + const xmlData = ` + + + + + + `; + const expected = { + "root": { + "a": [ + "", + "", + "", + "" + ], + "b": "" + } + }; + const options = { + attributeNamePrefix: "", + ignoreAttributes: false, + parseAttributeValue: true, + updateTag(tagName, jPath, attrs){ + for (var k in attrs){ + if (attrs.hasOwnProperty(k)){ + delete attrs[k]; + } + } + return tagName; + } + }; + + 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); + }); + it("should skip a tag or modify tag name", function() { + const xmlData = ` +
+ +

Post title

+ + +

some text

+ +

some text 2

+ +
+ + + `; + const expected = { + "html": { + "header": "", + "body": { + "h1": { + "#text": "Post title", + "class": "highlight underline" + }, + "div": { + "p": [ + "some text", + { + "#text": "some text 2", + "joint": "abcd" + } + ], + "img": { + "width": "200", + "height": "200" + } + } + } + } + }; + const options = { + ignoreAttributes: false, + attributeNamePrefix: "", + updateTag: function(tagname, jPath, attrs){ + + if(tagname ==="h1" && attrs["class"] && attrs["class"].indexOf("highlight") > -1){ + attrs["class"] += " underline" + }else if(attrs["join"]){ + let val = ""; + Object.keys(attrs).forEach( a => { + val+= attrs[a] + delete attrs[a]; + }); + attrs["joint"] = val; + } + if(tagname === "script") return false; + else if(tagname === "img"){ + if(attrs.width > 200 || attrs.height > 200) return false; + }else if(tagname === "content"){ + return "div" + } + return tagname; + }, + unpairedTags: ["img"] + }; + const parser = new XMLParser(options); + let result = parser.parse(xmlData); + + // console.log(JSON.stringify(result,null,4)); + expect(result).toEqual(expected); + + }); +}); diff --git a/src/fxp.d.ts b/src/fxp.d.ts index 881d146e..d622f27e 100644 --- a/src/fxp.d.ts +++ b/src/fxp.d.ts @@ -32,7 +32,14 @@ Control how tag value should be parsed. Called only if tag value is not empty ignorePiTags: boolean; transformTagName: ((tagName: string) => string) | false; transformAttributeName: ((attributeName: string) => string) | false; - updateAttributes(tagName: string, jPath: string, attrs: {[k: string]: string}): {[k: string]: string}; + /** +Change the tag name when a different name is returned. Skip the tag from parsed result when false is returned. +Modify `attrs` object to control attributes for the given tag. + +@returns {string} new tag name. +@returns false to skip the tag + */ + updateTag: (tagName: string, jPath: string, attrs: {[k: string]: string}) => string | boolean; }; type strnumOptions = { hex: boolean; diff --git a/src/xmlparser/OptionsBuilder.js b/src/xmlparser/OptionsBuilder.js index 5bf6b29d..85e99252 100644 --- a/src/xmlparser/OptionsBuilder.js +++ b/src/xmlparser/OptionsBuilder.js @@ -34,8 +34,8 @@ const defaultOptions = { ignorePiTags: false, transformTagName: false, transformAttributeName: false, - updateAttributes: function(tagName, attrs, jPath){ - return attrs; + updateTag: function(tagName, jPath, attrs){ + return tagName } }; diff --git a/src/xmlparser/OrderedObjParser.js b/src/xmlparser/OrderedObjParser.js index 6b3ab6d3..4f9bfe8e 100644 --- a/src/xmlparser/OrderedObjParser.js +++ b/src/xmlparser/OrderedObjParser.js @@ -50,6 +50,7 @@ class OrderedObjParser{ this.replaceEntitiesValue = replaceEntitiesValue; this.readStopNodeData = readStopNodeData; this.saveTextToParentTag = saveTextToParentTag; + this.addChild = addChild; } } @@ -171,7 +172,7 @@ function buildAttributesMap(attrStr, jPath, tagName) { attrCollection[this.options.attributesGroupName] = attrs; return attrCollection; } - return this.options.updateAttributes(tagName, attrs, jPath) + return attrs } } @@ -226,7 +227,7 @@ const parseXml = function(xmlData) { if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){ childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName); } - currentNode.addChild(childNode); + this.addChild(currentNode, childNode, jPath) } @@ -323,7 +324,7 @@ const parseXml = function(xmlData) { jPath = jPath.substr(0, jPath.lastIndexOf(".")); childNode.add(this.options.textNodeName, tagContent); - currentNode.addChild(childNode); + this.addChild(currentNode, childNode, jPath) }else{ //selfClosing tag if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){ @@ -343,7 +344,7 @@ const parseXml = function(xmlData) { childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName); } jPath = jPath.substr(0, jPath.lastIndexOf(".")); - currentNode.addChild(childNode); + this.addChild(currentNode, childNode, jPath) } //opening tag else{ @@ -353,7 +354,7 @@ const parseXml = function(xmlData) { if(tagName !== tagExp && attrExpPresent){ childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName); } - currentNode.addChild(childNode); + this.addChild(currentNode, childNode, jPath) currentNode = childNode; } textData = ""; @@ -367,6 +368,17 @@ const parseXml = function(xmlData) { return xmlObj.child; } +function addChild(currentNode, childNode, jPath){ + const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"]) + if(result === false){ + }else if(typeof result === "string"){ + childNode.tagname = result + currentNode.addChild(childNode); + }else{ + currentNode.addChild(childNode); + } +} + const replaceEntitiesValue = function(val){ if(this.options.processEntities){ @@ -423,7 +435,7 @@ function isItStopNode(stopNodes, jPath, currentTagName){ } /** - * Returns the tag Expression and where it is ending handling single-dobule quotes situation + * Returns the tag Expression and where it is ending handling single-double quotes situation * @param {string} xmlData * @param {number} i starting index * @returns