UNPKG

node-x2js

Version:

x2js - XML to JSON and vice versa for JavaScript

575 lines (503 loc) 17.8 kB
/* based on https://github.com/abdmob/x2js/blob/master/xml2json.js, VERSION = "1.2.0" */ /*eslint-disable quotes*/ /*eslint-disable prefer-template*/ /*eslint-disable camelcase*/ /*eslint-disable eqeqeq*/ /*eslint-disable prefer-const*/ /*eslint-disable space-unary-ops*/ /*eslint-disable semi-spacing*/ /*eslint-disable no-unused-vars*/ const X2JS = function (config) { 'use strict'; const 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; } } const DOMNodeTypes = { ELEMENT_NODE: 1, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, COMMENT_NODE: 8, DOCUMENT_NODE: 9 }; function initRequiredPolyfills() { } function getNodeLocalName(node) { let 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); else return str; } function unescapeXmlChars(str) { return str.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, '&'); } function checkInStdFiltersArrayForm(stdFiltersArrayForm, obj, name, path) { let idx = 0; for (; idx < stdFiltersArrayForm.length; idx++) { let 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 let bits = prop.split(/[-T:+Z]/g); let d = new Date(bits[0], bits[1] - 1, bits[2]); let 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]) { let offsetMinutes = bits[6] * 60 + Number(bits[7]); let 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) { let 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) { let result = {}; let nodeChildren = node.childNodes; // Alternative for firstElementChild which is not supported in some environments for (let cidx = 0; cidx < nodeChildren.length; cidx++) { let child = nodeChildren.item(cidx); if (child.nodeType == DOMNodeTypes.ELEMENT_NODE) { let childName = getNodeLocalName(child); result[childName] = parseDOMChildren(child, childName); } } return result; } else if (node.nodeType == DOMNodeTypes.ELEMENT_NODE) { let result = {}; result.__cnt = 0; let nodeChildren = node.childNodes; let childName = ''; // Children nodes for (let cidx = 0; cidx < nodeChildren.length; cidx++) { let child = nodeChildren.item(cidx); // nodeChildren[cidx]; childName = getNodeLocalName(child); if (child.nodeType != DOMNodeTypes.COMMENT_NODE) { let 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 (let aidx = 0; aidx < node.attributes.length; aidx++) { let attr = node.attributes.item(aidx); // [aidx]; result.__cnt++; result[config.attributePrefix + attr.name] = attr.value; } // Node namespace prefix let 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) { let resultStr = "<" + ((jsonObj != null && jsonObj.__prefix != null) ? (jsonObj.__prefix + ":") : "") + element; if (attrList != null) { for (let aidx = 0; aidx < attrList.length; aidx++) { let attrName = attrList[aidx]; let 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) { let elementsCnt = 0; if (jsonObj instanceof Object) { for (let 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) { let attrList = []; if (jsonObj instanceof Object) { for (let ait in jsonObj) { if (ait.toString().indexOf("__") == -1 && ait.toString().indexOf(config.attributePrefix) == 0) { attrList.push(ait); } } } return attrList; } function parseJSONTextAttrs(jsonTxtObj) { let 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) { let 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) { let result = ""; if (jsonArrRoot.length == 0) { result += startTag(jsonArrRoot, jsonArrObj, attrList, true); } else { for (let 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) { let result = ""; let elementsCnt = jsonXmlElemCount(jsonObj); if (elementsCnt > 0) { for (let it in jsonObj) { if (jsonXmlSpecialElem(jsonObj, it) || (jsonObjPath != "" && !checkJsonObjPropertiesFilter(jsonObj, it, getJsonPropertyPath(jsonObjPath, it)))) continue; let subObj = jsonObj[it]; let 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 { let 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) { let isIEParser = window.ActiveXObject || "ActiveXObject" in window; if (xmlDocStr === undefined) { return null; } let xmlDoc; if (window.DOMParser) { let parser = new window.DOMParser(); let 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"); // eslint-disable-line no-undef 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) { let 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) { let xmlDocStr = this.json2xml_str(jsonObj); return this.parseXmlString(xmlDocStr); }; this.getVersion = function () { return VERSION; }; }; export default X2JS;