1 /**
  2  * ====================================================================
  3  * About Sarissa: http://dev.abiss.gr/sarissa
  4  * ====================================================================
  5  * Sarissa is an ECMAScript library acting as a cross-browser wrapper for native XML APIs.
  6  * The library supports Gecko based browsers like Mozilla and Firefox,
  7  * Internet Explorer (5.5+ with MSXML3.0+), Konqueror, Safari and Opera
  8  * @author: @author: Copyright 2004-2007 Emmanouil Batsis, mailto: mbatsis at users full stop sourceforge full stop net
  9  * ====================================================================
 10  * Licence
 11  * ====================================================================
 12  * Sarissa is free software distributed under the GNU GPL version 2 (see <a href="gpl.txt">gpl.txt</a>) or higher, 
 13  * GNU LGPL version 2.1 (see <a href="lgpl.txt">lgpl.txt</a>) or higher and Apache Software License 2.0 or higher 
 14  * (see <a href="asl.txt">asl.txt</a>). This means you can choose one of the three and use that if you like. If 
 15  * you make modifications under the ASL, i would appreciate it if you submitted those.
 16  * In case your copy of Sarissa does not include the license texts, you may find
 17  * them online in various formats at <a href="http://www.gnu.org">http://www.gnu.org</a> and 
 18  * <a href="http://www.apache.org">http://www.apache.org</a>.
 19  *
 20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 
 21  * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
 22  * WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE 
 23  * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
 24  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 26  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 27  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 28  */
 29 /**
 30  * <p>Sarissa is a utility class. Provides "static" methods for DOMDocument, 
 31  * DOM Node serialization to XML strings and other utility goodies.</p>
 32  * @constructor
 33  * @static
 34  */
 35 function Sarissa(){}
 36 Sarissa.VERSION = "0.9.9.1-SNAPSHOT";
 37 Sarissa.PARSED_OK = "Document contains no parsing errors";
 38 Sarissa.PARSED_EMPTY = "Document is empty";
 39 Sarissa.PARSED_UNKNOWN_ERROR = "Not well-formed or other error";
 40 Sarissa.IS_ENABLED_TRANSFORM_NODE = false;
 41 Sarissa.REMOTE_CALL_FLAG = "gr.abiss.sarissa.REMOTE_CALL_FLAG";
 42 /** @private */
 43 Sarissa._sarissa_iNsCounter = 0;
 44 /** @private */
 45 Sarissa._SARISSA_IEPREFIX4XSLPARAM = "";
 46 /** @private */
 47 Sarissa._SARISSA_HAS_DOM_IMPLEMENTATION = document.implementation && true;
 48 /** @private */
 49 Sarissa._SARISSA_HAS_DOM_CREATE_DOCUMENT = Sarissa._SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.createDocument;
 50 /** @private */
 51 Sarissa._SARISSA_HAS_DOM_FEATURE = Sarissa._SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.hasFeature;
 52 /** @private */
 53 Sarissa._SARISSA_IS_MOZ = Sarissa._SARISSA_HAS_DOM_CREATE_DOCUMENT && Sarissa._SARISSA_HAS_DOM_FEATURE;
 54 /** @private */
 55 Sarissa._SARISSA_IS_SAFARI = navigator.userAgent.toLowerCase().indexOf("safari") != -1 || navigator.userAgent.toLowerCase().indexOf("konqueror") != -1;
 56 /** @private */
 57 Sarissa._SARISSA_IS_SAFARI_OLD = Sarissa._SARISSA_IS_SAFARI && (parseInt((navigator.userAgent.match(/AppleWebKit\/(\d+)/)||{})[1], 10) < 420);
 58 /** @private */
 59 Sarissa._SARISSA_IS_IE = document.all && window.ActiveXObject && navigator.userAgent.toLowerCase().indexOf("msie") > -1  && navigator.userAgent.toLowerCase().indexOf("opera") == -1;
 60 /** @private */
 61 Sarissa._SARISSA_IS_OPERA = navigator.userAgent.toLowerCase().indexOf("opera") != -1;
 62 if(!window.Node || !Node.ELEMENT_NODE){
 63     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};
 64 }
 65 
 66 //This breaks for(x in o) loops in the old Safari
 67 if(Sarissa._SARISSA_IS_SAFARI_OLD){
 68 	HTMLHtmlElement = document.createElement("html").constructor;
 69 	Node = HTMLElement = {};
 70 	HTMLElement.prototype = HTMLHtmlElement.__proto__.__proto__;
 71 	HTMLDocument = Document = document.constructor;
 72 	var x = new DOMParser();
 73 	XMLDocument = x.constructor;
 74 	Element = x.parseFromString("<Single />", "text/xml").documentElement.constructor;
 75 	x = null;
 76 }
 77 if(typeof XMLDocument == "undefined" && typeof Document !="undefined"){ XMLDocument = Document; } 
 78 
 79 // IE initialization
 80 if(Sarissa._SARISSA_IS_IE){
 81     // for XSLT parameter names, prefix needed by IE
 82     Sarissa._SARISSA_IEPREFIX4XSLPARAM = "xsl:";
 83     // used to store the most recent ProgID available out of the above
 84     var _SARISSA_DOM_PROGID = "";
 85     var _SARISSA_XMLHTTP_PROGID = "";
 86     var _SARISSA_DOM_XMLWRITER = "";
 87     /**
 88      * Called when the sarissa.js file is parsed, to pick most recent
 89      * ProgIDs for IE, then gets destroyed.
 90      * @memberOf Sarissa
 91      * @private
 92      * @param idList an array of MSXML PROGIDs from which the most recent will be picked for a given object
 93      * @param enabledList an array of arrays where each array has two items; the index of the PROGID for which a certain feature is enabled
 94      */
 95     Sarissa.pickRecentProgID = function (idList){
 96         // found progID flag
 97         var bFound = false, e;
 98         var o2Store;
 99         for(var i=0; i < idList.length && !bFound; i++){
100             try{
101                 var oDoc = new ActiveXObject(idList[i]);
102                 o2Store = idList[i];
103                 bFound = true;
104             }catch (objException){
105                 // trap; try next progID
106                 e = objException;
107             }
108         }
109         if (!bFound) {
110             throw "Could not retrieve a valid progID of Class: " + idList[idList.length-1]+". (original exception: "+e+")";
111         }
112         idList = null;
113         return o2Store;
114     };
115     // pick best available MSXML progIDs
116     _SARISSA_DOM_PROGID = null;
117     _SARISSA_THREADEDDOM_PROGID = null;
118     _SARISSA_XSLTEMPLATE_PROGID = null;
119     _SARISSA_XMLHTTP_PROGID = null;
120     if(!window.XMLHttpRequest){
121         /**
122          * Emulate XMLHttpRequest
123          * @constructor
124          */
125         XMLHttpRequest = function() {
126             if(!_SARISSA_XMLHTTP_PROGID){
127                 _SARISSA_XMLHTTP_PROGID = Sarissa.pickRecentProgID(["Msxml2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"]);
128             }
129             return new ActiveXObject(_SARISSA_XMLHTTP_PROGID);
130         };
131     }
132     // we dont need this anymore
133     //============================================
134     // Factory methods (IE)
135     //============================================
136     // see non-IE version
137     Sarissa.getDomDocument = function(sUri, sName){
138         if(!_SARISSA_DOM_PROGID){
139             _SARISSA_DOM_PROGID = Sarissa.pickRecentProgID(["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"]);
140         }
141         var oDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
142         // if a root tag name was provided, we need to load it in the DOM object
143         if (sName){
144             // create an artifical namespace prefix 
145             // or reuse existing prefix if applicable
146             var prefix = "";
147             if(sUri){
148                 if(sName.indexOf(":") > 1){
149                     prefix = sName.substring(0, sName.indexOf(":"));
150                     sName = sName.substring(sName.indexOf(":")+1); 
151                 }else{
152                     prefix = "a" + (Sarissa._sarissa_iNsCounter++);
153                 }
154             }
155             // use namespaces if a namespace URI exists
156             if(sUri){
157                 oDoc.loadXML('<' + prefix+':'+sName + " xmlns:" + prefix + "=\"" + sUri + "\"" + " />");
158             } else {
159                 oDoc.loadXML('<' + sName + " />");
160             }
161         }
162         return oDoc;
163     };
164     // see non-IE version   
165     Sarissa.getParseErrorText = function (oDoc) {
166         var parseErrorText = Sarissa.PARSED_OK;
167         if(oDoc && oDoc.parseError && oDoc.parseError.errorCode && oDoc.parseError.errorCode !== 0){
168             parseErrorText = "XML Parsing Error: " + oDoc.parseError.reason + 
169                 "\nLocation: " + oDoc.parseError.url + 
170                 "\nLine Number " + oDoc.parseError.line + ", Column " + 
171                 oDoc.parseError.linepos + 
172                 ":\n" + oDoc.parseError.srcText +
173                 "\n";
174             for(var i = 0;  i < oDoc.parseError.linepos;i++){
175                 parseErrorText += "-";
176             }
177             parseErrorText +=  "^\n";
178         }
179         else if(oDoc.documentElement === null){
180             parseErrorText = Sarissa.PARSED_EMPTY;
181         }
182         return parseErrorText;
183     };
184     // see non-IE version
185     Sarissa.setXpathNamespaces = function(oDoc, sNsSet) {
186         oDoc.setProperty("SelectionLanguage", "XPath");
187         oDoc.setProperty("SelectionNamespaces", sNsSet);
188     };
189     /**
190      * An implementation of Mozilla's XSLTProcessor for IE. 
191      * Reuses the same XSLT stylesheet for multiple transforms
192      * @constructor
193      */
194     XSLTProcessor = function(){
195         if(!_SARISSA_XSLTEMPLATE_PROGID){
196             _SARISSA_XSLTEMPLATE_PROGID = Sarissa.pickRecentProgID(["Msxml2.XSLTemplate.6.0", "MSXML2.XSLTemplate.3.0"]);
197         }
198         this.template = new ActiveXObject(_SARISSA_XSLTEMPLATE_PROGID);
199         this.processor = null;
200     };
201     /**
202      * Imports the given XSLT DOM and compiles it to a reusable transform
203      * <b>Note:</b> If the stylesheet was loaded from a URL and contains xsl:import or xsl:include elements,it will be reloaded to resolve those
204      * @argument xslDoc The XSLT DOMDocument to import
205      */
206     XSLTProcessor.prototype.importStylesheet = function(xslDoc){
207         if(!_SARISSA_THREADEDDOM_PROGID){
208             _SARISSA_THREADEDDOM_PROGID = Sarissa.pickRecentProgID(["MSXML2.FreeThreadedDOMDocument.6.0", "MSXML2.FreeThreadedDOMDocument.3.0"]);
209         }
210         xslDoc.setProperty("SelectionLanguage", "XPath");
211         xslDoc.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
212         // convert stylesheet to free threaded
213         var converted = new ActiveXObject(_SARISSA_THREADEDDOM_PROGID);
214         // make included/imported stylesheets work if exist and xsl was originally loaded from url
215         try{
216             converted.resolveExternals = true; 
217             converted.setProperty("AllowDocumentFunction", true); 
218         }
219         catch(e){
220             // Ignore. "AllowDocumentFunction" is only supported in MSXML 3.0 SP4 and later.
221         } 
222         if(xslDoc.url && xslDoc.selectSingleNode("//xsl:*[local-name() = 'import' or local-name() = 'include']") !== null){
223             converted.async = false;
224             converted.load(xslDoc.url);
225         } 
226         else {
227             converted.loadXML(xslDoc.xml);
228         }
229         converted.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
230         var output = converted.selectSingleNode("//xsl:output");
231         //this.outputMethod = output ? output.getAttribute("method") : "html";
232         if(output) {
233             this.outputMethod = output.getAttribute("method");
234         } 
235         else {
236             delete this.outputMethod;
237         } 
238         this.template.stylesheet = converted;
239         this.processor = this.template.createProcessor();
240         // for getParameter and clearParameters
241         this.paramsSet = [];
242     };
243 
244     /**
245      * Transform the given XML DOM and return the transformation result as a new DOM document
246      * @argument sourceDoc The XML DOMDocument to transform
247      * @return The transformation result as a DOM Document
248      */
249     XSLTProcessor.prototype.transformToDocument = function(sourceDoc){
250         // fix for bug 1549749
251         var outDoc;
252         if(_SARISSA_THREADEDDOM_PROGID){
253             this.processor.input=sourceDoc;
254             outDoc=new ActiveXObject(_SARISSA_DOM_PROGID);
255             this.processor.output=outDoc;
256             this.processor.transform();
257             return outDoc;
258         }
259         else{
260             if(!_SARISSA_DOM_XMLWRITER){
261                 _SARISSA_DOM_XMLWRITER = Sarissa.pickRecentProgID(["Msxml2.MXXMLWriter.6.0", "Msxml2.MXXMLWriter.3.0", "MSXML2.MXXMLWriter", "MSXML.MXXMLWriter", "Microsoft.XMLDOM"]);
262             }
263             this.processor.input = sourceDoc;
264             outDoc = new ActiveXObject(_SARISSA_DOM_XMLWRITER);
265             this.processor.output = outDoc; 
266             this.processor.transform();
267             var oDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
268             oDoc.loadXML(outDoc.output+"");
269             return oDoc;
270         }
271     };
272     
273     /**
274      * Transform the given XML DOM and return the transformation result as a new DOM fragment.
275      * <b>Note</b>: The xsl:output method must match the nature of the owner document (XML/HTML).
276      * @argument sourceDoc The XML DOMDocument to transform
277      * @argument ownerDoc The owner of the result fragment
278      * @return The transformation result as a DOM Document
279      */
280     XSLTProcessor.prototype.transformToFragment = function (sourceDoc, ownerDoc) {
281         this.processor.input = sourceDoc;
282         this.processor.transform();
283         var s = this.processor.output;
284         var f = ownerDoc.createDocumentFragment();
285         var container;
286         if (this.outputMethod == 'text') {
287             f.appendChild(ownerDoc.createTextNode(s));
288         } else if (ownerDoc.body && ownerDoc.body.innerHTML) {
289             container = ownerDoc.createElement('div');
290             container.innerHTML = s;
291             while (container.hasChildNodes()) {
292                 f.appendChild(container.firstChild);
293             }
294         }
295         else {
296             var oDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
297             if (s.substring(0, 5) == '<?xml') {
298                 s = s.substring(s.indexOf('?>') + 2);
299             }
300             var xml = ''.concat('<my>', s, '</my>');
301             oDoc.loadXML(xml);
302             container = oDoc.documentElement;
303             while (container.hasChildNodes()) {
304                 f.appendChild(container.firstChild);
305             }
306         }
307         return f;
308     };
309     
310     /**
311      * Set global XSLT parameter of the imported stylesheet
312      * @argument nsURI The parameter namespace URI
313      * @argument name The parameter base name
314      * @argument value The new parameter value
315      */
316      XSLTProcessor.prototype.setParameter = function(nsURI, name, value){
317          // make value a zero length string if null to allow clearing
318          value = value ? value : "";
319          // nsURI is optional but cannot be null
320          if(nsURI){
321              this.processor.addParameter(name, value, nsURI);
322          }else{
323              this.processor.addParameter(name, value);
324          }
325          // update updated params for getParameter
326          nsURI = "" + (nsURI || "");
327          if(!this.paramsSet[nsURI]){
328              this.paramsSet[nsURI] = [];
329          }
330          this.paramsSet[nsURI][name] = value;
331      };
332     /**
333      * Gets a parameter if previously set by setParameter. Returns null
334      * otherwise
335      * @argument name The parameter base name
336      * @argument value The new parameter value
337      * @return The parameter value if reviously set by setParameter, null otherwise
338      */
339     XSLTProcessor.prototype.getParameter = function(nsURI, name){
340         nsURI = "" + (nsURI || "");
341         if(this.paramsSet[nsURI] && this.paramsSet[nsURI][name]){
342             return this.paramsSet[nsURI][name];
343         }else{
344             return null;
345         }
346     };
347     
348     /**
349      * Clear parameters (set them to default values as defined in the stylesheet itself)
350      */
351     XSLTProcessor.prototype.clearParameters = function(){
352         for(var nsURI in this.paramsSet){
353             for(var name in this.paramsSet[nsURI]){
354                 if(nsURI!==""){
355                     this.processor.addParameter(name, "", nsURI);
356                 }else{
357                     this.processor.addParameter(name, "");
358                 }
359             }
360         }
361         this.paramsSet = [];
362     };
363 }else{ /* end IE initialization, try to deal with real browsers now ;-) */
364     if(Sarissa._SARISSA_HAS_DOM_CREATE_DOCUMENT){
365         /**
366          * <p>Ensures the document was loaded correctly, otherwise sets the
367          * parseError to -1 to indicate something went wrong. Internal use</p>
368          * @private
369          */
370         Sarissa.__handleLoad__ = function(oDoc){
371             Sarissa.__setReadyState__(oDoc, 4);
372         };
373         /**
374         * <p>Attached by an event handler to the load event. Internal use.</p>
375         * @private
376         */
377         _sarissa_XMLDocument_onload = function(){
378             Sarissa.__handleLoad__(this);
379         };
380         /**
381          * <p>Sets the readyState property of the given DOM Document object.
382          * Internal use.</p>
383          * @memberOf Sarissa
384          * @private
385          * @argument oDoc the DOM Document object to fire the
386          *          readystatechange event
387          * @argument iReadyState the number to change the readystate property to
388          */
389         Sarissa.__setReadyState__ = function(oDoc, iReadyState){
390             oDoc.readyState = iReadyState;
391             oDoc.readystate = iReadyState;
392             if (oDoc.onreadystatechange !== null && typeof oDoc.onreadystatechange == "function") {
393                 oDoc.onreadystatechange();
394             }
395         };
396         
397         Sarissa.getDomDocument = function(sUri, sName){
398             var oDoc = document.implementation.createDocument(sUri?sUri:null, sName?sName:null, null);
399             if(!oDoc.onreadystatechange){
400             
401                 /**
402                 * <p>Emulate IE's onreadystatechange attribute</p>
403                 */
404                 oDoc.onreadystatechange = null;
405             }
406             if(!oDoc.readyState){
407                 /**
408                 * <p>Emulates IE's readyState property, which always gives an integer from 0 to 4:</p>
409                 * <ul><li>1 == LOADING,</li>
410                 * <li>2 == LOADED,</li>
411                 * <li>3 == INTERACTIVE,</li>
412                 * <li>4 == COMPLETED</li></ul>
413                 */
414                 oDoc.readyState = 0;
415             }
416             oDoc.addEventListener("load", _sarissa_XMLDocument_onload, false);
417             return oDoc;
418         };
419         if(window.XMLDocument){
420             // do nothing
421         }// TODO: check if the new document has content before trying to copynodes, check  for error handling in DOM 3 LS
422         else if(Sarissa._SARISSA_HAS_DOM_FEATURE && window.Document && !Document.prototype.load && document.implementation.hasFeature('LS', '3.0')){
423     		//Opera 9 may get the XPath branch which gives creates XMLDocument, therefore it doesn't reach here which is good
424             /**
425             * <p>Factory method to obtain a new DOM Document object</p>
426             * @memberOf Sarissa
427             * @argument sUri the namespace of the root node (if any)
428             * @argument sUri the local name of the root node (if any)
429             * @returns a new DOM Document
430             */
431             Sarissa.getDomDocument = function(sUri, sName){
432                 var oDoc = document.implementation.createDocument(sUri?sUri:null, sName?sName:null, null);
433                 return oDoc;
434             };
435         }
436         else {
437             Sarissa.getDomDocument = function(sUri, sName){
438                 var oDoc = document.implementation.createDocument(sUri?sUri:null, sName?sName:null, null);
439                 // looks like safari does not create the root element for some unknown reason
440                 if(oDoc && (sUri || sName) && !oDoc.documentElement){
441                     oDoc.appendChild(oDoc.createElementNS(sUri, sName));
442                 }
443                 return oDoc;
444             };
445         }
446     }//if(Sarissa._SARISSA_HAS_DOM_CREATE_DOCUMENT)
447 }
448 //==========================================
449 // Common stuff
450 //==========================================
451 if(!window.DOMParser){
452     if(Sarissa._SARISSA_IS_SAFARI){
453         /*
454          * DOMParser is a utility class, used to construct DOMDocuments from XML strings
455          * @constructor
456          */
457         DOMParser = function() { };
458         /** 
459         * Construct a new DOM Document from the given XMLstring
460         * @param sXml the given XML string
461         * @param contentType the content type of the document the given string represents (one of text/xml, application/xml, application/xhtml+xml). 
462         * @return a new DOM Document from the given XML string
463         */
464         DOMParser.prototype.parseFromString = function(sXml, contentType){
465             var xmlhttp = new XMLHttpRequest();
466             xmlhttp.open("GET", "data:text/xml;charset=utf-8," + encodeURIComponent(sXml), false);
467             xmlhttp.send(null);
468             return xmlhttp.responseXML;
469         };
470     }else if(Sarissa.getDomDocument && Sarissa.getDomDocument() && Sarissa.getDomDocument(null, "bar").xml){
471         DOMParser = function() { };
472         DOMParser.prototype.parseFromString = function(sXml, contentType){
473             var doc = Sarissa.getDomDocument();
474             doc.loadXML(sXml);
475             return doc;
476         };
477     }
478 }
479 
480 if((typeof(document.importNode) == "undefined") && Sarissa._SARISSA_IS_IE){
481     try{
482         /**
483         * Implementation of importNode for the context window document in IE.
484         * If <code>oNode</code> is a TextNode, <code>bChildren</code> is ignored.
485         * @param oNode the Node to import
486         * @param bChildren whether to include the children of oNode
487         * @returns the imported node for further use
488         */
489         document.importNode = function(oNode, bChildren){
490             var tmp;
491             if (oNode.nodeName=='#text') {
492                 return document.createTextNode(oNode.data);
493             }
494             else {
495                 if(oNode.nodeName == "tbody" || oNode.nodeName == "tr"){
496                     tmp = document.createElement("table");
497                 }
498                 else if(oNode.nodeName == "td"){
499                     tmp = document.createElement("tr");
500                 }
501                 else if(oNode.nodeName == "option"){
502                     tmp = document.createElement("select");
503                 }
504                 else{
505                     tmp = document.createElement("div");
506                 }
507                 if(bChildren){
508                     tmp.innerHTML = oNode.xml ? oNode.xml : oNode.outerHTML;
509                 }else{
510                     tmp.innerHTML = oNode.xml ? oNode.cloneNode(false).xml : oNode.cloneNode(false).outerHTML;
511                 }
512                 return tmp.getElementsByTagName("*")[0];
513             }
514         };
515     }catch(e){ }
516 }
517 if(!Sarissa.getParseErrorText){
518     /**
519      * <p>Returns a human readable description of the parsing error. Usefull
520      * for debugging. Tip: append the returned error string in a <pre>
521      * element if you want to render it.</p>
522      * <p>Many thanks to Christian Stocker for the initial patch.</p>
523      * @memberOf Sarissa
524      * @argument oDoc The target DOM document
525      * @returns The parsing error description of the target Document in
526      *          human readable form (preformated text)
527      */
528     Sarissa.getParseErrorText = function (oDoc){
529         var parseErrorText = Sarissa.PARSED_OK;
530         if(!oDoc.documentElement){
531             parseErrorText = Sarissa.PARSED_EMPTY;
532         } else if(oDoc.documentElement.tagName == "parsererror"){
533             parseErrorText = oDoc.documentElement.firstChild.data;
534             parseErrorText += "\n" +  oDoc.documentElement.firstChild.nextSibling.firstChild.data;
535         } else if(oDoc.getElementsByTagName("parsererror").length > 0){
536             var parsererror = oDoc.getElementsByTagName("parsererror")[0];
537             parseErrorText = Sarissa.getText(parsererror, true)+"\n";
538         } else if(oDoc.parseError && oDoc.parseError.errorCode !== 0){
539             parseErrorText = Sarissa.PARSED_UNKNOWN_ERROR;
540         }
541         return parseErrorText;
542     };
543 }
544 /**
545  * Get a string with the concatenated values of all string nodes under the given node
546  * @memberOf Sarissa
547  * @argument oNode the given DOM node
548  * @argument deep whether to recursively scan the children nodes of the given node for text as well. Default is <code>false</code> 
549  */
550 Sarissa.getText = function(oNode, deep){
551     var s = "";
552     var nodes = oNode.childNodes;
553     for(var i=0; i < nodes.length; i++){
554         var node = nodes[i];
555         var nodeType = node.nodeType;
556         if(nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE){
557             s += node.data;
558         } else if(deep === true && (nodeType == Node.ELEMENT_NODE || nodeType == Node.DOCUMENT_NODE || nodeType == Node.DOCUMENT_FRAGMENT_NODE)){
559             s += Sarissa.getText(node, true);
560         }
561     }
562     return s;
563 };
564 if(!window.XMLSerializer && Sarissa.getDomDocument && Sarissa.getDomDocument("","foo", null).xml){
565     /**
566      * Utility class to serialize DOM Node objects to XML strings
567      * @constructor
568      */
569     XMLSerializer = function(){};
570     /**
571      * Serialize the given DOM Node to an XML string
572      * @param oNode the DOM Node to serialize
573      */
574     XMLSerializer.prototype.serializeToString = function(oNode) {
575         return oNode.xml;
576     };
577 }
578 
579 /**
580  * Strips tags from the given markup string
581  * @memberOf Sarissa
582  */
583 Sarissa.stripTags = function (s) {
584     return s.replace(/<[^>]+>/g,"");
585 };
586 /**
587  * <p>Deletes all child nodes of the given node</p>
588  * @memberOf Sarissa
589  * @argument oNode the Node to empty
590  */
591 Sarissa.clearChildNodes = function(oNode) {
592     // need to check for firstChild due to opera 8 bug with hasChildNodes
593     while(oNode.firstChild) {
594         oNode.removeChild(oNode.firstChild);
595     }
596 };
597 /**
598  * <p> Copies the childNodes of nodeFrom to nodeTo</p>
599  * <p> <b>Note:</b> The second object's original content is deleted before 
600  * the copy operation, unless you supply a true third parameter</p>
601  * @memberOf Sarissa
602  * @argument nodeFrom the Node to copy the childNodes from
603  * @argument nodeTo the Node to copy the childNodes to
604  * @argument bPreserveExisting whether to preserve the original content of nodeTo, default is false
605  */
606 Sarissa.copyChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
607     if(Sarissa._SARISSA_IS_SAFARI && nodeTo.nodeType == Node.DOCUMENT_NODE){ // SAFARI_OLD ??
608     	nodeTo = nodeTo.documentElement; //Appearantly there's a bug in safari where you can't appendChild to a document node
609     }
610     
611     if((!nodeFrom) || (!nodeTo)){
612         throw "Both source and destination nodes must be provided";
613     }
614     if(!bPreserveExisting){
615         Sarissa.clearChildNodes(nodeTo);
616     }
617     var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
618     var nodes = nodeFrom.childNodes;
619     var i;
620     if(typeof(ownerDoc.importNode) != "undefined")  {
621         for(i=0;i < nodes.length;i++) {
622             nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
623         }
624     } else {
625         for(i=0;i < nodes.length;i++) {
626             nodeTo.appendChild(nodes[i].cloneNode(true));
627         }
628     }
629 };
630 
631 /**
632  * <p> Moves the childNodes of nodeFrom to nodeTo</p>
633  * <p> <b>Note:</b> The second object's original content is deleted before 
634  * the move operation, unless you supply a true third parameter</p>
635  * @memberOf Sarissa
636  * @argument nodeFrom the Node to copy the childNodes from
637  * @argument nodeTo the Node to copy the childNodes to
638  * @argument bPreserveExisting whether to preserve the original content of nodeTo, default is
639  */ 
640 Sarissa.moveChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
641     if((!nodeFrom) || (!nodeTo)){
642         throw "Both source and destination nodes must be provided";
643     }
644     if(!bPreserveExisting){
645         Sarissa.clearChildNodes(nodeTo);
646     }
647     var nodes = nodeFrom.childNodes;
648     // if within the same doc, just move, else copy and delete
649     if(nodeFrom.ownerDocument == nodeTo.ownerDocument){
650         while(nodeFrom.firstChild){
651             nodeTo.appendChild(nodeFrom.firstChild);
652         }
653     } else {
654         var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
655         var i;
656         if(typeof(ownerDoc.importNode) != "undefined") {
657            for(i=0;i < nodes.length;i++) {
658                nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
659            }
660         }else{
661            for(i=0;i < nodes.length;i++) {
662                nodeTo.appendChild(nodes[i].cloneNode(true));
663            }
664         }
665         Sarissa.clearChildNodes(nodeFrom);
666     }
667 };
668 
669 /** 
670  * <p>Serialize any <strong>non</strong> DOM object to an XML string. All properties are serialized using the property name
671  * as the XML element name. Array elements are rendered as <code>array-item</code> elements, 
672  * using their index/key as the value of the <code>key</code> attribute.</p>
673  * @memberOf Sarissa
674  * @argument anyObject the object to serialize
675  * @argument objectName a name for that object
676  * @return the XML serialization of the given object as a string
677  */
678 Sarissa.xmlize = function(anyObject, objectName, indentSpace){
679     indentSpace = indentSpace?indentSpace:'';
680     var s = indentSpace  + '<' + objectName + '>';
681     var isLeaf = false;
682     if(!(anyObject instanceof Object) || anyObject instanceof Number || anyObject instanceof String || anyObject instanceof Boolean || anyObject instanceof Date){
683         s += Sarissa.escape(""+anyObject);
684         isLeaf = true;
685     }else{
686         s += "\n";
687         var isArrayItem = anyObject instanceof Array;
688         for(var name in anyObject){
689             s += Sarissa.xmlize(anyObject[name], (isArrayItem?"array-item key=\""+name+"\"":name), indentSpace + "   ");
690         }
691         s += indentSpace;
692     }
693     return (s += (objectName.indexOf(' ')!=-1?"</array-item>\n":"</" + objectName + ">\n"));
694 };
695 
696 /** 
697  * Escape the given string chacters that correspond to the five predefined XML entities
698  * @memberOf Sarissa
699  * @param sXml the string to escape
700  */
701 Sarissa.escape = function(sXml){
702     return sXml.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
703 };
704 
705 /** 
706  * Unescape the given string. This turns the occurences of the predefined XML 
707  * entities to become the characters they represent correspond to the five predefined XML entities
708  * @memberOf Sarissa
709  * @param sXml the string to unescape
710  */
711 Sarissa.unescape = function(sXml){
712     return sXml.replace(/'/g,"'").replace(/"/g,"\"").replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&");
713 };
714 
715 /** @private */
716 Sarissa.updateCursor = function(oTargetElement, sValue) {
717     if(oTargetElement && oTargetElement.style && oTargetElement.style.cursor !== undefined ){
718         oTargetElement.style.cursor = sValue;
719     }
720 };
721 
722 /**
723  * Asynchronously update an element with response of a GET request on the given URL.  Passing a configured XSLT 
724  * processor will result in transforming and updating oNode before using it to update oTargetElement.
725  * You can also pass a callback function to be executed when the update is finished. The function will be called as 
726  * <code>functionName(oNode, oTargetElement);</code>
727  * @memberOf Sarissa
728  * @param sFromUrl the URL to make the request to
729  * @param oTargetElement the element to update
730  * @param xsltproc (optional) the transformer to use on the returned
731  *                  content before updating the target element with it
732  * @param callback (optional) a Function object to execute once the update is finished successfuly, called as <code>callback(oNode, oTargetElement)</code>
733  * @param skipCache (optional) whether to skip any cache
734  */
735 Sarissa.updateContentFromURI = function(sFromUrl, oTargetElement, xsltproc, callback, skipCache) {
736     try{
737         Sarissa.updateCursor(oTargetElement, "wait");
738         var xmlhttp = new XMLHttpRequest();
739         xmlhttp.open("GET", sFromUrl, true);
740         sarissa_dhtml_loadHandler = function() {
741             if (xmlhttp.readyState == 4) {
742                 Sarissa.updateContentFromNode(xmlhttp.responseXML, oTargetElement, xsltproc, callback);
743             }
744         };
745         xmlhttp.onreadystatechange = sarissa_dhtml_loadHandler;
746         if (skipCache) {
747              var oldage = "Sat, 1 Jan 2000 00:00:00 GMT";
748              xmlhttp.setRequestHeader("If-Modified-Since", oldage);
749         }
750         xmlhttp.send("");
751     }
752     catch(e){
753         Sarissa.updateCursor(oTargetElement, "auto");
754         throw e;
755     }
756 };
757 
758 /**
759  * Update an element's content with the given DOM node. Passing a configured XSLT 
760  * processor will result in transforming and updating oNode before using it to update oTargetElement.
761  * You can also pass a callback function to be executed when the update is finished. The function will be called as 
762  * <code>functionName(oNode, oTargetElement);</code>
763  * @memberOf Sarissa
764  * @param oNode the URL to make the request to
765  * @param oTargetElement the element to update
766  * @param xsltproc (optional) the transformer to use on the given 
767  *                  DOM node before updating the target element with it
768  * @param callback (optional) a Function object to execute once the update is finished successfuly, called as <code>callback(oNode, oTargetElement)</code>
769  */
770 Sarissa.updateContentFromNode = function(oNode, oTargetElement, xsltproc, callback) {
771     try {
772         Sarissa.updateCursor(oTargetElement, "wait");
773         Sarissa.clearChildNodes(oTargetElement);
774         // check for parsing errors
775         var ownerDoc = oNode.nodeType == Node.DOCUMENT_NODE?oNode:oNode.ownerDocument;
776         if(ownerDoc.parseError && ownerDoc.parseError !== 0) {
777             var pre = document.createElement("pre");
778             pre.appendChild(document.createTextNode(Sarissa.getParseErrorText(ownerDoc)));
779             oTargetElement.appendChild(pre);
780         }
781         else {
782             // transform if appropriate
783             if(xsltproc) {
784                 oNode = xsltproc.transformToDocument(oNode);
785             }
786             // be smart, maybe the user wants to display the source instead
787             if(oTargetElement.tagName.toLowerCase() == "textarea" || oTargetElement.tagName.toLowerCase() == "input") {
788                 oTargetElement.value = new XMLSerializer().serializeToString(oNode);
789             }
790             else {
791                 // ok that was not smart; it was paranoid. Keep up the good work by trying to use DOM instead of innerHTML
792                 if(oNode.nodeType == Node.DOCUMENT_NODE || oNode.ownerDocument.documentElement == oNode) {
793                     oTargetElement.innerHTML = new XMLSerializer().serializeToString(oNode);
794                 }
795                 else{
796                     oTargetElement.appendChild(oTargetElement.ownerDocument.importNode(oNode, true));
797                 }
798             }
799         }
800         if (callback) {
801             callback(oNode, oTargetElement);
802         }
803     }
804     catch(e) {
805             throw e;
806     }
807     finally{
808         Sarissa.updateCursor(oTargetElement, "auto");
809     }
810 };
811 
812 
813 /**
814  * Creates an HTTP URL query string from the given HTML form data
815  * @memberOf Sarissa
816  */
817 Sarissa.formToQueryString = function(oForm){
818     var qs = "";
819     for(var i = 0;i < oForm.elements.length;i++) {
820         var oField = oForm.elements[i];
821         var sFieldName = oField.getAttribute("name") ? oField.getAttribute("name") : oField.getAttribute("id"); 
822         // ensure we got a proper name/id and that the field is not disabled
823         if(sFieldName && 
824             ((!oField.disabled) || oField.type == "hidden")) {
825             switch(oField.type) {
826                 case "hidden":
827                 case "text":
828                 case "textarea":
829                 case "password":
830                     qs += sFieldName + "=" + encodeURIComponent(oField.value) + "&";
831                     break;
832                 case "select-one":
833                     qs += sFieldName + "=" + encodeURIComponent(oField.options[oField.selectedIndex].value) + "&";
834                     break;
835                 case "select-multiple":
836                     for (var j = 0; j < oField.length; j++) {
837                         var optElem = oField.options[j];
838                         if (optElem.selected === true) {
839                             qs += sFieldName + "[]" + "=" + encodeURIComponent(optElem.value) + "&";
840                         }
841                      }
842                      break;
843                 case "checkbox":
844                 case "radio":
845                     if(oField.checked) {
846                         qs += sFieldName + "=" + encodeURIComponent(oField.value) + "&";
847                     }
848                     break;
849             }
850         }
851     }
852     // return after removing last '&'
853     return qs.substr(0, qs.length - 1); 
854 };
855 
856 
857 /**
858  * Asynchronously update an element with response of an XMLHttpRequest-based emulation of a form submission. <p>The form <code>action</code> and 
859  * <code>method</code> attributess will be followed. Passing a configured XSLT processor will result in 
860  * transforming and updating the server response before using it to update the target element.
861  * You can also pass a callback function to be executed when the update is finished. The function will be called as 
862  * <code>functionName(oNode, oTargetElement);</code></p>
863  * <p>Here is an example of using this in a form element:</p>
864  * <pre name="code" class="xml"><form action="/my/form/handler" method="post" 
865  *     onbeforesubmit="return Sarissa.updateContentFromForm(this, document.getElementById('targetId'));"><pre>
866  * <p>If JavaScript is supported, the form will not be submitted. Instead, Sarissa will
867  * scan the form and make an appropriate AJAX request, also adding a parameter 
868  * to signal to the server that this is an AJAX call. The parameter is 
869  * constructed as <code>Sarissa.REMOTE_CALL_FLAG = "=true"</code> so you can change the name in your webpage
870  * simply by assigning another value to Sarissa.REMOTE_CALL_FLAG. If JavaScript is not supported
871  * the form will be submitted normally.
872  * @memberOf Sarissa
873  * @param oForm the form submition to emulate
874  * @param oTargetElement the element to update
875  * @param xsltproc (optional) the transformer to use on the returned
876  *                  content before updating the target element with it
877  * @param callback (optional) a Function object to execute once the update is finished successfuly, called as <code>callback(oNode, oTargetElement)</code>
878  * @param skipCache (optional) whether to skip any cache
879  */
880 Sarissa.updateContentFromForm = function(oForm, oTargetElement, xsltproc, callback) {
881     try{
882         Sarissa.updateCursor(oTargetElement, "wait");
883         // build parameters from form fields
884         var params = Sarissa.formToQueryString(oForm) + "&" + Sarissa.REMOTE_CALL_FLAG + "=true";
885         var xmlhttp = new XMLHttpRequest();
886         if(oForm.getAttribute("method") && oForm.getAttribute("method").toLowerCase() == "get") {
887             xmlhttp.open("GET", oForm.getAttribute("action")+"?"+params, true);
888         }
889         else{
890             xmlhttp.open('POST', oForm.getAttribute("action"), true);
891             xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
892             xmlhttp.setRequestHeader("Content-length", params.length);
893             xmlhttp.setRequestHeader("Connection", "close");
894         }
895         sarissa_dhtml_loadHandler = function() {
896             if (xmlhttp.readyState == 4) {
897                 Sarissa.updateContentFromNode(xmlhttp.responseXML, oTargetElement, xsltproc, callback);
898             }
899         };
900         xmlhttp.onreadystatechange = sarissa_dhtml_loadHandler;
901         xmlhttp.send("");
902     }
903     catch(e){
904         Sarissa.updateCursor(oTargetElement, "auto");
905         throw e;
906     }
907     return false;
908 };
909 
910 //   EOF
911