diff --git a/bin/parser.js b/bin/parser.js index 2eacfc07..55a0d9ed 100644 --- a/bin/parser.js +++ b/bin/parser.js @@ -18,6 +18,7 @@ var tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>("+cdataRegx+")*([^<]+)?" var defaultOptions = { attrPrefix : "@_", + attrNodeName: false, textNodeName : "#text", ignoreNonTextNodeAttr : true, ignoreTextNodeAttr : true, @@ -30,7 +31,7 @@ var defaultOptions = { var buildOptions = function (options){ if(!options) options = {}; - var props = ["attrPrefix","ignoreNonTextNodeAttr","ignoreTextNodeAttr","ignoreNameSpace","ignoreRootElement","textNodeName","textNodeConversion","textAttrConversion","arrayMode"]; + var props = ["attrPrefix","attrNodeName","ignoreNonTextNodeAttr","ignoreTextNodeAttr","ignoreNameSpace","ignoreRootElement","textNodeName","textNodeConversion","textAttrConversion","arrayMode"]; for (var i = 0; i < props.length; i++) { if(options[props[i]] === undefined){ options[props[i]] = defaultOptions[props[i]]; @@ -60,11 +61,11 @@ var getTraversalObj =function (xmlData,options){ var childNode = new xmlNode(tag,currentNode); if(selfClosingTag){ - attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.ignoreNameSpace,options.textAttrConversion); + attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.attrNodeName,options.ignoreNameSpace,options.textAttrConversion); childNode.val = attrs || ""; currentNode.addChild(childNode); }else if( ("/" + tag) === nexttag){ //Text node - attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.ignoreNameSpace,options.textAttrConversion); + attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.attrNodeName,options.ignoreNameSpace,options.textAttrConversion); val = parseValue(val,options.textNodeConversion); if(attrs){ attrs[options.textNodeName] = val; @@ -79,7 +80,7 @@ var getTraversalObj =function (xmlData,options){ currentNode.addChild(childNode); i++; }else{//starting tag - attrs = buildAttributesArr(attrsStr,options.ignoreNonTextNodeAttr,options.attrPrefix,options.ignoreNameSpace,options.textAttrConversion); + attrs = buildAttributesArr(attrsStr,options.ignoreNonTextNodeAttr,options.attrPrefix,options.attrNodeName,options.ignoreNameSpace,options.textAttrConversion); if(attrs){ for (var prop in attrs) { if(attrs.hasOwnProperty(prop)){ @@ -140,16 +141,20 @@ function parseValue(val,conversion){ //var attrsRegx = new RegExp("(\\S+)=\\s*[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?","g"); //var attrsRegx = new RegExp("(\\S+)=\\s*(['\"])((?:.(?!\\2))*.)","g"); var attrsRegx = new RegExp("(\\S+)\\s*=\\s*(['\"])(.*?)\\2","g"); -function buildAttributesArr(attrStr,ignore,prefix,ignoreNS,conversion){ +function buildAttributesArr(attrStr,ignore,prefix,attrNodeName,ignoreNS,conversion){ attrStr = attrStr || attrStr.trim(); if(!ignore && attrStr.length > 3){ var matches = getAllMatches(attrStr,attrsRegx); var attrs = {}; + var attrsCollection = attrs; + if(attrNodeName && matches.length){ + attrsCollection = attrs[attrNodeName] = {}; + } for (var i = 0; i < matches.length; i++) { var attrName = prefix + resolveNameSpace( matches[i][1],ignoreNS); - attrs[attrName] = parseValue(matches[i][3],conversion); + attrsCollection[attrName] = parseValue(matches[i][3],conversion); } return attrs; } diff --git a/lib/parser.js b/lib/parser.js index 0837f05a..71251fbd 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -19,6 +19,7 @@ var tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>("+cdataRegx+")*([^<]+)?" var defaultOptions = { attrPrefix : "@_", + attrNodeName: false, textNodeName : "#text", ignoreNonTextNodeAttr : true, ignoreTextNodeAttr : true, @@ -31,7 +32,7 @@ var defaultOptions = { var buildOptions = function (options){ if(!options) options = {}; - var props = ["attrPrefix","ignoreNonTextNodeAttr","ignoreTextNodeAttr","ignoreNameSpace","ignoreRootElement","textNodeName","textNodeConversion","textAttrConversion","arrayMode"]; + var props = ["attrPrefix","attrNodeName","ignoreNonTextNodeAttr","ignoreTextNodeAttr","ignoreNameSpace","ignoreRootElement","textNodeName","textNodeConversion","textAttrConversion","arrayMode"]; for (var i = 0; i < props.length; i++) { if(options[props[i]] === undefined){ options[props[i]] = defaultOptions[props[i]]; @@ -61,11 +62,11 @@ var getTraversalObj =function (xmlData,options){ var childNode = new xmlNode(tag,currentNode); if(selfClosingTag){ - attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.ignoreNameSpace,options.textAttrConversion); + attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.attrNodeName,options.ignoreNameSpace,options.textAttrConversion); childNode.val = attrs || ""; currentNode.addChild(childNode); }else if( ("/" + tag) === nexttag){ //Text node - attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.ignoreNameSpace,options.textAttrConversion); + attrs = buildAttributesArr(attrsStr,options.ignoreTextNodeAttr,options.attrPrefix,options.attrNodeName,options.ignoreNameSpace,options.textAttrConversion); val = parseValue(val,options.textNodeConversion); if(attrs){ attrs[options.textNodeName] = val; @@ -80,7 +81,7 @@ var getTraversalObj =function (xmlData,options){ currentNode.addChild(childNode); i++; }else{//starting tag - attrs = buildAttributesArr(attrsStr,options.ignoreNonTextNodeAttr,options.attrPrefix,options.ignoreNameSpace,options.textAttrConversion); + attrs = buildAttributesArr(attrsStr,options.ignoreNonTextNodeAttr,options.attrPrefix,options.attrNodeName,options.ignoreNameSpace,options.textAttrConversion); if(attrs){ for (var prop in attrs) { if(attrs.hasOwnProperty(prop)){ @@ -141,16 +142,20 @@ function parseValue(val,conversion){ //var attrsRegx = new RegExp("(\\S+)=\\s*[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?","g"); //var attrsRegx = new RegExp("(\\S+)=\\s*(['\"])((?:.(?!\\2))*.)","g"); var attrsRegx = new RegExp("(\\S+)\\s*=\\s*(['\"])(.*?)\\2","g"); -function buildAttributesArr(attrStr,ignore,prefix,ignoreNS,conversion){ +function buildAttributesArr(attrStr,ignore,prefix,attrNodeName,ignoreNS,conversion){ attrStr = attrStr || attrStr.trim(); if(!ignore && attrStr.length > 3){ var matches = getAllMatches(attrStr,attrsRegx); var attrs = {}; + var attrsCollection = attrs; + if(attrNodeName && matches.length){ + attrsCollection = attrs[attrNodeName] = {}; + } for (var i = 0; i < matches.length; i++) { - var attrName = prefix + resolveNameSpace( matches[i][1],ignoreNS); - attrs[attrName] = parseValue(matches[i][3],conversion); + var attrName = (attrNodeName ? "" : prefix) + resolveNameSpace( matches[i][1],ignoreNS); + attrsCollection[attrName] = parseValue(matches[i][3],conversion); } return attrs; } diff --git a/spec/xmlParser_spec.js b/spec/xmlParser_spec.js index 40d5a2c3..7257889a 100644 --- a/spec/xmlParser_spec.js +++ b/spec/xmlParser_spec.js @@ -372,6 +372,49 @@ describe("XMLParser", function () { expect(result).toEqual(expected); }); + it("should parse nested elements with attributes wrapped in array", function () { + var xmlData = '' + +'' + + '' + + 'Bob' + + '' + + '' + +''; + var expected = { + "root": { + "Meet": { + "$": { + "date": "2017-05-03", + "type": "A", + "name": "Meeting 'A'" + }, + "Event": { + "$": { + "time": "00:05:00", + "ID": "574", + "Name": "Some Event Name" + }, + "User": { + "$": { + "ID": "1" + }, + "#text": "Bob" + } + } + } + } + }; + + var result = parser.parse(xmlData, { + attrPrefix:"", + attrNodeName:"$", + ignoreTextNodeAttr: false, + ignoreNonTextNodeAttr: false + }); + + expect(result).toEqual(expected); + }); + it("should parse all type of nodes", function () { var fs = require("fs"); var path = require("path");