--- /dev/null
+/*\r
+ Copyright 2011-2013 Abdulla Abdurakhmanov\r
+ Original sources are available at https://code.google.com/p/x2js/\r
+\r
+ Licensed under the Apache License, Version 2.0 (the "License");\r
+ you may not use this file except in compliance with the License.\r
+ You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+ */\r
+\r
+function X2JS(config) {\r
+ 'use strict';\r
+ \r
+ var VERSION = "1.1.5";\r
+ \r
+ config = config || {};\r
+ initConfigDefaults();\r
+ initRequiredPolyfills();\r
+ \r
+ function initConfigDefaults() {\r
+ if(config.escapeMode === undefined) {\r
+ config.escapeMode = true;\r
+ }\r
+ config.attributePrefix = config.attributePrefix || "_";\r
+ config.arrayAccessForm = config.arrayAccessForm || "none";\r
+ config.emptyNodeForm = config.emptyNodeForm || "text";\r
+ if(config.enableToStringFunc === undefined) {\r
+ config.enableToStringFunc = true; \r
+ }\r
+ config.arrayAccessFormPaths = config.arrayAccessFormPaths || []; \r
+ if(config.skipEmptyTextNodesForObj === undefined) {\r
+ config.skipEmptyTextNodesForObj = true;\r
+ }\r
+ if(config.stripWhitespaces === undefined) {\r
+ config.stripWhitespaces = true;\r
+ }\r
+ config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || [];\r
+ }\r
+\r
+ var DOMNodeTypes = {\r
+ ELEMENT_NODE : 1,\r
+ TEXT_NODE : 3,\r
+ CDATA_SECTION_NODE : 4,\r
+ COMMENT_NODE : 8,\r
+ DOCUMENT_NODE : 9\r
+ };\r
+ \r
+ function initRequiredPolyfills() {\r
+ function pad(number) {\r
+ var r = String(number);\r
+ if ( r.length === 1 ) {\r
+ r = '0' + r;\r
+ }\r
+ return r;\r
+ }\r
+ // Hello IE8-\r
+ if(typeof String.prototype.trim !== 'function') { \r
+ String.prototype.trim = function() {\r
+ return this.replace(/^\s+|^\n+|(\s|\n)+$/g, '');\r
+ }\r
+ }\r
+ if(typeof Date.prototype.toISOString !== 'function') {\r
+ // Implementation from http://stackoverflow.com/questions/2573521/how-do-i-output-an-iso-8601-formatted-string-in-javascript\r
+ Date.prototype.toISOString = function() {\r
+ return this.getUTCFullYear()\r
+ + '-' + pad( this.getUTCMonth() + 1 )\r
+ + '-' + pad( this.getUTCDate() )\r
+ + 'T' + pad( this.getUTCHours() )\r
+ + ':' + pad( this.getUTCMinutes() )\r
+ + ':' + pad( this.getUTCSeconds() )\r
+ + '.' + String( (this.getUTCMilliseconds()/1000).toFixed(3) ).slice( 2, 5 )\r
+ + 'Z';\r
+ };\r
+ }\r
+ }\r
+ \r
+ function getNodeLocalName( node ) {\r
+ var nodeLocalName = node.localName; \r
+ if(nodeLocalName == null) // Yeah, this is IE!! \r
+ nodeLocalName = node.baseName;\r
+ if(nodeLocalName == null || nodeLocalName=="") // =="" is IE too\r
+ nodeLocalName = node.nodeName;\r
+ return nodeLocalName;\r
+ }\r
+ \r
+ function getNodePrefix(node) {\r
+ return node.prefix;\r
+ }\r
+ \r
+ function escapeXmlChars(str) {\r
+ if(typeof(str) == "string")\r
+ return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');\r
+ else\r
+ return str;\r
+ }\r
+\r
+ function unescapeXmlChars(str) {\r
+ return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(///g, '\/');\r
+ }\r
+ \r
+ function toArrayAccessForm(obj, childName, path) {\r
+ switch(config.arrayAccessForm) {\r
+ case "property":\r
+ if(!(obj[childName] instanceof Array))\r
+ obj[childName+"_asArray"] = [obj[childName]];\r
+ else\r
+ obj[childName+"_asArray"] = obj[childName];\r
+ break; \r
+ /*case "none":\r
+ break;*/\r
+ }\r
+ \r
+ if(!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) {\r
+ var idx = 0;\r
+ for(; idx < config.arrayAccessFormPaths.length; idx++) {\r
+ var arrayPath = config.arrayAccessFormPaths[idx];\r
+ if( typeof arrayPath === "string" ) {\r
+ if(arrayPath == path)\r
+ break;\r
+ }\r
+ else\r
+ if( arrayPath instanceof RegExp) {\r
+ if(arrayPath.test(path))\r
+ break;\r
+ } \r
+ else\r
+ if( typeof arrayPath === "function") {\r
+ if(arrayPath(obj, childName, path))\r
+ break;\r
+ }\r
+ }\r
+ if(idx!=config.arrayAccessFormPaths.length) {\r
+ obj[childName] = [obj[childName]];\r
+ }\r
+ }\r
+ }\r
+ \r
+ function fromXmlDateTime(prop) {\r
+ // Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object\r
+ // Improved to support full spec and optional parts\r
+ var bits = prop.split(/[-T:+Z]/g);\r
+ \r
+ var d = new Date(bits[0], bits[1]-1, bits[2]); \r
+ var secondBits = bits[5].split("\.");\r
+ d.setHours(bits[3], bits[4], secondBits[0]);\r
+ if(secondBits.length>1)\r
+ d.setMilliseconds(secondBits[1]);\r
+\r
+ // Get supplied time zone offset in minutes\r
+ if(bits[6] && bits[7]) {\r
+ var offsetMinutes = bits[6] * 60 + Number(bits[7]);\r
+ var sign = /\d\d-\d\d:\d\d$/.test(prop)? '-' : '+';\r
+\r
+ // Apply the sign\r
+ offsetMinutes = 0 + (sign == '-'? -1 * offsetMinutes : offsetMinutes);\r
+\r
+ // Apply offset and local timezone\r
+ d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset())\r
+ }\r
+ else\r
+ if(prop.indexOf("Z", prop.length - 1) !== -1) {\r
+ d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds())); \r
+ }\r
+\r
+ // d is now a local time equivalent to the supplied time\r
+ return d;\r
+ }\r
+ \r
+ function checkFromXmlDateTimePaths(value, childName, fullPath) {\r
+ if(config.datetimeAccessFormPaths.length > 0) {\r
+ var path = fullPath.split("\.#")[0];\r
+ var idx = 0;\r
+ for(; idx < config.datetimeAccessFormPaths.length; idx++) {\r
+ var dtPath = config.datetimeAccessFormPaths[idx];\r
+ if( typeof dtPath === "string" ) {\r
+ if(dtPath == path)\r
+ break;\r
+ }\r
+ else\r
+ if( dtPath instanceof RegExp) {\r
+ if(dtPath.test(path))\r
+ break;\r
+ } \r
+ else\r
+ if( typeof dtPath === "function") {\r
+ if(dtPath(obj, childName, path))\r
+ break;\r
+ }\r
+ }\r
+ if(idx!=config.datetimeAccessFormPaths.length) {\r
+ return fromXmlDateTime(value);\r
+ }\r
+ else\r
+ return value;\r
+ }\r
+ else\r
+ return value;\r
+ }\r
+\r
+ function parseDOMChildren( node, path ) {\r
+ if(node.nodeType == DOMNodeTypes.DOCUMENT_NODE) {\r
+ var result = new Object;\r
+ var nodeChildren = node.childNodes;\r
+ // Alternative for firstElementChild which is not supported in some environments\r
+ for(var cidx=0; cidx <nodeChildren.length; cidx++) {\r
+ var child = nodeChildren.item(cidx);\r
+ if(child.nodeType == DOMNodeTypes.ELEMENT_NODE) {\r
+ var childName = getNodeLocalName(child);\r
+ result[childName] = parseDOMChildren(child, childName);\r
+ }\r
+ }\r
+ return result;\r
+ }\r
+ else\r
+ if(node.nodeType == DOMNodeTypes.ELEMENT_NODE) {\r
+ var result = new Object;\r
+ result.__cnt=0;\r
+ \r
+ var nodeChildren = node.childNodes;\r
+ \r
+ // Children nodes\r
+ for(var cidx=0; cidx <nodeChildren.length; cidx++) {\r
+ var child = nodeChildren.item(cidx); // nodeChildren[cidx];\r
+ var childName = getNodeLocalName(child);\r
+ \r
+ if(child.nodeType!= DOMNodeTypes.COMMENT_NODE) {\r
+ result.__cnt++;\r
+ if(result[childName] == null) {\r
+ result[childName] = parseDOMChildren(child, path+"."+childName);\r
+ toArrayAccessForm(result, childName, path+"."+childName); \r
+ }\r
+ else {\r
+ if(result[childName] != null) {\r
+ if( !(result[childName] instanceof Array)) {\r
+ result[childName] = [result[childName]];\r
+ toArrayAccessForm(result, childName, path+"."+childName);\r
+ }\r
+ }\r
+ (result[childName])[result[childName].length] = parseDOMChildren(child, path+"."+childName);\r
+ }\r
+ } \r
+ }\r
+ \r
+ // Attributes\r
+ for(var aidx=0; aidx <node.attributes.length; aidx++) {\r
+ var attr = node.attributes.item(aidx); // [aidx];\r
+ result.__cnt++;\r
+ result[config.attributePrefix+attr.name]=attr.value;\r
+ }\r
+ \r
+ // Node namespace prefix\r
+ var nodePrefix = getNodePrefix(node);\r
+ if(nodePrefix!=null && nodePrefix!="") {\r
+ result.__cnt++;\r
+ result.__prefix=nodePrefix;\r
+ }\r
+ \r
+ if(result["#text"]!=null) { \r
+ result.__text = result["#text"];\r
+ if(result.__text instanceof Array) {\r
+ result.__text = result.__text.join("\n");\r
+ }\r
+ if(config.escapeMode)\r
+ result.__text = unescapeXmlChars(result.__text);\r
+ if(config.stripWhitespaces)\r
+ result.__text = result.__text.trim();\r
+ delete result["#text"];\r
+ if(config.arrayAccessForm=="property")\r
+ delete result["#text_asArray"];\r
+ result.__text = checkFromXmlDateTimePaths(result.__text, childName, path+"."+childName);\r
+ }\r
+ if(result["#cdata-section"]!=null) {\r
+ result.__cdata = result["#cdata-section"];\r
+ delete result["#cdata-section"];\r
+ if(config.arrayAccessForm=="property")\r
+ delete result["#cdata-section_asArray"];\r
+ }\r
+ \r
+ if( result.__cnt == 1 && result.__text!=null ) {\r
+ result = result.__text;\r
+ }\r
+ else\r
+ if( result.__cnt == 0 && config.emptyNodeForm=="text" ) {\r
+ result = '';\r
+ }\r
+ else\r
+ if ( result.__cnt > 1 && result.__text!=null && config.skipEmptyTextNodesForObj) {\r
+ if( (config.stripWhitespaces && result.__text=="") || (result.__text.trim()=="")) {\r
+ delete result.__text;\r
+ }\r
+ }\r
+ delete result.__cnt; \r
+ \r
+ if( config.enableToStringFunc && (result.__text!=null || result.__cdata!=null )) {\r
+ result.toString = function() {\r
+ return (this.__text!=null? this.__text:'')+( this.__cdata!=null ? this.__cdata:'');\r
+ };\r
+ }\r
+ \r
+ return result;\r
+ }\r
+ else\r
+ if(node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) {\r
+ return node.nodeValue;\r
+ } \r
+ }\r
+ \r
+ function startTag(jsonObj, element, attrList, closed) {\r
+ var resultStr = "<"+ ( (jsonObj!=null && jsonObj.__prefix!=null)? (jsonObj.__prefix+":"):"") + element;\r
+ if(attrList!=null) {\r
+ for(var aidx = 0; aidx < attrList.length; aidx++) {\r
+ var attrName = attrList[aidx];\r
+ var attrVal = jsonObj[attrName];\r
+ if(config.escapeMode)\r
+ attrVal=escapeXmlChars(attrVal);\r
+ resultStr+=" "+attrName.substr(config.attributePrefix.length)+"='"+attrVal+"'";\r
+ }\r
+ }\r
+ if(!closed)\r
+ resultStr+=">";\r
+ else\r
+ resultStr+="/>";\r
+ return resultStr;\r
+ }\r
+ \r
+ function endTag(jsonObj,elementName) {\r
+ return "</"+ (jsonObj.__prefix!=null? (jsonObj.__prefix+":"):"")+elementName+">";\r
+ }\r
+ \r
+ function endsWith(str, suffix) {\r
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;\r
+ }\r
+ \r
+ function jsonXmlSpecialElem ( jsonObj, jsonObjField ) {\r
+ if((config.arrayAccessForm=="property" && endsWith(jsonObjField.toString(),("_asArray"))) \r
+ || jsonObjField.toString().indexOf(config.attributePrefix)==0 \r
+ || jsonObjField.toString().indexOf("__")==0\r
+ || (jsonObj[jsonObjField] instanceof Function) )\r
+ return true;\r
+ else\r
+ return false;\r
+ }\r
+ \r
+ function jsonXmlElemCount ( jsonObj ) {\r
+ var elementsCnt = 0;\r
+ if(jsonObj instanceof Object ) {\r
+ for( var it in jsonObj ) {\r
+ if(jsonXmlSpecialElem ( jsonObj, it) )\r
+ continue; \r
+ elementsCnt++;\r
+ }\r
+ }\r
+ return elementsCnt;\r
+ }\r
+ \r
+ function parseJSONAttributes ( jsonObj ) {\r
+ var attrList = [];\r
+ if(jsonObj instanceof Object ) {\r
+ for( var ait in jsonObj ) {\r
+ if(ait.toString().indexOf("__")== -1 && ait.toString().indexOf(config.attributePrefix)==0) {\r
+ attrList.push(ait);\r
+ }\r
+ }\r
+ }\r
+ return attrList;\r
+ }\r
+ \r
+ function parseJSONTextAttrs ( jsonTxtObj ) {\r
+ var result ="";\r
+ \r
+ if(jsonTxtObj.__cdata!=null) { \r
+ result+="<![CDATA["+jsonTxtObj.__cdata+"]]>"; \r
+ }\r
+ \r
+ if(jsonTxtObj.__text!=null) { \r
+ if(config.escapeMode)\r
+ result+=escapeXmlChars(jsonTxtObj.__text);\r
+ else\r
+ result+=jsonTxtObj.__text;\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ function parseJSONTextObject ( jsonTxtObj ) {\r
+ var result ="";\r
+\r
+ if( jsonTxtObj instanceof Object ) {\r
+ result+=parseJSONTextAttrs ( jsonTxtObj );\r
+ }\r
+ else\r
+ if(jsonTxtObj!=null) {\r
+ if(config.escapeMode)\r
+ result+=escapeXmlChars(jsonTxtObj);\r
+ else\r
+ result+=jsonTxtObj;\r
+ }\r
+ \r
+ return result;\r
+ }\r
+ \r
+ function parseJSONArray ( jsonArrRoot, jsonArrObj, attrList ) {\r
+ var result = ""; \r
+ if(jsonArrRoot.length == 0) {\r
+ result+=startTag(jsonArrRoot, jsonArrObj, attrList, true);\r
+ }\r
+ else {\r
+ for(var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {\r
+ result+=startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);\r
+ result+=parseJSONObject(jsonArrRoot[arIdx]);\r
+ result+=endTag(jsonArrRoot[arIdx],jsonArrObj); \r
+ }\r
+ }\r
+ return result;\r
+ }\r
+ \r
+ function parseJSONObject ( jsonObj ) {\r
+ var result = ""; \r
+\r
+ var elementsCnt = jsonXmlElemCount ( jsonObj );\r
+ \r
+ if(elementsCnt > 0) {\r
+ for( var it in jsonObj ) {\r
+ \r
+ if(jsonXmlSpecialElem ( jsonObj, it) )\r
+ continue; \r
+ \r
+ var subObj = jsonObj[it]; \r
+ \r
+ var attrList = parseJSONAttributes( subObj )\r
+ \r
+ if(subObj == null || subObj == undefined) {\r
+ result+=startTag(subObj, it, attrList, true);\r
+ }\r
+ else\r
+ if(subObj instanceof Object) {\r
+ \r
+ if(subObj instanceof Array) { \r
+ result+=parseJSONArray( subObj, it, attrList ); \r
+ }\r
+ else if(subObj instanceof Date) {\r
+ result+=startTag(subObj, it, attrList, false);\r
+ result+=subObj.toISOString();\r
+ result+=endTag(subObj,it);\r
+ }\r
+ else {\r
+ var subObjElementsCnt = jsonXmlElemCount ( subObj );\r
+ if(subObjElementsCnt > 0 || subObj.__text!=null || subObj.__cdata!=null) {\r
+ result+=startTag(subObj, it, attrList, false);\r
+ result+=parseJSONObject(subObj);\r
+ result+=endTag(subObj,it);\r
+ }\r
+ else {\r
+ result+=startTag(subObj, it, attrList, true);\r
+ }\r
+ }\r
+ }\r
+ else {\r
+ result+=startTag(subObj, it, attrList, false);\r
+ result+=parseJSONTextObject(subObj);\r
+ result+=endTag(subObj,it);\r
+ }\r
+ }\r
+ }\r
+ result+=parseJSONTextObject(jsonObj);\r
+ \r
+ return result;\r
+ }\r
+ \r
+ this.parseXmlString = function(xmlDocStr) {\r
+ var isIEParser = window.ActiveXObject || "ActiveXObject" in window;\r
+ if (xmlDocStr === undefined) {\r
+ return null;\r
+ }\r
+ var xmlDoc;\r
+ if (window.DOMParser) {\r
+ var parser=new window.DOMParser(); \r
+ var parsererrorNS = null;\r
+ // IE9+ now is here\r
+ if(!isIEParser) {\r
+ try {\r
+ parsererrorNS = parser.parseFromString("INVALID", "text/xml").childNodes[0].namespaceURI;\r
+ }\r
+ catch(err) { \r
+ parsererrorNS = null;\r
+ }\r
+ }\r
+ try {\r
+ xmlDoc = parser.parseFromString( xmlDocStr, "text/xml" );\r
+ if( parsererrorNS!= null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) {\r
+ //throw new Error('Error parsing XML: '+xmlDocStr);\r
+ xmlDoc = null;\r
+ }\r
+ }\r
+ catch(err) {\r
+ xmlDoc = null;\r
+ }\r
+ }\r
+ else {\r
+ // IE :(\r
+ if(xmlDocStr.indexOf("<?")==0) {\r
+ xmlDocStr = xmlDocStr.substr( xmlDocStr.indexOf("?>") + 2 );\r
+ }\r
+ xmlDoc=new ActiveXObject("Microsoft.XMLDOM");\r
+ xmlDoc.async="false";\r
+ xmlDoc.loadXML(xmlDocStr);\r
+ }\r
+ return xmlDoc;\r
+ };\r
+ \r
+ this.asArray = function(prop) {\r
+ if(prop instanceof Array)\r
+ return prop;\r
+ else\r
+ return [prop];\r
+ };\r
+ \r
+ this.toXmlDateTime = function(dt) {\r
+ if(dt instanceof Date)\r
+ return dt.toISOString();\r
+ else\r
+ if(typeof(dt) === 'number' )\r
+ return new Date(dt).toISOString();\r
+ else \r
+ return null;\r
+ };\r
+ \r
+ this.asDateTime = function(prop) {\r
+ if(typeof(prop) == "string") {\r
+ return fromXmlDateTime(prop);\r
+ }\r
+ else\r
+ return prop;\r
+ };\r
+\r
+ this.xml2json = function (xmlDoc) {\r
+ return parseDOMChildren ( xmlDoc );\r
+ };\r
+ \r
+ this.xml_str2json = function (xmlDocStr) {\r
+ var xmlDoc = this.parseXmlString(xmlDocStr);\r
+ if(xmlDoc!=null)\r
+ return this.xml2json(xmlDoc);\r
+ else\r
+ return null;\r
+ };\r
+\r
+ this.json2xml_str = function (jsonObj) {\r
+ return parseJSONObject ( jsonObj );\r
+ };\r
+\r
+ this.json2xml = function (jsonObj) {\r
+ var xmlDocStr = this.json2xml_str (jsonObj);\r
+ return this.parseXmlString(xmlDocStr);\r
+ };\r
+ \r
+ this.getVersion = function () {\r
+ return VERSION;\r
+ };\r
+ \r
+}\r