3 * ====================================================================
5 * ====================================================================
6 * Sarissa is an ECMAScript library acting as a cross-browser wrapper for native XML APIs.
7 * The library supports Gecko based browsers like Mozilla and Firefox,
8 * Internet Explorer (5.5+ with MSXML3.0+), Konqueror, Safari and a little of Opera
10 * @author: Manos Batsis, mailto: mbatsis at users full stop sourceforge full stop net
11 * ====================================================================
13 * ====================================================================
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License version 2 or
16 * the GNU Lesser General Public License version 2.1 as published by
17 * the Free Software Foundation (your choice between the two).
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License or GNU Lesser General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * or GNU Lesser General Public License along with this program; if not,
26 * write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 * or visit http://www.gnu.org
31 * <p>Sarissa is a utility class. Provides "static" methods for DOMDocument and
32 * XMLHTTP objects, DOM Node serializatrion to XML strings and other goodies.</p>
37 Sarissa.PARSED_OK = "Document contains no parsing errors";
39 * Tells you whether transformNode and transformNodeToObject are available. This functionality
40 * is contained in sarissa_ieemu_xslt.js and is deprecated. If you want to control XSLT transformations
41 * use the XSLTProcessor
45 Sarissa.IS_ENABLED_TRANSFORM_NODE = false;
47 * tells you whether XMLHttpRequest (or equivalent) is available
50 Sarissa.IS_ENABLED_XMLHTTP = false;
52 * tells you whether selectNodes/selectSingleNode is available
55 Sarissa.IS_ENABLED_SELECT_NODES = false;
56 var _sarissa_iNsCounter = 0;
57 var _SARISSA_IEPREFIX4XSLPARAM = "";
58 var _SARISSA_HAS_DOM_IMPLEMENTATION = document.implementation && true;
59 var _SARISSA_HAS_DOM_CREATE_DOCUMENT = _SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.createDocument;
60 var _SARISSA_HAS_DOM_FEATURE = _SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.hasFeature;
61 var _SARISSA_IS_MOZ = _SARISSA_HAS_DOM_CREATE_DOCUMENT && _SARISSA_HAS_DOM_FEATURE;
62 var _SARISSA_IS_SAFARI = (navigator.userAgent && navigator.vendor && (navigator.userAgent.toLowerCase().indexOf("applewebkit") != -1 || navigator.vendor.indexOf("Apple") != -1));
63 var _SARISSA_IS_IE = document.all && window.ActiveXObject && navigator.userAgent.toLowerCase().indexOf("msie") > -1 && navigator.userAgent.toLowerCase().indexOf("opera") == -1;
64 if(!window.Node || !window.Node.ELEMENT_NODE){
65 var Node = {ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12};
70 // for XSLT parameter names, prefix needed by IE
71 _SARISSA_IEPREFIX4XSLPARAM = "xsl:";
72 // used to store the most recent ProgID available out of the above
73 var _SARISSA_DOM_PROGID = "";
74 var _SARISSA_XMLHTTP_PROGID = "";
76 * Called when the Sarissa_xx.js file is parsed, to pick most recent
77 * ProgIDs for IE, then gets destroyed.
78 * @param idList an array of MSXML PROGIDs from which the most recent will be picked for a given object
79 * @param enabledList an array of arrays where each array has two items; the index of the PROGID for which a certain feature is enabled
81 pickRecentProgID = function (idList, enabledList){
84 for(var i=0; i < idList.length && !bFound; i++){
86 var oDoc = new ActiveXObject(idList[i]);
89 for(var j=0;j<enabledList.length;j++)
90 if(i <= enabledList[j][1])
91 Sarissa["IS_ENABLED_"+enabledList[j][0]] = true;
92 }catch (objException){
93 // trap; try next progID
97 throw "Could not retreive a valid progID of Class: " + idList[idList.length-1]+". (original exception: "+e+")";
101 // pick best available MSXML progIDs
102 _SARISSA_DOM_PROGID = pickRecentProgID(["Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"], [["SELECT_NODES", 2],["TRANSFORM_NODE", 2]]);
103 _SARISSA_XMLHTTP_PROGID = pickRecentProgID(["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"], [["XMLHTTP", 4]]);
104 _SARISSA_THREADEDDOM_PROGID = pickRecentProgID(["Msxml2.FreeThreadedDOMDocument.5.0", "MSXML2.FreeThreadedDOMDocument.4.0", "MSXML2.FreeThreadedDOMDocument.3.0"]);
105 _SARISSA_XSLTEMPLATE_PROGID = pickRecentProgID(["Msxml2.XSLTemplate.5.0", "Msxml2.XSLTemplate.4.0", "MSXML2.XSLTemplate.3.0"], [["XSLTPROC", 2]]);
106 // we dont need this anymore
107 pickRecentProgID = null;
108 //============================================
109 // Factory methods (IE)
110 //============================================
111 // see non-IE version
112 Sarissa.getDomDocument = function(sUri, sName){
113 var oDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
114 // if a root tag name was provided, we need to load it in the DOM
117 // if needed, create an artifical namespace prefix the way Moz
120 oDoc.loadXML("<a" + _sarissa_iNsCounter + ":" + sName + " xmlns:a" + _sarissa_iNsCounter + "=\"" + sUri + "\" />");
121 // don't use the same prefix again
122 ++_sarissa_iNsCounter;
125 oDoc.loadXML("<" + sName + "/>");
129 // see non-IE version
130 Sarissa.getParseErrorText = function (oDoc) {
131 var parseErrorText = Sarissa.PARSED_OK;
132 if(oDoc.parseError != 0){
133 parseErrorText = "XML Parsing Error: " + oDoc.parseError.reason +
134 "\nLocation: " + oDoc.parseError.url +
135 "\nLine Number " + oDoc.parseError.line + ", Column " +
136 oDoc.parseError.linepos +
137 ":\n" + oDoc.parseError.srcText +
139 for(var i = 0; i < oDoc.parseError.linepos;i++){
140 parseErrorText += "-";
142 parseErrorText += "^\n";
144 return parseErrorText;
146 // see non-IE version
147 Sarissa.setXpathNamespaces = function(oDoc, sNsSet) {
148 oDoc.setProperty("SelectionLanguage", "XPath");
149 oDoc.setProperty("SelectionNamespaces", sNsSet);
152 * Basic implementation of Mozilla's XSLTProcessor for IE.
153 * Reuses the same XSLT stylesheet for multiple transforms
156 XSLTProcessor = function(){
157 this.template = new ActiveXObject(_SARISSA_XSLTEMPLATE_PROGID);
158 this.processor = null;
161 * Impoprts the given XSLT DOM and compiles it to a reusable transform
162 * @argument xslDoc The XSLT DOMDocument to import
164 XSLTProcessor.prototype.importStylesheet = function(xslDoc){
165 // convert stylesheet to free threaded
166 var converted = new ActiveXObject(_SARISSA_THREADEDDOM_PROGID);
167 converted.loadXML(xslDoc.xml);
168 this.template.stylesheet = converted;
169 this.processor = this.template.createProcessor();
170 // (re)set default param values
171 this.paramsSet = new Array();
174 * Transform the given XML DOM
175 * @argument sourceDoc The XML DOMDocument to transform
176 * @return The transformation result as a DOM Document
178 XSLTProcessor.prototype.transformToDocument = function(sourceDoc){
179 this.processor.input = sourceDoc;
180 var outDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
181 this.processor.output = outDoc;
182 this.processor.transform();
186 * Set global XSLT parameter of the imported stylesheet
187 * @argument nsURI The parameter namespace URI
188 * @argument name The parameter base name
189 * @argument value The new parameter value
191 XSLTProcessor.prototype.setParameter = function(nsURI, name, value){
192 /* nsURI is optional but cannot be null */
194 this.processor.addParameter(name, value, nsURI);
196 this.processor.addParameter(name, value);
198 /* update updated params for getParameter */
199 if(!this.paramsSet[""+nsURI]){
200 this.paramsSet[""+nsURI] = new Array();
202 this.paramsSet[""+nsURI][name] = value;
205 * Gets a parameter if previously set by setParameter. Returns null
207 * @argument name The parameter base name
208 * @argument value The new parameter value
209 * @return The parameter value if reviously set by setParameter, null otherwise
211 XSLTProcessor.prototype.getParameter = function(nsURI, name){
213 if(nsURI in this.paramsSet && name in this.paramsSet[nsURI]){
214 return this.paramsSet[nsURI][name];
220 else{ /* end IE initialization, try to deal with real browsers now ;-) */
221 if(_SARISSA_HAS_DOM_CREATE_DOCUMENT){
223 * <p>Ensures the document was loaded correctly, otherwise sets the
224 * parseError to -1 to indicate something went wrong. Internal use</p>
227 Sarissa.__handleLoad__ = function(oDoc){
228 if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror")
229 oDoc.parseError = -1;
230 Sarissa.__setReadyState__(oDoc, 4);
233 * <p>Attached by an event handler to the load event. Internal use.</p>
236 _sarissa_XMLDocument_onload = function(){
237 Sarissa.__handleLoad__(this);
240 * <p>Sets the readyState property of the given DOM Document object.
243 * @argument oDoc the DOM Document object to fire the
244 * readystatechange event
245 * @argument iReadyState the number to change the readystate property to
247 Sarissa.__setReadyState__ = function(oDoc, iReadyState){
248 oDoc.readyState = iReadyState;
249 if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function")
250 oDoc.onreadystatechange();
252 Sarissa.getDomDocument = function(sUri, sName){
253 var oDoc = document.implementation.createDocument(sUri?sUri:"", sName?sName:"", null);
254 oDoc.addEventListener("load", _sarissa_XMLDocument_onload, false);
257 if(window.XMLDocument){
259 * <p>Emulate IE's onreadystatechange attribute</p>
261 XMLDocument.prototype.onreadystatechange = null;
263 * <p>Emulates IE's readyState property, which always gives an integer from 0 to 4:</p>
264 * <ul><li>1 == LOADING,</li>
265 * <li>2 == LOADED,</li>
266 * <li>3 == INTERACTIVE,</li>
267 * <li>4 == COMPLETED</li></ul>
269 XMLDocument.prototype.readyState = 0;
271 * <p>Emulate IE's parseError attribute</p>
273 XMLDocument.prototype.parseError = 0;
275 // NOTE: setting async to false will only work with documents
276 // called over HTTP (meaning a server), not the local file system,
277 // unless you are using Moz 1.4+.
278 // BTW the try>catch block is for 1.4; I haven't found a way to check if
279 // the property is implemented without
280 // causing an error and I dont want to use user agent stuff for that...
281 var _SARISSA_SYNC_NON_IMPLEMENTED = false;// ("async" in XMLDocument.prototype) ? false: true;
283 * <p>Keeps a handle to the original load() method. Internal use and only
284 * if Mozilla version is lower than 1.4</p>
287 XMLDocument.prototype._sarissa_load = XMLDocument.prototype.load;
290 * <p>Overrides the original load method to provide synchronous loading for
291 * Mozilla versions prior to 1.4, using an XMLHttpRequest object (if
292 * async is set to false)</p>
293 * @returns the DOM Object as it was before the load() call (may be empty)
295 XMLDocument.prototype.load = function(sURI) {
296 var oDoc = document.implementation.createDocument("", "", null);
297 Sarissa.copyChildNodes(this, oDoc);
299 Sarissa.__setReadyState__(this, 1);
301 if(this.async == false && _SARISSA_SYNC_NON_IMPLEMENTED) {
302 var tmp = new XMLHttpRequest();
303 tmp.open("GET", sURI, false);
305 Sarissa.__setReadyState__(this, 2);
306 Sarissa.copyChildNodes(tmp.responseXML, this);
307 Sarissa.__setReadyState__(this, 3);
310 this._sarissa_load(sURI);
313 catch (objException) {
314 this.parseError = -1;
317 if(this.async == false){
318 Sarissa.__handleLoad__(this);
325 }//if(window.XMLDocument)
326 else if(document.implementation && document.implementation.hasFeature && document.implementation.hasFeature('LS', '3.0')){
327 Document.prototype.async = true;
328 Document.prototype.onreadystatechange = null;
329 Document.prototype.parseError = 0;
330 Document.prototype.load = function(sURI) {
331 var parser = document.implementation.createLSParser(this.async ? document.implementation.MODE_ASYNCHRONOUS : document.implementation.MODE_SYNCHRONOUS, null);
334 parser.addEventListener("load",
337 Sarissa.copyChildNodes(e.newDocument, self.documentElement, false);
338 self.onreadystatechange.call();
343 var oDoc = parser.parseURI(sURI);
346 this.parseError = -1;
349 Sarissa.copyChildNodes(oDoc, this.documentElement, false);
353 * <p>Factory method to obtain a new DOM Document object</p>
354 * @argument sUri the namespace of the root node (if any)
355 * @argument sUri the local name of the root node (if any)
356 * @returns a new DOM Document
358 Sarissa.getDomDocument = function(sUri, sName){
359 return document.implementation.createDocument(sUri?sUri:"", sName?sName:"", null);
362 };//if(_SARISSA_HAS_DOM_CREATE_DOCUMENT)
364 //==========================================
366 //==========================================
367 if(!window.DOMParser){
369 * DOMParser is a utility class, used to construct DOMDocuments from XML strings
372 DOMParser = function() {
374 if(_SARISSA_IS_SAFARI){
376 * Construct a new DOM Document from the given XMLstring
377 * @param sXml the given XML string
378 * @param contentType the content type of the document the given string represents (one of text/xml, application/xml, application/xhtml+xml).
379 * @return a new DOM Document from the given XML string
381 DOMParser.prototype.parseFromString = function(sXml, contentType){
382 if(contentType.toLowerCase() != "application/xml"){
383 throw "Cannot handle content type: \"" + contentType + "\"";
385 var xmlhttp = new XMLHttpRequest();
386 xmlhttp.open("GET", "data:text/xml;charset=utf-8," + encodeURIComponent(str), false);
388 return xmlhttp.responseXML;
390 }else if(Sarissa.getDomDocument && Sarissa.getDomDocument() && "loadXML" in Sarissa.getDomDocument()){
391 DOMParser.prototype.parseFromString = function(sXml, contentType){
392 var doc = Sarissa.getDomDocument();
399 if(window.XMLHttpRequest){
400 Sarissa.IS_ENABLED_XMLHTTP = true;
402 else if(_SARISSA_IS_IE){
404 * Emulate XMLHttpRequest
407 XMLHttpRequest = function() {
408 return new ActiveXObject(_SARISSA_XMLHTTP_PROGID);
410 Sarissa.IS_ENABLED_XMLHTTP = true;
413 if(!window.document.importNode && _SARISSA_IS_IE){
416 * Implements importNode for the current window document in IE using innerHTML.
417 * Testing showed that DOM was multiple times slower than innerHTML for this,
418 * sorry folks. If you encounter trouble (who knows what IE does behind innerHTML)
419 * please gimme a call.
420 * @param oNode the Node to import
421 * @param bChildren whether to include the children of oNode
422 * @returns the imported node for further use
424 window.document.importNode = function(oNode, bChildren){
425 var importNode = document.createElement("div");
427 importNode.innerHTML = Sarissa.serialize(oNode);
429 importNode.innerHTML = Sarissa.serialize(oNode.cloneNode(false));
430 return importNode.firstChild;
434 if(!Sarissa.getParseErrorText){
436 * <p>Returns a human readable description of the parsing error. Usefull
437 * for debugging. Tip: append the returned error string in a <pre>
438 * element if you want to render it.</p>
439 * <p>Many thanks to Christian Stocker for the initial patch.</p>
440 * @argument oDoc The target DOM document
441 * @returns The parsing error description of the target Document in
442 * human readable form (preformated text)
444 Sarissa.getParseErrorText = function (oDoc){
445 var parseErrorText = Sarissa.PARSED_OK;
446 if(oDoc && oDoc.parseError && oDoc.parseError != 0){
448 if(oDoc.documentElement.tagName == "parsererror"){
449 parseErrorText = oDoc.documentElement.firstChild.data;
450 parseErrorText += "\n" + oDoc.documentElement.firstChild.nextSibling.firstChild.data;
453 parseErrorText = Sarissa.getText(oDoc.documentElement);/*.getElementsByTagName("h1")[0], false) + "\n";
454 parseErrorText += Sarissa.getText(oDoc.documentElement.getElementsByTagName("body")[0], false) + "\n";
455 parseErrorText += Sarissa.getText(oDoc.documentElement.getElementsByTagName("pre")[0], false);*/
458 return parseErrorText;
461 Sarissa.getText = function(oNode, deep){
463 var nodes = oNode.childNodes;
464 for(var i=0; i < nodes.length; i++){
466 var nodeType = node.nodeType;
467 if(nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE){
469 }else if(deep == true
470 && (nodeType == Node.ELEMENT_NODE
471 || nodeType == Node.DOCUMENT_NODE
472 || nodeType == Node.DOCUMENT_FRAGMENT_NODE)){
473 s += Sarissa.getText(node, true);
478 if(window.XMLSerializer){
480 * <p>Factory method to obtain the serialization of a DOM Node</p>
481 * @returns the serialized Node as an XML string
483 Sarissa.serialize = function(oDoc){
486 s = oDoc.innerHTML?oDoc.innerHTML:(new XMLSerializer()).serializeToString(oDoc);
491 if(Sarissa.getDomDocument && (Sarissa.getDomDocument("","foo", null)).xml){
492 // see non-IE version
493 Sarissa.serialize = function(oDoc) {
496 s = oDoc.innerHTML?oDoc.innerHTML:oDoc.xml;
501 * Utility class to serialize DOM Node objects to XML strings
504 XMLSerializer = function(){};
506 * Serialize the given DOM Node to an XML string
507 * @param oNode the DOM Node to serialize
509 XMLSerializer.prototype.serializeToString = function(oNode) {
516 * strips tags from a markup string
518 Sarissa.stripTags = function (s) {
519 return s.replace(/<[^>]+>/g,"");
522 * <p>Deletes all child nodes of the given node</p>
523 * @argument oNode the Node to empty
525 Sarissa.clearChildNodes = function(oNode) {
526 // need to check for firstChild due to opera 8 bug with hasChildNodes
527 while(oNode.firstChild){
528 oNode.removeChild(oNode.firstChild);
532 * <p> Copies the childNodes of nodeFrom to nodeTo</p>
533 * <p> <b>Note:</b> The second object's original content is deleted before
534 * the copy operation, unless you supply a true third parameter</p>
535 * @argument nodeFrom the Node to copy the childNodes from
536 * @argument nodeTo the Node to copy the childNodes to
537 * @argument bPreserveExisting whether to preserve the original content of nodeTo, default is false
539 Sarissa.copyChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
540 if((!nodeFrom) || (!nodeTo)){
541 throw "Both source and destination nodes must be provided";
543 if(!bPreserveExisting){
544 Sarissa.clearChildNodes(nodeTo);
546 var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
547 var nodes = nodeFrom.childNodes;
548 if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
549 for(var i=0;i < nodes.length;i++) {
550 nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
554 for(var i=0;i < nodes.length;i++) {
555 nodeTo.appendChild(nodes[i].cloneNode(true));
561 * <p> Moves the childNodes of nodeFrom to nodeTo</p>
562 * <p> <b>Note:</b> The second object's original content is deleted before
563 * the move operation, unless you supply a true third parameter</p>
564 * @argument nodeFrom the Node to copy the childNodes from
565 * @argument nodeTo the Node to copy the childNodes to
566 * @argument bPreserveExisting whether to preserve the original content of nodeTo, default is
568 Sarissa.moveChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
569 if((!nodeFrom) || (!nodeTo)){
570 throw "Both source and destination nodes must be provided";
572 if(!bPreserveExisting){
573 Sarissa.clearChildNodes(nodeTo);
575 var nodes = nodeFrom.childNodes;
576 // if within the same doc, just move, else copy and delete
577 if(nodeFrom.ownerDocument == nodeTo.ownerDocument){
578 while(nodeFrom.firstChild){
579 nodeTo.appendChild(nodeFrom.firstChild);
582 var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
583 if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
584 for(var i=0;i < nodes.length;i++) {
585 nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
588 for(var i=0;i < nodes.length;i++) {
589 nodeTo.appendChild(nodes[i].cloneNode(true));
592 Sarissa.clearChildNodes(nodeFrom);
597 * <p>Serialize any object to an XML string. All properties are serialized using the property name
598 * as the XML element name. Array elements are rendered as <code>array-item</code> elements,
599 * using their index/key as the value of the <code>key</code> attribute.</p>
600 * @argument anyObject the object to serialize
601 * @argument objectName a name for that object
602 * @return the XML serializationj of the given object as a string
604 Sarissa.xmlize = function(anyObject, objectName, indentSpace){
605 indentSpace = indentSpace?indentSpace:'';
606 var s = indentSpace + '<' + objectName + '>';
608 if(!(anyObject instanceof Object) || anyObject instanceof Number || anyObject instanceof String
609 || anyObject instanceof Boolean || anyObject instanceof Date){
610 s += Sarissa.escape(""+anyObject);
615 var isArrayItem = anyObject instanceof Array;
616 for(var name in anyObject){
617 s += Sarissa.xmlize(anyObject[name], (isArrayItem?"array-item key=\""+name+"\"":name), indentSpace + " ");
621 return s += (objectName.indexOf(' ')!=-1?"</array-item>\n":"</" + objectName + ">\n");
625 * Escape the given string chacters that correspond to the five predefined XML entities
626 * @param sXml the string to escape
628 Sarissa.escape = function(sXml){
629 return sXml.replace(/&/g, "&")
630 .replace(/</g, "<")
631 .replace(/>/g, ">")
632 .replace(/"/g, """)
633 .replace(/'/g, "'");
637 * Unescape the given string. This turns the occurences of the predefined XML
638 * entities to become the characters they represent correspond to the five predefined XML entities
639 * @param sXml the string to unescape
641 Sarissa.unescape = function(sXml){
642 return sXml.replace(/'/g,"'")
643 .replace(/"/g,"\"")
644 .replace(/>/g,">")
645 .replace(/</g,"<")
646 .replace(/&/g,"&");