/** * JS XML merging class * merge multiple XML sources * supports browser and NodeJS environments * * @package MergeXML * @author Vallo Reima * @copyright (C)2014-2019 */ /** * AMD/CommonJS wrapper * @author Martijn van de Rijdt * * @param {object} root * @param {function} factory */ (function (root, factory) { "use strict"; if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module define([], factory); } else if (typeof exports === 'object') { // Does not work with strict CommonJS, // but only CommonJS-like environments // that support module.exports, like Node module.exports = factory(); } else { // Direct call, root is the owner (window) root.MergeXML = factory(); } }(this, function () { /** * Return a function as the exported value * @param {object} opts -- processiong options (see readme) */ return function (opts) { var mde; /* access mode: 1 - IE, 2 - browser, 3 - nodejs */ var msv; /* MS DOM version */ var psr; /* DOMParser object */ var xpe; /* xPath evaluator object */ var xpr; /* XPathResult object */ var nsr; /* namespace resolver method */ var nsd = {/* default namespace prefix and URIs */ pfx: '_', psr: 'http://www.w3.org/1999/xhtml', xpe: 'http://www.w3.org/2000/xmlns/' }; var erp; /* parsing error flag */ var stay; /* overwrite protection */ var join; /* joining root name and status*/ var updn; /* update nodes sequentially by name */ var XML_ELEMENT_NODE = 1; var XML_TEXT_NODE = 3; var XML_COMMENT_NODE = 8; var XML_PI_NODE = 7; var that = this; var Init = function () { that.error = {}; /* detect NodeJS environment */ if (typeof process !== 'undefined' && process.versions && process.versions.node) { var p = typeof opts === 'object' && typeof opts.path === 'string' ? opts.path : ''; try { //obtain xml support global.XPathEvaluator = require(p + 'xpath'); global.DOMParser = require((p || '@xmldom/') + 'xmldom').DOMParser; global.XMLSerializer = require((p || '@xmldom/') + 'xmldom').XMLSerializer; } catch (e) { console.log(e.message); } } mde = Setup(); //set mode that.Init(opts); }; /** * determine mode, set functionality * @returns {mixed} -- int - mode * false - failed */ var Setup = function () { var m; var f = false; var vers = [//IE 'MSXML2.DOMDocument.6.0', 'MSXML2.DOMDocument.3.0', 'MSXML2.DOMDocument', 'Microsoft.XmlDom' ]; var n = vers.length; for (var i = 0; i < n; i++) { try { var d = new ActiveXObject(vers[i]); d.async = false; f = true; /* DOM supported */ if (d.loadXML('') && d.selectSingleNode('/')) { break; /* xPath supported */ } } catch (e) { /* skip */ } } if (f) { if (i < n) { msv = vers[i]; m = 1; /* IE mode */ } else { m = 'nox'; /* no xPath */ } } else { var env; if (typeof window !== 'undefined') { env = window; m = 2; // any browser } else if (typeof global !== 'undefined') { env = global; m = 3; // NodeJS } else { env = {}; //unknown } if (!env.DOMParser) { m = 'nod'; /* no DOM */ } else if (!env.XMLSerializer) { m = 'nos'; /* no Serializer */ } else if (!env.XPathEvaluator) { m = 'nox'; /* no xPath */ } else if (m === 2) { //browser psr = new env.DOMParser(); xpe = new env.XPathEvaluator(); xpr = env.XPathResult; f = psr.parseFromString(' 1 && !DOMParser) || (mde === 1 && !doc.selectSingleNode('/')))) { doc = null; /* not compatible */ } } else { try { doc = Load(xml); } catch (e) { doc = false; } } } if (!mde) { rlt = mde; } else if (doc === null) { rlt = Error('nob'); } else if (doc === false) { rlt = Error('inv'); } else if (doc === true) { that.nsp = NameSpaces(that.dom.documentElement); that.count = 1; rlt = that.dom; } else if (CheckSource(doc)) { Merge(doc, '/'); /* add to existing */ if (join[1] === true) { var tmp = that.dom.createTextNode("\r\n"); that.dom.documentElement.appendChild(tmp); } that.count++; rlt = that.dom; } else { rlt = false; } return rlt; }; /** * load the source into dom object * @param {object|string} src -- the source * @return {mixed} -- false - error * true - 1st load * object - loaded doc */ var Load = function (src) { var rlt, doc; if (mde > 1) { erp = false; if (that.dom) { doc = psr.parseFromString(src, 'text/xml'); rlt = ParseError(doc) ? doc : false; } else { that.dom = psr.parseFromString(src, 'text/xml'); rlt = ParseError(that.dom) ? true : false; } } else if (that.dom) { doc = new ActiveXObject(msv); doc.async = false; rlt = doc.loadXML(src) ? doc : false; } else { that.dom = new ActiveXObject(msv); that.dom.async = false; that.dom.setProperty('SelectionLanguage', 'XPath'); rlt = that.dom.loadXML(src) ? true : false; } return rlt; }; /** * check for xml syntax (mode 2) * @param {object} doc * @return {bool} -- true - ok */ var ParseError = function (doc) { return !erp && !doc.getElementsByTagNameNS(nsd.psr, 'parsererror').length; }; /** * * @param {object} doc * @return {bool} -- true - ok */ var CheckSource = function (doc) { var rlt = true; var charSet1 = that.dom.characterSet || that.dom.inputEncoding || that.dom.xmlEncoding; var charSet2 = doc.characterSet || doc.inputEncoding || doc.xmlEncoding; if (charSet2 !== charSet1) { rlt = Error('enc'); } else if (doc.documentElement.namespaceURI !== that.dom.documentElement.namespaceURI) { /* $dom->documentElement->lookupnamespaceURI(NULL) */ rlt = Error('nse'); } else if (doc.documentElement.nodeName !== that.dom.documentElement.nodeName) { if (!join[0]) { rlt = Error('dif'); } else if (!join[1]) { var enc = typeof charSet1 !== 'undefined' ? charSet1 : 'UTF-8'; var ver = that.dom.xmlVersion ? that.dom.xmlVersion : '1.0'; var xml = '\r\n<" + join[0] + ">\r\n'; var d = Load(xml); if (d) { var tmp = that.dom.documentElement.cloneNode(true); d.documentElement.appendChild(tmp); tmp = d.createTextNode("\r\n"); d.documentElement.appendChild(tmp); that.dom = d; join[1] = true; } else { rlt = Error('jne'); join[1] = null; } } } if (rlt) { var a = NameSpaces(doc.documentElement); for (var c in a) { if (!that.nsp[c]) { if (typeof that.dom.documentElement.setAttributeNS !== 'undefined') { that.dom.documentElement.setAttributeNS(nsd.xpe, 'xmlns:' + c, a[c]); } else { // no choice but to use the incorrect setAttribute instead that.dom.documentElement.setAttribute('xmlns:' + c, a[c]); } that.nsp[c] = a[c]; } } if (!updn) { nsr = null; } else if (mde === 1) { ResolverIE(); } else if (mde === 3) { nsr = that; } else { nsr = that.lookupNamespaceURI; } } return rlt; }; /** * join 2 dom objects recursively * @param {object} src -- current source node * @param {string} pth -- current source path */ var Merge = function (src, pth) { for (var i = 0; i < src.childNodes.length; i++) { var tmp; var node = src.childNodes[i]; //$node->getNodePath() var path = GetNodePath(src.childNodes, node, pth, i); var obj = that.Query(path); if (node.nodeType === XML_ELEMENT_NODE) { var flg = true; /* replace existing node by default */ if (obj === null || obj.namespaceURI !== node.namespaceURI) { tmp = node.cloneNode(true); /* take existing node */ obj = that.Query(pth); /* destination parent */ obj.appendChild(tmp); /* add a node */ } else { if (ArraySearch(obj.getAttribute('stay'), stay) !== false) { flg = false; /* don't replace */ } if (flg) { try { for (var j = 0; j < node.attributes.length; j++) { /* add/replace attributes */ if (node.attributes[j].namespaceURI && typeof node.setAttributeNS !== 'undefined') { obj.setAttributeNS(node.attributes[j].namespaceURI, node.attributes[j].nodeName, node.attributes[j].nodeValue); } else { obj.setAttribute(node.attributes[j].nodeName, node.attributes[j].nodeValue); } } } catch (e) { /* read-only node */ } } } if (node.hasChildNodes() && flg) { Merge(node, path); /* go to subnodes */ } } else if (node.nodeType === XML_TEXT_NODE || node.nodeType === XML_COMMENT_NODE) { /* leaf node */ if (obj === null || obj.nodeType !== node.nodeType) { obj = that.Query(pth); /* destination parent node */ if (node.nodeType === XML_TEXT_NODE) { tmp = that.dom.createTextNode(node.nodeValue); /* add text */ } else { tmp = that.dom.createComment(node.nodeValue); /* add comment */ } obj.appendChild(tmp); /* add leaf */ } else { obj.nodeValue = node.nodeValue; /* replace leaf */ obj.data = node.data; //to ensure serializing } } } }; /** * form the node xPath * @param {object} nodes -- child nodes * @param {object} node -- current child * @param {string} pth -- parent path * @param {int} eln -- element sequence number * @return {string} query path */ var GetNodePath = function (nodes, node, pth, eln) { var p, i; var j = 0; if (node.nodeType === XML_ELEMENT_NODE) { for (i = 0; i <= eln; i++) { if ((updn && nodes[i].nodeType === node.nodeType && nodes[i].nodeName === node.nodeName) || (!updn && nodes[i].nodeType !== XML_PI_NODE)) { j++; } } if (updn) { var f = false; var a = NameSpaces(node); for (var c in a) { if (c !== nsd.pfx) { that.nsp[c] = a[c]; f = (mde === 1); } } if (f) { ResolverIE(); } if (node.prefix) { p = node.prefix + ':'; } else if (that.nsp[nsd.pfx]) { p = nsd.pfx + ':'; } else { p = ''; } p += (node.localName ? node.localName : node.baseName); } else { p = 'node()'; } } else if (node.nodeType === XML_TEXT_NODE || node.nodeType === XML_COMMENT_NODE) { for (i = 0; i <= eln; i++) { if (nodes[i].nodeType === node.nodeType) { j++; } } p = node.nodeType === XML_TEXT_NODE ? 'text()' : 'comment()'; } else { p = pth; } if (j) { p = pth + (pth.slice(-1) === '/' ? '' : '/') + p + '[' + j + ']'; } return p; }; /** * get node's namespaces * @param {object} node * @return {array} */ var NameSpaces = function (node) { var rlt = {}; var attrs = node.attributes; for (var i = 0; i < attrs.length; ++i) { var a = attrs[i].name.split(':'); if (a[0] === 'xmlns') { var c = a[1] ? a[1] : nsd.pfx; rlt[c] = attrs[i].value; } } return rlt; }; /** * xPath query * @param {string} qry -- query statement * @return {object} */ that.Query = function (qry) { if (!mde) { return null; } var rlt; if (join[1]) { qry = '/' + that.dom.documentElement.nodeName + (qry === '/' ? '' : qry); } try { if (mde > 1) { rlt = xpe.evaluate(qry, that.dom, nsr, xpr.FIRST_ORDERED_NODE_TYPE, null); rlt = rlt.singleNodeValue; } else { rlt = that.dom.selectSingleNode(qry); } } catch (e) { rlt = null; /* no such path */ } return rlt; }; /** * XPathNSResolver * @param {string} pfx node prefix * @return {string} namespace URI */ that.lookupNamespaceURI = function (pfx) { return that.nsp[pfx] || null; }; /** * XPath IE Resolver */ var ResolverIE = function () { var p = ''; for (var c in that.nsp) { p += ' xmlns:' + c + '=' + "'" + that.nsp[c] + "'"; } if (p) { that.dom.setProperty('SelectionNamespaces', p.substr(1)); } }; /** * find array memeber by value * @param {mixed} val * @param {array} arr * @returns {mixed} */ var ArraySearch = function (val, arr) { var rlt = false; for (var key in arr) { if (arr[key] === val) { rlt = key; break; } } return rlt; }; /** * get result * @param {int} flg -- 0 - object * 1 - xml * 2 - html * @param {object} doc * @return {mixed} */ that.Get = function (flg, doc) { var rlt; if (flg && !doc) { doc = that.dom; } if (!mde) { rlt = that.error.text; } else if (!flg) { rlt = that.dom; } else if (!doc) { rlt = ''; } else if (doc.xml) { rlt = doc.xml; } else { try { rlt = (new XMLSerializer()).serializeToString(doc); } catch (e) { rlt = e.message; flg = null; } } if (rlt && flg === 2) { /* make html view */ if (join[1]) { var k = rlt.indexOf('<' + join[0]); rlt = rlt.substr(0, k) + "\r\n" + rlt.substr(k); } rlt = rlt.replace(//g, '>').replace(/ |\t/g, ' '); /* tags and spaces */ rlt = rlt.replace(/(\r\n|\n|\r)/g, '
'); /* line breaks */ } return rlt; }; /** * set error message * @param {string} err -- token * @return {bool} false */ var Error = function (err) { var errs = { nod: 'XML DOM is not supported', nos: 'Serializer is not supported', nox: 'xPath is not supported', nob: 'Incompatible source object', nof: 'File not found', emf: 'File is empty', /* possible delivery fault */ inv: 'Invalid XML source', enc: 'Different encoding', dif: 'Different root nodes', jne: 'Invalid join parameter', nse: 'Namespace incompatibility', und: 'Undefined error' }; that.error.code = errs[err] ? err : 'und'; that.error.text = errs[that.error.code]; return false; }; Init(); }; }));