dc955723b2c03c4328494b554e6920503ff1e179
[pazpar2-moved-to-github.git] / js / pz2.js
1 /*
2 ** $Id: pz2.js,v 1.70 2008-03-12 11:36:57 jakub Exp $
3 ** pz2.js - pazpar2's javascript client library.
4 */
5
6 //since explorer is flawed
7 if (!window['Node']) {
8     window.Node = new Object();
9     Node.ELEMENT_NODE = 1;
10     Node.ATTRIBUTE_NODE = 2;
11     Node.TEXT_NODE = 3;
12     Node.CDATA_SECTION_NODE = 4;
13     Node.ENTITY_REFERENCE_NODE = 5;
14     Node.ENTITY_NODE = 6;
15     Node.PROCESSING_INSTRUCTION_NODE = 7;
16     Node.COMMENT_NODE = 8;
17     Node.DOCUMENT_NODE = 9;
18     Node.DOCUMENT_TYPE_NODE = 10;
19     Node.DOCUMENT_FRAGMENT_NODE = 11;
20     Node.NOTATION_NODE = 12;
21 }
22
23 // prevent execution of more than once
24 if(typeof window.pz2 == "undefined") {
25 window.undefined = window.undefined;
26
27 var pz2 = function ( paramArray )
28 {
29     
30     // at least one callback required
31     if ( !paramArray )
32         throw new Error("Pz2.js: Array with parameters has to be suplied."); 
33
34     //supported pazpar2's protocol version
35     this.suppProtoVer = '1';
36     if (typeof paramArray.pazpar2path != "undefined")
37         this.pz2String = paramArray.pazpar2path;
38     else
39         this.pz2String = "/pazpar2/search.pz2";
40     this.useSessions = true;
41     
42     this.stylesheet = paramArray.detailstylesheet || null;
43     //load stylesheet if required in async mode
44     if( this.stylesheet ) {
45         var context = this;
46         var request = new pzHttpRequest( this.stylesheet );
47         request.get( {}, function ( doc ) { context.xslDoc = doc; } );
48     }
49     
50     this.errorHandler = paramArray.errorhandler || null;
51     
52     // function callbacks
53     this.initCallback = paramArray.oninit || null;
54     this.statCallback = paramArray.onstat || null;
55     this.showCallback = paramArray.onshow || null;
56     this.termlistCallback = paramArray.onterm || null;
57     this.recordCallback = paramArray.onrecord || null;
58     this.bytargetCallback = paramArray.onbytarget || null;
59     this.resetCallback = paramArray.onreset || null;
60
61     // termlist keys
62     this.termKeys = paramArray.termlist || "subject";
63     
64     // some configurational stuff
65     this.keepAlive = 50000;
66     
67     if ( paramArray.keepAlive < this.keepAlive )
68         this.keepAlive = paramArray.keepAlive;
69
70     this.sessionID = null;
71     this.initStatusOK = false;
72     this.pingStatusOK = false;
73     this.searchStatusOK = false;
74     
75     // for sorting
76     this.currentSort = "relevance";
77
78     // where are we?
79     this.currentStart = 0;
80     this.currentNum = 20;
81
82     // last full record retrieved
83     this.currRecID = null;
84     
85     // current query
86     this.currQuery = null;
87
88     //current raw record offset
89     this.currRecOffset = null;
90
91     //timers
92     this.statTime = paramArray.stattime || 1000;
93     this.statTimer = null;
94     this.termTime = paramArray.termtime || 1000;
95     this.termTimer = null;
96     this.showTime = paramArray.showtime || 1000;
97     this.showTimer = null;
98     this.showFastCount = 4;
99     this.bytargetTime = paramArray.bytargettime || 1000;
100     this.bytargetTimer = null;
101
102     // counters for each command and applied delay
103     this.dumpFactor = 500;
104     this.showCounter = 0;
105     this.termCounter = 0;
106     this.statCounter = 0;
107     this.bytargetCounter = 0;
108
109     // active clients, updated by stat and show
110     // might be an issue since bytarget will poll accordingly
111     this.activeClients = 1;
112
113     // if in proxy mode no need to init
114     if (paramArray.usesessions != undefined) {
115          this.useSessions = paramArray.usesessions;
116         this.initStatusOK = true;
117     }
118     // else, auto init session or wait for a user init?
119     if (this.useSessions && paramArray.autoInit !== false) {
120         this.init();
121     }
122 };
123
124 pz2.prototype = 
125 {
126     //error handler for async error throws
127    throwError: function (errMsg, errCode)
128    {
129         var err = new Error(errMsg);
130         if (errCode) err.code = errCode;
131                 
132         if (this.errorHandler) {
133             this.errorHandler(err);
134         }
135         else {
136             throw err;
137         }
138    },
139
140     // stop activity by clearing tiemouts 
141    stop: function ()
142    {
143        clearTimeout(this.statTimer);
144        clearTimeout(this.showTimer);
145        clearTimeout(this.termTimer);
146        clearTimeout(this.bytargetTimer);
147     },
148     
149     // reset status variables
150     reset: function ()
151     {   
152         if ( this.useSessions ) {
153             this.sessionID = null;
154             this.initStatusOK = false;
155             this.pingStatusOK = false;
156         }
157         this.searchStatusOK = false;
158         this.stop();
159             
160         if ( this.resetCallback )
161                 this.resetCallback();
162     },
163
164     init: function ( sessionId ) 
165     {
166         this.reset();
167         
168         // session id as a param
169         if ( sessionId != undefined && this.useSessions ) {
170             this.initStatusOK = true;
171             this.sessionID = sessionId;
172             this.ping();
173         // old school direct pazpar2 init
174         } else if (this.useSessions) {
175             var context = this;
176             var request = new pzHttpRequest(this.pz2String, this.errorHandler);
177             request.postParams(
178                 { "command": "init" },
179                 function(data) {
180                     if ( data.getElementsByTagName("status")[0]
181                             .childNodes[0].nodeValue == "OK" ) {
182                         if ( data.getElementsByTagName("protocol")[0]
183                                 .childNodes[0].nodeValue 
184                             != context.suppProtoVer )
185                             throw new Error(
186                                 "Server's protocol not supported by the client"
187                             );
188                         context.initStatusOK = true;
189                         context.sessionID = 
190                             data.getElementsByTagName("session")[0]
191                                 .childNodes[0].nodeValue;
192                         setTimeout(
193                             function () {
194                                 context.ping();
195                             },
196                             context.keepAlive
197                         );
198                         if ( context.initCallback )
199                             context.initCallback();
200                     }
201                     else
202                         context.throwError('Init failed. Malformed WS resonse.',
203                                             110);
204                 }
205             );
206         // when through proxy no need to init
207         } else {
208             this.initStatusOK = true;
209         }
210     },
211     // no need to ping explicitly
212     ping: function () 
213     {
214         // pinging only makes sense when using pazpar2 directly
215         if( !this.initStatusOK || !this.useSessions )
216             throw new Error(
217             'Pz2.js: Ping not allowed (proxy mode) or session not initialized.'
218             );
219         var context = this;
220         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
221         request.postParams(
222             { "command": "ping", "session": this.sessionID },
223             function(data) {
224                 if ( data.getElementsByTagName("status")[0]
225                         .childNodes[0].nodeValue == "OK" ) {
226                     context.pingStatusOK = true;
227                     setTimeout(
228                         function () {
229                             context.ping();
230                         }, 
231                         context.keepAlive
232                     );
233                 }
234                 else
235                     context.throwError('Ping failed. Malformed WS resonse.',
236                                         111);
237             }
238         );
239     },
240     search: function (query, num, sort, filter, showfrom)
241     {
242         clearTimeout(this.statTimer);
243         clearTimeout(this.showTimer);
244         clearTimeout(this.termTimer);
245         clearTimeout(this.bytargetTimer);
246         
247         this.showCounter = 0;
248         this.termCounter = 0;
249         this.bytargetCounter = 0;
250         this.statCounter = 0;
251         
252         // no proxy mode
253         if( !this.initStatusOK )
254             throw new Error('Pz2.js: session not initialized.');
255         
256         if( query !== undefined )
257             this.currQuery = query;
258         else
259             throw new Error("Pz2.js: no query supplied to the search command.");
260         
261         if ( showfrom !== undefined )
262             var start = showfrom;
263         else
264             var start = 0;
265
266         var searchParams = { 
267             "command": "search",
268             "query": this.currQuery, 
269             "session": this.sessionID 
270         };
271         
272         if (filter !== undefined)
273             searchParams["filter"] = filter;
274         
275         var context = this;
276         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
277         request.postParams(
278             searchParams,
279             function(data) {
280                 if ( data.getElementsByTagName("status")[0]
281                         .childNodes[0].nodeValue == "OK" ) {
282                     context.searchStatusOK = true;
283                     //piggyback search
284                     context.show(start, num, sort);
285                     if ( context.statCallback )
286                         context.stat();
287                     if ( context.termlistCallback )
288                         context.termlist();
289                     if ( context.bytargetCallback )
290                         context.bytarget();
291                 }
292                 else
293                     context.throwError('Search failed. Malformed WS resonse.',
294                                         112);
295             }
296         );
297     },
298     stat: function()
299     {
300         if( !this.initStatusOK )
301             throw new Error('Pz2.js: session not initialized.');
302         
303         // if called explicitly takes precedence
304         clearTimeout(this.statTimer);
305         
306         var context = this;
307         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
308         request.postParams(
309             { "command": "stat", "session": this.sessionID },
310             function(data) {
311                 if ( data.getElementsByTagName("stat") ) {
312                     var activeClients = 
313                         Number( data.getElementsByTagName("activeclients")[0]
314                                     .childNodes[0].nodeValue );
315                     context.activeClients = activeClients;
316                     var stat = {
317                         "activeclients": activeClients,
318                         "hits": 
319                             Number( data.getElementsByTagName("hits")[0]
320                                         .childNodes[0].nodeValue ),
321                         "records": 
322                             Number( data.getElementsByTagName("records")[0]
323                                         .childNodes[0].nodeValue ),
324                         "clients": 
325                             Number( data.getElementsByTagName("clients")[0]
326                                         .childNodes[0].nodeValue ),
327                         "initializing": 
328                             Number( data.getElementsByTagName("initializing")[0]
329                                         .childNodes[0].nodeValue ),
330                         "searching": 
331                             Number( data.getElementsByTagName("searching")[0]
332                                         .childNodes[0].nodeValue ),
333                         "presenting": 
334                             Number( data.getElementsByTagName("presenting")[0]
335                                         .childNodes[0].nodeValue ),
336                         "idle": 
337                             Number( data.getElementsByTagName("idle")[0]
338                                         .childNodes[0].nodeValue ),
339                         "failed": 
340                             Number( data.getElementsByTagName("failed")[0]
341                                         .childNodes[0].nodeValue ),
342                         "error": 
343                             Number( data.getElementsByTagName("error")[0]
344                                         .childNodes[0].nodeValue )
345                     };
346                     
347                     context.statCounter++;
348                     var delay = context.statTime 
349                         + context.statCounter * context.dumpFactor;
350                     
351                     if ( activeClients > 0 )
352                         context.statTimer = 
353                             setTimeout( 
354                                 function () {
355                                     context.stat();
356                                 },
357                                 delay
358                             );
359                     context.statCallback(stat);
360                 }
361                 else
362                     context.throwError('Stat failed. Malformed WS resonse.',
363                                         113);
364             }
365         );
366     },
367     show: function(start, num, sort)
368     {
369         if( !this.searchStatusOK && this.useSessions )
370             throw new Error(
371                 'Pz2.js: show command has to be preceded with a search command.'
372             );
373         
374         // if called explicitly takes precedence
375         clearTimeout(this.showTimer);
376         
377         if( sort !== undefined )
378             this.currentSort = sort;
379         if( start !== undefined )
380             this.currentStart = Number( start );
381         if( num !== undefined )
382             this.currentNum = Number( num );
383
384         var context = this;
385         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
386         request.postParams(
387             { 
388                 "command": "show", 
389                 "session": this.sessionID, 
390                 "start": this.currentStart,
391                 "num": this.currentNum, 
392                 "sort": this.currentSort, 
393                 "block": 1 
394             },
395             function(data) {
396                 if ( data.getElementsByTagName("status")[0]
397                         .childNodes[0].nodeValue == "OK" ) {
398                     // first parse the status data send along with records
399                     // this is strictly bound to the format
400                     var activeClients = 
401                         Number( data.getElementsByTagName("activeclients")[0]
402                                     .childNodes[0].nodeValue );
403                     context.activeClients = activeClients; 
404                     var show = {
405                         "activeclients": activeClients,
406                         "merged": 
407                             Number( data.getElementsByTagName("merged")[0]
408                                         .childNodes[0].nodeValue ),
409                         "total": 
410                             Number( data.getElementsByTagName("total")[0]
411                                         .childNodes[0].nodeValue ),
412                         "start": 
413                             Number( data.getElementsByTagName("start")[0]
414                                         .childNodes[0].nodeValue ),
415                         "num": 
416                             Number( data.getElementsByTagName("num")[0]
417                                         .childNodes[0].nodeValue ),
418                         "hits": []
419                     };
420                     // parse all the first-level nodes for all <hit> tags
421                     var hits = data.getElementsByTagName("hit");
422                     for (i = 0; i < hits.length; i++)
423                         show.hits[i] = Element_parseChildNodes(hits[i]);
424                     
425                     context.showCounter++;
426                     var delay = context.showTime;
427                     if (context.showCounter > context.showFastCount)
428                             delay += context.showCounter * context.dumpFactor;
429                     if ( activeClients > 0 )
430                         context.showTimer = setTimeout(
431                             function () {
432                                 context.show();
433                             }, 
434                             delay);
435                     global_show = show;
436                     context.showCallback(show);
437                 }
438                 else
439                     context.throwError('Show failed. Malformed WS resonse.',
440                                         114);
441             }
442         );
443     },
444     record: function(id, offset, syntax, handler)
445     {
446         // we may call record with no previous search if in proxy mode
447         if(!this.searchStatusOK && this.useSessions)
448            throw new Error(
449             'Pz2.js: record command has to be preceded with a search command.'
450             );
451         
452         if( id !== undefined )
453             this.currRecID = id;
454         
455         var recordParams = { 
456             "command": "record", 
457             "session": this.sessionID,
458             "id": this.currRecID 
459         };
460         
461         this.currRecOffset = null;
462         if (offset != undefined) {
463             recordParams["offset"] = offset;
464             this.currRecOffset = offset;
465         }
466
467         if (syntax != undefined)
468             recordParams['syntax'] = syntax;
469
470         //overwrite default callback id needed
471         var callback = this.recordCallback;
472         var args = undefined;
473         if (handler != undefined) {
474             callback = handler['callback'];
475             args = handler['args'];
476         }
477         
478         var context = this;
479         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
480
481         request.postParams(
482             recordParams,
483             function(data) {
484                 var recordNode;
485                 var record;
486                 //raw record
487                 if (context.currRecOffset !== null) {
488                     record = new Array();
489                     record['xmlDoc'] = data;
490                     record['offset'] = context.currRecOffset;
491                     callback(record, args);
492                 //pz2 record
493                 } else if ( recordNode = 
494                     data.getElementsByTagName("record")[0] ) {
495                     // if stylesheet was fetched do not parse the response
496                     if ( context.xslDoc ) {
497                         record = new Array();
498                         record['xmlDoc'] = data;
499                         record['xslDoc'] = context.xslDoc;
500                         record['recid'] = 
501                             recordNode.getElementsByTagName("recid")[0]
502                                 .firstChild.nodeValue;
503                     //parse record
504                     } else {
505                         record = Element_parseChildNodes(recordNode);
506                     }                    
507                     callback(record, args);
508                 }
509                 else
510                     context.throwError('Record failed. Malformed WS resonse.',
511                                         115);
512             }
513         );
514     },
515
516     termlist: function()
517     {
518         if( !this.searchStatusOK && this.useSessions )
519             throw new Error(
520             'Pz2.js: termlist command has to be preceded with a search command.'
521             );
522
523         // if called explicitly takes precedence
524         clearTimeout(this.termTimer);
525         
526         var context = this;
527         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
528         request.postParams(
529             { 
530                 "command": "termlist", 
531                 "session": this.sessionID, 
532                 "name": this.termKeys 
533             },
534             function(data) {
535                 if ( data.getElementsByTagName("termlist") ) {
536                     var activeClients = 
537                         Number( data.getElementsByTagName("activeclients")[0]
538                                     .childNodes[0].nodeValue );
539                     context.activeClients = activeClients;
540                     var termList = { "activeclients":  activeClients };
541                     var termLists = data.getElementsByTagName("list");
542                     //for each termlist
543                     for (i = 0; i < termLists.length; i++) {
544                         var listName = termLists[i].getAttribute('name');
545                         termList[listName] = new Array();
546                         var terms = termLists[i].getElementsByTagName('term');
547                         //for each term in the list
548                         for (j = 0; j < terms.length; j++) { 
549                             var term = {
550                                 "name": 
551                                     (terms[j].getElementsByTagName("name")[0]
552                                         .childNodes.length 
553                                     ? terms[j].getElementsByTagName("name")[0]
554                                         .childNodes[0].nodeValue
555                                     : 'ERROR'),
556                                 "freq": 
557                                     terms[j]
558                                     .getElementsByTagName("frequency")[0]
559                                     .childNodes[0].nodeValue || 'ERROR'
560                             };
561
562                             var termIdNode = 
563                                 terms[j].getElementsByTagName("id");
564                             if(terms[j].getElementsByTagName("id").length)
565                                 term["id"] = 
566                                     termIdNode[0].childNodes[0].nodeValue;
567                             termList[listName][j] = term;
568                         }
569                     }
570
571                     context.termCounter++;
572                     var delay = context.termTime 
573                         + context.termCounter * context.dumpFactor;
574                     if ( activeClients > 0 )
575                         context.termTimer = 
576                             setTimeout(
577                                 function () {
578                                     context.termlist();
579                                 }, 
580                                 delay
581                             );
582                    
583                    context.termlistCallback(termList);
584                 }
585                 else
586                     context.throwError('Termlist failed. Malformed WS resonse.',
587                                         116);
588             }
589         );
590
591     },
592     bytarget: function()
593     {
594         if( !this.initStatusOK && this.useSessions )
595             throw new Error(
596             'Pz2.js: bytarget command has to be preceded with a search command.'
597             );
598         
599         // no need to continue
600         if( !this.searchStatusOK )
601             return;
602
603         // if called explicitly takes precedence
604         clearTimeout(this.bytargetTimer);
605         
606         var context = this;
607         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
608         request.postParams(
609             { "command": "bytarget", "session": this.sessionID },
610             function(data) {
611                 if ( data.getElementsByTagName("status")[0]
612                         .childNodes[0].nodeValue == "OK" ) {
613                     var targetNodes = data.getElementsByTagName("target");
614                     var bytarget = new Array();
615                     for ( i = 0; i < targetNodes.length; i++) {
616                         bytarget[i] = new Array();
617                         for( j = 0; j < targetNodes[i].childNodes.length; j++ ) {
618                             if ( targetNodes[i].childNodes[j].nodeType 
619                                 == Node.ELEMENT_NODE ) {
620                                 var nodeName = 
621                                     targetNodes[i].childNodes[j].nodeName;
622                                 var nodeText = 
623                                     targetNodes[i].childNodes[j]
624                                         .firstChild.nodeValue;
625                                 bytarget[i][nodeName] = nodeText;
626                             }
627                         }
628                     }
629                     
630                     context.bytargetCounter++;
631                     var delay = context.bytargetTime 
632                         + context.bytargetCounter * context.dumpFactor;
633                     if ( context.activeClients > 0 )
634                         context.bytargetTimer = 
635                             setTimeout(
636                                 function () {
637                                     context.bytarget();
638                                 }, 
639                                 delay
640                             );
641
642                     context.bytargetCallback(bytarget);
643                 }
644                 else
645                     context.throwError('Bytarget failed. Malformed WS resonse.',
646                                         117);
647             }
648         );
649     },
650     
651     // just for testing, probably shouldn't be here
652     showNext: function(page)
653     {
654         var step = page || 1;
655         this.show( ( step * this.currentNum ) + this.currentStart );     
656     },
657
658     showPrev: function(page)
659     {
660         if (this.currentStart == 0 )
661             return false;
662         var step = page || 1;
663         var newStart = this.currentStart - (step * this.currentNum );
664         this.show( newStart > 0 ? newStart : 0 );
665     },
666
667     showPage: function(pageNum)
668     {
669         //var page = pageNum || 1;
670         this.show(pageNum * this.currentNum);
671     }
672 };
673
674 /*
675 ********************************************************************************
676 ** AJAX HELPER CLASS ***********************************************************
677 ********************************************************************************
678 */
679 var pzHttpRequest = function ( url, errorHandler ) {
680         this.request = null;
681         this.url = url;
682         this.errorHandler = errorHandler || null;
683         this.async = true;
684         this.requestHeaders = {};
685         
686         if ( window.XMLHttpRequest ) {
687             this.request = new XMLHttpRequest();
688         } else if ( window.ActiveXObject ) {
689             try {
690                 this.request = new ActiveXObject( 'Msxml2.XMLHTTP' );
691             } catch (err) {
692                 this.request = new ActiveXObject( 'Microsoft.XMLHTTP' );
693             }
694         }
695 };
696
697 pzHttpRequest.prototype = 
698 {
699     postParams: function ( params, callback )
700     {
701         this.requestHeaders["Content-Type"]="application/x-www-form-urlencoded";
702         this._send('POST', {}, this.encodeParams(params), callback);
703     },
704
705     get: function ( params, callback ) 
706     {
707         this._send( 'GET', params, '', callback );
708     },
709
710     post: function ( params, data, callback )
711     {
712         this._send( 'POST', params, data, callback );
713     },
714
715     load: function ()
716     {
717         this.async = false;
718         this.request.open( 'GET', this.url, this.async );
719         this.request.send('');
720         if ( this.request.status == 200 )
721             return this.request.responseXML;
722     },
723
724     encodeParams: function (params)
725     {
726         var sep = "";
727         var encoded = "";
728         for (var key in params) {
729             if (params[key] != null) {
730                 encoded += sep + key + '=' + encodeURIComponent(params[key]);
731                 sep = '&';
732             }
733         }
734         return encoded;
735     },
736
737     _send: function ( type, params, data, callback )
738     {
739         this.callback = callback;
740         var context = this;
741         this.async = true;
742         this.request.open( type, this._urlAppendParams(params), this.async );
743         for (var key in this.requestHeaders)
744             this.request.setRequestHeader(key, this.requestHeaders[key]);
745         this.request.onreadystatechange = function () {
746             context._handleResponse();
747         }
748         this.request.send(data);
749     },
750
751     _urlAppendParams: function (params)
752     {
753         var encodedParams = this.encodeParams(params);
754         if (encodedParams)
755             return this.url + "?" + encodedParams;
756         else
757             return this.url;
758     },
759
760     _handleResponse: function ()
761     {
762         if ( this.request.readyState == 4 ) { 
763             // pick up pazpr2 errors first
764             if ( this.request.responseXML 
765                 && this.request.responseXML.documentElement.nodeName == 'error'
766                 && this.request.responseXML.getElementsByTagName("error")
767                     .length ) {
768                 var errAddInfo = '';
769                 if ( this.request.responseXML.getElementsByTagName("error")[0]
770                         .childNodes.length )
771                     errAddInfo = ': ' + 
772                         this.request.responseXML
773                             .getElementsByTagName("error")[0]
774                             .childNodes[0].nodeValue;
775                 var errMsg = 
776                     this.request.responseXML.getElementsByTagName("error")[0]
777                         .getAttribute("msg");
778                 var errCode = 
779                     this.request.responseXML.getElementsByTagName("error")[0]
780                         .getAttribute("code");
781             
782                 var err = new Error(errMsg + errAddInfo);
783                 err.code = errCode;
784             
785                 if (this.errorHandler) {
786                     this.errorHandler(err);
787                 }
788                 else {
789                     throw err;
790                 }
791             } else if ( this.request.status == 200 ) {
792                 this.callback( this.request.responseXML );
793             } else {
794                 var err = new Error("Pz2.js: HTTP request error (AJAX). Code: " 
795                             + this.request.status + " Info: " 
796                             + this.request.statusText );
797                 err.code = 'HTTP';
798                 
799                 if (this.errorHandler) {
800                     this.errorHandler(err);
801                 }
802                 else {
803                     throw err;
804                 }
805             }
806         }
807     }
808 };
809
810 /*
811 ********************************************************************************
812 ** XML HELPER FUNCTIONS ********************************************************
813 ********************************************************************************
814 */
815
816 // DOMDocument
817
818 if ( window.ActiveXObject) {
819     var DOMDoc = document;
820 } else {
821     var DOMDoc = Document.prototype;
822 }
823
824 DOMDoc.newXmlDoc = function ( root )
825 {
826     var doc;
827
828     if (document.implementation && document.implementation.createDocument) {
829         doc = document.implementation.createDocument('', root, null);
830     } else if ( window.ActiveXObject ) {
831         doc = new ActiveXObject("MSXML2.DOMDocument");
832         doc.loadXML('<' + root + '/>');
833     } else {
834         throw new Error ('No XML support in this browser');
835     }
836
837     return doc;
838 }
839
840    
841 DOMDoc.parseXmlFromString = function ( xmlString ) 
842 {
843     var doc;
844
845     if ( window.DOMParser ) {
846         var parser = new DOMParser();
847         doc = parser.parseFromString( xmlString, "text/xml");
848     } else if ( window.ActiveXObject ) {
849         doc = new ActiveXObject("MSXML2.DOMDocument");
850         doc.loadXML( xmlString );
851     } else {
852         throw new Error ("No XML parsing support in this browser.");
853     }
854
855     return doc;
856 }
857
858 DOMDoc.transformToDoc = function (xmlDoc, xslDoc)
859 {
860     if ( window.XSLTProcessor ) {
861         var proc = new XSLTProcessor();
862         proc.importStylesheet( xslDoc );
863         return proc.transformToDocument(xmlDoc);
864     } else if ( window.ActiveXObject ) {
865         return document.parseXmlFromString(xmlDoc.transformNode(xslDoc));
866     } else {
867         alert( 'Unable to perform XSLT transformation in this browser' );
868     }
869 }
870  
871 // DOMElement
872
873 Element_removeFromDoc = function (DOM_Element)
874 {
875     DOM_Element.parentNode.removeChild(DOM_Element);
876 }
877
878 Element_emptyChildren = function (DOM_Element)
879 {
880     while( DOM_Element.firstChild ) {
881         DOM_Element.removeChild( DOM_Element.firstChild )
882     }
883 }
884
885 Element_appendTransformResult = function ( DOM_Element, xmlDoc, xslDoc )
886 {
887     if ( window.XSLTProcessor ) {
888         var proc = new XSLTProcessor();
889         proc.importStylesheet( xslDoc );
890         var docFrag = false;
891         docFrag = proc.transformToFragment( xmlDoc, DOM_Element.ownerDocument );
892         DOM_Element.appendChild(docFrag);
893     } else if ( window.ActiveXObject ) {
894         DOM_Element.innerHTML = xmlDoc.transformNode( xslDoc );
895     } else {
896         alert( 'Unable to perform XSLT transformation in this browser' );
897     }
898 }
899  
900 Element_appendTextNode = function (DOM_Element, tagName, textContent )
901 {
902     var node = DOM_Element.ownerDocument.createElement(tagName);
903     var text = DOM_Element.ownerDocument.createTextNode(textContent);
904
905     DOM_Element.appendChild(node);
906     node.appendChild(text);
907
908     return node;
909 }
910
911 Element_setTextContent = function ( DOM_Element, textContent )
912 {
913     if (typeof DOM_Element.textContent !== "undefined") {
914         DOM_Element.textContent = textContent;
915     } else if (typeof DOM_Element.innerText !== "undefined" ) {
916         DOM_Element.innerText = textContent;
917     } else {
918         throw new Error("Cannot set text content of the node, no such method.");
919     }
920 }
921
922 Element_getTextContent = function (DOM_Element)
923 {
924     if ( typeof DOM_Element.textContent != 'undefined' ) {
925         return DOM_Element.textContent;
926     } else if (typeof DOM_Element.text != 'undefined') {
927         return DOM_Element.text;
928     } else {
929         throw new Error("Cannot get text content of the node, no such method.");
930     }
931 }
932
933 Element_parseChildNodes = function (node)
934 {
935     var parsed = {};
936     var hasChildElems = false;
937
938     if (node.hasChildNodes()) {
939         var children = node.childNodes;
940         for (var i = 0; i < children.length; i++) {
941             var child = children[i];
942             if (child.nodeType == Node.ELEMENT_NODE) {
943                 hasChildElems = true;
944                 var nodeName = child.nodeName; 
945                 if (!(nodeName in parsed))
946                     parsed[nodeName] = [];
947                 parsed[nodeName].push(Element_parseChildNodes(child));
948             }
949         }
950     }
951
952     var attrs = node.attributes;
953     for (var i = 0; i < attrs.length; i++) {
954         var attrName = '@' + attrs[i].nodeName;
955         var attrValue = attrs[i].nodeValue;
956         parsed[attrName] = attrValue;
957     }
958
959     // if no nested elements, get text content
960     if (node.hasChildNodes() && !hasChildElems) {
961         if (node.attributes.length) 
962             parsed['textContent'] = node.firstChild.nodeValue;
963         else
964             parsed = node.firstChild.nodeValue;
965     }
966     
967     return parsed;
968 }
969
970 /* do not remove trailing bracket */
971 }