diff --git a/spec/xmlParser_spec.js b/spec/xmlParser_spec.js index e97aab34..86033f7a 100644 --- a/spec/xmlParser_spec.js +++ b/spec/xmlParser_spec.js @@ -3,23 +3,157 @@ const parser = require("../src/parser"); const validator = require("../src/validator"); -describe("XMLParser", function() { +describe("XMLParser", function () { - it("should parse all values as string, int, boolean, float, hexadecimal", function() { + it("should preserve the position of tags when specified", function () { + const xmlData = ` locates in + US + and + Japan + only + ` + + const expected = { + "store": [{ + "@_attr": "value", + "#ordered": [ + { "#text": "locates in" }, + { "region": "US" }, + { "#text": "and" }, + { "region": "Japan" }, + { "#text": "only" } + ] + }] + } + + const result = parser.parse(xmlData, { arrayMode: true, preserveOrder: true, ignoreAttributes: false }); + //console.log(JSON.stringify(result, null, 4)); + expect(result).toEqual(expected); + }) + + it("should only preserve the position of text nodes when specified", function () { + const xmlData = ` + + locates in + US + and + Japan + only + + + 1 + 2 + 3 + + ` + + const expected = { + root: [{ + store: [{ + "#ordered": [ + { "#text": "locates in" }, + { "region": "US" }, + { "#text": "and" }, + { "region": "Japan" }, + { "#text": "only" } + ] + }], + second: [{ + a: [1, 3], + b: 2 + }] + }] + } + + const result = parser.parse(xmlData, { arrayMode: true, preserveOrder: "text" }); + // console.log(JSON.stringify(result, null, 4)); + expect(result).toEqual(expected); + }) + + it("should preserve the position of all nodes when specified", function () { + const xmlData = ` + + locates in + US + and + Japan + only + + + 1 + 2 + 3 + + ` + + const expected = { + root: [{ + "#ordered": [ + { + store: { + "#ordered": [ + { "#text": "locates in" }, + { "region": "US" }, + { "#text": "and" }, + { "region": "Japan" }, + { "#text": "only" } + ] + } + }, + { + second: { + "#ordered": [{ a: 1 }, { b: 2 }, { a: 3 }] + } + } + ] + }] + } + + const result = parser.parse(xmlData, { arrayMode: true, preserveOrder: true }); + //console.log(JSON.stringify(result, null, 4)); + expect(result).toEqual(expected); + }) + + it("should ignore the position of tags by default", function () { + const xmlData = ` locates in + US + and + Japan + only + ` + + const expected = { + "store": [ + { + "#text": "locates inandonly", + "region": [ + "US", + "Japan" + ] + } + ] + } + + const result = parser.parse(xmlData, { arrayMode: true }); + //console.log(JSON.stringify(result, null, 4)); + expect(result).toEqual(expected); + }) + + it("should parse all values as string, int, boolean, float, hexadecimal", function () { const xmlData = ` - value - true - 045 - 65.34 - 0x15 - `; + value + true + 045 + 65.340 + 0x15 + `; const expected = { "rootNode": { - "tag": "value", - "boolean": true, - "intTag": 45, + "tag": "value", + "boolean": true, + "intTag": 45, "floatTag": 65.34, - "hexadecimal" : 21 + "hexadecimal": 21 } }; @@ -29,38 +163,38 @@ describe("XMLParser", function() { }); - it("should parse only true numbers", function() { + it("should parse only true numbers", function () { const xmlData = ` - value - true - 045 - 65.340 - 420926189200190257681175017717 - `; + value + true + 045 + 65.34 + 420926189200190257681175017717 + `; const expected = { "rootNode": { - "tag": "value", - "boolean": true, - "intTag": "045", + "tag": "value", + "boolean": true, + "intTag": "045", "floatTag": 65.34, "long": "420926189200190257681175017717" } }; const result = parser.parse(xmlData, { - parseTrueNumberOnly : true + parseTrueNumberOnly: true }); //console.log(JSON.stringify(result,null,4)); expect(result).toEqual(expected); }); - it("should not parse values to primitive type", function() { + it("should not parse values to primitive type", function () { const xmlData = `valuetrue04565.34`; const expected = { "rootNode": { - "tag": "value", - "boolean": "true", - "intTag": "045", + "tag": "value", + "boolean": "true", + "intTag": "045", "floatTag": "65.34" } }; @@ -71,14 +205,14 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse number values of attributes as number", function() { + it("should parse number values of attributes as number", function () { const xmlData = `value`; const expected = { "rootNode": { "tag": { - "#text": "value", - "@_int": 45, - "@_intNegative": -45, + "#text": "value", + "@_int": 45, + "@_intNegative": -45, "@_float": 65.34, "@_floatNegative": -65.34 } @@ -86,19 +220,19 @@ describe("XMLParser", function() { }; const result = parser.parse(xmlData, { - ignoreAttributes: false, + ignoreAttributes: false, parseAttributeValue: true }); expect(result).toEqual(expected); }); - it("should parse number values as number if flag is set", function() { + it("should parse number values as number if flag is set", function () { const xmlData = `value045065.34`; const expected = { "rootNode": { - "tag": "value", - "intTag": [45, 0], + "tag": "value", + "intTag": [45, 0], "floatTag": 65.34 } }; @@ -109,12 +243,12 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should skip tag arguments", function() { + it("should skip tag arguments", function () { const xmlData = `value4565.34`; const expected = { "rootNode": { - "tag": "value", - "intTag": 45, + "tag": "value", + "intTag": 45, "floatTag": 65.34 } }; @@ -123,29 +257,29 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should ignore namespace and text node attributes", function() { + it("should ignore namespace and text node attributes", function () { const xmlData = `\ - - value - 45 - 65.34 - - -`; + + value + 45 + 65.34 + + + `; const expected = { "node": { - "tag": { + "tag": { "@_arg": "value", "#text": "value" }, - "intTag": { - "@_arg": "value", + "intTag": { + "@_arg": "value", "@_arg2": "value2", - "#text": 45 + "#text": 45 }, - "floatTag": 65.34, - "nsTag": { + "floatTag": 65.34, + "nsTag": { "@_attr": "tns" //"#text": "" }, @@ -154,14 +288,14 @@ describe("XMLParser", function() { }; const result = parser.parse(xmlData, { - ignoreNameSpace: true, + ignoreNameSpace: true, ignoreAttributes: false }); expect(result).toEqual(expected); }); - it("should parse empty text Node", function() { + it("should parse empty text Node", function () { const xmlData = ``; const expected = { "rootNode": { @@ -173,7 +307,7 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse self closing tags", function() { + it("should parse self closing tags", function () { const xmlData = ""; const expected = { "rootNode": { @@ -189,7 +323,7 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse single self closing tag", function() { + it("should parse single self closing tag", function () { const xmlData = ``; const expected = { "tag": { @@ -204,13 +338,13 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse repeated nodes in array", function() { + it("should parse repeated nodes in array", function () { const xmlData = `\ - - value - 45 - 65.34 -`; + + value + 45 + 65.34 + `; const expected = { "rootNode": { "tag": ["value", 45, 65.34] @@ -221,15 +355,15 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse nested nodes in nested properties", function() { + it("should parse nested nodes in nested properties", function () { const xmlData = `\ - - - value - 45 - 65.34 - -`; + + + value + 45 + 65.34 + + `; const expected = { "rootNode": { "parenttag": { @@ -242,44 +376,44 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse non-text nodes with value for repeated nodes", function() { + it("should parse non-text nodes with value for repeated nodes", function () { const xmlData = ` - - - value - 45 - 65.34 - - - value - 45 - 65.34 - -`; + + + value + 45 + 65.34 + + + value + 45 + 65.34 + + `; const expected = { "rootNode": { "parenttag": [ { "@_attr1": "some val", "@_attr2": "another val", - "tag": [ + "tag": [ "value", { "@_attr1": "val", "@_attr2": "234", - "#text": 45 + "#text": 45 }, 65.34 ] }, { "@_attr1": "some val", "@_attr2": "another val", - "tag": [ + "tag": [ "value", { "@_attr1": "val", "@_attr2": "234", - "#text": 45 + "#text": 45 }, 65.34 ] @@ -294,30 +428,30 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should preserve node value", function() { + it("should preserve node value", function () { const xmlData = ` some val `; const expected = { "rootNode": { "@_attr1": " some val ", - "@_name": "another val", - "#text": " some val " + "@_name": "another val", + "#text": " some val " } }; const result = parser.parse(xmlData, { ignoreAttributes: false, - trimValues: false + trimValues: false }); expect(result).toEqual(expected); }); - it("should parse with attributes and value when there is single node", function() { + it("should parse with attributes and value when there is single node", function () { const xmlData = `val`; const expected = { "rootNode": { "@_attr1": "some val", "@_attr2": "another val", - "#text": "val" + "#text": "val" } }; @@ -327,7 +461,7 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse different tags", function() { + it("should parse different tags", function () { const xmlData = `val1val2`; const expected = { "tag.1": "val1", @@ -340,46 +474,46 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should not parse text value with tag", function() { + it("should not parse text value with tag", function () { const xmlData = `712329`; const expected = { "score": { "c1": { "message": 23, - "_text": "7129" + "_text": "7129" } } }; const result = parser.parse(xmlData, { - textNodeName: "_text", + textNodeName: "_text", ignoreAttributes: false }); expect(result).toEqual(expected); }); - it("should parse nested elements with attributes", function() { + it("should parse nested elements with attributes", function () { const xmlData = `\ - - - - Bob - - -`; + + + + Bob + + + `; const expected = { "root": { "Meet": { "@_date": "2017-05-03", "@_type": "A", "@_name": "Meeting 'A'", - "Event": { + "Event": { "@_time": "00:05:00", - "@_ID": "574", + "@_ID": "574", "@_Name": "Some Event Name", - "User": { - "@_ID": "1", + "User": { + "@_ID": "1", "#text": "Bob" } } @@ -388,39 +522,39 @@ describe("XMLParser", function() { }; const result = parser.parse(xmlData, { - ignoreAttributes: false, + ignoreAttributes: false, ignoreNonTextNodeAttr: false }); expect(result).toEqual(expected); }); - it("should parse nested elements with attributes wrapped in object", function() { + it("should parse nested elements with attributes wrapped in object", function () { const xmlData = `\ - - - - Bob - - -`; + + + + Bob + + + `; const expected = { "root": { "Meet": { - "$": { + "$": { "nsattr": "attr", - "date": "2017-05-03", - "type": "A", - "name": "Meeting 'A'" + "date": "2017-05-03", + "type": "A", + "name": "Meeting 'A'" }, "Event": { - "$": { + "$": { "time": "00:05:00", - "ID": "574", + "ID": "574", "Name": "Some Event Name" }, "User": { - "$": { + "$": { "ID": "1" }, "#text": "Bob" @@ -432,16 +566,16 @@ describe("XMLParser", function() { const result = parser.parse(xmlData, { attributeNamePrefix: "", - attrNodeName: "$", - ignoreNameSpace: true, - ignoreAttributes: false + attrNodeName: "$", + ignoreNameSpace: true, + ignoreAttributes: false }); //console.log(JSON.stringify(result,null,4)); expect(result).toEqual(expected); }); - it("should parse all type of nodes", function() { + it("should parse all type of nodes", function () { const fs = require("fs"); const path = require("path"); const fileNamePath = path.join(__dirname, "assets/sample.xml"); @@ -449,14 +583,14 @@ describe("XMLParser", function() { const expected = { "any_name": { - "@attr": "https://example.com/somepath", + "@attr": "https://example.com/somepath", "person": [ { - "@id": "101", - "phone": [122233344550, 122233344551], - "name": "Jack", - "age": 33, - "emptyNode": "", + "@id": "101", + "phone": [122233344550, 122233344551], + "name": "Jack", + "age": 33, + "emptyNode": "", "booleanNode": [false, true], "selfclosing": [ "", @@ -464,46 +598,46 @@ describe("XMLParser", function() { "@with": "value" } ], - "married": { + "married": { "@firstTime": "No", - "@attr": "val 2", - "#_text": "Yes" + "@attr": "val 2", + "#_text": "Yes" }, - "birthday": "Wed, 28 Mar 1979 12:13:14 +0300", - "address": [ + "birthday": "Wed, 28 Mar 1979 12:13:14 +0300", + "address": [ { - "city": "New York", - "street": "Park Ave", + "city": "New York", + "street": "Park Ave", "buildingNo": 1, - "flatNo": 1 + "flatNo": 1 }, { - "city": "Boston", - "street": "Centre St", + "city": "Boston", + "street": "Centre St", "buildingNo": 33, - "flatNo": 24 + "flatNo": 24 } ] }, { - "@id": "102", - "phone": [122233344553, 122233344554], - "name": "Boris", - "age": 34, - "married": { + "@id": "102", + "phone": [122233344553, 122233344554], + "name": "Boris", + "age": 34, + "married": { "@firstTime": "Yes", - "#_text": "Yes" + "#_text": "Yes" }, "birthday": "Mon, 31 Aug 1970 02:03:04 +0300", - "address": [ + "address": [ { - "city": "Moscow", - "street": "Kahovka", + "city": "Moscow", + "street": "Kahovka", "buildingNo": 1, - "flatNo": 2 + "flatNo": 2 }, { - "city": "Tula", - "street": "Lenina", + "city": "Tula", + "street": "Lenina", "buildingNo": 3, - "flatNo": 78 + "flatNo": 78 } ] } @@ -512,10 +646,10 @@ describe("XMLParser", function() { }; const result = parser.parse(xmlData, { - ignoreAttributes: false, + ignoreAttributes: false, ignoreNonTextNodeAttr: false, - attributeNamePrefix: "@", - textNodeName: "#_text" + attributeNamePrefix: "@", + textNodeName: "#_text" }); //console.log(JSON.stringify(result,null,4)); expect(result).toEqual(expected); @@ -595,28 +729,28 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); */ - it("should skip namespace", function() { + it("should skip namespace", function () { const xmlData = `\ - - - dashboardweb - abc - - - - - 123456789 - - - -`; + + + dashboardweb + abc + + + + + 123456789 + + + + `; const expected = { "Envelope": { "Header": { "applicationID": "dashboardweb", - "providerID": "abc" + "providerID": "abc" }, - "Body": { + "Body": { "getOffers": { "customerId": { "msisdn": 123456789 @@ -626,24 +760,24 @@ describe("XMLParser", function() { } }; - const result = parser.parse(xmlData, {ignoreNameSpace: true}); + const result = parser.parse(xmlData, { ignoreNameSpace: true }); expect(result).toEqual(expected); }); - it("should not trim tag value if not allowed ", function() { + it("should not trim tag value if not allowed ", function () { const xmlData = " 123 "; const expected = { "rootNode": " 123 " }; const result = parser.parse(xmlData, { parseNodeValue: false, - trimValues: false + trimValues: false }); //console.log(JSON.stringify(result,null,4)); expect(result).toEqual(expected); }); - it("should not trim tag value but not parse if not allowed ", function() { + it("should not trim tag value but not parse if not allowed ", function () { const xmlData = " 123 "; const expected = { "rootNode": "123" @@ -655,7 +789,7 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should not decode HTML entities by default", function() { + it("should not decode HTML entities by default", function () { const xmlData = " foo&bar' "; const expected = { "rootNode": "foo&bar'" @@ -667,16 +801,16 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse XML with DOCTYPE", function() { + it("should parse XML with DOCTYPE", function () { const xmlData = "" + - "" + - "" + - "" + - "" + - "]>" + - "Hello World."; + "" + + "" + + "" + + "" + + "]>" + + "Hello World."; const expected = { foo: "Hello World." @@ -690,14 +824,14 @@ describe("XMLParser", function() { }); //Issue #77 - it("should parse node with space in closing node", function() { + it("should parse node with space in closing node", function () { const xmlData = "" - + "" - + " " - + " Jack 1" - + " Jack 2" - + " " - + ""; + + "" + + " " + + " Jack 1" + + " Jack 2" + + " " + + ""; const expected = { "any_name": { @@ -712,16 +846,16 @@ describe("XMLParser", function() { expect(result).toEqual(expected); }); - it("should parse node with text before, after and between of subtags", function() { + it("should parse node with text before, after and between of subtags", function () { const xmlData = "" - + "before" - + " subtag text" - + " middle" - + " " - + " after self" - + " subtag text" - + " after" - + ""; + + "before" + + " subtag text" + + " middle" + + " " + + " after self" + + " subtag text" + + " after" + + ""; const expected = { "tag": { @@ -735,16 +869,16 @@ describe("XMLParser", function() { var result = validator.validate(xmlData); expect(result).toBe(true); - result = parser.parse(xmlData,{trimValues:false}); + result = parser.parse(xmlData, { trimValues: false }); //console.log(JSON.stringify(result,null,4)); expect(result).toEqual(expected); }); - it("should validate before parsing", function() { + it("should validate before parsing", function () { const xmlData = "" - + "" - + " subtag text" - + "" + + " subtag text" + + " { parser.parse(xmlData,{trimValues:true}, true); @@ -752,11 +886,11 @@ describe("XMLParser", function() { }); - it("should validate with options before parsing", function() { + it("should validate with options before parsing", function () { const xmlData = "" - + "" - + " subtag text" - + ""; + + "" + + " subtag text" + + ""; const expected = { "tag": { @@ -764,7 +898,7 @@ describe("XMLParser", function() { } }; - let result = parser.parse(xmlData,{trimValues:true}, { allowBooleanAttributes: true }); + let result = parser.parse(xmlData, { trimValues: true }, { allowBooleanAttributes: true }); //console.log(JSON.stringify(result,null,4)); expect(result).toEqual(expected); }); diff --git a/src/json2xml.js b/src/json2xml.js index 1c5a9e3b..59443aa9 100644 --- a/src/json2xml.js +++ b/src/json2xml.js @@ -12,10 +12,10 @@ const defaultOptions = { format: false, indentBy: ' ', supressEmptyNode: false, - tagValueProcessor: function(a) { + tagValueProcessor: function (a) { return a; }, - attrValueProcessor: function(a) { + attrValueProcessor: function (a) { return a; }, }; @@ -37,7 +37,7 @@ const props = [ function Parser(options) { this.options = buildOptions(options, defaultOptions, props); if (this.options.ignoreAttributes || this.options.attrNodeName) { - this.isAttribute = function(/*a*/) { + this.isAttribute = function (/*a*/) { return false; }; } else { @@ -47,7 +47,7 @@ function Parser(options) { if (this.options.cdataTagName) { this.isCDATA = isCDATA; } else { - this.isCDATA = function(/*a*/) { + this.isCDATA = function (/*a*/) { return false; }; } @@ -59,7 +59,7 @@ function Parser(options) { this.tagEndChar = '>\n'; this.newLine = '\n'; } else { - this.indentate = function() { + this.indentate = function () { return ''; }; this.tagEndChar = '>'; @@ -78,11 +78,11 @@ function Parser(options) { this.buildObjectNode = buildObjectNode; } -Parser.prototype.parse = function(jObj) { +Parser.prototype.parse = function (jObj) { return this.j2x(jObj, 0).val; }; -Parser.prototype.j2x = function(jObj, level) { +Parser.prototype.j2x = function (jObj, level) { let attrStr = ''; let val = ''; const keys = Object.keys(jObj); @@ -158,7 +158,7 @@ Parser.prototype.j2x = function(jObj, level) { } } } - return {attrStr: attrStr, val: val}; + return { attrStr: attrStr, val: val }; }; function replaceCDATAstr(str, cdata) { @@ -183,6 +183,12 @@ function replaceCDATAarr(str, cdata) { } function buildObjectNode(val, key, attrStr, level) { + // #ordered is used to specify an array of ordered tags, + // only when the preserveOrder option is set. + if (key === "#ordered") { + return val + } + if (attrStr && !val.includes('<')) { return ( this.indentate(level) + diff --git a/src/node2json.js b/src/node2json.js index 1180bdb1..09df698e 100644 --- a/src/node2json.js +++ b/src/node2json.js @@ -2,7 +2,7 @@ const util = require('./util'); -const convertToJson = function(node, options) { +const convertToJson = function (node, options) { const jObj = {}; //when no child node or attr is present @@ -12,9 +12,9 @@ const convertToJson = function(node, options) { //otherwise create a textnode if node has some text if (util.isExist(node.val)) { if (!(typeof node.val === 'string' && (node.val === '' || node.val === options.cdataPositionChar))) { - if(options.arrayMode === "strict"){ - jObj[options.textNodeName] = [ node.val ]; - }else{ + if (options.arrayMode === "strict") { + jObj[options.textNodeName] = [node.val]; + } else { jObj[options.textNodeName] = node.val; } } @@ -24,6 +24,22 @@ const convertToJson = function(node, options) { util.merge(jObj, node.attrsMap, options.arrayMode); const keys = Object.keys(node.child); + + if (keys.length > 1 && (options.preserveOrder === true || + (options.preserveOrder === "text" && keys.includes(options.textNodeName)))) { + const jArray = new Array(node.children) + + for (let key of keys) { + for (var child of node.child[key]) { + jArray[child.indexInParent] = { [key]: convertToJson(child, options) }; + } + } + + const result = { "#ordered": jArray } + util.merge(result, node.attrsMap, options.arrayMode) + return result + } + for (let index = 0; index < keys.length; index++) { var tagname = keys[index]; if (node.child[tagname] && node.child[tagname].length > 1) { @@ -32,15 +48,15 @@ const convertToJson = function(node, options) { jObj[tagname].push(convertToJson(node.child[tagname][tag], options)); } } else { - if(options.arrayMode === true){ + if (options.arrayMode === true) { const result = convertToJson(node.child[tagname][0], options) - if(typeof result === 'object') - jObj[tagname] = [ result ]; + if (typeof result === 'object') + jObj[tagname] = [result]; else jObj[tagname] = result; - }else if(options.arrayMode === "strict"){ - jObj[tagname] = [convertToJson(node.child[tagname][0], options) ]; - }else{ + } else if (options.arrayMode === "strict") { + jObj[tagname] = [convertToJson(node.child[tagname][0], options)]; + } else { jObj[tagname] = convertToJson(node.child[tagname][0], options); } } diff --git a/src/parser.d.ts b/src/parser.d.ts index 3ba98910..3cdede26 100644 --- a/src/parser.d.ts +++ b/src/parser.d.ts @@ -15,6 +15,7 @@ type X2jOptions = { tagValueProcessor: (tagValue: string, tagName: string) => string; attrValueProcessor: (attrValue: string, attrName: string) => string; stopNodes: string[]; + preserveOrder: boolean | 'text' }; type X2jOptionsOptional = Partial; type validationOptions = { diff --git a/src/xmlNode.js b/src/xmlNode.js index e5835a93..1f128bd4 100644 --- a/src/xmlNode.js +++ b/src/xmlNode.js @@ -1,12 +1,14 @@ 'use strict'; module.exports = function(tagname, parent, val) { + this.children = 0; this.tagname = tagname; this.parent = parent; this.child = {}; //child tags this.attrsMap = {}; //attributes map this.val = val; //text only this.addChild = function(child) { + child.indexInParent = this.children++ if (Array.isArray(this.child[child.tagname])) { //already presents this.child[child.tagname].push(child); diff --git a/src/xmlstr2xmlnode.js b/src/xmlstr2xmlnode.js index 8af32164..a79ea84d 100644 --- a/src/xmlstr2xmlnode.js +++ b/src/xmlstr2xmlnode.js @@ -39,7 +39,8 @@ const defaultOptions = { attrValueProcessor: function(a, attrName) { return a; }, - stopNodes: [] + stopNodes: [], + preserveOrder: false //decodeStrict: false, }; @@ -61,11 +62,39 @@ const props = [ 'tagValueProcessor', 'attrValueProcessor', 'parseTrueNumberOnly', - 'stopNodes' + 'stopNodes', + 'preserveOrder' ]; exports.props = props; -const getTraversalObj = function(xmlData, options) { +const nodeHasExtraText = (currentNode, textAfterNode) => { + return currentNode.parent && textAfterNode +} + +const storeTextOrTextNode = (currentNode, options, tag) => { + if (!nodeHasExtraText(currentNode, tag[12])) { + return + } + + if (options.preserveOrder) { // if the closing tag is followed by text, we add a textNodeName node. + const textValue = '' + processTagValue(tag, options) + if (textValue) { + currentNode.parent.addChild(new xmlNode(options.textNodeName, currentNode.parent, textValue)) + } + } else { + currentNode.parent.val = util.getValue(currentNode.parent.val) + '' + processTagValue(tag, options, currentNode.parent.tagname); + } +} + +// If the current node has a value, and we open a new tag, we replace this node value by a textNodeName tag. +const tryStoreValueAsTextNode = (currentNode, options) => { + if (options.preserveOrder && currentNode.val) { + currentNode.addChild(new xmlNode(options.textNodeName, currentNode, currentNode.val)) + currentNode.val = undefined + } +} + +const getTraversalObj = function (xmlData, options) { options = buildOptions(options, defaultOptions, props); //xmlData = xmlData.replace(/\r?\n/g, " ");//make it single line xmlData = xmlData.replace(//g, ''); //Remove comments @@ -81,9 +110,9 @@ const getTraversalObj = function(xmlData, options) { if (tagType === TagType.CLOSING) { //add parsed data to parent node - if (currentNode.parent && tag[12]) { - currentNode.parent.val = util.getValue(currentNode.parent.val) + '' + processTagValue(tag, options, currentNode.parent.tagname); - } + + storeTextOrTextNode(currentNode, options, tag) + if (options.stopNodes.length && options.stopNodes.includes(currentNode.tagname)) { currentNode.child = [] if (currentNode.attrsMap == undefined) { currentNode.attrsMap = {}} @@ -106,16 +135,15 @@ const getTraversalObj = function(xmlData, options) { currentNode.val = (currentNode.val || '') + (tag[3] || '') + processTagValue(tag, options); } } else if (tagType === TagType.SELF) { - if (currentNode && tag[12]) { - currentNode.val = util.getValue(currentNode.val) + '' + processTagValue(tag, options); - } - const childNode = new xmlNode(options.ignoreNameSpace ? tag[7] : tag[5], currentNode, ''); if (tag[8] && tag[8].length > 0) { tag[8] = tag[8].substr(0, tag[8].length - 1); } childNode.attrsMap = buildAttributesMap(tag[8], options); currentNode.addChild(childNode); + + storeTextOrTextNode(childNode, options, tag) + } else { //TagType.OPENING const childNode = new xmlNode( @@ -127,6 +155,9 @@ const getTraversalObj = function(xmlData, options) { childNode.startIndex=tag.index + tag[1].length } childNode.attrsMap = buildAttributesMap(tag[8], options); + + tryStoreValueAsTextNode(currentNode, options) + currentNode.addChild(childNode); currentNode = childNode; }