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