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