/*
    Copyright (c) 2003-2005 Jan-Klaas Kollhof
    
    This file is part of the JavaScript O Lait library(jsolait).
    
    jsolait is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or
    (at your option) any later version.
    
    This software is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    
    You should have received a copy of the GNU Lesser General Public License
    along with this software; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
    The main jsolait script.
    It provides the core functionalities  for creating classes, modules and for importing modules.
    
    $LastChangedBy: Jan-Klaas Kollhof $
    $Date: 2005-09-16 01:06:59 +0100 (Fri, 16 Sep 2005) $
    $Revision: 56 $
**/


//<no-doc>
/**
    Creates a new class object which inherits from superClass.
    @param className="anonymous"  The name of the new class.
                                                  If the created class is a public member of a module then 
                                                  the className is automatically set.
    @param superClass=Object         The class to inherit from (super class).
    @param mixinClass(*)                 The mixin class.
    @param classScope(-1)               A function which is executed for class construction.
                                                As 1st parameter it will get the new class' protptype for 
                                                overrideing or extending the super class. As 2nd parameter it will get
                                                the super class' wrapper for calling inherited methods.
**/
Class=function(className, superClass, mixinClass, classScope){
    var args=[];
    for(var i=0;i<arguments.length;i++){
        args[i] = arguments[i];
    }
    //last arg should be a classScope
    classScope = args.pop();
    
    //first arg is the class' name if it is a string
    if((args.length>0) && (typeof args[0] =='string')){
        className=args.shift();
    }else{
        className = "anonymous";
    }
    //the next arg should be the superclass
    if(args.length > 0){
        superClass = args.shift();
    }else{
        superClass = Object;
    }
    var mixinClasses=args;
    
    //this is the constructor for the new objects created from the new class.
    //if and only if it is NOT used for prototyping/subclassing the init method of the newly created object will be called.
    var NewClass = function(calledBy){
        if(calledBy !== Class){
            //simulate Array and Function subclassing
            if(NewClass.prototype instanceof Array || NewClass.prototype instanceof Function){
                if(NewClass.prototype instanceof Array){
                    var rslt=[];
                }else{
                    var rslt = function(){
                        return rslt.__call__.apply(rslt, arguments);
                    };
                }
                //transfer all properties defined in teh class to the new object(as it is empty)
                for(var n in NewClass.prototype){
                    rslt[n] = NewClass.prototype[n];
                }
                //these props are not copied in the above loop
                rslt.constructor = NewClass;
                rslt.toString = NewClass.prototype.toString;
                    
                rslt.init.apply(rslt,arguments);
                return rslt;
            }else{
                //todo should a constructor be able to return something?
                this.init.apply(this, arguments);
            }
        }
    }
    //This will create a new prototype object of the new class.
    NewClass.createPrototype = function(){
        return new NewClass(Class);
    }
    //setting class properties for the new class.
    NewClass.superClass = superClass;
    NewClass.className=className; 
    NewClass.toString = function(){
        return "[class %s]".format(NewClass.className);
    }
    
    NewClass.isSubclassOf=function(cls){
        return this.prototype instanceof cls;
    }
    
   //see if the super class can create prototypes. (creating an object without calling init())
    if(superClass.createPrototype!==undefined){
        NewClass.prototype = superClass.createPrototype();
    }else{//just create an object of the super class
        NewClass.prototype = new superClass();
    }
    //reset the constructor for new objects to the actual constructor.
    NewClass.prototype.constructor = NewClass;
    
    
    if(superClass == Object){//all other objects already have a nice toString method.
        NewClass.prototype.toString = function(){
            return "[object %s]".format(this.constructor.className);
        }
    }
    //make sure the new class has an init method if it inherits from Object,Array or Function
    switch(superClass){
        case Object:
            NewClass.prototype.init=function(){
            }    
            break;
        case Array:
            //immitate a call to new Array()
            NewClass.prototype.init=function(){
                if (arguments.length==0){
                
                }else if(arguments.lengt==1){
                    this.length=arguments[0];
                }else{
                    for(var i=0;i<arguments.length;i++){
                        this.push(arguments[i]);
                    }
                }
            }    
            break;
        case Function:
            //Subclasses of Function do not impl. Function's default behavior
            //as this would not make much sense, we allow subclassing of functions so people can create callable objects
            NewClass.prototype.init=function(){
            }
            //needs to be overwritten by users
            NewClass.prototype.__call__=function(){
            }
            NewClass.prototype.toString=function(){
                return "[callable %s]".format(this.constructor.className);
                //return Function.prototype.toString.call(this);
            }
            break;
    }
    
    for(var i=0;i<mixinClasses.length;i++){
        var mixin = mixinClasses[i].prototype;
        for(var n in mixin){
            if(n != "init"){
                NewClass.prototype[n] = mixin[n];
            }
        }
    }
       
    //create a supr  function to be used to call methods of the super class
    var supr = function(self){
        //make sure its a legal call to supr
        if(! (self.constructor.isSubclassOf(superClass))){
            print ("Illegal call of supr:\n %s is not an instance of %s".format(self, superClass));
        }
        var wrapper;
        //if a wrapper has been created already then use it
        if(self[" super_this_" + superClass.className]){
            wrapper = self[" super_this_" + superClass.className];
        }else{
            //set up super class functionality so a call to super(this) will return an object with all super class methods 
            //the methods can be called like super(this).foo and the this object will be bound to that method.
            wrapper = {};
            var superProto = superClass.prototype;
            for(var n in superProto){
                if(superProto[n] instanceof Function){
                    wrapper[n] = function(){
                                            var f = arguments.callee;
                                            return superProto[f._name].apply(self, arguments);
                                        }
                    wrapper[n]._name = n;
                }
            }
            //save the wrapper so calling of supr a second time is much faster and skips the wrapper setup
            self[" super_this_" + superClass.className]=wrapper;
        }
        return wrapper;
    }
        
    //execute the scope of the class
    classScope(NewClass.prototype, supr);
    return NewClass;
}    
Class.toString = function(){
    return "[object Class]";
}
Class.createPrototype=function(){ 
    throw "Can't use Class as a super class.";
}


/**
    Creates a new module and registers it.
    @param name              The name of the module.
    @param version            The version of a module.
    @param imports=[]        A string containing a comma seperated lists of modules to import before the moduleScope is executed.
    @param moduleScope    A function which is executed for module creation.
                                     As 1st parameter it will get the module variable.                          
                                     The imported modules(imports) will be passed to the moduleScope starting with the 2nd parameter.
**/
Module=function(name, version, imports, moduleScope){
    if(moduleScope === undefined){
        moduleScope = imports;
        imports = [];
    }else{
       imports = imports.split(",");
    }

    var mod = {};
    mod.name = name;
    mod.version = version;

    mod.toString=function(){
        //todo:SVN adaption
        return "[module '%s' version: %s]".format(mod.name, mod.version);
    }

    //give a module it's own exception class which makes debugging easier
    mod.Exception=Class(Module.Exception, function(publ, supr){
        publ.module = mod;
    })

    //this is for hooks that need to work with modules before their scope is run
    Module.preScopeExecution(mod);

    var args=[mod];
    //import all modules that were specified in imports and add them to args
    for(var i=0;i<imports.length;i++){
        args.push(jsolait.imprt(imports[i]));
    }

    try{//to execute the scope of the module
        moduleScope.apply(mod, args);
    }catch(e){
        throw new Module.ModuleScopeExecFailed(mod, e);
    }
    
    //todo: set classNames for anonymous classes.
    for(var n in mod){
        if(mod[n]){
            if(mod[n].className == "anonymous"){
                mod[n].className = n;
            }
        }
    }
    jsolait.registerModule(mod);
    return mod;
}

Module.toString=function(){
    return "[object Module]";
}
Module.createPrototype=function(){ 
    throw "Can't use Module as a super class.";
}
/**
    Base class for all module-Exceptions.
    This class should not be instaciated directly but rather
    use the exception that is part of the module.
**/
Module.Exception=Class("Exception", function(publ){
    /**
        Initializes a new Exception.
        @param msg           The error message for the user.
        @param trace=undefined  The error causing this Exception if available.
    **/
    publ.init=function(msg, trace){
        this.name = this.constructor.className;
        this.message = msg;
        this.trace = trace;
    }
    
    publ.toString=function(){
        var s = "%s %s\n\n".format(this.name, this.module);
        s += this.message;
        return s;
    }
    /**
        Returns the complete trace of the exception.
        @return The error trace.
    **/
    publ.toTraceString=function(){
        var s = "%s in %s:\n    ".format(this.name, this.module );
        s+="%s\n\n".format(this.message);
        if(this.trace){
            if(this.trace.toTraceString){
                s+= this.trace.toTraceString();
            }else{
                s+= this.trace;
            }
        }
        return s;
    }
    ///The name of the Exception(className).
    publ.name;
    ///The error message.
    publ.message;
    ///The module the Exception belongs to.
    publ.module="jsolait";
    ///The error which caused the Exception or undefined.
    publ.trace;      
})

/**
    Thrown if a module scope could not be run.
**/
Module.ModuleScopeExecFailed=Class("ModuleScopeExecFailed", Module.Exception, function(publ, supr){
    /**
        Initializes a new ModuleScopeExecFailed Exception.
        @param mod      The module.
        @param trace      The error cousing this Exception.
    **/
    publ.init=function(mod, trace){
        supr(this).init("Failed to run the module scope for %s".format(mod), trace);
        this.failedModule = mod;
    }
    ///The module that could not be createed.
    publ.module;
})
///a hook for other modules to override for customization todo:doc
Module.preScopeExecution=function(mod){};
  
//</no-doc>
    
/**
    
    @author                 Jan-Klaas Kollhof
    @lastchangedby       $LastChangedBy: Jan-Klaas Kollhof $
    @lastchangeddate    $Date: 2005-09-16 01:06:59 +0100 (Fri, 16 Sep 2005) $
**/
Module("jsolait", "$Revision: 56 $", function(mod){
    jsolait=mod;
    mod.modules={};
    
    /**
        Thrown when a module could not be found.
    **/
    mod.ImportFailed=Class(mod.Exception, function(publ, supr){
        /**
            Initializes a new ModuleImportFailed Exception.
            @param name      The name of the module.
            @param trace      The error cousing this Exception.
        **/
        publ.init=function(moduleName, trace){
            supr(this).init("Failed to import module: '%s'".format(moduleName), trace);
            this.moduleName = moduleName;
        }
        ///The  name of the module that was not found.
        publ.moduleName;
    })
        
    
    mod.__import__=function(modName){
        var modImp = mod.modules[modName];
        if(modImp !== undefined){
            return modImp;
        }else{
            if(mod.modules.moduleLoader){
                return mod.modules.moduleLoader.imprt(modName);
            }else{
                throw mod.ImportFailed(modName, new mod.Exception("No module loader available"))
            }
        }
    }
    
    /**
        Imports a module given its name.
        Modules must have registered themselfes before they can be imported.
        @param name   The name of the module to load.
        @return           The module object.
    **/
    mod.imprt = function(name){
        return mod.__import__(name);
    }
    
    imprt = function(name){
        return mod.imprt(name);
    }
    
    
    importModule = imprt;
    
    mod.__registerModule__=function(modObj, modName){
        if(modName != 'jsolait'){
            mod.modules[modName] = modObj;
        }
    }
       
    mod.registerModule=function(modObj, modName){
        modName = modName===undefined?modObj.name : modName;
        mod.__registerModule__(modObj, modName);
    }


//---------------------------------------------------String Format -------------------------------------------------------    
    /**
        Creates a format specifier object. 
    **/
    var FormatSpecifier=function(s){
        var s = s.match(/%(\(\w+\)){0,1}([ 0-]){0,1}(\+){0,1}(\d+){0,1}(\.\d+){0,1}(.)/);
        if(s[1]){
            this.key=s[1].slice(1,-1);
        }else{
            this.key = null;
        }
        this.paddingFlag = s[2];
        if(this.paddingFlag==""){
            this.paddingFlag =" " 
        }
        this.signed=(s[3] == "+");
        this.minLength = parseInt(s[4]);
        if(isNaN(this.minLength)){
            this.minLength=0;
        }
        if(s[5]){
            this.percision = parseInt(s[5].slice(1,s[5].length));
        }else{
            this.percision=-1;
        }
        this.type = s[6];
    }

    /**
        Formats a string replacing formatting specifiers with values provided as arguments
        which are formatted according to the specifier.
        This is an implementation of  python's % operator for strings and is similar to sprintf from C.
        Usage:
            resultString = formatString.format(value1, v2, ...);
        
        Each formatString can contain any number of formatting specifiers which are
        replaced with the formated values.
        
        specifier([...]-items are optional): 
            "%(key)[flag][sign][min][percision]typeOfValue"
            
            (key)  If specified the 1st argument is treated as an object/associative array and the formating values 
                     are retrieved from that object using the key.
                
            flag:
                0      Use 0s for padding.
                -      Left justify result, padding it with spaces.
                        Use spaces for padding.
            sign:
                +      Numeric values will contain a +|- infront of the number.
            min:
                l      The string will be padded with the padding character until it has a minimum length of l. 
            percision:
               .x     Where x is the percision for floating point numbers and the lenght for 0 padding for integers.
            typeOfValue:
                d    Signed integer decimal.  	 
                i     Signed integer decimal. 	 
                b    Unsigned binary.                       //This does not exist in python!
                o    Unsigned octal. 	
                u    Unsigned decimal. 	 
                x    Unsigned hexidecimal (lowercase). 	
                X   Unsigned hexidecimal (uppercase). 	
                e   Floating point exponential format (lowercase). 	 
                E   Floating point exponential format (uppercase). 	 
                f    Floating point decimal format. 	 
                F   Floating point decimal format. 	 
                c   Single character (accepts byte or single character string). 	 
                s   String (converts any object using object.toString()). 	
        Examples:
            "%02d".format(8) == "08"
            "%05.2f".format(1.234) == "01.23"
            "123 in binary is: %08b".format(123) == "123 in binary is: 01111011"
            
        @param *  Each parameter is treated as a formating value. 
        @return The formated String.
    **/
    String.prototype.format=function(){
        var sf = this.match(/(%(\(\w+\)){0,1}[ 0-]{0,1}(\+){0,1}(\d+){0,1}(\.\d+){0,1}[dibouxXeEfFgGcrs%])|([^%]+)/g);
        if(sf){
            if(sf.join("") != this){
                throw new mod.Exception("Unsupported formating string.");
            }
        }else{
            throw new mod.Exception("Unsupported formating string.");
        }
        var rslt ="";
        var s;
        var obj;
        var cnt=0;
        var frmt;
        var sign="";
        
        for(var i=0;i<sf.length;i++){
            s=sf[i];
            if(s == "%%"){
                s = "%";
            }else if(s.slice(0,1) == "%"){
                frmt = new FormatSpecifier(s);//get the formating object
                if(frmt.key){//an object was given as formating value
                    if((typeof arguments[0]) == "object" && arguments.length == 1){
                        obj = arguments[0][frmt.key];
                    }else{
                        throw new mod.Exception("Object or associative array expected as formating value.");
                    }
                }else{//get the current value
                    if(cnt>=arguments.length){
                        throw new mod.Exception("Not enough arguments for format string.");
                    }else{
                        obj=arguments[cnt];
                        cnt++;
                    }
                }
                    
                if(frmt.type == "s"){//String
                    if (obj === null){
                        obj = "null";
                    }else if(obj===undefined){
                        obj = "undefined";
                    }
                    s=obj.toString().pad(frmt.paddingFlag, frmt.minLength);
                    
                }else if(frmt.type == "c"){//Character
                    if(frmt.paddingFlag == "0"){
                        frmt.paddingFlag=" ";//padding only spaces
                    }
                    if(typeof obj == "number"){//get the character code
                        s = String.fromCharCode(obj).pad(frmt.paddingFlag , frmt.minLength) ;
                    }else if(typeof obj == "string"){
                        if(obj.length == 1){//make sure it's a single character
                            s=obj.pad(frmt.paddingFlag, frmt.minLength);
                        }else{
                            throw new mod.Exception("Character of length 1 required.");
                        }
                    }else{
                        throw new mod.Exception("Character or Byte required.");
                    }
                }else if(typeof obj == "number"){
                    //get sign of the number
                    if(obj < 0){
                        obj = -obj;
                        sign = "-"; //negative signs are always needed
                    }else if(frmt.signed){
                        sign = "+"; // if sign is always wanted add it 
                    }else{
                        sign = "";
                    }
                    //do percision padding and number conversions
                    switch(frmt.type){
                        case "f": //floats
                        case "F":
                            if(frmt.percision > -1){
                                s = obj.toFixed(frmt.percision).toString();
                            }else{
                                s = obj.toString();
                            }
                            break;
                        case "E"://exponential
                        case "e":
                            if(frmt.percision > -1){
                                s = obj.toExponential(frmt.percision);
                            }else{
                                s = obj.toExponential();
                            }
                            s = s.replace("e", frmt.type);
                            break;
                        case "b"://binary
                            s = obj.toString(2);
                            s = s.pad("0", frmt.percision);
                            break;
                        case "o"://octal
                            s = obj.toString(8);
                            s = s.pad("0", frmt.percision);
                            break;
                        case "x"://hexadecimal
                            s = obj.toString(16).toLowerCase();
                            s = s.pad("0", frmt.percision);
                            break;
                        case "X"://hexadecimal
                            s = obj.toString(16).toUpperCase();
                            s = s.pad("0", frmt.percision);
                            break;
                        default://integers
                            s = parseInt(obj).toString();
                            s = s.pad("0", frmt.percision);
                            break;
                    }
                    if(frmt.paddingFlag == "0"){//do 0-padding
                        //make sure that the length of the possible sign is not ignored
                        s=s.pad("0", frmt.minLength - sign.length);
                    }
                    s=sign + s;//add sign
                    s=s.pad(frmt.paddingFlag, frmt.minLength);//do padding and justifiing
                }else{
                    throw new mod.Exception("Number required.");
                }
            }
            rslt += s;
        }
        return rslt;
    }
    ///Backwards compatibility;
    String.prototype.format=String.prototype.format;
    
    /**
        Padds a String with a character to have a minimum length.
        
        @param flag   "-":      to padd with " " and left justify the string.
                            Other: the character to use for padding. 
        @param len    The minimum length of the resulting string.
    **/
    String.prototype.pad = function(flag, len){
        var s = "";
        if(flag == "-"){
            var c = " ";
        }else{
            var c = flag;
        }
        for(var i=0;i<len-this.length;i++){
            s += c;
        }
        if(flag == "-"){
            s = this + s;
        }else{
            s += this;
        }
        return s;
    }
    

    ///Tests the module.
    mod.test=function(){
        
    }
})

//--------------------------------------------Module loader--------------------------------------------


Module("moduleLoader", "$Revision: 56 $", function(mod){
    
    ///The paths to search for modules
    mod.moduleSearchPaths = ["."];
    
    ///The location where jsolait is installed.
    mod.installPath="./jsolait";
    
    ///The paths of  the modules that come with jsolait.
    var modulePathMap={codecs:"%(installPath)s/lib/codecs.js",
                                    crypto:"%(installPath)s/lib/crypto.js",
                                    dom:"%(installPath)s/lib/dom.js",
                                    forms:"%(installPath)s/lib/forms.js",
                                    iter:"%(installPath)s/lib/iter.js",
                                    jsonrpc:"%(installPath)s/lib/jsonrpc.js",
                                    lang:"%(installPath)s/lib/lang.js",
                                    sets:"%(installPath)s/lib/sets.js",
                                    testing:"%(installPath)s/lib/testing.js",
                                    urllib:"%(installPath)s/lib/urllib.js",
                                    xml:"%(installPath)s/lib/xml.js",
                                    xmlrpc:"%(installPath)s/lib/xmlrpc.js"}                                      
    
    /**
       Imports a module given its name(someModule.someSubModule).
       A module's file location is determined by treating each module name as a directory.
       Only the last one points to a file.
       If the module's URL is not known to jsolait then it will be searched for in jsolait.baseURL which is "." by default.
       @param name   The name of the module to load.
       @return           The module object.
    */
    mod.imprt = function(name){

        if (jsolait.modules[name]){ //module already loaded
            return mod.modules[name];
        }else{
            var src,modPath;
            
            //check if jsolait already knows the path of the module
            if(modulePathMap[name]){
                modPath = modulePathMap[name].format(mod);
                try{//to load the source of the module
                    src = getFile(modPath);
                }catch(e){
                    throw new mod.ImportFailed(name, modPath, e);
                }
            }else{//go through the search path and try loading the module
                var failedPaths=[];
                for(var i=0;i<mod.moduleSearchPaths.length; i++){
                    modPath = "%s/%s.js".format(mod.moduleSearchPaths[i], name.split(".").join("/"));
                    try{
                        src = getFile(modPath);
                        break;
                    }catch(e){
                        failedPaths.push(modPath);
                    }
                }
                if(src == null){
                    throw new mod.ModuleImportFailed(name, failedPaths, e);
                }
            }
            
            try{//interpret the script
                (new Function("",src))(); //todo should it use globalEval ?
            }catch(e){
                throw new mod.ImportFailed(name, modPath, e);
            }
            
            return jsolait.__import__(name); 
        }
    } 
    
    /**
        Thrown when a module could not be found.
    **/
    mod.ImportFailed=Class(mod.Exception, function(publ, supr){
        /**
            Initializes a new ModuleImportFailed Exception.
            @param name      The name of the module.
            @param modulePath The path or a list of paths jsolait tried to load the modules from
            @param trace      The error cousing this Exception.
        **/
        publ.init=function(moduleName, modulePath, trace){
            supr(this).init("Failed to import module: '%s' from:\n%s".format(moduleName, modulePath), trace);
            this.moduleName = moduleName;
            this.modulePath = modulePath;
        }
        ///The  name of the module that was not found.
        publ.moduleName;
        ///The path or a list of paths jsolait tried to load the modules from.
        publ.modulePath;
    })
    
    /**
        Creates an HTTP request object for retreiving files.
        @return HTTP request object.
    */
    var getHTTP=function() {
        var obj;
        try{ //to get the mozilla httprequest object
            obj = new XMLHttpRequest();
        }catch(e){
            try{ //to get MS HTTP request object
                obj=new ActiveXObject("Msxml2.XMLHTTP.4.0");
            }catch(e){
                try{ //to get MS HTTP request object
                    obj=new ActiveXObject("Msxml2.XMLHTTP");
                }catch(e){
                    try{// to get the old MS HTTP request object
                        obj = new ActiveXObject("microsoft.XMLHTTP"); 
                    }catch(e){
                        throw new mod.Exception("Unable to get an HTTP request object.");
                    }
                }    
            }
        }
        return obj;
    }
    
    /**
        Retrieves a file given its URL.
        @param url             The url to load.
        @param headers=[]  The headers to use.
        @return                 The content of the file.
    */
    var getFile=function(url, headers) { 
        //if callback is defined then the operation is done async
        headers = (headers !== undefined) ? headers : [];
        //setup the request
        try{
            var xmlhttp= getHTTP();
            xmlhttp.open("GET", url, false);
            for(var i=0;i< headers.length;i++){
                xmlhttp.setRequestHeader(headers[i][0], headers[i][1]);    
            }
            xmlhttp.send("");
        }catch(e){
            throw new mod.Exception("Unable to load URL: '%s'.".format(url), e);
        }
        if(xmlhttp.status == 200 || xmlhttp.status == 0){
            return xmlhttp.responseText;
        }else{
             throw new mod.Exception("File not loaded: '%s'.".format(url));
        }
    }
    
    /**
        Loads and interprets a script file.
        @param url  The url of the script to load.
    */
    mod.loadScript=function(url){
        var src = getFile(url);
        try{//to interpret the source 
            (new Function("",src))();
        }catch(e){
            throw new mod.EvalFailed(url, e);
        }
    }  
})


/*
  Copyright (c) 2003 Jan-Klaas Kollhof
  
  This file is part of the JavaScript o lait library(jsolait).
  
  jsolait is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.
 
  This software is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.
 
  You should have received a copy of the GNU Lesser General Public License
  along with this software; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
    Provides methods for making HTTP requests.
    @creator                 Jan-Klaas Kollhof
    @created                2003-07-10
    @lastchangedby       $LastChangedBy: Jan-Klaas Kollhof $
    @lastchangeddate    $Date: 2005-09-16 01:06:59 +0100 (Fri, 16 Sep 2005) $
*/
Module("urllib","$Revision: 56 $", function(mod){
    /**
        Thrown if no request object could be instanciated.
    */
    mod.NoHTTPRequestObject=Class("NoHTTPRequestObject", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param trace The error causing this exception.
        */
        publ.init=function(trace){
            supr(this).init( "Could not create an HTTP request object", trace);
        }
    })
    
    /**
        Thrown if an HTTP request could not be opened.
    */
    mod.RequestOpenFailed = Class("RequestOpenFailed", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param trace The error causing this exception.
        */
        publ.init=function(trace){
            supr(this).init( "Opening of HTTP request failed.", trace);
        }
    })
    
    /**
        Thrown is arequest could not be sent to the server.
    */
    mod.SendFailed=Class("SendFailed", mod.Exception, function(publ, supr){
         /**
            Initializes the Exception.
            @param trace The error causing this exception.
        */
        publ.init = function(trace){
            supr(this).init( "Sending of HTTP request failed.", trace);
        }
    })
    
    /**
        Mimics the HTTPRequest object using Adobe's SVG Viewer's postURL and getURL.
        It can only process asyncronous connection and the only header that's supported is 'Content-Type'.
    */
    var ASVRequest=Class("ASVRequest", function(publ){
        /**
            Initializes the ASVRequest.
        */
        publ.init = function(){
            if((getURL==null) || (postURL==null)){
                throw "getURL and postURL are not available!";
            }else{
                this.readyState=0;
                this.responseText="";
                this.__contType ="";
                this.status=200;
            }
        }
        /**
            Mimics the open method without actually opening a connection.
            @param type          "GET" or "POST".
            @param url             The url to open.
            @param async=true True for async. connection. Otherwhise an exception is thrown.
        */
        publ.open=function(type,url,async){
            if (async == false){
                throw "Can only open asynchronous connections!";
            }
            this.__type = type;
            this.__url = url;
            this.readyState=0;
        }
        /**
            Sets a header.
            @param name  The header name. All but "Content-Type" are ignored.
            @param value  The value of the header.
        */
        publ.setRequestHeader=function(name, value){
            if (name=="Content-Type"){
                this.__contType =value;
            }
        }
        /**
            Sends the request.
            @param data   The data to send when doing a post.
        */
        publ.send=function(data){
            var self=this;
            var cbh=new Object();
            cbh.operationComplete = function(rsp){
                self.readyState=4;
                self.responseText=rsp.content;
                if(this.ignoreComplete == false){
                    if(self.onreadystatechange){
                        self.onreadystatechange();
                    }
                }
            }
            cbh.ignoreComplete = false;
            try{
                if(this.__type =="GET"){
                    getURL(this.__url,cbh);
                }else if (this.__type == "POST"){
                    postURL(this.__url, data, cbh, this.__contType);
                }
            }catch(e){
                cbh.ignoreComplete=true;
                throw e;
            }
        }
    })
    
    /**
        Creates an HTTP request object for retreiving files.
        @return  HTTP request object.
    */    
    var getHTTP=function() {
        var obj;
        try{ //to get the mozilla httprequest object
            obj = new XMLHttpRequest();
        }catch(e){
            try{ //to get MS HTTP request object
                obj=new ActiveXObject("Msxml2.XMLHTTP.4.0");
            }catch(e){
                try{ //to get MS HTTP request object
                    obj=new ActiveXObject("Msxml2.XMLHTTP")
                }catch(e){
                    try{// to get the old MS HTTP request object
                        obj = new ActiveXObject("microsoft.XMLHTTP"); 
                    }catch(e){
                        try{//to create the ASV request object.
                            obj = new ASVRequest();
                        }catch(e){
                            throw new mod.NoHTTPRequestObject("Neither Mozilla, IE nor ASV found. Can't do HTTP request without them.");
                        }
                    }
                }    
            }
        }
        return obj;
    }
    /**
        Sends a request to a server.
        To explain the way the optional arguments work I will give examples:
        simple:
            sendRequest("get", "url")
            sendRequest("post", "url", "data")
        
        with headers:
            sendRequest("get", "url", [["headername","value"]])
            sendRequest("post", "url", "data", [["headername","value"]])
        
        with user information:
            sendRequest("get", "url", "user", "pass")
            sendRequest("post", "url", "user", "pass", "data")
        
        with headers and user information:
            sendRequest("get", "url", "user", "pass", [["headername","value"]])
            sendRequest("post", "url", "user", "pass", "data", [["headername","value"]])
        
        To make the request asynchronous just add a callback function as the last argument to the calls above.
 
        @param type              Type of connection (GET, POST, ...).
        @param url                 The URL to retrieve.
        @param user=null        The username for auth.
        @param pass=null        The password. (must be set if user is set!)
        @param data=""          The data to send with the request.
        @param headers=[]      Array of headers. Each element in the array should be another array containing [headername,value].
        @param callback=null   Callback for asynchronous connections. The callback is called after completion and is passed the request object as 1st Parameter.
        @return                     HTTP request object.
    */
    mod.sendRequest=function(type, url, user, pass, data, headers, callback){
        var async=false;
        //check if the last argument is a function and treat it as callback;
        if(arguments[arguments.length-1]  instanceof Function){
            var async=true;
            callback = arguments[arguments.length-1];
        }
        //treat sencond last(if callback)/last(if no callback) argument as headers
        var headindex=arguments.length-((async || arguments[arguments.length-1] == null) ?2:1);
        //is it an array then it's headers
        if(arguments[headindex] instanceof Array){
            headers=arguments[headindex];
        }else{
            headers=[];
        }
        //are user AND password not specified then assume data as 3rd argument.
        if(typeof user == "string" && typeof pass == "string"){
            if(typeof data != "string"){
                data="";
            }
        }else if (typeof user == "string"){
            data = user;
            user=null;
            pass=null;
        }else{
            user=null;
            pass=null;
        }
        var xmlhttp= getHTTP();
        try{
            if(user!=null){
                xmlhttp.open(type, url, async, user, pass);
            }else{
                xmlhttp.open(type, url, async);
            }
        }catch(e){
            throw new mod.RequestOpenFailed(e);
        }
        //set headers
        for(var i=0;i< headers.length;i++){
            try{//opera 8b does not support setRequestHeader todo:
                xmlhttp.setRequestHeader(headers[i][0], headers[i][1]);    
            }catch(e){
            }
        }
        
        if(async){//set up a callback
            xmlhttp.onreadystatechange=function(){
                if (xmlhttp.readyState==4) {
                    callback(xmlhttp);
                    xmlhttp = null; //help IE with garbage collection
                }else if (xmlhttp.readyState==2){
                    //status property should be available (MS IXMLHTTPRequest documentation) 
                    //in Mozilla it is not if the request failed(server not reachable)
                    //in IE it is not available at all ?!
                    try{//see if it is mozilla otherwise don't care.
                        var isNetscape = netscape;
                        try{//if status is not available the request failed.
                            var s=xmlhttp.status;
                        }catch(e){//call the callback because Mozilla will not get to readystate 4
                            callback(xmlhttp);
                            xmlhttp = null;
                        }
                    }catch(e){
                        
                    }
                }
            }
        }
        
        try{
            xmlhttp.send(data);
        }catch(e){            
            if(async){
                callback(xmlhttp, e);
                xmlhttp=null;
            }else{
                throw new mod.SendFailed(e);
            }
        }
        return xmlhttp;
    }
    /**
        Shorthand for a GET request.
        It calls sendRequest with "GET" as first argument.
        See the sendRequest method for more information.
        @param url                 The URL to retrieve.
        @param user=null        The username for auth.
        @param pass=null        The password. (must be set if user is set!)
        @param headers=[]      Array of headers. Each element in the array should be another array containing [headername,value].
        @param callback=null   Callback for asynchronous connections. The callback is called after completion and is passed the request object as 1st Parameter.
        @return                     HTTP request object.
    */
    mod.getURL=function(url, user, pass, headers, callback) { 
        var a=["GET"];
        for(var i=0;i<arguments.length;i++){
            a.push(arguments[i]);
        }
        return mod.sendRequest.apply(this,a)
    }
    /**
        Shorthand for a POST request.
        It calls sendRequest with "POST" as first argument.
        See the sendRequest method for more information.
        @param url                 The URL to retrieve.
        @param user=null        The username for auth.
        @param pass=null        The password. (must be set if user is set!)
        @param data=""          The data to send with the request.
        @param headers=[]      Array of headers. Each element in the array should be another array containing [headername,value].
        @param callback=null   Callback for asynchronous connections. The callback is called after completion and is passed the request object as 1st Parameter.
        @return                     HTTP request object.
    */
    mod.postURL=function(url, user, pass, data, headers, callback) { 
        var a= ["POST"];
        for(var i=0;i<arguments.length;i++){
            a.push(arguments[i]);
        }
        return mod.sendRequest.apply(this,a)
    }
})

/*
  Copyright (c) 2003-2005 Jan-Klaas Kollhof
  
  This file is part of the JavaScript o lait library(jsolait).
  
  jsolait is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.
 
  This software is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.
 
  You should have received a copy of the GNU Lesser General Public License
  along with this software; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


/**
    Provides parseXML and an importNode implementation.
    @creator                Jan-Klaas Kollhof
    @created                2003-07-10
    @lastchangedby       $LastChangedBy: Jan-Klaas Kollhof $
    @lastchangeddate    $Date: 2005-09-16 01:06:59 +0100 (Fri, 16 Sep 2005) $
*/
Module("xml","$Revision: 56 $", function(mod){
    mod.XMLNS="http://www.w3.org/2000/xmlns/";
    mod.NSXML ="http://www.w3.org/XML/1998/namespace";
    mod.nsPrefixMap={"http://www.w3.org/2000/xmlns/" : "xmlns","http://www.w3.org/XML/1998/namespace":"xml"};
    
    /**
        Thrown if no parser could be instanciated.
    */
    mod.NoXMLParser=Class("NoXMLParser", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param trace  The error causing the Exception.
        */
        publ.init=function(trace){
            supr(this).init("Could not create an XML parser.", trace);
        }
    })
    /**
        Thrown if a document could not be parsed.
    */
    mod.ParsingFailed=Class("ParsingFailed", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param xml    The xml source which could not be parsed.
            @param trace The error causing this Exception.
        */
        publ.init=function(xml,trace){
             supr(this).init("Failed parsing XML document.",trace);
            this.xml = xml;
        }
        ///The xml source which could not be parsed.
        publ.xml;
    })
    /**
        Parses an xml document.
        @param xml     The xml text.
        @return          A DOM of the xml document.
    */
    mod.parseXML=function(xml){
        var obj=null;
        var isMoz=false;
        var isIE=false;
        var isASV=false;
        
        try{//to get Adobe's SVG parseXML
            var p=window.parseXML;
            if(p==null){
                throw "No ASV paseXML";
            }
            isASV=true;
        }catch(e){
            try{//to get the mozilla parser
                obj = new DOMParser();
                isMoz=true;
            }catch(e){
                try{//to get the MS XML parser
                    obj = new ActiveXObject("Msxml2.DomDocument.4.0"); 
                    isIE=true;
                }catch(e){
                    try{//to get the MS XML parser
                        obj = new ActiveXObject("Msxml2.DomDocument"); 
                        isIE=true;
                    }catch(e){
                        try{//to get the old MS XML parser
                            obj = new ActiveXObject("microsoft.XMLDOM"); 
                            isIE=true;
                        }catch(e){
                            throw new mod.NoXMLParser(e);
                        }
                    }
                }
            }
        }
        try{
            if(isMoz){
                obj = obj.parseFromString(xml, "text/xml");
                return obj;
            }else if(isIE){
                obj.loadXML(xml);
                return obj;
            }else if(isASV){
                return window.parseXML(xml, null);
            }
        }catch(e){
            throw new mod.ParsingFailed(xml,e);
        }
    }
    /**
        DOM2 implimentation of document.importNode().
        This will import into the current document. In SVG it will create SVG nodes in HTML it will create HTML nodes....
        This might become customizable in the future.
        @param importedNode   The node to import.
        @param deep=true        Import all childNodes recursively.
        @return                      The imported Node.
    */
    mod.importNode=function(importedNode, deep){
        deep = (deep==null) ? true : deep;
        //constants from doom2
        var ELEMENT_NODE = 1;
        var ATTRIBUTE_NODE = 2;
        var TEXT_NODE = 3;
        var CDATA_SECTION_NODE = 4;
        var ENTITY_REFERENCE_NODE = 5;
        var ENTITY_NODE = 6;
        var PROCESSING_INSTRUCTION_NODE = 7;
        var COMMENT_NODE = 8;
        var DOCUMENT_NODE = 9;
        var DOCUMENT_TYPE_NODE = 10;
        var DOCUMENT_FRAGMENT_NODE = 11;
        var NOTATION_NODE = 12;
        var importChildren=function(srcNode, parent){
            if(deep){
                 for(var i=0; i<srcNode.childNodes.length; i++){
                    var n=mod.importNode(srcNode.childNodes.item(i), true);
                    parent.appendChild(n);
                }
            }
        }
        var node=null;
        switch(importedNode.nodeType){
            case ATTRIBUTE_NODE:
                node=document.createAttributeNS(importedNode.namespaceURI, importedNode.nodeName);
                node.value=importedNode.value;
                break;
            case DOCUMENT_FRAGMENT_NODE:
                node=document.createDocumentFragment();
                importChildren(importedNode,node);
                break;
            case ELEMENT_NODE:
                node=document.createElementNS(importedNode.namespaceURI, importedNode.tagName);
                //import all attributes
                for(var i=0; i<importedNode.attributes.length; i++){
                    var attr=this.importNode(importedNode.attributes.item(i), deep);
                    node.setAttributeNodeNS(attr);
                }
                importChildren(importedNode,node);
                break;
            case ENTITY_REFERENCE_NODE:
                node=importedNode;
                break;
            case PROCESSING_INSTRUCTION_NODE:
                node=document.createProcessingInstruction(importedNode.target, importedNode.data);
                break;
            case TEXT_NODE:
            case CDATA_SECTION_NODE:
            case COMMENT_NODE:
                node=document.createTextNode(importedNode.nodeValue);
                break;
            case DOCUMENT_NODE:
                //Document nodes cannot be imported.
            case DOCUMENT_TYPE_NODE:
                //DocumentType nodes cannot be imported.
            case NOTATION_NODE:
                //readonly in DOM2
            case ENTITY_NODE:
                //readonly in DOM2
                throw "not supported in DOM2";
                break;
        }
        return node;
    }
    
    var getNSPrefix = function(node, namespaceURI, nsPrefixMap){
        if(! namespaceURI){
            return "";
        }else if(mod.nsPrefixMap[namespaceURI]){
            return mod.nsPrefixMap[namespaceURI] + ":";
        }else if(nsPrefixMap[namespaceURI] != null){
            return nsPrefixMap[namespaceURI] + ":";
        }
        if(node.nodeType == 1){
            //check in the attributes of the node if the NS is defined.
            for(var i=0;i<node.attributes.length;i++){
                var attr =node.attributes.item(i);
                if(attr.namespaceURI == mod.XMLNS && attr.value == namespaceURI){
                    return attr.localName +":";
                }
            }
        }else{
            throw new Error("Cannot find a namespace prefix for " + namespaceURI);
        }
        
        if(node.parentNode){//try the parent node
            return getNSPrefix(node.parentNode, namespaceURI, nsPrefixMap);    
        }else{
            //todo:
            throw new Error("Cannot find a namespace prefix for " + namespaceURI);
        }
    }
    
    /**
        Turns an XML document into a String.
        @param node   The node to print.
        @return           A string containing the text for the XML.
    */
    mod.node2XML = function(node, nsPrefixMap, attrParent){
        nsPrefixMap = (nsPrefixMap == null)?{}:nsPrefixMap;
        var ELEMENT_NODE = 1;
        var ATTRIBUTE_NODE = 2;
        var TEXT_NODE = 3;
        var CDATA_SECTION_NODE = 4;
        var ENTITY_REFERENCE_NODE = 5;
        var ENTITY_NODE = 6;
        var PROCESSING_INSTRUCTION_NODE = 7;
        var COMMENT_NODE = 8;
        var DOCUMENT_NODE = 9;
        var DOCUMENT_TYPE_NODE = 10;
        var DOCUMENT_FRAGMENT_NODE = 11;
        var NOTATION_NODE = 12;
        var s="";
        switch(node.nodeType){
            case ATTRIBUTE_NODE:
                try{
                    var nsprefix=getNSPrefix(attrParent, node.namespaceURI, nsPrefixMap);
                }catch(e){
                    alert(node.namespaceURI + "\n" + e.message);
                }
                if(nsprefix + node.localName == "xmlns:xmlns"){
                    nsprefix="";
                }                
                s+=nsprefix + node.localName+'="' + node.value + '"';
                break;
            case DOCUMENT_NODE:
                s+=this.node2XML(node.documentElement, nsPrefixMap);
                break;
            case ELEMENT_NODE:
                s+="<" + node.tagName;
                //attributes
                for(var i=0; i<node.attributes.length; i++){
                    s+=" " + this.node2XML(node.attributes.item(i), nsPrefixMap, node);
                }
                //children
                if(node.childNodes.length==0){
                    s+="/>\n";
                }else{
                    s+=">";
                    for(var child=node.firstChild; child != null; child=child.nextSibling){
                        s+=this.node2XML(child, nsPrefixMap);
                    }
                        s+="</" + node.tagName+ ">\n";
                }
                break;
            case PROCESSING_INSTRUCTION_NODE:
                s+="<?" + node.target + " " + node.data + " ?>";
                break;
            case TEXT_NODE:
                s+=node.nodeValue;
                break;
            case CDATA_SECTION_NODE:
                s+="<" +"![CDATA[" + node.nodeValue + "]" + "]>";
                break;
            case COMMENT_NODE:
                s+="<!--" + node.nodeValue + "-->";
                break;
            case ENTITY_REFERENCE_NODE:
            case DOCUMENT_FRAGMENT_NODE:
            case DOCUMENT_TYPE_NODE:
            case NOTATION_NODE:
            case ENTITY_NODE:
                throw new mod.Exception("Nodetype(%s) not supported.".format(node.nodeType));
                break;
        }
        return s;
    }
})
/*
  Copyright (c) 2003-2005 Jan-Klaas Kollhof
  
  This file is part of the JavaScript o lait library(jsolait).
  
  jsolait is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.
 
  This software is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.
 
  You should have received a copy of the GNU Lesser General Public License
  along with this software; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
    Provides an XML-RPC imlementation.
    It is similar to python's xmlrpclib module.
    @creator                 Jan-Klaas Kollhof
    @created                2003-07-10
    @lastchangedby       $LastChangedBy: Jan-Klaas Kollhof $
    @lastchangeddate    $Date: 2005-09-14 22:45:10 +0100 (Wed, 14 Sep 2005) $
*/
Module("xmlrpc","$Revision: 52 $", function(mod){
    var xmlext = imprt("xml");
    var urllib = imprt("urllib");
    /**
        Thrown if a  server did not respond with response status 200 (OK).
    */
    mod.InvalidServerResponse = Class("InvalidServerResponse", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param status       The status returned by the server.
        */
        publ.init= function(status){
            supr(this).init("The server did not respond with a status 200 (OK) but with: " + status);
            this.status = status;
        }
         ///The status returned by the server.
        publ.status;
    })
    
    /**
        Thrown if an XML-RPC response is not well formed.
    */
    mod.MalformedXmlRpc = Class("MalformedXmlRpc", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param msg          The error message of the user.
            @param xml           The xml document's source.
            @param trace=null  The error causing this Exception
        */
        publ.init= function(msg, xml, trace){
            supr(this).init(msg,trace);
            this.xml = xml;
        }
         ///The xml source which was mal formed.
        publ.xml;
    })
    /**
        Thrown if the RPC response is a Fault.        
    */
    mod.Fault = Class("Fault", mod.Exception, function(publ, supr){
        /**
            Initializes the Exception.
            @param faultCode       The fault code returned by the rpc call.
            @param faultString      The fault string returned by the rpc call.
        */
        publ.init= function(faultCode, faultString){
            supr(this).init("XML-RPC Fault: " +  faultCode + "\n\n" + faultString);
            this.faultCode = faultCode;
            this.faultString = faultString;
        }
        ///The fault code returned from the rpc call.
        publ.faultCode;
        ///The fault string returned from the rpc call.
        publ.faultString;
    })

    /**
        Marshalls an object to XML-RPC.(Converts an object into XML-RPC conforming xml.)
        It just calls the toXmlRpc function of the objcect.
        So, to customize serialization of objects one just needs to specify/override the toXmlRpc method 
        which should return an xml string conforming with XML-RPC spec.
        @param obj    The object to marshall
        @return         An xml representation of the object.
    */
    mod.marshall = function(obj){
        if(obj.toXmlRpc!=null){
            return obj.toXmlRpc();
        }else{
            var s = "<struct>";
            for(var attr in obj){
                if(typeof obj[attr] != "function"){
                    s += "<member><name>" + attr + "</name><value>" + mod.marshall(obj[attr]) + "</value></member>";
                }
            }
            s += "</struct>";
            return s;
        }
    }
    
    /**
        Unmarshalls an XML document to a JavaScript object. (Converts xml to JavaScript object.)
        It parses the xml source and creates a JavaScript object.
        @param xml    The xml document source to unmarshall.
        @return         The JavaScript object created from the XML.
    */
    mod.unmarshall = function(xml){
        try {//try to parse xml ... this will throw an Exception if failed
            var doc = xmlext.parseXML(xml);
        }catch(e){
            throw new mod.MalformedXmlRpc("The server's response could not be parsed.", xml, e);
        }
        var rslt = mod.unmarshallDoc(doc, xml);
        doc=null;
        return rslt;
    }
    
    /**
        Unmarshalls an XML document to a JavaScript object like unmarshall but expects a DOM document as parameter.
        It parses the xml source and creates a JavaScript object.
        @param doc   The xml document(DOM compatible) to unmarshall.
        @return         The JavaScript object created from the XML.
    */
    mod.unmarshallDoc = function(doc, xml){
        try{
            var node = doc.documentElement;
            if(node==null){//just in case parse xml didn't throw an Exception but returned nothing usefull.
                throw new mod.MalformedXmlRpc("No documentElement found.", xml);
            }
            switch(node.tagName){
                case "methodResponse":
                    return parseMethodResponse(node);
                case "methodCall":
                    return parseMethodCall(node);
                default://nothing usefull returned by parseXML.
                    throw new mod.MalformedXmlRpc("'methodCall' or 'methodResponse' element expected.\nFound: '" + node.tagName + "'", xml);
            }
        }catch(e){
            if(e instanceof mod.Fault){//just rethrow the fault.
                throw e;
            }else {
                throw new mod.MalformedXmlRpc("Unmarshalling of XML failed.", xml, e);    
            }
        }
    }
    
    /**
        Parses a methodeResponse element.
        @param node  The methodResponse element.
        @return          The return value of the XML-RPC.
    */
    var parseMethodResponse=function(node){
        try{
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "fault": //a fault is thrown as an Exception
                            throw parseFault(child);
                        case "params":
                            var params = parseParams(child);
                            if(params.length == 1){//params should only have one param
                                return params[0];
                            }else{
                                throw new mod.MalformedXmlRpc("'params' element inside 'methodResponse' must have exactly ONE 'param' child element.\nFound: " + params.length);
                            }
                        default:
                            throw new mod.MalformedXmlRpc("'fault' or 'params' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            //no child elements found
            throw new mod.MalformedXmlRpc("No child elements found.");    
        }catch(e){
            if(e instanceof mod.Fault){
                throw e;
            }else{
                throw new mod.MalformedXmlRpc("'methodResponse' element could not be parsed.",null,e);    
            }
        }
    }
    /**
        Parses a methodCall element.
        @param node  The methodCall element.
        @return          Array [methodName,params]. 
    */        
    var parseMethodCall = function(node){
        try{
            var methodName = null;
            var params = new Array();//default is no parameters
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "methodName":
                            methodName = new String(child.firstChild.nodeValue);
                            break;
                        case "params":
                            params = parseParams(child);
                            break;
                        default:
                            throw new mod.MalformedXmlRpc("'methodName' or 'params' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            if(methodName==null){
                throw new mod.MalformedXmlRpc("'methodName' element expected.");
            }else{
                return new Array(methodName, params);
            }
        }catch(e){
            throw new mod.MalformedXmlRpc("'methodCall' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a params element.
        @param node  The params element.
        @return          Array of params values. 
    */
    var parseParams = function(node){
        try{
            var params=new Array();
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "param":
                            params.push(parseParam(child));
                            break;
                        default:
                            throw new mod.MalformedXmlRpc("'param' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            //the specs say a 'params' element can contain any number of 'param' elements. That includes 0 ?!
            return params;
        }catch(e){
            throw new mod.MalformedXmlRpc("'params' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a param element.
        @param node  The param node.
        @return          The value of the param.
    */
    var parseParam = function(node){
        try{
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "value":
                            return parseValue(child);
                        default:
                            throw new mod.MalformedXmlRpc("'value' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            //no child elements found, that's an error
            throw new mod.MalformedXmlRpc("'value' element expected.But none found.");
        }catch(e){
            throw new mod.MalformedXmlRpc("'param' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a value element.
        @param node  The value element.
        @return         The value.
    */
    var parseValue = function(node){
        try{
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "string":
                            var s="" 
                            //Mozilla has many textnodes with a size of 4096 chars each instead of one large one.
                            //They all need to be concatenated.
                            for(var j=0;j<child.childNodes.length;j++){
                                s+=new String(child.childNodes.item(j).nodeValue);
                            }
                            return s;
                        case "int":
                        case "i4":
                        case "double":
                            return (child.firstChild) ? Number(child.firstChild.nodeValue) : 0;
                        case "boolean":
                            return Boolean(isNaN(parseInt(child.firstChild.nodeValue)) ? (child.firstChild.nodeValue == "true") : parseInt(child.firstChild.nodeValue));
                        case "base64":
                            return parseBase64(child);
                        case "dateTime.iso8601":
                            return parseDateTime(child);
                        case "array":
                            return parseArray(child);
                        case "struct":
                            return parseStruct(child);
                        case "nil": //for python None todo: ??? is this valid XML-RPC
                            return null;
                        default:
                            throw new mod.MalformedXmlRpc("'string','int','i4','double','boolean','base64','dateTime.iso8601','array' or 'struct' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            if(node.firstChild){
                var s="" 
                //Mozilla has many textnodes with a size of 4096 chars each instead of one large one.
                //They all need to be concatenated.
                for(var j=0;j<node.childNodes.length;j++){
                    s+=new String(node.childNodes.item(j).nodeValue);
                }
                return s;
            }else{
                return "";
            }
        }catch(e){
            throw new mod.MalformedXmlRpc("'value' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a base64 element.
        @param node   The base64 element.
        @return          A string with the decoded base64.
    */
    var parseBase64=function(node){
        try{
            var s = node.firstChild.nodeValue;
            return s.decode("base64");
        }catch(e){
            throw new mod.MalformedXmlRpc("'base64' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a dateTime.iso8601 element.
        @param node   The dateTime.iso8601 element.
        @return           A JavaScript date.
    */
    var parseDateTime=function(node){
        try{
            if(/^(\d{4})-?(\d{2})-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})/.test(node.firstChild.nodeValue)){
                return new Date(Date.UTC(RegExp.$1, RegExp.$2-1, RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6));
            }else{ //todo error message
                throw new mod.MalformedXmlRpc("Could not convert the given date.");
            }
        }catch(e){
            throw new mod.MalformedXmlRpc("'dateTime.iso8601' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses an array element.
        @param node   The array element.
        @return           An Array.
    */
    var parseArray=function(node){
        try{
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "data":
                            return parseData(child);
                        default:
                            throw new mod.MalformedXmlRpc("'data' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            throw new mod.MalformedXmlRpc("'data' element expected. But not found.");   
        }catch(e){
            throw new mod.MalformedXmlRpc("'array' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a data element.
        @param node   The data element.
        @return           The value of a data element.
    */
    var parseData=function(node){
        try{
            var rslt = new Array();
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "value":
                            rslt.push(parseValue(child));
                            break;
                        default:
                            throw new mod.MalformedXmlRpc("'value' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }            
            }
            return rslt;
        }catch(e){
            throw new mod.MalformedXmlRpc("'data' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a struct element.
        @param node   The struct element.
        @return           A JavaScript object. Struct memembers are properties of the object.
    */
    var parseStruct=function(node){
        try{
            var struct = new Object();
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "member":
                            var member = parseMember(child); //returns [name, value]
                            if(member[0] != ""){
                                struct[member[0]] = member[1];
                            }
                            break;
                        default:
                            throw new mod.MalformedXmlRpc("'data' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            return struct;
        }catch(e){
            throw new mod.MalformedXmlRpc("'struct' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a member element.
        @param node  The member element.
        @return          Array containing [memberName, value].
    */
    var parseMember=function(node){
        try{
            var name="";
            var value=null;
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "value":
                            value = parseValue(child); 
                            break;
                        case "name":
                            if(child.hasChildNodes()){
                                name = new String(child.firstChild.nodeValue);
                            }
                            break;
                        default:
                            throw new mod.MalformedXmlRpc("'value' or 'name' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            /*if(name == ""){
                throw new mod.MalformedXmlRpc("Name for member not found/convertable.");
            }else{
                return new Array(name, value);
            }*/
            return [name, value];
        }catch(e){
            throw new mod.MalformedXmlRpc("'member' element could not be parsed.",null,e);    
        }
    }
    /**
        Parses a fault element.
        @param node  The fault element.
        @return          A Fault Exception object.
    */
    var parseFault = function(node){
        try{
            for(var i=0;i<node.childNodes.length;i++){
                var child = node.childNodes.item(i);
                if (child.nodeType == 1){
                    switch (child.tagName){
                        case "value":
                            var flt = parseValue(child); 
                            return new mod.Fault(flt.faultCode, flt.faultString);
                        default:
                            throw new mod.MalformedXmlRpc("'value' element expected.\nFound: '" + child.tagName + "'");                        
                    }
                }
            }
            throw new mod.MalformedXmlRpc("'value' element expected. But not found.");                        
        }catch(e){
            throw new mod.MalformedXmlRpc("'fault' element could not be parsed.",null,e);    
        }
    }

    
    /**
        Class for creating XML-RPC methods.
        Calling the created method will result in an XML-RPC call to the service.
        The return value of this call will be the return value of the RPC call.
        RPC-Faults will be raised as Exceptions.
        
        Asynchronous operation:
        If the last parameter passed to the method is an XMLRPCAsyncCallback object, 
        then the remote method will be called asynchronously. 
        The results and errors are passed to the callback.
    */
    mod.XMLRPCMethod =Class(Function, function(publ){
        var postData = function(url, user, pass, data, callback){
            if(callback == null){
                var rslt = urllib.postURL(url, user, pass, data, [["Content-Type", "text/xml"]]);
                return rslt;
            }else{
                return urllib.postURL(url, user, pass, data, [["Content-Type", "text/xml"]], callback);
            }
        }
        
        var handleResponse=function(resp){
            var status=null;
            try{//see if the server responded with a response code 200 OK.
                status = resp.status;
            }catch(e){
            }
            if(status == 200){
                var respDoc=null;
                try{
                    respDoc = resp.responseXML;
                }catch(e){
                }
                var respTxt = ""; 
                try{                 
                    respTxt=resp.responseText;
                }catch(e){
                }
                if(respDoc == null){
                    if(respTxt == null || respTxt == ""){
                        throw new mod.MalformedXmlRpc("The server responded with an empty document.", "");
                    }else{
                        return mod.unmarshall(respTxt);
                    }
                }else{ //use the respDoc directly so the xml does not have to be parsed.
                    return mod.unmarshallDoc(respDoc, respTxt);
                }
            }else{
                throw new mod.InvalidServerResponse(status);
            }
        }
        
        var getXML = function(methodName, args){
            var data='<?xml version="1.0"?><methodCall><methodName>' + methodName + '</methodName>';
            if (args.length>0){
                data += "<params>";
                for(var i=0;i<args.length;i++){
                    data += '<param><value>' + mod.marshall(args[i]) + '</value></param>';
                }
                data += '</params>';
            }
            data += '</methodCall>';
            return data;
        }
        
        
        /**
            Initializes the XML-RPC method.
            @param url                 The URL of the service providing the method.
            @param methodName   The name of the method to invoke.
            @param user=null             The user name to use for HTTP authentication.
            @param pass=null             The password to use for HTTP authentication.
        */
        publ.init = function(url, methodName, user, pass){
            //make sure the function has the same property as an object created from this class.
            this.methodName = methodName;
            this.url = url;
            this.user = user;
            this.password=pass;
        }
        
        
        publ.__call__=function(){
            //sync or async call
            if(!(arguments[arguments.length-1] instanceof Function)){
                var data=getXML(this.methodName,arguments);
                var resp = postData(this.url, this.user, this.password, data);
                return handleResponse(resp);
            }else{
                var args=new Array();
                for(var i=0;i<arguments.length;i++){
                    args.push(arguments[i]);
                }
                var cb = args.pop();
                var data=getXML(this.methodName, args);
                return postData(this.url, this.user, this.password, data, function(resp){
                    var rslt = null;
                    var exc =null;
                    try{
                        rslt = handleResponse(resp);
                    }catch(e){
                        exc = e;
                    }
                    try{//call the callback for the async call.
                        cb(rslt,exc);
                    }catch(e){
                    }
                    args = null;
                    resp = null;
                });
            }
        }
                
        /**
            Returns the method representation for system.multicall.
            @param   All params will be passed to the remote method.
            @return   An object containing a member methodName and a member params(As required by system.multicall).
        */
        publ.toMulticall = function(){
            var multiCallable = new Object();
            multiCallable.methodName = this.methodName;
            var params = [];
            for(var i=0;i<arguments.length;i++){
                params[i] = arguments[i];
            }
            multiCallable.params = params;
            return multiCallable;
        }
        /**
            Sets username and password for HTTP Authentication.
            @param user    The user name.
            @param pass    The password.
        */
        publ.setAuthentication = function(user, pass){
            this.user = user;
            this.password = pass;
        }
        ///The name of the remote method.
        publ.methodName;
        ///The url of the remote service containing the method.
        publ.url;
        ///The user name used for HTTP authorization.
        publ.user;
        ///The password used for HTTP authorization.
        publ.password;
    })
    
    /**
        Creates proxy objects which resemble the remote service.
        Method calls of this proxy will result in calls to the service.
    */
    mod.ServiceProxy=Class("ServiceProxy", function(publ){
        /**
            Initializes a new ServerProxy.
            The arguments are interpreted as shown in the examples:
            ServerProxy("url")
            ServerProxy("url", ["methodName1",...])
            ServerProxy("url", ["methodName1",...], "user", "pass")
            ServerProxy("url", "user", "pass")
            
            @param url                     The url of the service.
            @param methodNames=[]  Array of names of methods that can be called on the server.
                                                If no methods are given then introspection is used to get the methodnames from the server.
            @param user=null             The user name to use for HTTP authentication.
            @param pass=null             The password to use for HTTP authentication.
        */
        publ.init = function(url, methodNames, user, pass){
            if(methodNames instanceof Array){
                if(methodNames.length > 0){
                    var tryIntrospection=false;
                }else{
                    var tryIntrospection=true;
                }
            }else{
                pass=user;
                user=methodNames;
                methodNames=[];
                var tryIntrospection=true;
            }
            this._url = url;
            this._user = user;
            this._password = pass;
            this._addMethodNames(methodNames);
            if(tryIntrospection){
                try{//it's ok if it fails.
                    this._introspect();
                }catch(e){
                }
            }
        }
        
        /**
            Adds new XMLRPCMethods to the proxy server which can then be invoked.
            @param methodNames   Array of names of methods that can be called on the server.
        */
        publ._addMethodNames = function(methodNames){
            for(var i=0;i<methodNames.length;i++){
                var obj = this;
                //setup obj.childobj...method
                var names = methodNames[i].split(".");
                for(var n=0;n<names.length-1;n++){
                    var name = names[n];
                    if(obj[name]){
                        obj = obj[name];
                    }else{
                        obj[name]  = new Object();
                        obj = obj[name];
                    }
                }
                var name = names[names.length-1];
                if(obj[name]){
                }else{
                    var mth = new mod.XMLRPCMethod(this._url, methodNames[i], this._user, this._password);
                    obj[name] = mth;
                    this._methods.push(mth);
                }
            }
        }
        
        /**
            Sets username and password for HTTP Authentication for all methods of this service.
            @param user    The user name.
            @param pass    The password.
        */
        publ._setAuthentication = function(user, pass){
            this._user = user;
            this._password = pass;
            for(var i=0;i<this._methods.length;i++){
                this._methods[i].setAuthentication(user, pass);
            }
        }
        
        /**
            Initiate XML-RPC introspection to retrieve methodnames from the server
            and add them to the server proxy.
        */
        publ._introspect = function(){
            this._addMethodNames(["system.listMethods","system.methodHelp", "system.methodSignature"]);
            var m = this.system.listMethods();
            this._addMethodNames(m);
        }
        ///The url of the service to resemble.
        publ._url;
        ///The user used for HTTP authentication.
        publ._user;
        ///The password used for HTTP authentication.
        publ._password;
        ///All methods.
        publ._methods=new Array();
    })
    
    ///@deprecated  Use ServiceProxy instead.
    mod.ServerProxy= mod.ServiceProxy;
    
    /**
        XML-RPC representation of a string.
        All '&' and '<' are replaced with the '&amp;'  and  '&lt'.
        @return  A string containing the String's representation in XML.
    */
    String.prototype.toXmlRpc = function(){
        return "<string>" + this.replace(/&/g, "&amp;").replace(/</g, "&lt;") + "</string>";
    }
    /**
        XML-RPC representation of a number.
        @return A string containing the Number's representation in XML.
    */
    Number.prototype.toXmlRpc = function(){
        if(this == parseInt(this)){
            return "<int>" + this + "</int>";
        }else if(this == parseFloat(this)){
            return "<double>" + this + "</double>";
        }else{
            return false.toXmlRpc();
        }
    }
    /**
        XML-RPC representation of a boolean.
        @return A string containing the Boolean's representation in XML.
    */
    Boolean.prototype.toXmlRpc = function(){
        if(this == true) {
            return "<boolean>1</boolean>";
        }else{
            return "<boolean>0</boolean>";
        }
    }
    /**
        XML-RPC representation of a date(iso 8601).
        @return A string containing the Date's representation in XML.
    */
    Date.prototype.toXmlRpc = function(){
        var padd=function(s, p){
            s=p+s
            return s.substring(s.length - p.length)
        }
        var y = padd(this.getUTCFullYear(), "0000");
        var m = padd(this.getUTCMonth() + 1, "00");
        var d = padd(this.getUTCDate(), "00");
        var h = padd(this.getUTCHours(), "00");
        var min = padd(this.getUTCMinutes(), "00");
        var s = padd(this.getUTCSeconds(), "00");
        
        var isodate = y +  m  + d + "T" + h +  ":" + min + ":" + s
    
        return "<dateTime.iso8601>" + isodate + "</dateTime.iso8601>";
    }
    /**
        XML-RPC representation of an array.
        Each entry in the array is a value in the XML-RPC.
        @return A string containing the Array's representation in XML.
    */
    Array.prototype.toXmlRpc = function(){
        var retstr = "<array><data>";
        for(var i=0;i<this.length;i++){
            retstr += "<value>" + mod.marshall(this[i]) + "</value>";
        }
        return retstr + "</data></array>";
    }


    mod.test = function(){
        var s = new mod.ServiceProxy("http://jsolait.net/test.py",['echo']);
        print("creating ServiceProxy object using introspection for method construction...\n");
        print("%s created\n".format(s));
        print("creating and marshalling test data:\n");
        var o = [1.234, 5, {a:"Hello & < ", b:new Date()}];
        print(mod.marshall(o));
        print("\ncalling echo() on remote service...\n");
        var r = s.echo(o);
        print("service returned data(marshalled again):\n")
        print(mod.marshall(r));
    }
})





jsolait_done_loading = 1;
