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;
}