1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 2009, 2010, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2005, 2006, 2007, 2009, 2010, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 25 /** 26 * Default constructor. 27 * @class 28 * Do not directly instantiate {@link AjxXmlDoc}, use one of the create factory methods instead. 29 * 30 */ 31 AjxXmlDoc = function() { 32 if (!AjxXmlDoc._inited) 33 AjxXmlDoc._init(); 34 } 35 36 AjxXmlDoc.prototype.isAjxXmlDoc = true; 37 AjxXmlDoc.prototype.toString = function() { return "AjxXmlDoc"; } 38 39 // 40 // Constants 41 // 42 43 /** 44 * <strong>Note:</strong> 45 * Anybody that uses these regular expressions MUST reset the <code>lastIndex</code> 46 * property to zero or else the results are not guaranteed to be correct. You should 47 * use {@link AjxXmlDoc.replaceInvalidChars} instead. 48 * 49 * @private 50 */ 51 AjxXmlDoc.INVALID_CHARS_RE = /[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/g; 52 AjxXmlDoc.REC_AVOID_CHARS_RE = /[\u007F-\u0084\u0086-\u009F\uFDD0-\uFDDF]/g; 53 54 // 55 // Data 56 // 57 58 AjxXmlDoc._inited = false; 59 AjxXmlDoc._msxmlVers = null; 60 AjxXmlDoc._useDOM = 61 Boolean(document.implementation && document.implementation.createDocument); 62 AjxXmlDoc._useActiveX = 63 !AjxXmlDoc._useDOM && Boolean(window.ActiveXObject); 64 65 /** 66 * Creates an XML doc. 67 * 68 * @return {AjxXmlDoc} the XML doc 69 */ 70 AjxXmlDoc.create = 71 function() { 72 var xmlDoc = new AjxXmlDoc(); 73 var newDoc = null; 74 if (AjxXmlDoc._useActiveX) { 75 newDoc = new ActiveXObject(AjxXmlDoc._msxmlVers); 76 newDoc.async = true; // Force Async loading 77 if (AjxXmlDoc._msxmlVers == "MSXML2.DOMDocument.4.0") { 78 newDoc.setProperty("SelectionLanguage", "XPath"); 79 newDoc.setProperty("SelectionNamespaces", "xmlns:zimbra='urn:zimbra' xmlns:mail='urn:zimbraMail' xmlns:account='urn:zimbraAccount'"); 80 } 81 } else if (AjxXmlDoc._useDOM) { 82 newDoc = document.implementation.createDocument("", "", null); 83 } else { 84 throw new AjxException("Unable to create new Doc", AjxException.INTERNAL_ERROR, "AjxXmlDoc.create"); 85 } 86 xmlDoc._doc = newDoc; 87 return xmlDoc; 88 } 89 90 /** 91 * Creates an XML doc from a document object. 92 * 93 * @param {Document} doc the document object 94 * @return {AjxXmlDoc} the XML doc 95 */ 96 AjxXmlDoc.createFromDom = 97 function(doc) { 98 var xmlDoc = new AjxXmlDoc(); 99 xmlDoc._doc = doc; 100 return xmlDoc; 101 } 102 103 /** 104 * Creates an XML doc from an XML string. 105 * 106 * @param {string} xml the XML string 107 * @return {AjxXmlDoc} the XML doc 108 */ 109 AjxXmlDoc.createFromXml = 110 function(xml) { 111 var xmlDoc = AjxXmlDoc.create(); 112 xmlDoc.loadFromString(xml); 113 return xmlDoc; 114 } 115 116 /** 117 * Replaces invalid characters in the given string. 118 * 119 * @param {string} s the string 120 * @return {string} the resulting string 121 */ 122 AjxXmlDoc.replaceInvalidChars = function(s) { 123 AjxXmlDoc.INVALID_CHARS_RE.lastIndex = 0; 124 return s.replace(AjxXmlDoc.INVALID_CHARS_RE, "?"); 125 }; 126 127 AjxXmlDoc.getXml = function(node) { 128 129 if (!node) { 130 return ''; 131 } 132 133 var xml = node.xml; 134 if (!xml) { 135 var ser = new XMLSerializer(); 136 xml = ser.serializeToString(node); 137 } 138 139 return AjxXmlDoc.replaceInvalidChars(xml); 140 }; 141 142 /** 143 * Gets the document. 144 * 145 * @return {Document} the document 146 */ 147 AjxXmlDoc.prototype.getDoc = 148 function() { 149 return this._doc; 150 } 151 152 AjxXmlDoc.prototype.loadFromString = 153 function(str) { 154 var doc = this._doc; 155 doc.loadXML(str); 156 if (AjxXmlDoc._useActiveX) { 157 if (doc.parseError.errorCode != 0) 158 throw new AjxException(doc.parseError.reason, AjxException.INVALID_PARAM, "AjxXmlDoc.loadFromString"); 159 } 160 } 161 162 AjxXmlDoc.prototype.loadFromUrl = 163 function(url) { 164 if(AjxEnv.isChrome || AjxEnv.isSafari) { 165 var xmlhttp = new window.XMLHttpRequest(); 166 xmlhttp.open("GET", url, false); 167 xmlhttp.send(null); 168 var xmlDoc = xmlhttp.responseXML; 169 this._doc = xmlDoc; 170 } else { 171 this._doc.load(url); 172 } 173 } 174 175 /** 176 * This function tries to create a JavaScript representation of the DOM. In some cases, 177 * it is easier to work with JS objects rather than do DOM lookups. 178 * 179 * <p> 180 * Rules: 181 * <ol> 182 * <li>The top-level tag gets lost; only it's content is seen important.</li> 183 * <li>Each node will be represented as a JS object. It's textual content 184 * will be saved in node.__msh_content (returned by <code>toString()</code>).</li> 185 * <li>Attributes get discarded.</li> 186 * <li>Each subnode will map to a property with its tagName in the parent 187 * node <code>parent[subnode.tagName] == subnode</code></li> 188 * <li>If multiple nodes with the same tagName have the same parent node, then 189 * <code>parent[tagName]</code> will be an array containing the objects, rather than a 190 * single object.</li> 191 * </ol> 192 * 193 * So what this function allows us to do is for instance this, starting with this XML doc: 194 * 195 * <pre> 196 * <error> 197 * <code>404</code> 198 * <name>Not Found</name> 199 * <description>Page wasn't found on this server.</description> 200 * </error> 201 * </pre> 202 * 203 * <pre> 204 * var obj = AjxXmlDoc.createFromXml(XML).toJSObject(); 205 * alert(obj.code + " " + obj.name + " " + obj.description); 206 * </pre> 207 * 208 * Here's an array example: 209 * <pre> 210 * <return> 211 * <item> 212 * <name>John Doe</name> 213 * <email>foo@bar.com</email> 214 * </item> 215 * <item> 216 * <name>Johnny Bravo</name> 217 * <email>bravo@cartoonnetwork.com</email> 218 * </item> 219 * </return> 220 * </pre> 221 * 222 * <pre> 223 * var obj = AjxXmlDoc.createFromXml(XML).toJSObject(); 224 * for (var i = 0; i < obj.item.length; ++i) { 225 * alert(obj.item[i].name + " / " + obj.item[i].email); 226 * } 227 * </pre> 228 * 229 * Note that if there's only one <item> tag, then obj.item will be an object 230 * rather than an array. And if there is no <item> tag, then obj.item will be 231 * undefined. These are cases that the calling application must take care of. 232 */ 233 AjxXmlDoc.prototype.toJSObject = 234 function(dropns, lowercase, withAttrs) { 235 _node = function() { this.__msh_content = ''; }; 236 _node.prototype.toString = function() { return this.__msh_content; }; 237 rec = function(i, o) { 238 var tags = {}, t, n; 239 for (i = i.firstChild; i; i = i.nextSibling) { 240 if (i.nodeType == 1) { 241 t = i.tagName; 242 if (dropns) t = t.replace(/^.*?:/, ""); 243 if (lowercase) t = t.toLowerCase(); 244 n = new _node(); 245 if (tags[t]) { 246 if (tags[t] == 1) { 247 o[t] = [ o[t] ]; 248 tags[t] = 2; 249 } 250 o[t].push(n); 251 } else { 252 o[t] = n; 253 tags[t] = 1; 254 } 255 //do attributes 256 if(withAttrs) { 257 if(i.attributes && i.attributes.length) { 258 for(var ix = 0;ix<i.attributes.length;ix++) { 259 attr = i.attributes[ix]; 260 n[attr.name] = AjxUtil.isNumeric(attr.value) ? attr.value : String(attr.value); 261 } 262 } 263 } 264 rec(i, n); 265 } else if (i.nodeType == 3) 266 o.__msh_content += i.nodeValue; 267 } 268 }; 269 var o = new _node(); 270 rec(this._doc.documentElement, o); 271 return o; 272 }; 273 274 AjxXmlDoc.prototype.getElementsByTagNameNS = 275 function(ns, tag) { 276 var doc = this.getDoc(); 277 return !doc.getElementsByTagNameNS 278 ? doc.getElementsByTagName(ns + ":" + tag) 279 : doc.getElementsByTagNameNS(ns, tag); 280 }; 281 282 AjxXmlDoc.prototype.getFirstElementByTagNameNS = 283 function(ns, tag) { 284 return this.getElementsByTagNameNS(ns, tag)[0]; 285 }; 286 287 AjxXmlDoc.prototype.getElementsByTagName = 288 function(tag) { 289 var doc = this.getDoc(); 290 return doc.getElementsByTagName(tag); 291 }; 292 293 AjxXmlDoc._init = 294 function() { 295 if (AjxXmlDoc._useActiveX) { 296 var msxmlVers = ["MSXML4.DOMDocument", "MSXML3.DOMDocument", "MSXML2.DOMDocument.4.0", 297 "MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", 298 "Microsoft.XmlDom"]; 299 for (var i = 0; i < msxmlVers.length; i++) { 300 try { 301 new ActiveXObject(msxmlVers[i]); 302 AjxXmlDoc._msxmlVers = msxmlVers[i]; 303 break; 304 } catch (ex) { 305 } 306 } 307 if (!AjxXmlDoc._msxmlVers) { 308 throw new AjxException("MSXML not installed", AjxException.INTERNAL_ERROR, "AjxXmlDoc._init"); 309 } 310 } else if (!Document.prototype.loadXML) { 311 // add loadXML to Document's API 312 Document.prototype.loadXML = function(str) { 313 var domParser = new DOMParser(); 314 var domObj = domParser.parseFromString(str, "text/xml"); 315 // remove old child nodes since we recycle DOMParser and append new 316 while (this.hasChildNodes()) { 317 this.removeChild(this.lastChild); 318 } 319 var len = domObj.childNodes.length; 320 for (var i = 0; i < len; i++) { 321 var importedNode = this.importNode(domObj.childNodes[i], true); 322 this.appendChild(importedNode); 323 } 324 } 325 326 if (AjxEnv.isNav) { 327 _NodeGetXml = function() { 328 var ser = new XMLSerializer(); 329 return ser.serializeToString(this); 330 } 331 Node.prototype.__defineGetter__("xml", _NodeGetXml); 332 } 333 } 334 335 AjxXmlDoc._inited = true; 336 }; 337 338 AjxXmlDoc.prototype.set = 339 function(name, value, element) { 340 var p = this._doc.createElement(name); 341 if (value != null) { 342 var cdata = this._doc.createTextNode(""); 343 p.appendChild(cdata); 344 cdata.nodeValue = value; 345 } 346 if (element == null) { 347 this.root.appendChild(p); 348 } else { 349 element.appendChild(p); 350 } 351 return p; 352 }; 353 354 AjxXmlDoc.prototype.getXml = function() { 355 return AjxXmlDoc.getXml(this.getDoc()); 356 }; 357 AjxXmlDoc.prototype.getDocXml = AjxXmlDoc.prototype.getXml; // back-compatibility with old name 358 359 /** 360 * Creates an XML document with a root element. 361 * 362 * @param {string} rootName the root name 363 * @return {AjxXmlDoc} the XML document 364 */ 365 AjxXmlDoc.createRoot = 366 function(rootName) { 367 var xmldoc = AjxXmlDoc.create(); 368 var d = xmldoc.getDoc(); 369 xmldoc.root = d.createElement(rootName); 370 371 d.appendChild(xmldoc.root); 372 return xmldoc; 373 }; 374 375 /** 376 * Creates an XML document with the element. 377 * 378 * @param {string} name the element name 379 * @param {string} value the element value 380 * @return {AjxXmlDoc} the XML document 381 */ 382 AjxXmlDoc.createElement = 383 function(name, value) { 384 385 var xmldoc = AjxXmlDoc.create(); 386 var d = xmldoc.getDoc(); 387 xmldoc.root = d.createElement(name); 388 if (value != null) { 389 //xmldoc.root.nodeValue = value; 390 var cdata = d.createTextNode(""); 391 xmldoc.root.appendChild(cdata); 392 cdata.nodeValue = value; 393 } 394 395 d.appendChild(xmldoc.root); 396 return xmldoc; 397 398 }; 399 400 401 AjxXmlDoc.prototype.appendChild = 402 function(xmldoc){ 403 //Security Exception WRONG_DOCUMENT_ERR thrown when we append nodes created of diff. documents 404 //Chrome/Safari does not like it. 405 if(this._doc != xmldoc._doc && ( AjxEnv.isChrome || AjxEnv.isSafari )){ 406 this.root.appendChild(this.getDoc().importNode(xmldoc.root, true)); 407 }else{ 408 this.root.appendChild(xmldoc.root); 409 } 410 }; 411 412