
/*FILEHEAD(/var/lib/jpf/src/jpack_begin.js)SIZE(-1077090856)TIME(1224578767)*/



/*FILEHEAD(/var/lib/jpf/src/jpf.js)SIZE(-1077090856)TIME(1238933683)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

/**
 * Javeline Platform
 *
 * @author    Ruben Daniels ruben@javeline.com
 * @version   2.0
 * @url       http://www.ajax.org
 *
 * @event domready      Fires when the browsers' dom is ready to be manipulated.
 * @event movefocus         Fires when the focus moves from one element to another.
 *   object:
 *   {JMLElement} toElement the element that will receive the focus.
 * @event exit              Fires when the application wants to exit.
 *   cancellable:  Prevents the application from exiting. The returnValue of the
 *   event object is displayed in a popup which asks the user for permission.
 * @event keyup         Fires when the user stops pressing a key.
 *   cancellable: Prevents the behaviour.
 *   object:
 *   {Number}  keyCode   the char code of the pressed key.
 *   {Boolean} ctrlKey   whether the ctrl key was pressed.
 *   {Boolean} shiftKey  whether the shift key was pressed.
 *   {Boolean} altKey    whether the alt key was pressed.
 *   {Object}  htmlEvent the html event object.
 * @event mousescroll   Fires when the user scrolls the mouse
 *   cancellable: Prevents the container to scroll
 *   object:
 *   {Number} delta the scroll impulse.
 * @event hotkey        Fires when the user presses a hotkey
 *   bubbles: yes
 *   cancellable: Prevents the default hotkey behaviour.
 *   object:
 *   {Number}  keyCode   the char code of the pressed key.
 *   {Boolean} ctrlKey   whether the ctrl key was pressed.
 *   {Boolean} shiftKey  whether the shift key was pressed.
 *   {Boolean} altKey    whether the alt key was pressed.
 *   {Object}  htmlEvent the html event object.
 * @event keydown       Fires when the user presses a key
 *   bubbles: yes
 *   cancellable: Prevents the behaviour.
 *   object:
 *   {Number}  keyCode   the char code of the pressed key.
 *   {Boolean} ctrlKey   whether the ctrl key was pressed.
 *   {Boolean} shiftKey  whether the shift key was pressed.
 *   {Boolean} altKey    whether the alt key was pressed.
 *   {Object}  htmlEvent the html event object.
 * @event mousedown     Fires when the user presses a mouse button
 *   object:
 *   {Event}      htmlEvent the char code of the pressed key.
 *   {JMLElement} jmlNode   the element on which is clicked.
 * @event onbeforeprint Fires before the application will print.
 * @event onafterprint  Fires after the application has printed.
 * @event load          Fires after the application is loaded.
 * @event error         Fires when a communication error has occured while making a request for this element.
 *   cancellable: Prevents the error from being thrown.
 *   bubbles:
 *   object:
 *   {Error}          error     the error object that is thrown when the event callback doesn't return false.
 *   {Number}         state     the state of the call
 *     Possible values:
 *     jpf.SUCCESS  the request was successfull
 *     jpf.TIMEOUT  the request has timed out.
 *     jpf.ERROR    an error has occurred while making the request.
 *     jpf.OFFLINE  the request was made while the application was offline.
 *   {mixed}          userdata  data that the caller wanted to be available in the callback of the http request.
 *   {XMLHttpRequest} http      the object that executed the actual http request.
 *   {String}         url       the url that was requested.
 *   {Http}           tpModule  the teleport module that is making the request.
 *   {Number}         id        the id of the request.
 *   {String}         message   the error message.
 */
var jpf = {
VERSION:'1.0rc1',
    // Content Distribution Network URL:
    CDN            : "",

    READY          : false,

    //JML nodeFunc constants
    NODE_HIDDEN    : 101,
    NODE_VISIBLE   : 102,
    NODE_MEDIAFLOW : 103,

    //DOM nodeType constants
    NODE_ELEMENT                : 1,
    NODE_ATTRIBUTE              : 2,
    NODE_TEXT                   : 3,
    NODE_CDATA_SECTION          : 4,
    NODE_ENTITY_REFERENCE       : 5,
    NODE_ENTITY                 : 6,
    NODE_PROCESSING_INSTRUCTION : 7,
    NODE_COMMENT                : 8,
    NODE_DOCUMENT               : 9,
    NODE_DOCUMENT_TYPE          : 10,
    NODE_DOCUMENT_FRAGMENT      : 11,
    NODE_NOTATION               : 12,

    KEYBOARD       : 2,
    KEYBOARD_MOUSE : true,

    SUCCESS : 1,
    TIMEOUT : 2,
    ERROR   : 3,
    OFFLINE : 4,

    debug         : true,
    debugType     : "Memory",
    debugFilter   : "!teleport",

    includeStack  : [],
    initialized   : false,
    autoLoadSkin  : false,
    crypto        : {}, //namespace
    _GET          : {},
    basePath      : "./",

    /**
     * {Object} contains several known and often used namespace URI's.
         * @private
     */
    ns : {
        jpf    : "http://www.javeline.com/2005/jml",
        jml    : "http://www.javeline.com/2005/jml",
        xsd    : "http://www.w3.org/2001/XMLSchema",
        xhtml  : "http://www.w3.org/1999/xhtml",
        xslt   : "http://www.w3.org/1999/XSL/Transform",
        xforms : "http://www.w3.org/2002/xforms",
        ev     : "http://www.w3.org/2001/xml-events"
    },

    /**
     * @private
     */
    browserDetect : function(){
        if (this.$bdetect)
            return;
        this.$bdetect = true;

        var sAgent = navigator.userAgent.toLowerCase();

        //Browser Detection
        this.isOpera = sAgent.indexOf("opera") != -1;

        this.isKonqueror = sAgent.indexOf("konqueror") != -1;
        this.isSafari    = !this.isOpera && ((navigator.vendor
            && navigator.vendor.match(/Apple/) ? true : false)
            || sAgent.indexOf("safari") != -1 || this.isKonqueror);
        this.isSafariOld = false;

        if (this.isSafari) {
            var matches  = sAgent.match(/applewebkit\/(\d+)/);
            if (matches)
                this.isSafariOld = parseInt(matches[1]) < 420;
        }

        this.isChrome    = sAgent.indexOf("chrome/") != -1;
        this.isGecko     = !this.isOpera && !this.isSafari && sAgent.indexOf("gecko") != -1;
        this.isGecko3    = this.isGecko && sAgent.indexOf("firefox/3") != -1;
        
        var found;
        this.isIE        = document.all && !this.isOpera && !this.isSafari ? true : false;
        this.isIE8       = this.isIE && sAgent.indexOf("msie 8.") != -1 && (found = true);
        this.isIE7       = this.isIE && !found && sAgent.indexOf("msie 7.") != -1 && (found = true);
        this.isIE6       = this.isIE && !found && sAgent.indexOf("msie 6.") != -1 && (found = true);
        this.isIE55      = this.isIE && !found && sAgent.indexOf("msie 5.5") != -1 && (found = true);
        this.isIE50      = this.isIE && !found && sAgent.indexOf("msie 5.0") != -1 && (found = true);

        this.isWin       = sAgent.indexOf("win") != -1 || sAgent.indexOf("16bit") != -1;
        this.isMac       = sAgent.indexOf("mac") != -1;

        this.isAIR       = sAgent.indexOf("adobeair") != -1;

        try {
            //this.isDeskrun = window.external.shell.runtime == 2;
        }
        catch(e) {
            this.isDeskrun = false;
        }
    },

    /**
     * @private
     */
    setCompatFlags : function(){
        //Set Compatibility
        this.TAGNAME                   = jpf.isIE ? "baseName" : "localName";
        this.supportVML                = jpf.isIE;
        this.supportCanvas             = !jpf.isIE;
        this.supportSVG                = !jpf.isIE;
        this.styleSheetRules           = jpf.isIE ? "rules" : "cssRules";
        this.brokenHttpAbort           = jpf.isIE6;
        this.canUseHtmlAsXml           = jpf.isIE;
        this.supportNamespaces         = !jpf.isIE;
        this.cannotSizeIframe          = jpf.isIE;
        this.supportOverflowComponent  = jpf.isIE;
        this.hasEventSrcElement        = jpf.isIE;
        this.canHaveHtmlOverSelects    = !jpf.isIE6 && !jpf.isIE5;
        this.hasInnerText              = jpf.isIE;
        this.hasMsRangeObject          = jpf.isIE;
        this.hasContentEditable        = jpf.isIE || jpf.isOpera;
        this.descPropJs                = jpf.isIE;
        this.hasClickFastBug           = jpf.isIE;
        this.hasExecScript             = window.execScript ? true : false;
        this.canDisableKeyCodes        = jpf.isIE;
        this.hasTextNodeWhiteSpaceBug  = jpf.isIE;
        this.hasCssUpdateScrollbarBug  = jpf.isIE;
        this.canUseInnerHtmlWithTables = !jpf.isIE;
        this.hasSingleResizeEvent      = !jpf.isIE;
        this.hasStyleFilters           = jpf.isIE;
        this.supportOpacity            = !jpf.isIE;
        this.supportPng24              = !jpf.isIE6 && !jpf.isIE5;
        this.cantParseXmlDefinition    = jpf.isIE50;
        this.hasDynamicItemList        = !jpf.isIE || jpf.isIE7;
        this.canImportNode             = jpf.isIE;
        this.hasSingleRszEvent         = !jpf.isIE;
        this.hasXPathHtmlSupport       = !jpf.isIE;
        this.hasFocusBug               = jpf.isIE;
        this.hasReadyStateBug          = jpf.isIE50;
        this.dateSeparator             = jpf.isIE ? "-" : "/";
        this.canCreateStyleNode        = !jpf.isIE;
        this.supportFixedPosition      = !jpf.isIE || jpf.isIE7;
        this.hasHtmlIdsInJs            = jpf.isIE || jpf.isSafari;
        this.needsCssPx                = !jpf.isIE;
        this.hasAutocompleteXulBug     = jpf.isGecko;
        this.mouseEventBuffer          = jpf.isIE ? 20 : 6;
        this.hasComputedStyle          = typeof document.defaultView != "undefined"
                                           && typeof document.defaultView.getComputedStyle != "undefined";
        this.locale                    = (this.isIE
                                            ? navigator.userLanguage
                                            : navigator.language).toLowerCase();

        //Other settings
        this.maxHttpRetries = this.isOpera ? 0 : 3;

        this.dynPropMatch = new RegExp();
        this.dynPropMatch.compile("^[{\\[].*[}\\]]$");

        this.percentageMatch = new RegExp();
        this.percentageMatch.compile("([\\-\\d\\.]+)\\%", "g");

        jpf.isGears      = !!jpf.initGears() || 0;
    },

    /**
     * Restarts the application.
     */
    reboot : function(){
        jpf.console.info("Restarting application...");

        location.href = location.href;
    },

    /**
     * Extends an object with one or more other objects by copying all their
     * properties.
     * @param {Object} dest the destination object.
     * @param {Object} src the object that is copies from.
     * @return {Object} the destination object.
     */
    extend : function(dest, src){
        var prop, i, x = !dest.notNull;
        if (arguments.length == 2) {
            for (prop in src) {
                if (x || src[prop])
                    dest[prop] = src[prop];
            }
            return dest;
        }

        for (i = 1; i < arguments.length; i++) {
            src = arguments[i];
            for (prop in src) {
                if (x || src[prop])
                    dest[prop] = src[prop];
            }
        }
        return dest;
    },

    /**
     * Starts the application.
     */
    start : function(){
        this.started = true;
        var sHref = location.href.split("#")[0].split("?")[0];

        //Set Variables
        this.host     = location.hostname && sHref.replace(/(\/\/[^\/]*)\/.*$/, "$1");
        this.hostPath = sHref.replace(/\/[^\/]*$/, "") + "/";
        this.CWD      = sHref.replace(/^(.*\/)[^\/]*$/, "$1") + "/";

        jpf.console.info("Starting Javeline PlatForm Application...");
        jpf.console.warn("This is a debug build of Javeline PlatForm; \
                          beware that execution speed of this build is \
                          <strong>several times</strong> slower than a \
                          release build of Javeline PlatForm.");

        //mozilla root detection
        //try{ISROOT = !window.opener || !window.opener.jpf}catch(e){ISROOT = true}

        //Browser Specific Stuff
        this.browserDetect();
        this.setCompatFlags();

        jpf.debugwin.init();

        //Load Browser Specific Code
        if (this.isIE) jpf.runIE();
            //this.importClass(jpf.runIE, true, self);
        if (this.isSafari) jpf.runSafari();
            //this.importClass(jpf.runSafari, true, self);
        if (this.isOpera) jpf.runOpera();
            //this.importClass(jpf.runOpera, true, self);
        if (this.isGecko || !this.isIE && !this.isSafari && !this.isOpera)
            jpf.runGecko();
            //this.importClass(jpf.runGecko, true, self);

        for (var i, a, m, n, o, v, p = location.href.split(/[?&]/), l = p.length, k = 1; k < l; k++)
            if (m = p[k].match(/(.*?)(\..*?|\[.*?\])?=([^#]*)/)) {
                n = decodeURI(m[1]).toLowerCase(), o = this._GET;
                if (m[2])
                    for (a = decodeURI(m[2]).replace(/\[\s*\]/g, "[-1]").split(/[\.\[\]]/), i = 0; i < a.length; i++)
                        v = a[i], o = o[n]
                            ? o[n]
                            : o[n] = (parseInt(v) == v)
                                ? []
                                : {}, n = v.replace(/^["\'](.*)["\']$/,"$1");
                n != '-1'
                    ? o[n] = decodeURI(m[3])
                    : o[o.length] = decodeURI(m[3]);
            }

        // Start HTTP object
        this.oHttp = new this.http();

        // Load user defined includes
        this.Init.addConditional(this.loadIncludes, jpf, ['body', 'xmldb']);
        //@todo, as an experiment I removed 'HTTP' and 'Teleport'

        //IE fix
        try {
            if (jpf.isIE)
                document.execCommand("BackgroundImageCache", false, true);
        }
        catch(e) {};

        //try{jpf.root = !window.opener || !window.opener.jpf;}
        //catch(e){jpf.root = false}
        this.root = true;
    },

    // # ifndef __PACKAGED
    /**
     * @private
     */
    startDependencies : function(){
        if (location.protocol != "file:") {
            jpf.console.warn("You are serving multiple files from a (local)\
                   webserver - please consider using the file:// protocol to \
                   load your files, because that will make your application \
                   load several times faster.\
                   On a webserver, we recommend using a release or debug build \
                   of Javeline Platform.");
        }

        jpf.console.info("Loading Dependencies...");

        var i;
        // Load Kernel Modules
        for (i = 0; i < this.KernelModules.length; i++)
            jpf.include("core/" + this.KernelModules[i], true);

        // Load TelePort Modules
        for (i = 0; i < this.TelePortModules.length; i++)
            jpf.include("elements/teleport/" + this.TelePortModules[i], true);

        // Load Elements
        for (i = 0; i < this.Elements.length; i++) {
            var c = this.Elements[i];
            jpf.include("elements/" + c + ".js", true);
        }

        jpf.Init.interval = setInterval(
            "if (jpf.checkLoadedDeps()) {\
                clearInterval(jpf.Init.interval);\
                jpf.start();\
            }", 100);
    },

    /**
     * @private
     */
    nsqueue   : {},

    /**
     * Offers a way to load modules into a javascript namespace before the root
     * of that namespace is loaded.
     * @private
     */
    namespace : function(name, oNamespace){
        try{
            eval("jpf." + name + " = oNamespace");
            delete this.nsqueue[name];

            for (var ns in this.nsqueue) {
                if (ns.indexOf(name) > -1) {
                    this.namespace(ns, this.nsqueue[ns]);
                }
            }
            
            return true;
        }catch(e){
            this.nsqueue[name] = oNamespace;
            
            return false;
        }
    },
    //# endif

    /**
     * @private
     */
    findPrefix : function(xmlNode, xmlns){
        var docEl;
        if (xmlNode.nodeType == 9) {
            if (!xmlNode.documentElement) return false;
            if (xmlNode.documentElement.namespaceURI == xmlns)
                return xmlNode.prefix || xmlNode.scopeName;
            docEl = xmlNode.documentElement;
        }
        else {
            if (xmlNode.namespaceURI == xmlns)
                return xmlNode.prefix || xmlNode.scopeName;
            docEl = xmlNode.ownerDocument.documentElement;
            if (docEl && docEl.namespaceURI == xmlns)
                return xmlNode.prefix || xmlNode.scopeName;

            while (xmlNode.parentNode) {
                xmlNode = xmlNode.parentNode;
                if (xmlNode.namespaceURI == xmlns)
                    return xmlNode.prefix || xmlNode.scopeName;
            }
        }

        if (docEl) {
            for (var i=0; i<docEl.attributes.length; i++) {
                if (docEl.attributes[i].nodeValue == xmlns)
                    return docEl.attributes[i][jpf.TAGNAME]
            }
        }

        return false;
    },

    /**
     * @private
     */
    importClass : function(ref, strip, win){
        if (!ref)
            throw new Error(jpf.formatErrorString(1018, null,
                "importing class",
                "Could not load reference. Reference is null"));

        //if (!jpf.hasExecScript)
            //return ref();//.call(self);

        if (!strip)
            return jpf.exec(ref.toString(), win);

        var q = ref.toString().replace(/^\s*function\s*\w*\s*\([^\)]*\)\s*\{/, "");
        q = q.replace(/\}\s*$/, "");

        //var q = ref.toString().split("\n");q.shift();q.pop();
        //if(!win.execScript) q.shift();q.pop();

        return jpf.exec(q, win);
    },

    /**
    * This method returns a string representation of the object
    * @return {String}    Returns a string representing the object.
    */
    toString : function(){
        return "[Javeline (jpf)]";
    },

    all : [],

    /**
    * This method inherit all properties and methods to this object from another class
    * @param {Function}    classRef    Class reference
    */
    inherit : function(classRef){
        for (var i=0; i<arguments.length; i++) {
            if (!arguments[i]) {
                throw new Error(jpf.formatErrorString(0, this,
                    "Inheriting class",
                    "Could not inherit from '" + classRef + "'",
                    this.$jml));
            }

            arguments[i].call(this);//classRef
        }

        return this;
    },

    /**
    * This method transforms an object into a jpf class based object.
    * @param {Object} oBlank the object which will be transformed
    */
    makeClass : function(oBlank){
        if (oBlank.inherit) return;

        oBlank.inherit = this.inherit;
        oBlank.inherit(jpf.Class);

        oBlank.uniqueId = this.all.push(oBlank) - 1;
    },

    /**
     * @private
     */
    uniqueHtmlIds : 0,

    /**
     * Adds a unique id attribute to an html element.
     * @param {HTMLElement} oHtml the object getting the attribute.
     */
    setUniqueHtmlId : function(oHtml){
        oHtml.setAttribute("id", "q" + this.uniqueHtmlIds++);
    },

    /**
     * Retrieves a new unique id
     */
    getUniqueId : function(oHtml){
        return this.uniqueHtmlIds++;
    },

    /**
     * @private
     * @todo deprecate this in favor of jpf.component
     * @deprecated
     */
    register : function(o, tagName, nodeFunc){
        o.tagName  = tagName;
        o.nodeFunc = nodeFunc || jpf.NODE_HIDDEN;

        o.$domHandlers  = {"remove" : [], "insert" : [], "reparent" : [], "removechild" : []};
        o.$propHandlers = {}; //@todo fix this in each component

        if (nodeFunc != jpf.NODE_HIDDEN) {
            o.$booleanProperties = {
                "visible"          : true,
                "focussable"       : true,
                //"disabled"         : true,
                "disable-keyboard" : true
            }

            o.$supportedProperties = [
                "draggable", "resizable",
                "focussable", "zindex", "disabled", "tabindex",
                "disable-keyboard", "contextmenu", "visible", "autosize",
                "loadjml", "actiontracker", "alias"];
        }
        else {
            o.$booleanProperties = {}; //@todo fix this in each component
            o.$supportedProperties = []; //@todo fix this in each component
        }

        if (!o.inherit) {
            o.inherit = this.inherit;
            o.inherit(jpf.Class);
            o.uniqueId = this.all.push(o) - 1;
         }

        if(o.nodeFunc == jpf.NODE_MEDIAFLOW)
            DeskRun.register(o);
    },

    /**
     * Finds a jml element based on it's uniqueId
     */
    lookup : function(uniqueId){
        return this.all[uniqueId];
    },

    /**
     * Searches in the html tree from a certain point to find the
     * jml element that is responsible for rendering the specified html
     * element.
     * @param {HTMLElement} oHtml the html context to start the search from.
     */
    findHost : function(o){
        while (o && !o.host && o.parentNode)
            o = o.parentNode;
        return (o && o.host && typeof o.host != "string") ? o.host : false;
    },

    /**
     * Sets a reference to an object by name in the global javascript space.
     * @param {String} name the name of the reference.
     * @param {mixed}  o    the reference to the object subject to the reference.
     */
    setReference : function(name, o){
        return self[name] && self[name].hasFeature
            ? 0
            : (self[name] = o);
    },

    /**
     * The console outputs to the debug screen and offers differents ways to do
     * this.
     */
    console : {
        /**
         * @private
         */
        data : {
            time  : {
                icon     : "time.png",
                color    : "black",
                messages : {}
            },

            info  : {
                icon     : "bullet_green.png",
                color    : "black",
                messages : {}
            },

            warn  : {
                icon     : "error.png",
                color    : "green",
                messages : {}
            },

            error : {
                icon     : "exclamation.png",
                color    : "red",
                messages : {}
            }
        },

        /**
         * @private
         */
        toggle : function(node, id){
            var sPath = jpf.debugwin ? jpf.debugwin.resPath : jpf.basePath + "core/debug/resources/";
            if (node.style.display == "block") {
                node.style.display = "none";
                node.parentNode.style.backgroundImage = "url(" + sPath + "splus.gif)";
                node.innerHTML = "";
            }
            else {
                node.style.display = "block";
                node.parentNode.style.backgroundImage = "url(" + sPath + "smin.gif)";
                node.innerHTML = this.cache[id]
                    .replace(/\&/g, "&amp;")
                    .replace(/\t/g,"&nbsp;&nbsp;&nbsp;")
                    .replace(/ /g,"&nbsp;")
                    .replace(/\</g, "&lt;")
                    .replace(/\n/g, "<br />");

                var p = node.parentNode.parentNode.parentNode;
                var el = node.parentNode.parentNode;
                if(p.scrollTop + p.offsetHeight < el.offsetTop + el.offsetHeight)
                    p.scrollTop = el.offsetTop + el.offsetHeight - p.offsetHeight;
            }
        },

        /**
         * @private
         */
        cache : [],

        /**
         * @private
         * @event debug Fires when a message is sent to the console.
         *   object:
         *      {String} message the content of the message.
         */
        write : function(msg, type, subtype, data, forceWin, nodate){
            //if (!jpf.debug) return;
            if (!Number.prototype.toPrettyDigit) {
                Number.prototype.toPrettyDigit = function() {
                    var n = this.toString();
                    return (n.length == 1) ? "0" + n : n;
                }
            }

            var dt   = new Date();
            var ms   = String(dt.getMilliseconds());
            while (ms.length < 3) ms += "0";
            var date = dt.getHours().toPrettyDigit() + ":"
                + dt.getMinutes().toPrettyDigit()    + ":"
                + dt.getSeconds().toPrettyDigit()    + "."
                + ms;

            msg = (!nodate ? "[" + date + "] " : "")
                    + String(msg).replace(/ +/g, " ").replace(/\n/g, "\n<br />")
                         .replace(/\t/g,"&nbsp;&nbsp;&nbsp;");
            var sPath = jpf.debugwin
                ? (jpf.debugwin.resPath || "{imgpath}")
                : jpf.basePath + "core/debug/resources/";

            if (data) {
                msg += "<blockquote style='margin:2px 0 0 0;\
                        background:url(" + sPath + "splus.gif) no-repeat 2px 3px'>\
                        <strong style='width:120px;cursor:default;display:block;padding:0 0 0 17px' \
                        onmousedown='(self.jpf || window.opener.jpf).console.toggle(this.nextSibling, "
                        + (this.cache.push(data) - 1) + ")'>More information\
                        </strong><div style='display:none;background-color:#EEEEEE;\
                        padding:3px 3px 20px 3px;overflow:auto;max-height:200px'>\
                        </div></blockquote>";
            }

            msg = "<div style='min-height:15px;padding:2px 2px 2px 22px;\
                line-height:15px;border-bottom:1px solid #EEE;background:url("
                + sPath + this.data[type].icon + ") no-repeat 2px 2px;color:"
                + this.data[type].color + "'>" + msg + "\n<br style='line-height:0'/></div>";

            if (!subtype)
                subtype = "default";

            if (!this.data[type].messages[subtype])
                this.data[type].messages[subtype] = [];

            this.data[type].messages[subtype].push(msg);

            if (this.win && !this.win.closed)
                this.showWindow(msg);

            //if (jpf.debugFilter.match(new RegExp("!" + subtype + "(\||$)", "i")))
            //    return;

            this.debugInfo.push(msg);

            if (jpf.dispatchEvent)
                jpf.dispatchEvent("debug", {message: msg});
        },

        /**
         * Writes a message to the console.
         * @param {String} msg      the message to display in the console.
         * @param {String} subtype  the category for this message. This is used for filtering the messages.
         * @param {String} data     extra data that might help in debugging.
         */
        debug : function(msg, subtype, data){
            this.write(msg, "time", subtype, data);
        },

        /**
         * Writes a message to the console with the time icon next to it.
         * @param {String} msg      the message to display in the console.
         * @param {String} subtype  the category for this message. This is used for filtering the messages.
         * @param {String} data     extra data that might help in debugging.
         */
        time : function(msg, subtype, data){
            this.write(msg, "time", subtype, data);
        },

        /**
         * Writes a message to the console.
         * @param {String} msg      the message to display in the console.
         * @param {String} subtype  the category for this message. This is used for filtering the messages.
         * @param {String} data     extra data that might help in debugging.
         */
        log : function(msg, subtype, data){
            this.info(msg, subtype, data);
        },

        /**
         * Writes a message to the console with the visual "info" icon and color
         * coding.
         * @param {String} msg      the message to display in the console.
         * @param {String} subtype  the category for this message. This is used for filtering the messages.
         * @param {String} data     extra data that might help in debugging.
         */
        info : function(msg, subtype, data){
            this.write(msg, "info", subtype, data);
        },

        /**
         * Writes a message to the console with the visual "warning" icon and
         * color coding.
         * @param {String} msg      the message to display in the console.
         * @param {String} subtype  the category for this message. This is used for filtering the messages.
         * @param {String} data     extra data that might help in debugging.
         */
        warn : function(msg, subtype, data){
            this.write(msg, "warn", subtype, data);
        },

        /**
         * Writes a message to the console with the visual "error" icon and
         * color coding.
         * @param {String} msg      the message to display in the console.
         * @param {String} subtype  the category for this message. This is used for filtering the messages.
         * @param {String} data     extra data that might help in debugging.
         */
        error : function(msg, subtype, data){
            this.write(msg, "error", subtype, data);
        },

        /**
         * Prints a listing of all properties of the object.
         * @param {mixed} obj the object for which the properties are displayed.
         */
        dir : function(obj){
            this.info(jpf.vardump(obj, null, false).replace(/ /g, "&nbsp;").replace(/</g, "&lt;"));
        }

        ,
        /**
         * @private
         */
        debugInfo : [],

        /**
         * @private
         */
        debugType : "",

        /**
         * Shows a browser window with the contents of the console.
         * @param {String} msg a new message to add to the new window.
         */
        showWindow : function(msg){
            if (!this.win || this.win.closed) {
                this.win = window.open("", "debug");
                this.win.document.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\
                                         <body style="margin:0;font-family:Verdana;font-size:8pt;"></body>');
            }
            if (!this.win) {
                if (!this.haspopupkiller)
                    alert("Could not open debug window, please check your popupkiller");
                this.haspopupkiller = true;
            }
            else {
                this.win.document.write((msg || this.debugInfo.join(""))
                    .replace(/\{imgpath\}/g, jpf.debugwin
                        ? jpf.debugwin.resPath
                        : jpf.basePath + "core/debug/resources/"));
            }
        }

    },

    /**
     * Formats a Javeline PlatForm error message.
     * @param {Number}      number      the number of the error. This can be used to look up more information about the error.
     * @param {JMLElement}  control     the jml element that will throw the error.
     * @param {String}      process     the action that was being executed.
     * @param {String}      message     the actual error message.
     * @param {XMLElement}  jmlContext  the xml relevant to the error. For instance a piece of javeline markup language xml.
     */
    formatErrorString : function(number, control, process, message, jmlContext, outputname, output){
        var str = ["---- Javeline Error ----"];
        if (jmlContext) {
            if (jmlContext.nodeType == 9)
                jmlContext = jmlContext.documentElement;

            //Determine file context
            var file = jmlContext.ownerDocument.documentElement.getAttribute("filename");
            if (!file && jmlContext.ownerDocument.documentElement.tagName == "html")
                file = location.href;
            file = file
                ? jpf.removePathContext(jpf.hostPath, file)
                : "Unkown filename";

            //Get serialized version of context
            var jmlStr = (jmlContext.outerHTML || jmlContext.xml || jmlContext.serialize())
                .replace(/\<\?xml\:namespace prefix = j ns = "http\:\/\/www.javeline.com\/2005\/jml" \/\>/g, "")
                .replace(/xmlns:j="[^"]*"\s*/g, "");

            //Determine line number
            var diff, linenr = 0, w = jmlContext.previousSibling
                || jmlContext.parentNode && jmlContext.parentNode.previousSibling;
            while(w && w[jpf.TAGNAME] != "body"){
                diff = (w.outerHTML || w.xml || w.serialize()).split("\n").length;
                linenr += diff - 1;
                w = w.previousSibling || w.parentNode
                    && w.parentNode.previousSibling;
            }
            if (w && w[jpf.TAGNAME] != "body")
                linenr = "unknown";
            else if(jmlContext.ownerDocument.documentElement.tagName == "html")
                linenr += jpf.lineBodyStart;

            //Grmbl line numbers are wrong when \n's in attribute space

            //Set file and line number
            str.push("jml file: [line: " + linenr + "] " + file);
        }
        if (control)
            str.push("Control: '"
                + (control.name
                    || (control.$jml ? control.$jml.getAttribute("id") : null)
                    || "{Anonymous}")
                + "' [" + control.tagName + "]");
        if (process)
            str.push("Process: " + process.replace(/ +/g, " "));
        if (message)
            str.push("Message: [" + number + "] " + message.replace(/ +/g, " "));
        if (outputname)
            str.push(outputname + ": " + output);
        if (jmlContext)
            str.push("\n===\n" + jmlStr);
        
        return (jpf.lastErrorMessage = str.join("\n"));
    },

    /* Init */

    /**
     * Loads javascript from a url.
     * @param {String} sourceFile the url where the javascript is located.
     */
    include : function(sourceFile, doBase, type){
        jpf.console.info("including js file: " + sourceFile);

        var sSrc = doBase ? (jpf.basePath || "") + sourceFile : sourceFile;
        if (jpf.isSafariOld || jpf.isSafari && !jpf.started) {
            document.write('<script type="text/javascript" src="' + sSrc + '"><\/script>');
        }
        else {
            var head     = document.getElementsByTagName("head")[0];//$("head")[0]
            var elScript = document.createElement("script");
            elScript.defer = true;
            if (type)
                elScript.setAttribute("_jpf_type", type);
            elScript.src   = sSrc;
            head.appendChild(elScript);
        }
    },

    /**
     * @private
     */
    Init : {
        queue : [],
        cond  : {
            combined : []
        },
        done  : {},

        add   : function(func, o){
            if (this.inited)
                func.call(o);
            else if (func)
                this.queue.push([func, o]);
        },

        addConditional : function(func, o, strObj){
            if (typeof strObj != "string") {
                if (this.checkCombined(strObj))
                    return func.call(o);
                this.cond.combined.push([func, o, strObj]);
            }
            else if (self[strObj]) {
                func.call(o);
            }
            else {
                if (!this.cond[strObj])
                    this.cond[strObj] = [];
                this.cond[strObj].push([func, o]);

                this.checkAllCombined();
            }
        },

        checkAllCombined : function(){
            for (var i=0; i<this.cond.combined.length; i++) {
                if (!this.cond.combined[i]) continue;

                if (this.checkCombined(this.cond.combined[i][2])) {
                    this.cond.combined[i][0].call(this.cond.combined[i][1])
                    this.cond.combined[i] = null;
                }
            }
        },

        checkCombined : function(arr){
            for (var i=0; i<arr.length; i++) {
                if (!this.done[arr[i]])
                    return false;
            }

            return true;
        },

        run : function(strObj){
            this.inited = this.done[strObj] = true;

            this.checkAllCombined();

            var data = strObj ? this.cond[strObj] : this.queue;
            if (!data) return;
            for (var i = 0; i < data.length; i++)
                data[i][0].call(data[i][1]);
        }
    },


    /**
     * @todo Build this function into the compressor for faster execution
     * @private
     */
    getJmlDocFromString : function(xmlString){
        //replace(/&\w+;/, ""). replace this by something else
        var str = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "").replace(/&nbsp;/g, " ")
            .replace(/^[\r\n\s]*/, "").replace(/<\s*\/?\s*(?:\w+:\s*)[\w-]*[\s>\/]/g,
            function(m){ return m.toLowerCase(); });

        if (!this.supportNamespaces)
            str = str.replace(/xmlns\=\"[^"]*\"/g, "");

        var xmlNode = jpf.getXmlDom(str, null, jpf.debug);

        // Case insensitive support
        var nodes = xmlNode.selectNodes("//@*[not(contains(local-name(), '.')) and not(translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = local-name())]");
        for (var i=0; i<nodes.length; i++) {
            (nodes[i].ownerElement || nodes[i].selectSingleNode(".."))
                .setAttribute(nodes[i].nodeName.toLowerCase(), nodes[i].nodeValue);
        }

        return xmlNode;
    },

    /**
     * @private
     */
    jmlParts : [],

    /**
     * {Number} parseStrategy
     *   Possible values:
     *   0    auto
     *   1    partial
     *   11   partial from a comment
     *   2    full from serialized document or file fallback
     *   21   full from file
     */
    parseStrategy : 0,

    parsePartialJml : function(docElement){
        jpf.console.warn("The jml namespace definition wasn't found \
                          on the root node of this document. We're assuming \
                          you want to load a partial piece of jml embedded\
                          in this document. Starting to search for it now.");

        if (jpf.isIE) {
            var findJml = function(htmlNode){
                if (htmlNode.outerHTML.match(/\/>$/)) {
                    throw new Error("Cannot have self closing elements!\n"
                        + htmlNode.outerHTML);
                }
                
                try {
                    var tags = {"IMG":1,"LINK":1,"META":1,"INPUT":1,"BR":1,"HR":1,"AREA":1,"BASEFONT":1};
                    var strXml = (htmlNode.parentNode.outerHTML.replace(/\n/g, "").match(
                      new RegExp(htmlNode.outerHTML.replace(/([\(\)\|\\\.\^\$\{\}\[\]])/g, "\\$1")
                      + ".*" + htmlNode.tagName))[0] + ">")
                        .replace(/(\w+)\s*=\s*([^\>="'\s ]+)( |\s|\>|\/\>)/g, "$1=\"$2\"$3")
                        .replace(/ disabled /g, " disabled='true' ")
                        .replace(/\]\]\&gt;/g, "]]>")
                        .replace(/<(\w+)(\s[^>]*[^\/])?>/g, function(m, tag, c){
                            if (tags[tag]) {
                                return "<" + tag + (c||"") + "/>";
                            }
                            else {
                                return m;
                            }
                        });
                } 
                catch(e) {
                    throw new Error(jpf.formatErrorString(0, null,
                        "Parsing inline jml (without xmlns on root node)",
                        "Could not parse inline jml. This happens when the html\
                         is mangled too much by Internet Explorer. Either you\
                         are using a cdata section or javascript containing\
                         symbols that throw off the browser. Please put this jml\
                         in a seperate file and load it using a j:include."));
                    
                    return;
                }

                var p = prefix.toLowerCase();
                var xmlNode = jpf.getJmlDocFromString("<div jid='"
                    + (id++) + "' " + strXmlns + ">"
                    + strXml + "</div>").documentElement;

                while(xmlNode.childNodes.length > 1) {
                    xmlNode.removeChild(xmlNode.lastChild);
                }

                jpf.AppNode.appendChild(xmlNode);
            }
        }
        else {
            var findJml = function(htmlNode){
                var strXml = htmlNode.outerHTML
                    .replace(/ _moz-userdefined=""/g, "");

                var p = prefix.toLowerCase();
                var xmlNode = jpf.getJmlDocFromString("<div jid='"
                    + (id++) + "' " + strXmlns + ">"
                    + strXml + "</div>").documentElement;

                while(xmlNode.childNodes.length > 1) {
                    xmlNode.removeChild(xmlNode.lastChild);
                }

                if (jpf.isSafari)
                    xmlNode = jpf.AppNode.ownerDocument.importNode(xmlNode, true);

                jpf.AppNode.appendChild(xmlNode);
            }
        }

        var strHtml = document.body.outerHTML;
        var match = strHtml.match(/(\w+)\s*=\s*["']http:\/\/www\.javeline\.com\/2005\/jml["']/);
        if (!match)
            return false;

        var strXmlns = "xmlns:" + match[0];
        var prefix = (RegExp.$1 || "").toUpperCase();
        if (jpf.isOpera)
            prefix = prefix.toLowerCase();
        if (!prefix)
            return false;

        prefix += ":";

        jpf.AppNode = jpf.getJmlDocFromString("<" + prefix.toLowerCase()
            + "application " + strXmlns + " />").documentElement;

        var temp, loop;
        var cnode, isPrefix = false, id = 0, str, x, node = document.body;
        while (node) {
            isPrefix = node.nodeType == 1
                && node.tagName.substr(0,2) == prefix;

            if (isPrefix) {
                findJml(cnode = node);

                if (jpf.isIE) {
                    loop = node;
                    var count = 1, next = loop.nextSibling;
                    if (next) {
                        loop.parentNode.removeChild(loop);

                        while (next && (next.nodeType != 1 || next.tagName.indexOf(prefix) > -1)){
                            if (next.nodeType == 1)
                                count += next.tagName.charAt(0) == "/" ? -1 : 1;

                            if (count == 0) {
                                if (temp)
                                    temp.parentNode.removeChild(temp);
                                temp = next;
                                break;
                            }

                            next = (loop = next).nextSibling;
                            if (!next) {
                                next = loop;
                                break;
                            }
                            if (loop.nodeType == 1) {
                                loop.parentNode.removeChild(loop);
                                if (temp) {
                                    temp.parentNode.removeChild(temp);
                                    temp = null;
                                }
                            }
                            else {
                                if (temp)
                                    temp.parentNode.removeChild(temp);

                                temp = loop;
                            }
                        }

                        node = next; //@todo item should be deleted
                        //check here for one too far
                    }
                    else {
                        if (temp)
                            temp.parentNode.removeChild(temp);
                        temp = loop;
                    }
                }
                else {
                    if (temp)
                        temp.parentNode.removeChild(temp);

                    temp = node;
                    //node = node.nextSibling;
                }

                if (jpf.jmlParts.length
                  && jpf.jmlParts[jpf.jmlParts.length-1][1] == cnode)
                    jpf.jmlParts[jpf.jmlParts.length-1][1] = -1;

                jpf.jmlParts.push([node.parentNode, jpf.isIE
                    ? node.nextSibling : node.nextSibling]);
            }
            else if (node.tagName == "SCRIPT" && node.getAttribute("src")
              && (node.getAttribute("src").indexOf("ajax.org") > -1
              || node.getAttribute("src").indexOf("javeline.com") > -1)) {
                var strXml = node.outerHTML
                    .replace(/&lt;/g, "<")
                    .replace(/&gt;/g, ">")
                    .replace(/&amp;/g, "&")
                    .replace(/<SCRIPT[^>]*\>\s*<\!\[CDATA\[>?/i, "")
                    .replace(/<SCRIPT[^>]*\>(?:<\!\-\-)?/i, "")
                    .replace(/(\/\/)?\s*\&\#8211;>\s*<\/SCRIPT>/i, "")
                    .replace(/\-\->\s*<\/SCRIPT>/i, "")
                    .replace(/\]\](?:\&gt\;|>)\s*<\/SCRIPT>/i, "")
                    .replace(/<\/SCRIPT>$/mi, "")
                    .replace(/<\/?\s*(?:p|br)\s*\/?>/ig, "")
                    .replace(/<\!--\s*.*?\s*-->\s*<script.*/ig, "")
                    .replace(/\\+(['"])/g, "$1");

                if (strXml.trim()) {
                    var xmlNode = jpf.getJmlDocFromString("<div jid='"
                        + (id++) + "' " + strXmlns + ">"
                        + strXml + "</div>").documentElement;

                    if (jpf.isSafari)
                        xmlNode = jpf.AppNode.ownerDocument.importNode(xmlNode, true);

                    jpf.AppNode.appendChild(xmlNode);

                    jpf.jmlParts.push([node.parentNode, node.nextSibling]);
                }
            }

            //Walk entire html tree
            if (!isPrefix && node.firstChild
              || node.nextSibling) {
                if (!isPrefix && node.firstChild) {
                    node = node.firstChild;
                }
                else {
                    node = node.nextSibling;
                }
            }
            else {
                do {
                    node = node.parentNode;

                    if (node.tagName == "BODY")
                        node = null;

                } while (node && !node.nextSibling)

                if (node) {
                    node = node.nextSibling;
                }
            }
        }

        if (temp)
            temp.parentNode.removeChild(temp);
    },

    /**
     * @private
     */
    loadIncludes : function(docElement){
        var isEmptyDocument = false;
        
        if (this.parseStrategy == 1 || !this.parseStrategy && !docElement
          && document.documentElement.outerHTML.split(">", 1)[0]
             .indexOf(jpf.ns.jml) == -1) {
            this.parsePartialJml(docElement);

            if (this.parseStrategy == 1 || jpf.jmlParts.length) {
                if (jpf.jmlParts.length)
                    jpf.console.warn("Jml found, parsing...");

                jpf.isParsingPartial = true;

                jpf.loadJmlIncludes(jpf.AppNode);

                if (!self.ERROR_HAS_OCCURRED) {
                    jpf.Init.interval = setInterval(function(){
                        if (jpf.checkLoaded())
                            jpf.initialize();
                    }, 20);
                }

                return;
            }
            else {
                jpf.console.warn("No jml found.");
                isEmptyDocument = true;
            }
        }


        
        if (isEmptyDocument && document.documentElement.outerHTML
          .split(">", 1)[0]
          .indexOf(jpf.ns.jml) == -1) {
            jpf.console.warn("The jml namespace declaration wasn't found. \
                              No jml elements were found in the body. Exiting");
            return false;
        }

        //Load current HTML document as 'second DOM'
        if (this.parseStrategy == 21 || !this.parseStrategy && !docElement) {
            return jpf.oHttp.get((document.body.getAttribute("xmlurl") || location.href).split(/#/)[0],
                function(xmlString, state, extra){
                    if (state != jpf.SUCCESS) {
                        var oError;
                        oError = new Error(jpf.formatErrorString(0, null,
                            "Loading XML application data", "Could not load \
                            XML from remote source: " + extra.message));

                        if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
                            return true;

                        throw oError;
                    }

                    jpf.lineBodyStart = (xmlString.replace(/\n/g, "\\n")
                        .match(/(.*)<body/) || [""])[0].split("\\n").length;

                    var xmlNode = jpf.getJmlDocFromString(xmlString);

                    //Clear Body
                    if (jpf.isIE)
                        document.body.innerHTML ="";
                    else {
                        var nodes = document.body.childNodes;
                        for (var i=nodes.length-1; i>=0; i--)
                            nodes[i].parentNode.removeChild(nodes[i]);
                    }

                    return jpf.loadIncludes(xmlNode);
                }, {ignoreOffline: true});
        }
        
        //Parse the second DOM (add includes)
        var prefix = jpf.findPrefix(docElement, jpf.ns.jml);
        if (jpf.isSafariOld || true)
            prefix = "j";
        
        if (!prefix)
            throw new Error(jpf.formatErrorString(0, null,
                "Parsing document",
                "Unable to find Javeline PlatForm namespace definition. \
                 (i.e. xmlns:j=\"" + jpf.ns.jml + "\")", docElement));

        jpf.AppData = jpf.supportNamespaces
            ? docElement.createElementNS(jpf.ns.jml, prefix + ":application")
            : docElement.createElement(prefix + ":application");

        var i, nodes;
        //Head support
        var head = $xmlns(docElement, "head", jpf.ns.xhtml)[0];
        if (head) {
            nodes = head.childNodes;
            for (i = nodes.length-1; i >= 0; i--)
                if (nodes[i].namespaceURI && nodes[i].namespaceURI != jpf.ns.xhtml)
                    jpf.AppData.insertBefore(nodes[i], jpf.AppData.firstChild);
        }

        //Body support
        var body = (docElement.body
            ? docElement.body
            : $xmlns(docElement, "body", jpf.ns.xhtml)[0]);
        for (i = 0; i < body.attributes.length; i++)
            jpf.AppData.setAttribute(body.attributes[i].nodeName,
                body.attributes[i].nodeValue);

        nodes = body.childNodes;
        for (i = nodes.length - 1; i >= 0; i--)
            jpf.AppData.insertBefore(nodes[i], jpf.AppData.firstChild);
        docElement.documentElement.appendChild(jpf.AppData); //Firefox fix for selectNode insertion need...

        /* 
        jpf.AppData = docElement.body ? docElement.body : $xmlns(docElement.documentElement, "body", jpf.ns.xhtml)[0];
        */

        jpf.loadJmlIncludes(jpf.AppData);

        if ($xmlns(jpf.AppData, "loader", jpf.ns.jml).length) {
            jpf.loadScreen = {
                show : function(){
                    this.oExt.style.display = "block";
                    //this.oExt.style.height = document.body.scrollHeight + "px";
                },

                hide : function(){
                    this.oExt.style.display = "none";
                }
            }

            if (jpf.isGecko || jpf.isSafari)
                document.body.innerHTML = "";

            if (jpf.isSafariOld) {
                var q = jpf.getFirstElement(
                    $xmlns(jpf.AppData, "loader", jpf.ns.jml)[0]).serialize();
                document.body.insertAdjacentHTML("beforeend", q);
                jpf.loadScreen.oExt = document.body.lastChild;
            }
            else
            {
                var htmlNode = jpf.getFirstElement(
                    $xmlns(jpf.AppData, "loader", jpf.ns.jml)[0]);

                //if(jpf.isSafari) jpf.loadScreen = document.body.appendChild(document.importNode(htmlNode, true));
                if (htmlNode.ownerDocument == document)
                    jpf.loadScreen.oExt = document.body.appendChild(
                        htmlNode.cloneNode(true));
                else {
                    document.body.insertAdjacentHTML("beforeend", htmlNode.xml
                        || htmlNode.serialize());
                    jpf.loadScreen.oExt = document.body.lastChild;
                }
            }
        }

        document.body.style.display = "block"; //might wanna make this variable based on layout loading...

        if (!self.ERROR_HAS_OCCURRED) {
            jpf.Init.interval = setInterval(function(){
                if (jpf.checkLoaded())
                    jpf.initialize()
            }, 20);
        }
    },

    /**
     * @private
     */
    checkForJmlNamespace : function(xmlNode){
        if (!xmlNode.ownerDocument.documentElement)
            return false;

        var nodes = xmlNode.ownerDocument.documentElement.attributes;
        for (var found = false, i=0; i<nodes.length; i++) {
            if (nodes[i].nodeValue == jpf.ns.jml) {
                found = true;
                break;
            }
        }

        if (!found) {
            throw new Error(jpf.formatErrorString(0, null,
                "Checking for the jml namespace",
                "The Javeline PlatForm xml namespace was not found in "
                + (xmlNode.getAttribute("filename")
                    ? "in '" + xmlNode.getAttribute("filename") + "'"
                    : "")));
        }

        return found;
    },

    /**
     * @private
     */
    loadJmlIncludes : function(xmlNode, doSync){

        var i, nodes, path;
        jpf.checkForJmlNamespace(xmlNode);

        var basePath = jpf.getDirname(xmlNode.getAttribute("filename")) || jpf.hostPath;

        nodes = $xmlns(xmlNode, "include", jpf.ns.jml);
        if (nodes.length) {
            xmlNode.setAttribute("loading", "loading");

            for (i = nodes.length - 1; i >= 0; i--) {
                if (!nodes[i].getAttribute("src"))
                    throw new Error(jpf.formatErrorString(0, null, "Loading includes", "Could not load Include file " + nodes[i].xml + ":\nCould not find the src attribute."))

                path = jpf.getAbsolutePath(basePath, nodes[i].getAttribute("src"));

                jpf.loadJmlInclude(nodes[i], doSync, path);
            }
        }
        else
            xmlNode.setAttribute("loading", "done");

        nodes = $xmlns(xmlNode, "skin", jpf.ns.jml);
        for (i = 0; i < nodes.length; i++) {
            if (!nodes[i].getAttribute("src") && !nodes[i].getAttribute("name")
              || nodes[i].childNodes.length)
                continue;

            path = nodes[i].getAttribute("src")
                ? jpf.getAbsolutePath(basePath, nodes[i].getAttribute("src"))
                : jpf.getAbsolutePath(basePath, nodes[i].getAttribute("name")) + "/index.xml";

            jpf.loadJmlInclude(nodes[i], doSync, path, true);

            //nodes[i].parentNode.removeChild(nodes[i]);
            nodes[i].setAttribute("j_preparsed", "9999")
        }

        //XForms and lazy devs support
        if (!nodes.length && !jpf.skins.skins["default"] && jpf.autoLoadSkin) {
            jpf.console.warn("No skin file found, attempting to autoload the \
                              default skin file: skins.xml");
            jpf.loadJmlInclude(null, doSync, "skins.xml", true);
        }


        return true;
    },

    /**
     * @private
     */
    loadJmlInclude : function(node, doSync, path, isSkin){

        jpf.console.info("Loading include file: " + (path || node && node.getAttribute("src")));

        this.oHttp.get(path || jpf.getAbsolutePath(jpf.hostPath, node.getAttribute("src")),
            function(xmlString, state, extra){
                 if (state != jpf.SUCCESS) {
                    var oError;
                    oError = new Error(jpf.formatErrorString(1007,
                        null, "Loading Includes", "Could not load Include file '"
                        + (path || extra.userdata[0].getAttribute("src"))
                        + "'\nReason: " + extra.message, node));

                    if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
                        return true;

                    //Check if we are autoloading
                    if (!node) {
                        //Fail silently
                        jpf.console.warn("Could not autload skin.");
                        jpf.includeStack[extra.userdata[1]] = true;
                        return;
                    }

                    throw oError;
                }

                var xmlNode, isTeleport;
                if (!isSkin) {
                    xmlNode = jpf.getJmlDocFromString(xmlString).documentElement;
                    var tagName = xmlNode[jpf.TAGNAME];

                    if (tagName == "skin")
                        isSkin = true;
                    else if (tagName == "teleport")
                        isTeleport = true;
                    else if(tagName != "application") {
                        throw new Error(jpf.formatErrorString(0, null,
                            "Loading Includes",
                            "Could not find handler to parse include file for '"
                            + xmlNode[jpf.TAGNAME]
                            + "' expected 'skin' or 'application'", node));
                    }
                }

                if (isSkin) {
                    if (xmlString.indexOf('xmlns="http://www.w3.org/1999/xhtml"') > -1){
                        jpf.console.warn("Found xhtml namespace as global \
                                          namespace of skin file. This is not \
                                          allowed. Please remove this before \
                                          use in production environments.")
                        xmlString = xmlString.replace('xmlns="http://www.w3.org/1999/xhtml"', '');
                    }

                    xmlNode = jpf.getJmlDocFromString(xmlString).documentElement;
                    jpf.skins.Init(xmlNode, node, path);
                    jpf.includeStack[extra.userdata[1]] = true;

                    if (jpf.isOpera && extra.userdata[0] && extra.userdata[0].parentNode) //for opera...
                        extra.userdata[0].parentNode.removeChild(extra.userdata[0]);
                }
                else if (isTeleport) {
                    jpf.teleport.loadJml(xmlNode);
                    jpf.includeStack[extra.userdata[1]] = true;
                }
                else {
                    jpf.includeStack[extra.userdata[1]] = xmlNode;//extra.userdata[0].parentNode.appendChild(xmlNode, extra.userdata[0]);
                    extra.userdata[0].setAttribute("iid", extra.userdata[1]);
                }

                xmlNode.setAttribute("filename", extra.url);

                jpf.console.info("Loading of " + xmlNode[jpf.TAGNAME].toLowerCase() + " include done from file: " + extra.url);

                jpf.loadJmlIncludes(xmlNode); //check for includes in the include (NOT recursive save)

            }, {
                async         : !doSync,
                userdata      : [node, jpf.includeStack.push(false) - 1],
                ignoreOffline : true
            });

    },

    /**
     * @private
     */
    checkLoaded : function(){
        for (var i = 0; i < jpf.includeStack.length; i++) {
            if (!jpf.includeStack[i]) {
                jpf.console.info("Waiting for: [" + i + "] " + jpf.includeStack[i]);
                return false;
            }
        }

        if (!document.body) return false;

        jpf.console.info("Dependencies loaded");

        return true;
    },


    /**
     * @private
     */
    initialize : function(){
        if (jpf.initialized) return;
        jpf.initialized = true;

        jpf.console.info("Initializing...");
        clearInterval(jpf.Init.interval);

        // Run Init
        jpf.Init.run(); //Process load dependencies
        

        if (jpf.isParsingPartial) {
            //Form jml parser
            if (!jpf.window) {
                jpf.window          = new jpf.WindowImplementation();
                jpf.document        = new jpf.DocumentImplementation();
                jpf.window.document = jpf.document;
                jpf.window.$at      = new jpf.actiontracker();
                jpf.nameserver.register("actiontracker", "default", jpf.window.$at);
            }

            jpf.appsettings.init();
            jpf.hasSingleRszEvent = true;

            var pHtmlNode = document.body;
            var lastChild = pHtmlNode.lastChild;
            jpf.JmlParser.parseMoreJml(jpf.AppNode, pHtmlNode, null,
                true, false);

            var pNode, firstNode, lastBefore = null, next, info, loop = pHtmlNode.lastChild;
            while (loop && lastChild != loop) {
                info = jpf.jmlParts[loop.getAttribute("jid")];
                next = loop.previousSibling;
                if (info) {
                    pNode = info[0];
                    if ("P".indexOf(pNode.tagName) > -1) {
                        lastBefore = pNode.parentNode.insertBefore(jpf.getNode(loop, [0]),
                            pNode);
                    }
                    else {
                        firstNode = jpf.getNode(loop, [0]);
                        while(firstNode){
                            if (firstNode) {
                                lastBefore = pNode.insertBefore(firstNode,
                                    typeof info[1] == "number" ? lastBefore : info[1]);
                            }
                            else {
                                lastBefore = typeof info[1] == "number" ? lastBefore : info[1];
                            }
                            firstNode = jpf.getNode(loop, [0]);
                        }
                    }

                    loop.parentNode.removeChild(loop);
                }
                loop = next;
            }

            setTimeout("jpf.layout.forceResize();");
        }
        else
        {
            // Start application
            if (jpf.JmlParser && jpf.AppData)
                jpf.JmlParser.parse(jpf.AppData);

            if (jpf.loadScreen && jpf.appsettings.autoHideLoading)
                jpf.loadScreen.hide();
        }
    },

    addDomLoadEvent: function(func) {
        if (!this.$bdetect)
            this.browserDetect();

        // create event function stack
        var load_events = [],
            load_timer,
            done   = arguments.callee.done,
            exec,
            init   = function () {
                if (done) return;
                // kill the timer
                clearInterval(load_timer);
                load_timer = null;
                done       = true;
                // execute each function in the stack in the order they were added
                var len = load_events.length;
                while (len--) {
                    (load_events.shift())();
                }
            };

        if (func && !load_events[0]) {
            // for Mozilla/Opera9.
            // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
            if (document.addEventListener && !jpf.isOpera) {
                // We're using "window" and not "document" here, because it results
                // in a memory leak, especially in FF 1.5:
                // https://bugzilla.mozilla.org/show_bug.cgi?id=241518
                // See also:
                // http://bitstructures.com/2007/11/javascript-method-callbacks
                // http://www-128.ibm.com/developerworks/web/library/wa-memleak/
                window.addEventListener("DOMContentLoaded", init, false);
            }
            // If IE is used and is not in a frame
            else if (jpf.isIE && window == top) {
                load_timer = setInterval(function() {
                    try {
                        // If IE is used, use the trick by Diego Perini
                        // http://javascript.nwbox.com/IEContentLoaded/
                        document.documentElement.doScroll("left");
                    }
                    catch(error) {
                        setTimeout(arguments.callee, 0);
                        return;
                    }
                    // no exceptions anymore, so we can call the init!
                    init();
                }, 10);
            }
            else if (jpf.isOpera) {
                document.addEventListener( "DOMContentLoaded", function () {
                    load_timer  = setInterval(function() {
                        for (var i = 0; i < document.styleSheets.length; i++) {
                            if (document.styleSheets[i].disabled)
                                return;
                        }
                        // all is fine, so we can call the init!
                        init();
                    }, 10);
                }, false);
            }
            else if (jpf.isSafari) {
                var aSheets = documents.getElementsByTagName("link");
                for (var i = aSheets.length; i >= 0; i++) {
                    if (!aSheets[i] || aSheets[i].getAttribute("rel") != "stylesheet")
                        aSheets.splice(i, 0);
                }
                var iSheets = aSheets.length;
                load_timer  = setInterval(function() {
                    if (/loaded|complete/.test(document.readyState)
                      && document.styleSheets.length == iSheets)
                        init(); // call the onload handler
                }, 10);
            }
            // for other browsers set the window.onload, but also execute the old window.onload
            else {
                var old_onload = window.onload;
                window.onload  = function () {
                    init();
                    if (old_onload)
                        old_onload();
                };
            }
        }
        load_events.push(func);
    },

    /* Destroy */

    /**
     * Unloads the jml application.
     */
    destroy : function(exclude){
        jpf.console.info("Initiating self destruct...");

        this.isDestroying = true;


        this.popup.destroy();

        for (i = 0; i < this.all.length; i++) {
            if (this.all[i] && this.all[i] != exclude && this.all[i].destroy)
                this.all[i].destroy(false);
        }

        for (i = this.$jmlDestroyers.length - 1; i >= 0; i--)
            this.$jmlDestroyers[i].call(this);
        this.$jmlDestroyers = undefined;

        jpf.teleport.destroy();

        if (jpf.xmldb)
            jpf.xmldb.unbind(jpf.window);

        this.isDestroying = false;
    }
};

/*
 * Replacement for getElementsByTagNameNS because some browsers don't support
 * this call yet.
 */
var $xmlns = function(xmlNode, tag, xmlns, prefix){
    if (!jpf.supportNamespaces) {
        if (!prefix)
            prefix = jpf.findPrefix(xmlNode, xmlns);

        if (xmlNode.style || xmlNode == document)
            return xmlNode.getElementsByTagName(tag)
        else {
            if (prefix)
                (xmlNode.nodeType == 9 ? xmlNode : xmlNode.ownerDocument)
                    .setProperty("SelectionNamespaces",
                        "xmlns:" + prefix + "='" + xmlns + "'");

            return xmlNode.selectNodes(".//" + (prefix ? prefix + ":" : "") + tag);
        }
    }
    else
        return xmlNode.getElementsByTagNameNS(xmlns, tag);
}

jpf.Init.run('jpf');


/*FILEHEAD(/var/lib/jpf/src/core/xmldatabase.js)SIZE(-1077090856)TIME(1238933677)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * The xml database object provides local storage for xml data. This object
 * routes all changes to the xml data to the data bound objects. It further
 * provides utility functions for xml handling.
 *
 * @constructor
 * @jpfclass
 *
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.8
 *
 * @default_private
 */
jpf.XmlDatabase = function(){
    this.xmlDocTag    = "j_doc";
    this.xmlIdTag     = "j_id";
    this.xmlListenTag = "j_listen";
    this.htmlIdTag    = "id";

    var xmlDocLut     = [];

    /**
     * @private
     */
    this.getElementById = function(id, doc){
        if (!doc)
            doc = xmlDocLut[id.split("\|")[0]];
        if (!doc)
            return false;

        return doc.selectSingleNode("descendant-or-self::node()[@"
            + this.xmlIdTag + "='" + id + "']");
    };

    /**
     * @private
     */
    this.getNode = function(htmlNode){
        if (!htmlNode || !htmlNode.getAttribute(this.htmlIdTag))
            return false;

        return this.getElementById(htmlNode.getAttribute(this.htmlIdTag)
            .split("\|", 2).join("|"));
    };

    /**
     * @private
     */
    this.getNodeById = function(id, doc){
        var q = id.split("\|");
        q.pop();
        return this.getElementById(q.join("|"), doc);//id.split("\|", 2).join("|")
    };

    /**
     * @private
     */
    this.getDocumentById = function(id){
        return xmlDocLut[id];
    };

    /**
     * @private
     */
    this.getDocument = function(node){
        return xmlDocLut[node.getAttribute(this.xmlIdTag).split("\|")[0]];
    };

    /**
     * @private
     */
    this.getID = function(xmlNode, o){
        return xmlNode.getAttribute(this.xmlIdTag) + "|" + o.uniqueId;
    };

    /**
     * Gets the child position of a dom node.
     *
     * @param {DOMNode} node the node for which the child position is determined.
     * @return {Number} the child position of the node.
     */
    this.getChildNumber = function(node){
        var p = node.parentNode;
        for (var i = 0; i < p.childNodes.length; i++)
            if (p.childNodes[i] == node)
                return i;
    };

    /**
     * Determines whether a node is a child of another node.
     *
     * @param {DOMNode} pNode      the potential parent element.
     * @param {DOMNode} childnode  the potential child node.
     * @param {Boolean} [orItself] whether the method also returns true when pNode is the childnode.
     * @return  {Number} the child position of the node. Or false if it's not a child.
     */
    this.isChildOf = function(pNode, childnode, orItself){
        if (!pNode || !childnode)
            return false;
        
        if (childnode.nodeType == 2)
            childnode = childnode.selectSingleNode("..");
        
        if (orItself && pNode == childnode)
            return true;

        var loopnode = childnode.parentNode;
        while(loopnode){
            if(loopnode == pNode)
                return true;
            loopnode = loopnode.parentNode;
        }

        return false;
    };

    /**
     * Determines whether a node is it's parent's only child.
     * @param {DOMNode} node     the potential only child.
     * @param {Array}   nodeType list of the node types that this child can be.
     * @returns {Boolean} whether the node is only child and optionally of one of the specified nodeTypes.
     */
    this.isOnlyChild = function(node, nodeType){
        if (!node || !node.parentNode || nodeType && nodeType.indexOf(node.nodeType) == -1)
            return false;

        var i, l, cnode, nodes = node.parentNode.childNodes;
        for (i = 0, l = nodes.length; i < l; i++) {
            cnode = nodes[i];
            if (cnode.nodeType == 1 && cnode != node)
                return false;
            if (cnode.nodeType == 3 && !cnode.nodeValue.trim())
                return false;
        }

        return true;
    };

    /**
     * Finds the html representation of an xml node for a certain element.
     *
     * @param {XMLNode} xmlNode  the data element which is represented by the hml element.
     * @param {JMLNode} oComp    the element that has created the representation.
     * @return {HTMLNode} the html node representing the xml node.
     */
    this.findHTMLNode = function(xmlNode, oComp){
        do {
            if (xmlNode.nodeType == 1 && xmlNode.getAttribute(this.xmlIdTag)) {
                return oComp.getNodeFromCache(xmlNode.getAttribute(this.xmlIdTag)
                    + "|" + oComp.uniqueId);
            }
            if (xmlNode == oComp.xmlRoot)
                return null;

            xmlNode = xmlNode.parentNode;
        }
        while (xmlNode && xmlNode.nodeType != 9)

        return null;
    };

    /**
     * Finds the xml data node that is represented by the html node.
     *
     * @param {HTMLNode} htmlNode  the html node representing the an xml node.
     * @return {XMLNode} the xml data element for which the html node is it's representation.
     */
    this.findXMLNode = function(htmlNode){
        if (!htmlNode)
            return false;

        while (htmlNode && htmlNode.nodeType == 1
          && htmlNode.tagName.toLowerCase() != "body"
          && !htmlNode.getAttribute("id")
          || htmlNode && htmlNode.nodeType == 1
          && htmlNode.getAttribute(this.htmlIdTag)
          && htmlNode.getAttribute(this.htmlIdTag).match(/^q/)) {
            if (htmlNode.host && htmlNode.host.oExt == htmlNode)
                return htmlNode.host.xmlRoot;

            htmlNode = htmlNode.parentNode;
        }
        if (!htmlNode || htmlNode.nodeType != 1)
            return false;

        if (htmlNode.tagName.toLowerCase() == "body")
            return false;

        return this.getNode(htmlNode);
    };

    /**
     * @private
     */
    this.getElement = function(parent, nr){
        var nodes = parent.childNodes;
        for (var j = 0, i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 1)
                continue;
            if (j++ == nr)
                return nodes[i];
        }
    };

    /**
     * @private
     */
    this.getModel = function(name){
        return jpf.nameserver.get("model", name);
    };

    /**
     * @private
     */
    this.setModel = function(model){
        jpf.nameserver.register("model", model.data.ownerDocument
            .documentElement.getAttribute(this.xmlDocTag), model);
    };

    /**
     * @private
     */
    this.findModel = function(xmlNode){
        return this.getModel(xmlNode.ownerDocument
            .documentElement.getAttribute(this.xmlDocTag));
    };

    /**
     * Creates an xml node from an xml string.
     *
     * @param {String}  strXml     the xml definition.
     * @param {Boolean} [noError]  whether an exception is thrown the parser throws an error.
     * @return {XMLNode} the created xml node.
     */
    this.getXml = function(strXml, noError, preserveWhiteSpace){
        return jpf.getXmlDom(strXml, noError, preserveWhiteSpace).documentElement;
    };

    this.getXmlId = function(xmlNode){
        return xmlNode.getAttribute(this.xmlIdTag) ||
          this.nodeConnect(jpf.xmldb.getXmlDocId(xmlNode), xmlNode);
    }

    this.nodeCount = {};
    /**
     * @private
     */
    this.nodeConnect = function(documentId, xmlNode, htmlNode, o){
        if (!this.nodeCount[documentId])
            this.nodeCount[documentId] = 0;

        var xmlId;
        xmlId = xmlNode.getAttribute(this.xmlIdTag)
          || xmlNode.setAttribute(this.xmlIdTag, (xmlId = documentId
               + "|" + ++this.nodeCount[documentId])) || xmlId;

        if (!o)
            return xmlId;

        var htmlId = xmlId + "|" + o.uniqueId;
        if (htmlNode)
            htmlNode.setAttribute(this.htmlIdTag, htmlId);

        return htmlId;
    };

    /**
     * @private
     */
    this.addNodeListener = function(xmlNode, o){
        if (!o.$xmlUpdate)
            throw new Error(jpf.formatErrorString(1040, null, 
                "Adding Node listener", 
                "Cannot attach this listener because it doesn't support the \
                 correct interface (this.$xmlUpdate)."));

        var listen = xmlNode.getAttribute(this.xmlListenTag);
        var nodes  = (listen ? listen.split(";") : []);
        var id = String(o.uniqueId);

        if (!nodes.contains(id)) {
            nodes.push(id);
            xmlNode.setAttribute(this.xmlListenTag, nodes.join(";"));
        }

        return xmlNode;
    };

    /**
     * @todo  Use this function when an element really unbinds from a
     *        piece of data and does not uses it for caching
     * @private
     */
    this.removeNodeListener = function(xmlNode, o){
        var listen = xmlNode.getAttribute(this.xmlListenTag);
        var nodes = (listen ? listen.split(";") : []);

        for (var newnodes = [], i = 0; i < nodes.length; i++) {
            if (nodes[i] != o.uniqueId)
                newnodes.push(nodes[i]);
        }

        xmlNode.setAttribute(this.xmlListenTag, newnodes.join(";"));

        return xmlNode;
    };

    //Does this need to be a seperate function??
    /**
     * @private
     */
    this.clearVirtualDataset = function(parentNode){
        var nodes = parentNode.childNodes;
        for (var i = nodes.length - 1; i >= 0; i--)
            parentNode.removeChild(nodes[i]);
    };

    /**
     * @private
     */
    this.createVirtualDataset = function(xmlNode, length, docId) {
        var marker = xmlNode.selectSingleNode("j_marker") || xmlNode.appendChild(xmlNode.ownerDocument.createElement("j_marker"));
        marker.setAttribute("start", "0");

        if (length) {
            marker.setAttribute("end",   length);
            marker.setAttribute("reserved", ++this.nodeCount[docId]);
            this.nodeCount[docId] += length;
        }
    };

    /**
     * Integrates nodes as children of a parent. Optionally attributes are
     * copied as well.
     *
     * @param {XMLNode} xmlNode the data to integrate.
     * @param {XMLNode} parent  the point of integration.
     * @param {Object}  options
     *   Properties:
     *   {Boolean} [copyAttributes] whether the attributes of xmlNode are copied as well.
     *   {Boolean} [clearContents]  whether the contents of parent is cleared.
     *   {Boolean} [start]          This feature is used for the virtual viewport. More information will follow.
     *   {Boolean} [length]         This feature is used for the virtual viewport. More information will follow.
     *   {Boolean} [documentId]     This feature is used for the virtual viewport. More information will follow.
     *   {Boolean} [marker]         This feature is used for the virtual viewport. More information will follow.
     * @return  {XMLNode}  the created xml node
     */
    this.integrate = function(XMLRoot, parentNode, options){
        if (typeof parentNode != "object")
            parentNode = getElementById(parentNode);

        if (options && options.clearContents) {
            //Signal listening elements
            var node, j, i, nodes = parentNode.selectNodes("descendant::node()[@" + this.xmlListenTag + "]");
            for (i = nodes.length - 1; i >= 0; i--) {
                var s = nodes[i].getAttribute(this.xmlListenTag).split(";");
                for (j = s.length - 1; j >= 0; j--) {
                    node = jpf.all[s[j]];
                    if (node.dataParent && node.dataParent.xpath)
                        node.dataParent.parent.signalXmlUpdate[node.uniqueId] = true;
                    else if (node.$model) {
                        node.$listenRoot = jpf.xmldb.addNodeListener(parentNode, node);
                        node.xmlRoot = null; //.load(null)
                    }
                }
            }
            
            //clean parent
            var nodes = parentNode.childNodes;
            for (var i = nodes.length - 1; i >= 0; i--)
                parentNode.removeChild(nodes[i]);
        }

        if (options && options.start) { //Assuming each node is in count
            var reserved, beforeNode, nodes, doc, i, l, marker = options.marker;
            if (!marker){
                //optionally find marker
            }

            //This code assumes that the dataset fits inside this marker

            //Start of marker
            if (marker.getAttribute("start") - options.start == 0) {
                marker.setAttribute("start", options.start + options.length);
                reserved = parseInt(marker.getAttribute("reserved"));
                marker.setAttribute("reserved", reserved + options.length);
                beforeNode = marker;
            }
            //End of marker
            else if (options.start + options.length == marker.getAttribute("end")) {
                marker.setAttribute("end", options.start + options.length);
                beforeNode = marker.nextSibling;
                reserved = parseInt(marker.getAttribute("reserved")) + parseInt(marker.getAttribute("end")) - options.length;
            }
            //Middle of marker
            else {
                var m2 = marker.parentNode.insertBefore(marker.cloneNode(true), marker);
                m2.setAttribute("end", options.start - 1);
                marker.setAttribute("start", options.start + options.length);
                reserved = parseInt(marker.getAttribute("reserved"));
                marker.setAttribute("reserved", reserved + options.length);
                beforeNode = marker;
            }

            nodes = XMLRoot.childNodes;

            if (parentNode.ownerDocument.importNode) {
                doc = parentNode.ownerDocument;
                for (i = 0, l = nodes.length; i < l; i++) {
                    parentNode.insertBefore(doc.importNode(nodes[i], true), beforeNode)
                      .setAttribute(this.xmlIdTag, options.documentId + "|" + (reserved + i));
                }
            }
            else {
                for (i = nodes.length - 1; i >= 0; i--) {
                    parentNode.insertBefore(nodes[0], beforeNode)
                      .setAttribute(this.xmlIdTag, options.documentId + "|" + (reserved + i));
                }
            }
        }
        else
        {
            beforeNode = jpf.getNode(parentNode, [0]);
            nodes      = XMLRoot.childNodes;

            if (parentNode.ownerDocument.importNode) {
                doc = parentNode.ownerDocument;
                for (i = 0, l = nodes.length; i < l; i++)
                    parentNode.insertBefore(doc.importNode(nodes[i], true), beforeNode);
            }
            else
                for (i = nodes.length - 1; i >= 0; i--)
                    parentNode.insertBefore(nodes[0], beforeNode);
        }

        if (options && options.copyAttributes) {
            var attr = XMLRoot.attributes;
            for (i = 0; i < attr.length; i++)
                if (attr[i].nodeName != this.xmlIdTag)
                    parentNode.setAttribute(attr[i].nodeName, attr[i].nodeValue);
        }

        return parentNode;
    };

    /**
     * @private
     * @description  Integrates current xmldb with parent xmldb
     *
     *    - assuming transparency of XMLDOM elements cross windows
     *      with no performence loss.
     */
    this.synchronize = function(){
        this.forkRoot.parentNode.replaceChild(this.root, this.forkRoot);
        this.parent.applyChanges("synchronize", this.root);
    };

    /**
     * Returns a cleaned copy of the passed xml data element.
     *
     * @param {XMLElement} xmlNode the xml element to copy.
     * @return {XMLElement} the copy of the xml element.
     */
    this.copyNode = function(xmlNode){
        return this.clearConnections(xmlNode.cloneNode(true));
    };

    /**
     * Sets the nodeValue of a dom node.
     *
     * @param {XMLElement} xmlNode       the xml node that should receive the nodeValue. When an element node is passed the first text node is set.
     * @param {String}     nodeValue     the value to set.
     * @param {Boolean}    applyChanges  whether the changes are propagated to the databound elements.
     * @param {UndoObj}    undoObj       the undo object that is responsible for archiving the changes.
     */
    this.setNodeValue = function(xmlNode, nodeValue, applyChanges, options){
        var undoObj, xpath, newNodes;
        if (options) {
            undoObj  = options.undoObj;
            xpath    = options.xpath;
            newNodes = options.newNodes;
            
            undoObj.extra.oldValue = options.forceNew
                ? ""
                : jpf.getXmlValue(xmlNode, xpath);

            undoObj.xmlNode        = xmlNode;
            if (xpath)
                xmlNode = jpf.xmldb.createNodeFromXpath(xmlNode, xpath, newNodes, options.forceNew);

            undoObj.extra.appliedNode = xmlNode;
        }
        
        if (xmlNode.nodeType == 1) {
            if (!xmlNode.firstChild)
                xmlNode.appendChild(xmlNode.ownerDocument.createTextNode("-"));

            xmlNode.firstChild.nodeValue = jpf.isNot(nodeValue) ? "" : nodeValue;

            if (applyChanges)
                jpf.xmldb.applyChanges("synchronize", xmlNode, undoObj);
        }
        else {
            xmlNode.nodeValue = jpf.isNot(nodeValue) ? "" : nodeValue;

            if (applyChanges)
                jpf.xmldb.applyChanges("synchronize", xmlNode.parentNode
                    || xmlNode.ownerElement || xmlNode.selectSingleNode(".."),
                    undoObj);
        }
    };

    /**
     * Retrieves the node value of an xml element. When an element node is passed
     * the value of the first text node is returned.
     * @returns {String} the node value found.
     */
    this.getNodeValue = function(xmlNode){
        if (!xmlNode)
            return "";
        return xmlNode.nodeType == 1
            ? (!xmlNode.firstChild ? "" : xmlNode.firstChild.nodeValue)
            : xmlNode.nodeValue;
    };

    /**
     * Retrieves the attribute of an xml node or the first parent node that has
     * that attribute set. If no attribute is set the value is looked for on
     * the appsettings element.
     *
     * @param {XMLElement} xml    the xml node that is the starting point of the search.
     * @param {String}     attr   the name of the attribute.
     * @param {Function}   [func] callback that is run for every node that is searched.
     * @return {String} the found value, or empty string if none was found.
     */
    this.getInheritedAttribute = function(xml, attr, func){
        var result;

        while (xml && xml.nodeType != 11 && xml.nodeType != 9
          && !(result = attr && xml.getAttribute(attr) || func && func(xml))) {
            xml = xml.parentNode;
        }

        return !result && attr && jpf.appsettings
            ? jpf.appsettings.tags[attr]
            : result;
    };

    /**
     * Sets the value of a text node. If the node doesn't exists it is created.
     * Changes are propagated to the databound elements listening for changes
     * on the data changed.
     *
     * @param {XMLElement} pNode     the parent of the text node.
     * @param {String}     value     the value of the text node.
     * @param {String}     [xpath]   the xpath statement which selects the text node.
     * @param {UndoObj}    [undoObj] the undo object that is responsible for archiving the changes.
     */
    this.setTextNode = function(pNode, value, xpath, undoObj){
        var tNode;

        if (xpath) {
            tNode = pNode.selectSingleNode(xpath);
            if (!tNode)
                return;
            pNode = tNode.nodeType == 1 ? tNode : null;
        }
        if (pNode || !tNode) {
            tNode = pNode.selectSingleNode("text()");

            if (!tNode)
                tNode = pNode.appendChild(pNode.ownerDocument.createTextNode(""));//createCDATASection
        }

        //Action Tracker Support
        if (undoObj)
            undoObj.extra.oldValue = tNode.nodeValue;

        //Apply Changes
        tNode.nodeValue = value;

        this.applyChanges("text", tNode.parentNode, undoObj);

        this.applyRSB(["setTextNode", pNode, value, xpath], undoObj);
    };

    /**
     * Sets an attribute on a node. Changes are propagated to the databound
     * elements listening for changes on the data changed.
     *
     * @param {XMLElement} xmlNode   the xml node to set the attribute on.
     * @param {String}     name      the name of the attribute.
     * @param {String}     value     the value of the attribute.
     * @param {String}     [xpath]   the xpath statement to select the attribute.
     * @param {UndoObj}    [undoObj] the undo object that is responsible for archiving the changes.
     */
    this.setAttribute = function(xmlNode, name, value, xpath, undoObj){
        //if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;
        //Apply Changes
        (xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).setAttribute(name, value);
        this.applyChanges("attribute", xmlNode, undoObj);
        this.applyRSB(["setAttribute", xmlNode, name, value, xpath], undoObj);
    };

    /**
     * Removes an attribute of an xml node. Changes are propagated to the
     * databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} xmlNode   the xml node to delete the attribute from
     * @param {String}     name      the name of the attribute.
     * @param {String}     [xpath]   the xpath statement to select the attribute.
     * @param {UndoObj}    [undoObj] the undo object that is responsible for archiving the changes.
     */
    this.removeAttribute = function(xmlNode, name, xpath, undoObj){
        //if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;

        //Action Tracker Support
        if (undoObj) undoObj.name = name;

        //Apply Changes
        (xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).removeAttribute(name);
        this.applyChanges("attribute", xmlNode, undoObj);

        this.applyRSB(["removeAttribute", xmlNode, name, xpath], undoObj);
    };

    /**
     * Replace one node with another. Changes are propagated to the
     * databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} oldNode   the xml node to remove.
     * @param {XMLElement} newNode   the xml node to set.
     * @param {String}     [xpath]   the xpath statement to select the attribute.
     * @param {UndoObj}    [undoObj] the undo object that is responsible for archiving the changes.
     */
    this.replaceNode = function(oldNode, newNode, xpath, undoObj){
        //if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;

        //Apply Changes
        if (xpath)
            oldNode = oldNode.selectSingleNode(xpath);

        //Action Tracker Support
        if (undoObj) {
            undoObj.oldNode = oldNode;
            undoObj.xmlNode = newNode;
        }

        oldNode.parentNode.replaceChild(newNode, oldNode);
        this.copyConnections(oldNode, newNode);

        this.applyChanges("replacechild", newNode, undoObj);

        this.applyRSB(["replaceChild", oldNode, newNode, xpath], undoObj);
    };

    /**
     * Creates a new element under a parent xml node. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} pNode       the parent xml node to add the new element to.
     * @param {String}     tagName     the tagName of the xml element to add.
     * @param {Array}      attr        list of the attributes to set. Each item is another array with the name and value.
     * @param {XMLElement} beforeNode  the xml node which indicates the insertion point.
     * @param {String}     [xpath]     the xpath statement to select the attribute.
     * @param {UndoObj}    [undoObj]   the undo object that is responsible for archiving the changes.
     */
    this.addChildNode = function(pNode, tagName, attr, beforeNode, undoObj){
        //Create New Node
        var xmlNode = pNode.insertBefore(pNode.ownerDocument
            .createElement(tagName), beforeNode);

        //Set Attributes
        for (var i = 0; i < attr.length; i++)
            xmlNode.setAttribute(attr[i][0], attr[i][1]);

        //Action Tracker Support
        if (undoObj)
            undoObj.extra.addedNode = xmlNode;

        this.applyChanges("add", xmlNode, undoObj);

        this.applyRSB(["addChildNode", pNode, tagName, attr, beforeNode], undoObj);

        return xmlNode;
    };

    /**
     * Appends an xml node to a parent. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} pNode       the parent xml node to add the element to.
     * @param {XMLElement} xmlNode     the xml node to insert.
     * @param {XMLElement} beforeNode  the xml node which indicates the insertion point.
     * @param {Boolean}    unique      whether the parent can only contain one element with a certain tagName.
     * @param {String}     [xpath]     the xpath statement to select the parent node.
     * @param {UndoObj}    [undoObj]   the undo object that is responsible for archiving the changes.
     */
    this.appendChild = function(pNode, xmlNode, beforeNode, unique, xpath, undoObj){
        if (unique && pNode.selectSingleNode(xmlNode.tagName))
            return false;

        if (undoObj)
            this.clearConnections(xmlNode);

        if (jpf.isSafari && pNode.ownerDocument != xmlNode.ownerDocument)
            xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes

        //Add xmlNode to parent pNode or one selected by xpath statement

        if (xpath) {
            var addedNodes = [];
            var pNode = this.createNodeFromXpath(pNode, xpath, addedNodes);
            if (addedNodes.length) {
                pNode.appendChild(xmlNode);
                while(addedNodes.length) {
                    if (pNode == addedNodes.pop() && addedNodes.length)
                        pNode = pNode.parentNode;
                }
            }
        }

        pNode.insertBefore(xmlNode, beforeNode);

        //detect if xmlNode should be removed somewhere else
        //- [17-2-2004] changed pNode (2nd arg applychange) into xmlNode

        this.applyChanges("add", xmlNode, undoObj);

        this.applyRSB(["appendChild", pNode, xmlNode.xml, beforeNode, unique, xpath], undoObj);

        return xmlNode;
    };

    /**
     * Moves an xml node to a parent node. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} pNode       the new parent xml node of the node.
     * @param {XMLElement} xmlNode     the xml node to move.
     * @param {XMLElement} beforeNode  the xml node which indicates the insertion point.
     * @param {String}     [xpath]     the xpath statement to select the parent node.
     * @param {UndoObj}    [undoObj]   the undo object that is responsible for archiving the changes.
     */
    this.moveNode = function(pNode, xmlNode, beforeNode, xpath, undoObj){
        //Action Tracker Support
        if (!undoObj)
            undoObj = {extra:{}};

        undoObj.extra.oldParent  = xmlNode.parentNode;
        undoObj.extra.beforeNode = xmlNode.nextSibling;
        undoObj.extra.parent     = (xpath ? pNode.selectSingleNode(xpath) : pNode);

        this.applyChanges("move-away", xmlNode, undoObj);

        this.applyRSB(["moveNode", pNode, xmlNode, beforeNode, xpath], undoObj); //note: important that transport of rsb is async

        //Set new id if the node change document (for safari this should be fixed)
        if (!jpf.isSafari
          && jpf.xmldb.getXmlDocId(xmlNode) != jpf.xmldb.getXmlDocId(pNode)) {
            xmlNode.removeAttributeNode(xmlNode.getAttributeNode(this.xmlIdTag));
            this.nodeConnect(jpf.xmldb.getXmlDocId(pNode), xmlNode);
        }

        if (jpf.isSafari && pNode.ownerDocument != xmlNode.ownerDocument)
            xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes

        undoObj.extra.parent.insertBefore(xmlNode, beforeNode);
        this.applyChanges("move", xmlNode, undoObj);
    };

    /**
     * Removes an xml node from it's parent. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} xmlNode     the xml node to remove from the dom tree.
     * @param {String}     [xpath]     the xpath statement to select the parent node.
     * @param {UndoObj}    [undoObj]   the undo object that is responsible for archiving the changes.
     */
    this.removeNode = function(xmlNode, xpath, undoObj){
        if (xpath)
            xmlNode = xmlNode.selectSingleNode(xpath);

        //ActionTracker Support
        if (undoObj) {
            undoObj.extra.parent      = xmlNode.parentNode;
            undoObj.extra.removedNode = xmlNode;
            undoObj.extra.beforeNode  = xmlNode.nextSibling;
        }

        this.applyRSB(["removeNode", xmlNode, xpath], undoObj); //note: important that transport of rsb is async

        //Apply Changes
        this.applyChanges("remove", xmlNode, undoObj);
        var p = xmlNode.parentNode;
        p.removeChild(xmlNode);
        this.applyChanges("redo-remove", xmlNode, null, p);//undoObj
    };

    /**
     * Removes a list of xml nodes from their parent. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {Array}   xmlNodeList list of xml nodes to remove.
     * @param {UndoObj} [undoObj]   the undo object that is responsible for archiving the changes.
     */
    this.removeNodeList = function(xmlNodeList, undoObj){
        //if(xpath) xmlNode = xmlNode.selectSingleNode(xpath);
        for (var rData = [], i = 0; i < xmlNodeList.length; i++) { //This can be optimized by looping nearer to xmlUpdate
            //ActionTracker Support
            if (undoObj) {
                rData.push({
                    pNode      : xmlNodeList[i].parentNode,
                    removedNode: xmlNodeList[i],
                    beforeNode : xmlNodeList[i].nextSibling
                });
            }

            //Apply Changes
            this.applyChanges("remove", xmlNodeList[i], undoObj);
            var p = xmlNodeList[i].parentNode;
            p.removeChild(xmlNodeList[i]);
            this.applyChanges("redo-remove", xmlNodeList[i], null, p);//undoObj
        }

        if (undoObj)
            undoObj.extra.removeList = rData;

        this.applyRSB(["removeNodeList", xmlNodeList, null], undoObj);
    };

    /**
     * Looks for listeners and executes their __xmlUpdate methods.
     * @private
     */
    var notifyQueue = {}, notifyTimer;
    this.applyChanges = function(action, xmlNode, undoObj, nextloop){
        if (typeof jpf.offline != "undefined" && jpf.offline.models.enabled
          && jpf.offline.models.realtime) {
            var model = jpf.nameserver.get("model", jpf.xmldb.getXmlDocId(xmlNode));
            if (model) jpf.offline.models.markForUpdate(model);
        }

        if (undoObj && !undoObj.xmlNode) //@todo are we sure about this?
            undoObj.xmlNode = xmlNode;

        //Set Variables
        var oParent  = nextloop;
        var loopNode = (xmlNode.nodeType == 1 ? xmlNode : xmlNode.parentNode);

        var xmlId = xmlNode.getAttribute(this.xmlIdTag);

        if (!this.delayUpdate && "|remove|move-away|".indexOf("|" + action + "|") > -1)
            this.notifyQueued(); //empty queue

        var listen, uIds, i, j, hash, info, jmlNode, runTimer, found;
        while (loopNode && loopNode.nodeType != 9) {
            //Get List of Node listeners ID's
            listen = loopNode.getAttribute(this.xmlListenTag);

            if (listen) {
                uIds = listen.split(";");

                for (i = 0; i < uIds.length; i++) {
                    hash = notifyQueue[uIds[i]];
                    if (!hash)
                        notifyQueue[uIds[i]] = hash = [];

                    // Filtering
                    if ("|update|attribute|text|".indexOf("|"
                      + action + "|") > -1) {
                        found = false;
                        for (j = 0; j < hash.length; j++) {
                            if (hash[j] && xmlNode == hash[j][1]
                              && "|update|attribute|text|"
                              .indexOf("|" + hash[j][0] + "|") > -1) {
                                hash[j] = null;
                                found = true;
                                continue;
                            }
                        }

                        hash.push(["update", xmlNode, loopNode, undoObj, oParent]);
                        runTimer = true;
                        continue;
                    }

                    if (!this.delayUpdate && "|remove|move-away|add|".indexOf("|" + action + "|") > -1) {
                        jmlNode = jpf.lookup(uIds[i]);
                        if (jmlNode)
                            jmlNode.$xmlUpdate(action, xmlNode,
                                loopNode, undoObj, oParent);
                    }
                    else {
                        hash.push([action, xmlNode, loopNode, undoObj, oParent]);
                        runTimer = true;
                    }
                }
            }

            //Go one level up
            loopNode = loopNode.parentNode || nextloop;
            if (loopNode == nextloop)
                nextloop = null;
        }

        if (undoObj && !this.delayUpdate) {
            //Ok this was an action let's not delay execution
            jpf.xmldb.notifyQueued();
        }
        else if (runTimer) {
            clearTimeout(notifyTimer);
            notifyTimer = setTimeout(function(){
                jpf.xmldb.notifyQueued();
            });
        }
    };

    /**
     *  @todo in actiontracker - add stack auto purging
     *        - when undo item is purged which was a removed, remove cache item
     *  @todo shouldn't the removeNode method remove all listeners?
     *  @todo rename to processQueue
     *  @private
     */
    this.notifyQueued = function(){
        clearTimeout(notifyTimer);
        
        for (var uId in notifyQueue) {
            var q = notifyQueue[uId];
            jmlNode = jpf.lookup(uId);
            if (!jmlNode || !q)
                continue;

            //Check if component is just waiting for data to become available
            if (jmlNode.$listenRoot) {
                var model = jmlNode.getModel();

                if (!model)
                    throw new Error(jpf.formatErrorString(this,
                        "Notifying Component of data change",
                        "Component without a model is listening for changes",
                        jmlNode.$jml));

                var xpath   = model.getXpathByJmlNode(jmlNode);
                var xmlRoot = xpath
                    ? model.data.selectSingleNode(xpath)
                    : model.data;
                if (xmlRoot) {
                    jpf.xmldb.removeNodeListener(jmlNode.$listenRoot, jmlNode);
                    jmlNode.$listenRoot = null;
                    jmlNode.load(xmlRoot);
                }

                continue;
            }

            //Run queue items
            for (var i = 0; i < q.length; i++) {
                if (!q[i])
                    continue;

                //Update xml data
                jmlNode.$xmlUpdate.apply(jmlNode, q[i]);
            }
        }

        notifyQueue = {}; // update shouldn't add anything to the queue
    }

    /**
     * @private
     */
    this.notifyListeners = function(xmlNode){
        //This should be done recursive
        var listen = xmlNode.getAttribute(jpf.xmldb.xmlListenTag);
        if (listen) {
            listen = listen.split(";");
            for (var j = 0; j < listen.length; j++) {
                jpf.lookup(listen[j]).$xmlUpdate("synchronize", xmlNode, xmlNode);
                //load(xmlNode);
            }
        }
    };

    /**
     * Sents Message through transport to tell remote databound listeners
     * that data has been changed
     * @private
     */
    this.applyRSB = function(args, undoObj){
        if (this.disableRSB)
            return;

        var xmlNode = args[1] && args[1].length && args[1][0] || args[1];
        var model = jpf.nameserver.get("model", jpf.xmldb.getXmlDocId(xmlNode));
        if (!model) {
            if (!jpf.nameserver.getAll("remove").length)
                return;

            jpf.console.warn("Could not find model for Remote SmartBinding connection, not sending change");
            return;
        }

        if (!model.rsb) return;

        // Add the messages to the undo object
        if (undoObj)
            model.rsb.queueMessage(args, model, undoObj);
        // Or sent message now
        else
            model.rsb.sendChange(args, model);

    };

    /**
     * @private
     */
    this.copyConnections = function(fromNode, toNode){
        //This should copy recursive
        try {
            toNode.setAttribute(this.xmlListenTag, fromNode.getAttribute(this.xmlListenTag));
            toNode.setAttribute(this.xmlIdTag, fromNode.getAttribute(this.xmlIdTag));
        }
        catch (e) {}
    };

    /**
     * @private
     */
    this.clearConnections = function(xmlNode){
        try {
            var i, nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlListenTag + "]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute(this.xmlListenTag);
            nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlIdTag + "]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute(this.xmlIdTag);
            nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlDocTag + "]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute(this.xmlDocTag);
            nodes = xmlNode.selectNodes("descendant-or-self::node()[@j_loaded]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute("j_loaded");
            // var nodes = xmlNode.selectNodes("descendant-or-self::node()[@j_selection]");
            // for (var i = nodes.length - 1; i >= 0; i--)
            //     nodes[i].removeAttributeNode(nodes[i].getAttributeNode("j_selection"));
        }
        catch (e) {}

        return xmlNode;
    };

    /**
     * Returns a string version of the xml data element.
     *
     * @param {XMLElement} xmlNode the xml element to serialize.
     * @return {String} the serilized version of the xml element.
     */
    this.serializeNode = function(xmlNode){
        var xml = this.clearConnections(xmlNode.cloneNode(true));
        return xml.xml || xml.serialize();
    };

    /**
     * Unbind all Javeline Elements from a certain Form
     * @private
     */
    this.unbind = function(frm){
        //Loop through objects of all jpf
        for (var lookup = {}, i = 0; i < frm.jpf.all.length; i++)
            if (frm.jpf.all[i] && frm.jpf.all[i].unloadBindings)
                lookup[frm.jpf.all[i].unloadBindings()] = true;

        //Remove Listen Nodes
        for (var k = 0; k < xmlDocLut.length; k++) {
            if (!xmlDocLut[k]) continue;

            var Nodes = xmlDocLut[k].selectNodes("//self::node()[@"
                + this.xmlListenTag + "]");

            //Loop through Nodes and rebuild listen array
            for (var i = 0; i < Nodes.length; i++) {
                var listen = Nodes[i].getAttribute(this.xmlListenTag).split(";");
                for (var nListen = [], j = 0; j < listen.length; j++)
                    if (!lookup[listen[j]])
                        nListen.push(listen[j]);

                //Optimization??
                if (nListen.length != listen.length)
                    Nodes[i].setAttribute(this.xmlListenTag, nListen.join(";"));
            }
        }
    };

    /**
     * Executes an xpath expression on any dom node. This is especially useful
     * for dom nodes that don't have a good native xpath processor such as html
     * in some versions of internet explorer and xml in webkit.
     *
     * @param {String}  sExpr        the xpath expression.
     * @param {DOMNode} contextNode  the xml node that is subject to the query.
     * @returns {Array} list of xml nodes found. The list can be empty.
     */
    this.selectNodes = function(sExpr, contextNode){
        if (contextNode && (jpf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style))
            return contextNode.selectNodes(sExpr); //IE55
        //if (contextNode.ownerDocument != document)
        //    return contextNode.selectNodes(sExpr);

        return jpf.XPath.selectNodes(sExpr, contextNode)
    };

    /**
     * Executes an xpath expression on any dom node. This is especially useful
     * for dom nodes that don't have a good native xpath processor such as html
     * in some versions of internet explorer and xml in webkit. This function
     * Only returns the first node found.
     *
     * @param {String}  sExpr        the xpath expression.
     * @param {DOMNode} contextNode  the dom node that is subject to the query.
     * @returns {XMLNode} the dom node found or null if none was found.
     */
    this.selectSingleNode = function(sExpr, contextNode){
        if (contextNode && (jpf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style))
            return contextNode.selectSingleNode(sExpr); //IE55
        //if (contextNode.ownerDocument != document)
        //    return contextNode.selectSingleNode(sExpr);

        var nodeList = this.selectNodes(sExpr + (jpf.isIE ? "" : "[1]"),
            contextNode ? contextNode : null);
        return nodeList.length > 0 ? nodeList[0] : null;
    };

    /**** General XML Handling ****/

    /**
     * Creates an xml node based on an xpath statement.
     *
     * @param {DOMNode} contextNode  the dom node that is subject to the query.
     * @param {String}  xPath        the xpath query.
     * @param {Array}   [addedNodes] this array is filled with the nodes added.
     * @param (Boolean) [forceNew]   wether a new node is always created.
     * @return {DOMNode} the last element found.
     * @todo generalize this to include attributes in if format []
     */
    this.createNodeFromXpath = function(contextNode, xPath, addedNodes, forceNew){
        var xmlNode, foundpath = "", paths = xPath.split("\|")[0].split("/");
        if (!forceNew && (xmlNode = contextNode.selectSingleNode(xPath)))
            return xmlNode;
        
        var len = paths.length -1;
        if (forceNew) {
            if (paths[len].trim().match(/^\@(.*)$|^text\(\)$/))
                len--;
        }
        
        for (var addedNode, isAdding = false, i = 0; i < len; i++) {
            if (!isAdding && contextNode.selectSingleNode(foundpath
              + (i != 0 ? "/" : "") + paths[i])) {
                foundpath += (i != 0 ? "/" : "") + paths[i];// + "/";
                continue;
            }
            
            //Temp hack 
            var isAddId = paths[i].match(/(\w+)\[@([\w-]+)=(\w+)\]/);
            if (!isAddId && paths[i].match(/\@|\[.*\]|\(.*\)/)) {
                throw new Error(jpf.formatErrorString(1041, this, 
                    "Select via xPath", 
                    "Could not use xPath to create xmlNode: " + xPath));
            }
            if (!isAddId && paths[i].match(/\/\//)) {
                throw new Error(jpf.formatErrorString(1041, this, 
                    "Select via xPath", 
                    "Could not use xPath to create xmlNode: " + xPath));
            }

            if (isAddId)
                paths[i] = isAddId[1];

            isAdding = true;
            addedNode = contextNode.selectSingleNode(foundpath || ".")
                .appendChild(contextNode.ownerDocument.createElement(paths[i]));

            if (isAddId) {
                addedNode.setAttribute(isAddId[2], isAddId[3]);
                foundpath += (foundpath ? "/" : "") + isAddId[0];// + "/";
            }
            else
                foundpath += (foundpath ? "/" : "") + paths[i];// + "/";

            if (addedNodes)
                addedNodes.push(addedNode);
        }

        if (!foundpath)
            foundpath = ".";

        var newNode, lastpath = paths[len];
        do {
            if (lastpath.match(/^\@(.*)$/)) {
                (newNode || contextNode.selectSingleNode(foundpath))
                    .setAttributeNode(newNode = contextNode.ownerDocument.createAttribute(RegExp.$1));
            }
            else if (lastpath.trim() == "text()") {
                newNode = (newNode || contextNode.selectSingleNode(foundpath))
                    .appendChild(contextNode.ownerDocument.createTextNode(""));
            }
            else {
                var hasId = lastpath.match(/(\w+)\[@([\w-]+)=(\w+)\]/);
                if (hasId) lastpath = hasId[1];
                newNode = (newNode || contextNode.selectSingleNode(foundpath))
                    .appendChild(contextNode.ownerDocument.createElement(lastpath));
                if (hasId)
                    newNode.setAttribute(hasId[2], hasId[3]);
                
                if (addedNodes)
                    addedNodes.push(newNode);
            }
            
            foundpath += (foundpath ? "/" : "") + paths[len];
        } while((lastpath = paths[++len]));

        return newNode;
    };

    /**
     * @private
     * @todo xml doc leakage
     */
    this.getXmlDocId = function(xmlNode, model){
        var docEl = xmlNode.ownerDocument.documentElement;
        if (!this.isChildOf(docEl, xmlNode))
            docEl = xmlNode;

        var docId = (docEl || xmlNode).getAttribute(this.xmlDocTag)
            || xmlDocLut.indexOf(docEl || xmlNode.ownerDocument || xmlNode);

        if (docId && docId > -1)
            return docId;

        docId = xmlDocLut.push(docEl || xmlNode.ownerDocument || xmlNode) - 1;
        if (docEl)
            docEl.setAttribute(this.xmlDocTag, docId);

        if (model)
            jpf.nameserver.register("model", docId, model);

        return xmlDocLut.length - 1;
    };

    /**
     * @private
     */
    this.getBindXmlNode = function(xmlRootNode){
        if (typeof xmlRootNode != "object")
            xmlRootNode = jpf.getXmlDom(xmlRootNode);
        if (xmlRootNode.nodeType == 9)
            xmlRootNode = xmlRootNode.documentElement;
        if (xmlRootNode.nodeType == 3 || xmlRootNode.nodeType == 4)
            xmlRootNode = xmlRootNode.parentNode;
        if (xmlRootNode.nodeType == 2)
            xmlRootNode = xmlRootNode.ownerElement 
                || xmlRootNode.parentNode 
                || xmlRootNode.selectSingleNode("..");

        return xmlRootNode;
    };

    /**
     * @private
     */
    this.convertMethods = {
        /**
         * Gets a JSON object containing all the name/value pairs of the elements
         * using this element as it's validation group.
         *
         * @return  {Object}  the created JSON object
         */
        "json": function(xml){
            var result = {}, filled = false, nodes = xml.childNodes;
            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i].nodeType != 1)
                    continue;
                var name = nodes[i].tagName;
                filled = true;

                //array
                var sameNodes = xml.selectNodes(x);
                if (sameNodes.length > 1) {
                    var z = [];
                    for (var j = 0; j < sameNodes.length; j++) {
                        z.push(this.json(sameNodes[j], result));
                    }
                    result[name] = z;
                }
                else //single value
                    result[name] = this.json(sameNodes[j], result);
            }

            return filled ? result : jpf.getXmlValue(xml, "text()");
        },

        "cgivars": function(xml, basename){
            if (!basename) 
                basename = "";
            
            var str = [], value, nodes = xml.childNodes, done = {};
            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i].nodeType != 1)
                    continue;
                var name = nodes[i].tagName;
                if (done[name])
                    continue;

                //array
                var sameNodes = xml.selectNodes(name);
                if (sameNodes.length > 1) {
                    done[name] = true;
                    for (var j = 0; j < sameNodes.length; j++) {
                        value = this.cgivars(sameNodes[j],
                            basename + name + "[" + j + "]");
                        if (value)
                            str.push(value);
                    }
                }
                else { //single value
                    value = this.cgivars(nodes[i], basename + name);
                    if (value)
                        str.push(value);
                }
            }

            var attr = xml.attributes;
            for (i = 0; i < attr.length; i++) {
                if (attr[i].nodeValue) {
                    if (basename) 
                        str.push(basename + "[" + attr[i].nodeName + "]="
                            + encodeURIComponent(attr[i].nodeValue));
                    else
                        str.push(attr[i].nodeName + "="
                            + encodeURIComponent(attr[i].nodeValue));
                }
            }

            if (str.length)
                return str.join("&");

            value = jpf.getXmlValue(xml, "text()");
            if (basename && value)
                return basename + "=" + encodeURIComponent(value);
        },

        "cgiobjects": function(xml, basename, isSub){
            if (!basename)
                basename = "";
            
            var str = [], value, nodes = xml.childNodes, done = {};
            for (var i = 0; i < nodes.length; i++) {
                var node = nodes[i];
                if (node.nodeType != 1)
                    continue;

                var name = node.tagName; //@hack
                if (name == "revision")
                    continue;

                var isOnlyChild = jpf.xmldb.isOnlyChild(node.firstChild, [3,4]);
                var count       = 0;

                //array
                if (!node.attributes.length && !isOnlyChild) {
                    var lnodes = node.childNodes;
                    for (var nm, j = 0, l = lnodes.length; j < l; j++) {
                        if (lnodes[j].nodeType != 1)
                            continue;
                        
                        nm = basename + (isSub ? "[" : "") + name + (isSub ? "]" : "") + "[" + count++ + "]";
                        value = this.cgiobjects(lnodes[j], nm, true);
                        if (value)
                            str.push(value);
                            
                        if (jpf.xmldb.isOnlyChild(lnodes[j].firstChild, [3,4]))
                            str.push(nm + "[" + lnodes[j].tagName + "]" + "=" 
                                + encodeURIComponent(lnodes[j].firstChild.nodeValue));
                        
                        var a, attr = lnodes[j].attributes;
                        for (k = 0; k < attr.length; k++) {
                            if (!(a = attr[k]).nodeValue)
                                continue;
                            
                            str.push(nm + "[" + a.nodeName + "]=" 
                                + encodeURIComponent(a.nodeValue));
                        }
                    }
                }
                //single value
                else {
                    if (isOnlyChild)
                        str.push(basename + (isSub ? "[" : "") + name + (isSub ? "]" : "") + "=" 
                            + encodeURIComponent(node.firstChild.nodeValue));
                    
                    var a, attr = node.attributes;
                    for (j = 0; j < attr.length; j++) {
                        if (!(a = attr[j]).nodeValue)
                            continue;
                        
                        str.push(basename + (isSub ? "[" : "") + name + "_" + a.nodeName + (isSub ? "]" : "") + "=" 
                            + encodeURIComponent(a.nodeValue));
                    }
                }
            }
            
            if (!isSub && xml.getAttribute("id"))
                str.push("id=" + encodeURIComponent(xml.getAttribute("id")));

            if (str.length)
                return str.join("&");
        }
    };

    /**
     * Converts xml to another format.
     *
     * @param {XMLElement} xml  the xml element to convert.
     * @param {String}     to   the format to convert the xml to.
     *   Possible values:
     *   json       converts to a json string
     *   cgivars    converts to cgi string.
     *   cgiobjects converts to cgi objects
     * @return {String} the result of the conversion.
     */
    this.convertXml = function(xml, to){
        return this.convertMethods[to](xml);
    };

    /**
     * Returns the first text or cdata child of an xml element.
     *
     * @param {XMLElement} x the xml node to search.
     * @return {XMLNode} the found xml node, or null.
     */
    this.getTextNode = function(x){
        for (var i = 0; i < x.childNodes.length; i++) {
            if (x.childNodes[i].nodeType == 3 || x.childNodes[i].nodeType == 4)
                return x.childNodes[i];
        }
        return false;
    };


    /**
     * @private
     */
    this.getBoundValue = function(jmlNode, xmlRoot, applyChanges){
        if (!xmlRoot && !jmlNode.xmlRoot)
            return "";

        var xmlNode = !jmlNode.nodeFunc
            ? xmlRoot.selectSingleNode(jmlNode.getAttribute("ref"))
            : jmlNode.getNodeFromRule("value", jmlNode.xmlRoot);

        return xmlNode ? this.getNodeValue(xmlNode) : "";
    };

    /**
     * @private
     */
    this.getArrayFromNodelist = function(nodelist){
        for (var nodes = [], j = 0; j < nodelist.length; j++)
            nodes.push(nodelist[j]);
        return nodes;
    };
};

/**
 * @alias XmlDatabase#getXml
 */
jpf.getXml = function(){
    return jpf.xmldb.getXml.apply(jpf.xmldb, arguments);
};

jpf.Init.run('XmlDatabase');



/*FILEHEAD(/var/lib/jpf/src/core/datainstructions.js)SIZE(-1077090856)TIME(1238944816)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * @term datainstruction Data instructions offer a single and consistent way for
 * storing and retrieving
 * data from different data sources. For instance from a webserver using REST
 * or RPC, or from local data sources such as gears, air, o3, html5, as well as
 * from in memory sources from javascript or cookies. There is often an xml
 * element which is relevant to storing information. This element can be
 * accessed using xpath statements in the data instruction using curly braces.
 *
 * Syntax:
 * Using data instructions to retrieve data
 * <code>
 * get="name_of_model"
 * get="name_of_model:xpath"
 * get="#element"
 * get="#element:select"
 * get="#element:select:xpath"
 * get="#element"
 * get="#element:choose"
 * get="#element:choose:xpath"
 * get="#element::xpath"
 * get="url:example.jsp"
 * get="url:http://www.bla.nl?blah=10&foo={@bar}&example=[10+5]"
 * get="rpc:comm.submit('abc', {@bar})"
 * get="call:submit('abc', {@bar})"
 * get="xmpp:login(username, password)"
 * get="webdav:getRoot()"
 * get="eval:10+5"
 * </code>
 *
 * Syntax:
 * Using data instructions to store data
 * <code>
 * set="url:http://www.bla.nl?blah=10&foo={/bar}&example=[10+5]"
 * set="url.post:http://www.bla.nl?blah=10&foo={/bar}&example=[10+5]"
 * set="rpc:comm.submit('abc', {/bar})"
 * set="call:submit('abc', {/bar})"
 * set="eval:example=5"
 * set="cookie:name.subname = {.}"
 * </code>
 */

/**
 * @private
 */
jpf.namespace("datainstr", {
    "call" : function(xmlContext, options, callback){
        var parsed = options.parsed || this.parseInstructionPart(
            options.instrData.join(":"), xmlContext, options.args, options);

        if (!self[parsed.name])
            throw new Error(jpf.formatErrorString(0, null,
                "Saving/Loading data", "Could not find Method '" + q[0] + "' \
                in process instruction '" + instruction + "'"));

        if (options.preparse) {
            options.parsed = parsed;
            options.preparse = -1;
            return;
        }

        //Call method
        var retvalue = self[parsed.name].apply(null, parsed.arguments);

        //Call callback
        if (callback)
            callback(retvalue, jpf.SUCCESS, options);
    },

    "eval" : function(xmlContext, options, callback){
        var parsed = options.parsed
            || this.parseInstructionPart(
                "(" + options.instrData.join(":") + ")", xmlContext,
                options.args, options);

        if (options.preparse) {
            options.parsed = parsed;
            options.preparse = -1;
            return;
        }

        try {
            var retvalue = eval(parsed.arguments[0]);
        }
        catch(e) {
            throw new Error(jpf.formatErrorString(0, null, "Saving data",
                "Could not execute javascript code in process instruction \
                '" + instruction + "' with error " + e.message));
        }

        if (callback)
            callback(retvalue, jpf.SUCCESS, options);
    }

    ,cookie: function(xmlContext, options, callback){
        var query  = options.instrData.join(":");
        var parsed = options.parsed || query.indexOf("=") > -1
            ? this.parseInstructionPart(query.replace(/\s*=\s*/, "(") + ")",
                xmlContext, options.args, options)
            : {name: query, args: options.args || [xmlContext]};

        if (options.preparse) {
            options.parsed = parsed;
            options.preparse = -1;
            return;
        }

        var value;
        if (options.isGetRequest) {
            value = jpf.getcookie(parsed.name);
            value = value ? jpf.unserialize(value) || "" : "";
        }
        else {
            value = jpf.setcookie(parsed.name,
                jpf.serialize(parsed.args[0]));
        }

        if (callback)
            callback(value || parsed.args[0], jpf.SUCCESS, options);
    }

});


/**
 * Stores data using a {@link term.datainstruction data instruction}.
 *
 * @param {String}      instruction  the {@link term.datainstruction data instruction} to be used to store the data.
 * @param {XMLElement}  [xmlContext] the subject of the xpath queries
 * @param {Object}      [options]    the options for this instruction
 *   Properties:
 *   {Boolean} multicall    whether this call should not be executed immediately but saved for later sending using the purge() command.
 *   {mixed}   userdata     any data that is useful to access in the callback function.
 *   {Array}   args         the arguments of the call, overriding any specified in the data instruction.
 * @param {Function}    [callback]   the code that is executed when the call returns, either successfully or not.
 */
jpf.saveData = function(instruction, xmlContext, options, callback){
    if (!instruction) return false;

    if (!options) options = {};
    options.instrData = instruction.split(":");
    options.instrType = options.instrData.shift();
    
    var instrType = options.instrType.indexOf("url.") == 0
        ? "url"
        : options.instrType;

    options.instruction = instruction;

    if (!this.datainstr[instrType])
        throw new Error(jpf.formatErrorString(0, null,
            "Processing a data instruction",
            "Unknown data instruction format: " + instrType));

    this.datainstr[instrType].call(this, xmlContext, options, callback);
};

/**
 * Retrieves data using a {@link term.datainstruction data instruction}.
 * Example:
 * Several uses for a data instruction
 * <code>
 *  <!-- loading jml from an xml file -->
 *  <j:bar jml="url:morejml.xml" />
 *
 *  <j:bindings>
 *    <!-- loads data using an remote procedure protocol -->
 *    <j:load   get = "rpc:comm.getData()" />
 *
 *    <!-- inserts data using an remote procedure protocol -->
 *    <j:insert get = "rpc:comm.getSubData({@id})" />
 *  </j:bindings>
 *
 *  <j:actions>
 *    <!-- notifies the server that a file is renamed -->
 *    <j:rename set = "url:update_file.jsp?id={@id}&name={@name}" />
 *
 *    <!-- adds a node by retrieving it's xml from the server. -->
 *    <j:add    get = "url:new_user.xml" />
 *  </j:actions>
 *
 *  <!-- creates a model which is loaded into a list -->
 *  <j:list model="webdav:getRoot()" />
 *
 *  <!-- loads data into a model and when submitted sends the altered data back -->
 *  <j:model load="url:load_contact.jsp" submission="save_contact.jsp" />
 * </code>
 *
 * @param {String}      instruction  the {@link term.datainstruction data instruction} to be used to retrieve the data.
 * @param {XMLElement}  [xmlContext] the subject of the xpath queries
 * @param {Object}      [options]    the options for this instruction
 *   Properties:
 *   {Boolean} multicall    whether this call should not be executed immediately but saved for later sending using the purge() command.
 *   {mixed}   userdata     any data that is useful to access in the callback function.
 *   {mixed}   data         data to use in the call
 *   {Array}   args         the arguments of the call, overriding any specified in the data instruction.
 * @param {Function}    [callback]   the code that is executed when the call returns, either successfully or not.
 */
//instrType, data, xmlContext, callback, multicall, userdata, arg, isGetRequest
jpf.getData = function(instruction, xmlContext, options, callback){
    var instrParts = instruction.match(/^(.*?)(?:\!\{(.*)$|$)/);//\[([^\]\[]*)\]
    var operators  = instrParts[2] ? instrParts[2].splitSafe("\}\s*,|,") : "";//\{|

    var gCallback  = function(data, state, extra){
        if (state != jpf.SUCCESS)
            return callback(data, state, extra);

        operators[2] = data;
        if (operators[0] && data) {
            if (typeof data == "string")
                data = jpf.xmldb.getXml(data);

            extra.data = data;
            data = data.selectSingleNode(operators[0]);

            //Change this to warning?
            if (!data) {
                throw new Error(jpf.formatErrorString(0, null,
                    "Loading new data", "Could not load data by doing \
                    selection on it using xPath: '" + operators[0] + "'."));
            }
        }

        extra.userdata = operators;
        return callback(data, state, extra);
    }

    if (!options) options = {};
    options.isGetRequest = true;
    options.userdata     = operators;

    //Get data operates exactly the same as saveData...
    if (this.saveData(instrParts[1], xmlContext, options, gCallback) !== false)
        return;

    //...and then some
    var data      = instruction.split(":");
    var instrType = data.shift();

    if (instrType.substr(0, 1) == "#") {
        instrType = instrType.substr(1);
        var retvalue, oJmlNode = self[instrType];

        if (!oJmlNode)
            throw new Error(jpf.formatErrorString(0, null, "Loading data",
                "Could not find object '" + instrType + "' referenced in \
                process instruction '" + instruction + "' with error "
                + e.message));

        if (!oJmlNode.value)
            retvalue = null;
        else
            retvalue = data[2]
                ? oJmlNode.value.selectSingleNode(data[2])
                : oJmlNode.value;
    }
    else {
        var model = jpf.nameserver.get("model", instrType);

        if (!model) {
            throw new Error(jpf.formatErrorString(1068, jmlNode,
                "Loading data", "Could not find model by name: "
                + instrType, x));
        }

        if (!model.data)
            retvalue = null;
        else
            retvalue = data[1]
                ? model.data.selectSingleNode(data[1])
                : model.data;
    }

    if (callback)
        gCallback(retvalue, jpf.SUCCESS, {userdata:operators});
    else {
        jpf.console.warn("Returning data directly in jpf.getData(). \
            This means that all callback communication ends in void!");
        return retvalue;
    }
};

/**
 * Creates a model object based on a {@link term.datainstruction data instruction}.
 *
 * @param {String} instruction  the {@link term.datainstruction data instruction} to be used to retrieve the data for the model.
 * @param {JmlNode} jmlNode     the element the model is added to.
 * @param {Boolean} isSelection whether the model provides data that determines the selection of the element.
 */
jpf.setModel = function(instruction, jmlNode, isSelection){
    if (!instruction) return;

    var data      = instruction.split(":");
    var instrType = data[0];

    //So are we sure we shouldn't also check .dataParent here?
    var model = isSelection
        ? jmlNode.$getMultiBind().getModel()
        : jmlNode.getModel && jmlNode.getModel();
    if(model)
        model.unregister(jmlNode);

    if (jpf.datainstr[instrType]) {
        jmlNode.setModel(new jpf.model().loadFrom(instruction));
    }
    else if (instrType.substr(0,1) == "#") {
        instrType = instrType.substr(1);

        if (isSelection) {
            var sb2 = jpf.isParsing
                ? jpf.JmlParser.getFromSbStack(jmlNode.uniqueId, 1)
                : jmlNode.$getMultiBind().smartBinding;
            if (sb2)
                sb2.$model = new jpf.model().loadFrom(instruction);
        }
        else if (!self[instrType] || !jpf.JmlParser.inited) {
            jpf.JmlParser.addToModelStack(jmlNode, data)
        }
        else {
            var oConnect = eval(instrType);
            if (oConnect.connect)
                oConnect.connect(jmlNode, null, data[2], data[1] || "select");
            else
                jmlNode.setModel(new jpf.model().loadFrom(instruction));
        }

        jmlNode.connectId = instrType;
    }
    else {
        var instrType = data.shift();
        model = instrType == "@default"
            ? jpf.globalModel
            : jpf.nameserver.get("model", instrType);

        if (!model) {
            throw new Error(jpf.formatErrorString(1068, jmlNode,
                "Finding model", "Could not find model by name: " + instrType));
        }

        if (isSelection) {
            var sb2 = jpf.isParsing
                ? jpf.JmlParser.getFromSbStack(jmlNode.uniqueId, 1)
                : jmlNode.$getMultiBind().smartBinding;
            if (sb2) {
                sb2.$model = model;
                sb2.$modelXpath[jmlNode.uniqueId] = data.join(":");
            }
        }
        else
            jmlNode.setModel(model, data.join(":"));
    }
};

/**
 * Parses argument list
 * Example:
 * Javascript
 * <code>
 *  jpf.parseInstructionPart('type(12+5,"test",{@value}.toLowerCase(),[0+2, "test"])', xmlNode);
 * </code>
 * Jml
 * <code>
 *  <j:rename set="rpc:comm.setFolder({@id}, {@name}, myObject.someProp);" />
 * </code>
 * @private
 */
jpf.parseInstructionPart = function(instrPart, xmlNode, arg, options){
    var parsed  = {}, s = instrPart.split("(");
    parsed.name = s.shift();

    //Get arguments for call
    if (!arg) {
        arg = s.join("(");

        if (arg.slice(-1) != ")") {
            throw new Error(jpf.formatErrorString(0, null, "Saving data",
                "Syntax error in instruction. Missing ) in " + instrPart));
        }

        function getXmlValue(xpath){
            var o = xmlNode ? xmlNode.selectSingleNode(xpath) : null;

            if (!o)
                return null;
            else if (o.nodeType >= 2 && o.nodeType <= 4)
                return o.nodeValue;
            else if (jpf.xmldb.isOnlyChild(o.firstChild, [3, 4]))
                return o.firstChild.nodeValue;
            else
                return o.xml || o.serialize();
        }

        arg = arg.slice(0, -1);
        var depth, lastpos = 0, result = ["["];
        arg.replace(/\\[\{\}\'\"]|(["'])|([\{\}])/g,
            function(m, chr, cb, pos){
                chr && (!depth && (depth = chr)
                    || depth == chr && (depth = null));

                if (!depth && cb) {
                    if (cb == "{") {
                        result.push(arg.substr(lastpos, pos - lastpos));
                        lastpos = pos + 1;
                    }
                    else {
                        result.push("getXmlValue('", arg
                            .substr(lastpos, pos - lastpos)
                            .replace(/([\\'])/g, "\\$1"), "')");
                        lastpos = pos + 1;
                    }
                }
            });

        result.push(arg.substr(lastpos), "]");

        //Safely set options
        (function(){
            try{
                with (options) {
                    arg = eval(result.join(""));
                }
            }
            catch(e) {
                throw new Error(jpf.formatErrorString(0, null, "Saving data",
                    "Error executing data instruction: " + arg
                    + "\nreason:" + e.message
                    + "\nAvailable properties:" + jpf.vardump(options)));

                arg = [];
            }
        })();
    }

    parsed.arguments = arg;

    return parsed;
};



/*FILEHEAD(/var/lib/jpf/src/core/class.js)SIZE(-1077090856)TIME(1239018216)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * BaseClass for any object offering property binding,
 * event handling, constructor and destructor hooks.
 *
 * @constructor
 * @baseclass
 *
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.8
 *
 * @event propertychange Fires when a property changes.
 *   object:
 *     {String} name          the name of the changed property
 *     {Mixed}  originalvalue the value it had before the change
 *     {Mixed}  value         the value it has after the change
 *
 */
jpf.Class = function(){
    this.$jmlLoaders   = [];
    this.$addJmlLoader = function(func){
        if (!this.$jmlLoaders)
            func.call(this, this.$jml);
        else
            this.$jmlLoaders.push(func);
    };

    this.$jmlDestroyers   = [];

    this.$regbase         = 0;
    this.hasFeature       = function(test){
        return this.$regbase&test;
    };

    /* ***********************
        PROPERTY BINDING
    ************************/



    var boundObjects       = {};
    var myBoundPlaces      = {};
    
    if (!this.$handlePropSet) {
        this.$handlePropSet    = function(prop, value){
            this[prop] = value;
        };
    }
    
    /*
    for (var i = 0; i < this.$supportedProperties.length;i++) {
        var p = uCaseFirst(this.$supportedProperties[i]);
        this["set" + p] = function(prop){return function(value){
            this.setProperty(prop, value);
        }}(this.$supportedProperties[i]);

        this["get" + p] = function(prop){return function(){
            return this.getProperty(prop);
        }}(this.$supportedProperties[i]);
    }
    */

    /**
     * Bind a property of another compontent to a property of this element.
     *
     * @param  {String} myProp           the name of the property of this element of which the value is communicated to <code>bObject</code>.
     * @param  {Class}  bObject          the object which will receive the property change message.
     * @param  {String} bProp            the property of <code>bObject</code> which will be set using the value of <code>myProp</code> optionally processed using <code>strDynamicProp</code>.
     * @param  {String} [strDynamicProp] a javascript statement which contains the value of <code>myProp</code>. The string is used to calculate a new value.
     */
    this.bindProperty = function(myProp, bObject, bProp, strDynamicProp){
        //#--ifdef __DEBUG
        if (!boundObjects[myProp])
            boundObjects[myProp] = {};
        if (!boundObjects[myProp][bObject.uniqueId])
            boundObjects[myProp][bObject.uniqueId] = [];

        if (boundObjects[myProp][bObject.uniqueId].contains(bProp)) {
            throw new Error(jpf.formatErrorString(0, this,
                "Property-binding",
                "Already bound " + bObject.name + "." + bProp + " to " + myProp));
            return;
        }

        if (strDynamicProp)
            boundObjects[myProp][bObject.uniqueId].push([bProp, strDynamicProp]);
        else
            boundObjects[myProp][bObject.uniqueId].pushUnique([bProp]); //The new array is always unique... right?
        /* #--else

        if(!boundObjects[myProp]) boundObjects[myProp] = [];
        boundObjects[myProp].push([bObject, bProp, strDynamicProp]);

        #--endif */

        bObject.$handlePropSet(bProp, strDynamicProp ? eval(strDynamicProp) : this[myProp]);
    };

    /**
     * Remove the binding of a property of another compontent to a property of this element.
     *
     * @param  {String} myProp  the name of the property of this element for which the property bind was registered.
     * @param  {Class}  bObject the object receiving the property change message.
     * @param  {String} bProp   the property of <code>bObject</code>.
     */
    this.unbindProperty = function(myProp, bObject, bProp){
        //#--ifdef __DEBUG
        boundObjects[myProp][bObject.uniqueId].remove(bProp);
        /* #--else

        if(!boundObjects[myProp]) return;
        for(var i=0;i<boundObjects[myProp].length;i++){
                if(boundObjects[myProp][0] == bObject && boundObjects[myProp][1] == bProp){
                        return boundObjects[myProp].removeIndex(i);
                }
        }

        #--endif */
    };
    

    /**
     * Unbinds all bound properties for this componet.
     */
    this.unbindAllProperties = function(){
        var prop;
        for (prop in myBoundPlaces) {
            //Remove any bounds if relevant
            if (myBoundPlaces[prop] && typeof myBoundPlaces[prop] != "function") {
                for (var i = 0; i < myBoundPlaces[prop].length; i++) {
                    if (!self[myBoundPlaces[prop][i][0]]) continue;

                    self[myBoundPlaces[prop][i][0]]
                        .unbindProperty(myBoundPlaces[prop][i][1], this, prop);
                }
            }
        }
    };

    /**
     * Gets an array of properties for this element which can be bound.
     */
    this.getAvailableProperties = function(){
        return this.$supportedProperties.slice();
    };

    /**
     * Sets a dynamic property from a string.
     * The string used for this function is the same as used in JML to set a dynamic property:
     * <j:button visible="{rbTest.value == 'up'}" />
     *
     * @param  {String}  prop   the name of the property of this element to set using a dynamic rule.
     * @param  {String}  pValue the dynamic property binding rule.
     */
    this.setDynamicProperty = function(prop, pValue){
        //pValue.match(/^([{\[])(.*)[}\]]$/); // Find dynamic or calculated property
        var pStart = pValue.substr(0,1);

        var pEnd = pValue.substr(pValue.length-1, 1);
        if (pStart == "[" && pEnd != "]" || pStart == "{" && pEnd != "}" ) {
            throw new Error(jpf.formatErrorString(0, this,
                "Dynamic Property Binding",
                "Invalid binding found: " + pValue));
        }

        //Remove any bounds if relevant
        if (myBoundPlaces[prop]) {
            for (var i = 0; i < myBoundPlaces[prop].length; i++) {
                self[myBoundPlaces[prop][i][0]].unbindProperty(myBoundPlaces[prop][i][1], this, prop);
            }
        }

        //Two Way property binds
        if (pStart == "[") {
            var p = pValue.substr(1,pValue.length-2).split(".");
            if (!self[p[0]]) return;

            if (!p[1])
                p[1] = self[p[0]].$supportedProperties[0]; // Default state property

            //Two way property binding
            self[p[0]].bindProperty(p[1], this, prop);
            myBoundPlaces[prop] = [p];
            this.bindProperty(prop, self[p[0]], p[1]);
        }
        else if (pStart == "{") { //One Way Dynamic Properties
            var o, node, bProp, p, matches = {};
            pValue = pValue.substr(1, pValue.length - 2);
            pValue.replace(/["'](?:\\.|[^"']+)*["']|\\(?:\\.|[^\\]+)*\/|(?:\W|^)([a-z]\w*\.\w+(?:\.\w+)*)(?!\()(?:\W|$)/gi,
                function(m, m1){
                    if(m1) matches[m1] = true;
                });

            pValue = pValue.replace(/\Wand\W/g, "&&").replace(/\Wor\W/g, "||");  //.replace(/\!\=|(\=)/g, function(m, m1){if(!m1) return m; return m1+"="})
            myBoundPlaces[prop] = [];

            var found = false;
            for (p in matches) {
                if (typeof matches[p] == "function")
                    continue;

                o = p.split(".");
                if (o.length > 2) { //jpf.offline.syncing
                    bProp = o.pop();
                    try{
                        node  = eval(o.join("."));
                    }
                    catch(e){
                        throw new Error(jpf.formatErrorString(0, this,
                            "Creating a dynamic property bind",
                            "invalid bind statement '" + pValue + "'"));
                    }

                    if (typeof node != "object" || !node.$regbase) {
                        bProp = o[1];
                        node  = self[o[0]];
                    }
                    else
                        o.push(bProp);
                }
                else {
                    bProp = o[1];
                    node  = self[o[0]];
                }

                if (!node || !node.bindProperty)
                    continue;  //return

                node.bindProperty(bProp, this, prop, pValue);
                myBoundPlaces[prop].push(o);
                found = true;
            }

            //if (!found)
                //this.$handlePropSet(prop, eval(pValue));
                var value = eval(pValue);
                this[prop] = !value;
                this.setProperty(prop, value);
        }
        else {
            //this.$handlePropSet(prop, pValue);
            var value = eval(pValue);
            this[prop] = !value;
            this.setProperty(prop, eval(pValue));
        }
    }


    /**
     * Sets the value of a property of this element.
     * Note: Only the value is set, dynamic properties will remain bound and the value will be overridden.
     *
     * @param  {String}  prop        the name of the property of this element to set using a dynamic rule.
     * @param  {String}  value       the value of the property to set.
     * @param  {Boolean} [reqValue]  Wether the method should return when value is null.
     * @param  {Boolean} [forceOnMe] Wether the property should be set even when its the same value.
     */
    this.setProperty = function(prop, value, reqValue, forceOnMe){
        if (reqValue && !value || !jpf || this.$ignoreSignals)
            return;

        var oldvalue = this[prop];
        if (String(this[prop]) !== String(value) || typeof value == "object") {
            if (typeof jpf.offline != "undefined") {
                if (jpf.loaded && jpf.offline.state.enabled
                  && (!this.bindingRules || !this.bindingRules[prop]
                  || this.traverse)) {
                    jpf.offline.state.set(this, prop, typeof value == "object"
                        ? value.name
                        : value);
                }
                else if (jpf.offline.enabled) {

                }
            }
            if (this.$handlePropSet(prop, value, forceOnMe) === false) {
                this[prop] = oldvalue;
                return false;
            }
        }
        
        
        if (this["onpropertychange"] || events_stack["propertychange"]) {
            this.dispatchEvent("propertychange", {
                name          : prop,
                value         : value,
                originalvalue : oldvalue
            });
        }
        
        var nodes = boundObjects[prop];
        if (!nodes) return;

        //#--ifdef __DEBUG
        var id, ovalue = this[prop];//value;
        for (id in nodes) {
            if (jpf.isSafari && (typeof nodes[id] != "object" || !nodes[id]))
                continue;

            for (var o = jpf.lookup(id), i = nodes[id].length - 1; i >= 0; --i) {
                try {
                    value = nodes[id][i][1] ? eval(nodes[id][i][1]) : ovalue;
                }
                catch(e) {
                    throw new Error(jpf.formatErrorString(0, this,
                        "Property-binding",
                        "Could not execute binding test: " + nodes[id][i][1]));
                }

                if (typeof o != "undefined" && o[nodes[id][i][0]] != value)
                    o.setProperty(nodes[id][i][0], value);//__handlePropSet
            }
        }
        /* #--else

        for(var i=0;i<nodes.length;i++){
        try{
            nodes[i][0].$handlePropSet(nodes[i][1],
            nodes[i][2] ? eval(nodes[i][2]) : value);
        }catch(e){}
        }
        #--endif */


        return value;
    };

    /**
     * Gets the value of a property of this element.
     *
     * @param  {String}  prop   the name of the property of this element for which to get the value.
     */
    this.getProperty = function(prop){
        return this[prop];
    };

    /* ***********************
        EVENT HANDLING
    ************************/

    var capture_stack = {}, events_stack = {};
    /**
     * Calls all functions associated with the event.
     *
     * @param  {String}  eventName  the name of the event to dispatch.
     * @param  {Object}  [options]  the properties of the event object that will be created and passed through.
     *   Properties:
     *   {Boolean} bubbles  whether the event should bubble up to it's parent
     * @return {mixed} return value of the event
     */
    this.dispatchEvent = function(eventName, options, e){
        var arr, result, rValue;


        if (options && options.name)
            e = options;
        else if (!e)
            e = new jpf.Event(eventName, options);

        if (this.disabled)
            result = false;
        else {
            if (!e.originalElement) {
                e.originalElement = this;
    
                //Capture support
                if (arr = capture_stack[eventName]) {
                    for (var i = 0; i < arr.length; i++) {
                        rValue = arr[i].call(this, e);
                        if (rValue != undefined)
                            result = rValue;
                    }
                }
            }
            
            if (options && options.captureOnly)
                return e.returnValue || rValue;
            else {
                if (this["on" + eventName])
                    result = this["on" + eventName].call(this, e); //Backwards compatibility
    
                if (arr = events_stack[eventName]) {
                    for (var i = 0; i < arr.length; i++) {
                        rValue = arr[i].call(this, e);
                        if (rValue != undefined)
                            result = rValue;
                    }
                }
            }
        }
        
        if (e.bubbles && !e.cancelBubble && this != jpf) {
            rValue = (this.parentNode || jpf).dispatchEvent(eventName, null, e);

            if (rValue != undefined)
                result = rValue;
        }

        return e.returnValue !== undefined ? e.returnValue : result;
    };

    /**
     * Add a function to be called when a event is called.
     *
     * @param  {String}   eventName the name of the event for which to register a function.
     * @param  {function} callback  the code to be called when event is dispatched.
     */
    this.addEventListener = function(eventName, callback, useCapture){

        if (eventName.indexOf("on") == 0)
            eventName = eventName.substr(2);

        var stack = useCapture ? capture_stack : events_stack;
        if (!stack[eventName])
            stack[eventName] = [];
        stack[eventName].pushUnique(callback);
    }

    /**
     * Remove a function registered for an event.
     *
     * @param  {String}   eventName the name of the event for which to unregister a function.
     * @param  {function} callback  the function to be removed from the event stack.
     */
    this.removeEventListener = function(eventName, callback, useCapture){
        var stack = useCapture ? capture_stack : events_stack;
        if (stack[eventName])
            stack[eventName].remove(callback);
    };

    /**
     * Checks if there is an event listener specified for the event.
     *
     * @param  {String}  eventName  the name of the event to check.
     * @return {Boolean} whether the event has listeners
     */
    this.hasEventListener = function(eventName){
        return (events_stack[eventName] && events_stack[eventName].length > 0);
    };

    /**
     * Destructor of a Class.
     * Calls all destructor functions and removes all mem leaking references.
     * This function is called when exiting the application or closing the window.
     * @param {Boolean} deep whether the children of this element should be destroyed.
     * @method
     */
    this.destroy = this.destroy || function(deep){
        if (!this.$jmlDestroyers) //@todo check why this happens
            return;

        if (this.$destroy)
            this.$destroy();

        for (var i = this.$jmlDestroyers.length - 1; i >= 0; i--)
            this.$jmlDestroyers[i].call(this);
        this.$jmlDestroyers = undefined;

        //Remove from jpf.all
        if (typeof this.uniqueId == "undefined")
            return;

        jpf.all[this.uniqueId] = undefined;

        if (!this.nodeFunc) { //If this is not a JmlNode, we're done.
            //Remove id from global js space
            if (this.name)
                self[this.name] = null;
            return;
        }

        if (this.oExt && !this.oExt.isNative && this.oExt.nodeType == 1) {
            this.oExt.oncontextmenu = this.oExt.host = null;
        }
        if (this.oInt && !this.oExt.isNative && this.oInt.nodeType == 1)
            this.oInt.host = null;

        this.$jml = null;

        //Remove from DOM tree if we are still connected
        if (this.parentNode)
            this.removeNode();

        //Remove from focus list - Should be in JmlNode
        if (this.$focussable && this.focussable)
            jpf.window.$removeFocus(this);

        //Remove dynamic properties
        this.unbindAllProperties();

        //Clear all children too
        if (deep && this.childNodes) {
            var i, nodes = this.childNodes;
            for (i = nodes.length - 1; i >= 0; i--) {
                if (nodes[i].destroy)
                    nodes[i].destroy(true);
            }
            this.childNodes = null;
        }

        if (deep !== false && this.childNodes) {
            jpf.console.warn("You have destroyed a Jml Node without destroying\
                              it's children. Please be aware that if you don't\
                              maintain a reference, memory might leak");
        }

        //Remove id from global js space
        if (this.name)
            self[this.name] = null;
    };
};

/**
 * @constructor
 */
jpf.Event = function(name, data){
    this.name = name;

    this.bubbles = false;
    this.cancelBubble = false;

    this.preventDefault = function(){
        this.returnValue = false;
    }

    this.stopPropagation = function(){
        this.cancelBubble = true;
    }
    
    //@todo should be implemented;
    this.isCharacter = function(){
        return (this.keyCode < 112 || this.keyCode > 122) 
          && (this.keyCode < 33  && this.keyCode > 31 || this.keyCode > 42 || this.keyCode == 8);
        
    }

    jpf.extend(this, data);
    //this.returnValue = undefined;
};

jpf.inherit(jpf.Class);
jpf.Init.run('class');



/*FILEHEAD(/var/lib/jpf/src/core/component.js)SIZE(-1077090856)TIME(1238944816)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * This function tries to simplify the development of new JML elements.
 * Creating a new element for JPF may now be as easy as:
 * Example:
 * <code language="javascript">
 * // create a new JML component: <j:foo />
 * jpf.foo = jpf.component(jpf.NODE_VISIBLE, {
 *     // component body (method and property declaration)
 * }).implement(jpf.barInterface);
 * </code>
 * 
 * @classDescription         This class serves as a baseclass for new elements
 * @param  {Number} nodeFunc A number constant, defining the type of element
 * @param  {mixed}  oBase    May be a function (will be instantiated) or object to populate the elements' prototype
 * @return {Element}       Returns a Function that will serve as the elements' constructor
 * @type   {Element}
 * @constructor
 * 
 * Note: we REALLY don't care about execution speed for this one! It will be
 * optimized by reformatting it using Jaw (compile-time), like C-style macros.
 * *sigh* don't worry, this implementation is still blazing fast and has been
 * profiled and optimized in all major browsers.
 * 
 * @author      Mike de Boer
 * @version     %I%, %G%
 * @since       1.0
 */

jpf.component = function(nodeFunc, oBase) {
    // the actual constructor for the new comp (see '__init()' below).
    var fC = function() {
        this.$init.apply(this, arguments);
    };
    
    // if oBase is provided, apply it as a prototype of the new comp.
    if (oBase) {
        // a function will be deferred to instantiation of the comp. to be inherited 
        if (typeof oBase == "function")
            fC.prototype.base = oBase;
        else
            fC.prototype = oBase;
    }

    // the 'nodeFunc' flag specifies the function that a node/ component represents
    // within JPF.
    fC.prototype.nodeFunc = nodeFunc || jpf.NODE_HIDDEN;

    // deprecated Deskrun feature
    if (nodeFunc == jpf.NODE_MEDIAFLOW)
        DeskRun.register(fC.prototype);

    // The inherit function is copied from 'jpf.inherit'
    fC.prototype.inherit = jpf.inherit;

    // If the '$init' function is not present yet, we shall define it - the
    // starting engine of a JPF component
    if (typeof fC.prototype['$init'] != "function") {
        var aImpl = [];
        /**
         * The developer may supply interfaces that will inherited upon element
         * instantiation with implement() below. Calls to 'implement()' may be
         * chained.
         * Note: duplicate interfaces will not be filtered out! This means that
         *       only the interface that was provided with implement() will be
         *       actually implemented.
         * 
         * @private
         */
        fC.implement = function() {
            aImpl = aImpl.concat(Array.prototype.slice.call(arguments));
            return fC;
        }
        
        /**
         * Even though '$init()' COULD be overridden, it is still the engine
         * for every new element. It takes care of the basic inheritance
         * difficulties and created the necessary hooks with the Javeline Platform.
         * Note: a developer can still use 'init()' as the function to execute
         *       upon instantiation, while '$init()' is used by JPF.
         * 
         * @param {Object} pHtmlNode
         * @param {Object} sName
         * @type void
         */
        fC.prototype.$init = function(pHtmlNode, sName){
            if (typeof sName != "string") 
                throw new Error(jpf.formatErrorString(0, this, 
                "Error creating component",
                "Dependencies not met, please provide a component name when \
                 instantiating it (ex.: new jpf.tree(oParent, 'tree') )"));

            this.tagName       = sName;
            this.pHtmlNode     = pHtmlNode || document.body;
            this.pHtmlDoc      = this.pHtmlNode.ownerDocument;
            
            this.uniqueId      = jpf.all.push(this) - 1;
            
            //Oops duplicate code.... (also in jpf.register)
            this.$propHandlers = {}; //@todo fix this in each component
            this.$domHandlers  = {
                "remove"      : [],
                "insert"      : [],
                "reparent"    : [],
                "removechild" : []
            };
            
            if (nodeFunc != jpf.NODE_HIDDEN) {
                if (typeof this.$focussable == "undefined")
                    this.$focussable = jpf.KEYBOARD_MOUSE; // Each GUINODE can get the focus by default
                
                this.$booleanProperties = {
                    "disable-keyboard" : true,
                    "visible"          : true,
                    "focussable"       : true
                    //"disabled"         : true
                };
                
                this.$supportedProperties = [
                    "draggable", "resizable",
                    "focussable", "zindex", "disabled", "tabindex",
                    "disable-keyboard", "contextmenu", "visible", "autosize", 
                    "loadjml", "actiontracker", "alias"];
            } 
            else {
                this.$booleanProperties   = {}; //@todo fix this in each component
                this.$supportedProperties = []; //@todo fix this in each component
            }
            
            /** 
             * @inherits jpf.Class
             * @inherits jpf.JmlElement
             */
            // the ORDER is crucial here.
            this.inherit(jpf.Class);
            this.inherit.apply(this, aImpl);
            this.inherit(jpf.JmlElement, this.base || jpf.K);
            
            if (typeof this['init'] == "function")
                this.init();
        }
    }
    
    return fC;
};

/**
 * This is code to construct a subnode, these are simpler and almost
 * have no inheritance
 */
jpf.subnode = function(nodeFunc, oBase) {
    // the actual constructor for the new comp (see '__init()' below).
    var fC = function() {
        this.$init.apply(this, arguments);
    };
    
    // if oBase is provided, apply it as a prototype of the new comp.
    if (oBase) {
        // a function will be deferred to instantiation of the comp. to be inherited 
        if (typeof oBase == "function")
            fC.prototype.base = oBase;
        else
            fC.prototype = oBase;
    }

    fC.prototype.nodeFunc = nodeFunc || jpf.NODE_HIDDEN;

    fC.prototype.inherit  = jpf.inherit;

    if (typeof fC.prototype['$init'] != "function") {
        var aImpl = [];
        /**
         * The developer may supply interfaces that will inherited upon element
         * instantiation with implement() below. Calls to 'implement()' may be
         * chained.
         * 
         * @private
         */
        fC.implement = function() {
            aImpl = aImpl.concat(Array.prototype.slice.call(arguments));
            return fC;
        }
        
        /**
         * Even though '__init()' COULD be overridden, it is still the engine
         * for every new element. It takes care of the basic inheritance
         * difficulties and created the necessary hooks with the Javeline Platform.
         * Note: a developer can still use 'init()' as the function to execute
         *       upon instantiation, while '__init()' is used by JPF.
         * 
         * @param {Object} pHtmlNode
         * @param {Object} sName
         * @param {Object} parentNode
         * @type void
         */
        fC.prototype.$init = function(pHtmlNode, sName, parentNode){
            if (typeof sName != "string") 
                throw new Error(jpf.formatErrorString(0, this, 
                    "Error creating component",
                    "Dependencies not met, please provide a component name when \
                     instantiating it (ex.: new jpf.tree(oParent, 'tree') )"));

            this.tagName      = sName;
            this.pHtmlNode    = pHtmlNode || document.body;
            this.pHtmlDoc     = this.pHtmlNode.ownerDocument;
            this.parentNode   = parentNode;
            this.$domHandlers = {
                "remove"      : [],
                "insert"      : [],
                "reparent"    : [],
                "removechild" : []
            };
            
            this.uniqueId     = jpf.all.push(this) - 1;
            
            /** 
             * @inherits jpf.Class
             */
            // the ORDER is crucial here.
            this.inherit(jpf.Class);
            this.inherit.apply(this, aImpl);
            this.inherit(jpf.JmlDom, this.base || jpf.K);
            
            if (typeof this['init'] == "function")
                this.init();
        }
    }
    
    return fC;
};



/*FILEHEAD(/var/lib/jpf/src/core/window.js)SIZE(-1077090856)TIME(1239027646)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * @private
 */
jpf.windowManager = {
    destroy: function(frm){
        //Remove All Cross Window References Created on Init by jpf.windowManager
        // for (var i = 0; i < this.globals.length; i++)
        //     frm.win[this.globals[i]] = null;
    },

    //root    : self,
    userdata: [],

    /* ********************************************************************
     FORMS
     *********************************************************************/
    forms   : new Array(),

    addForm: function(xmlFormNode){
        var x = {
            jml: xmlFormNode,
            show: function(){
                alert("not implemented");
            }
        };
        this.forms.push(jpf.setReference(x.name, x, true));
        return x;
    },

    getForm: function(value){
        for (var i = 0; i < this.forms.length; i++)
            if (this.forms[i].name == value)
                return this.forms[i];

        return this.forms.length ? this.forms[0] : false;
    },

    closeAll: function(){
        for (var i = 0; i < this.forms.length; i++)
            if (this.forms[i].name != "main" && this.forms[i].type != "modal")
                this.forms[i].hide();
    }
};

/**
 * Object representing the window of the jml application. The semantic is
 * similar to that of a window in the browser, except that this window is not
 * the same as the javascript global object. It handles the focussing within
 * the document and several other events such as exit and the keyboard events.
 *
 * @event blur              Fires when the browser window looses focus.
 * @event focus             Fires when the browser window receives focus.
 *
 * @constructor
 * @jpfclass
 *
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.8
 */
jpf.WindowImplementation = function(){
    jpf.register(this, "window", jpf.NODE_HIDDEN);/** @inherits jpf.Class */
    this.jpf = jpf;

    /**
     * Returns a string representation of this object.
     */
    this.toString = function(){
        return "[Javeline Component : " + (this.name || "") + " (jpf.window)]";
    };

    /**
     * Retrieves the root action tracker of the application.
     */
    this.getActionTracker = function(){
        return this.$at
    };

    /**
     * @private
     */
    this.loadCodeFile = function(url){
        //if(jpf.isSafari) return;
        if (self[url])
            jpf.importClass(self[url], true, this.win);
        else
            jpf.include(url);//, this.document);
    };

    /**
     * Flashes the task bar. This can be useful to signal the user that an
     * important event has occured. Only works in internet explorer under
     * certain conditions.
     */
    this.flash = function(){
        if (jpf.window.hasFocus())
            return;

        if (jpf.isDeskrun)
            jdwin.Flash();
        else if (jpf.isIE) {
            if (!this.popup)
                this.popup = window.createPopup();

            if (jpf.window.stopFlash)
                return;

            state += "x"

            function doFlash(nopopup) {
                if (jpf.window.hasFocus())
                    return;

                window.focus();

                function doPopup() {
                    if (jpf.window.hasFocus())
                        return;

                    this.popup.hide();
                    this.popup.show(0, 0, 0, 0, document.body);
                    this.popup.document.write("<body><script>\
                        document.p = window.createPopup();\
                        document.p.show(0, 0, 0, 0, document.body);\
                        </script></body>");
                    this.popup.document.focus();

                    clearInterval(this.flashTimer);
                    this.flashTimer = setInterval(function(){
                        if (!jpf.window.popup.isOpen
                          || !jpf.window.popup.document.p.isOpen) {
                            clearInterval(jpf.window.flashTimer);

                            if (!jpf.window.hasFocus()) {
                                jpf.window.popup.hide();
                                document.body.focus();
                                state = "d";
                                determineAction();
                            }
                            //when faster might have timing error
                        }
                    }, 10);
                }

                if (nopopup)
                    setTimeout(function(){
                        doPopup.call(jpf.window)
                    }, 10);
                else
                    doPopup.call(jpf.window);
            }

            if ("TEXTAREA|INPUT|SELECT".indexOf(document.activeElement.tagName) > -1) {
                document.activeElement.blur();
                document.body.focus();
                jpf.window.stopFlash = true;
                setTimeout(function(){
                    doFlash.call(jpf.window, true);
                    jpf.window.stopFlash = false;
                }, 10);
            }
            else {
                doFlash.call(jpf.window);
            }
        }
    };

    /**
     * Show the browser window.
     */
    this.show = function(){
        if (jpf.isDeskrun)
            jdwin.Show();
    };

    /**
     * Hide the browser window.
     */
    this.hide = function(){
        if (jpf.isDeskrun)
            jdwin.Hide();
        else {
            this.loaded = false;
            if (this.win)
                this.win.close();
        }
    };

    /**
     * Focus the browser window.
     */
    this.focus = function(){
        if (jpf.isDeskrun)
            jdwin.SetFocus();
        else
            window.focus();
    };

    /**
     * Set the icon of the browser window.
     * @param {String} url the location of the .ico file.
     */
    this.setIcon = function(url){
        if (jpf.isDeskrun)
            jdwin.icon = parseInt(url) == url ? parseInt(url) : url;
    };

    /**
     * Set the title of the browser window.
     * @param {String} value the new title of the window.
     */
    this.setTitle = function(value){
        this.title = value || "";

        if (jpf.isDeskrun)
            jdwin.caption = value;
        else
            document.title = (value || "");
    };

    /**
     * @private
     */
    this.loadJml = function(x){
        if (x[jpf.TAGNAME] == "deskrun")
            this.loadDeskRun(x);
        else {

        }
    };

    var jdwin   = jpf.isDeskrun ? window.external : null;
    var jdshell = jpf.isDeskrun ? jdwin.shell : null;

    /**
     * @private
     */
    this.loadDeskRun = function(q){
        jdwin.style = q.getAttribute("style") || "ismain|taskbar|btn-close|btn-max|btn-min|resizable";

        jpf.appsettings.drRegName = q.getAttribute("record");
        if (q.getAttribute("minwidth"))
            jdwin.setMin(q.getAttribute("minwidth"), q.getAttribute("minheight"));
        if (q.getAttribute("record")
          && jdshell.RegGet(jpf.appsettings.drRegName + "/window")) {
            var winpos = jdshell.RegGet(jpf.appsettings.drRegName + "/window");
            if (winpos) {
                winpos = winpos.split(",");
                window.external.width  = Math.max(q.getAttribute("minwidth"),
                    Math.min(parseInt(winpos[2]),
                    window.external.shell.GetSysValue("deskwidth")));
                window.external.height = Math.max(q.getAttribute("minheight"),
                    Math.min(parseInt(winpos[3]),
                    window.external.shell.GetSysValue("deskheight")));
                window.external.left   = Math.max(0, Math.min(parseInt(winpos[0]),
                    screen.width - window.external.width));
                window.external.top    = Math.max(0, Math.min(parseInt(winpos[1]),
                    screen.height - window.external.height));
            }
        }
        else {
            jdwin.left   = q.getAttribute("left")   || 200;
            jdwin.top    = q.getAttribute("top")    || 200;
            jdwin.width  = q.getAttribute("width")  || 800;
            jdwin.height = q.getAttribute("height") || 600;
        }

        jdwin.caption = q.getAttribute("caption") || "Javeline DeskRun";
        jdwin.icon    = q.getAttribute("icon") || 100;

        var ct = $xmlns(q, "context", jpf.ns.jml);
        if (ct.length) {
            ct = ct[0];
            if (!jpf.appsettings.tray)
                jpf.appsettings.tray = window.external.CreateWidget("trayicon")
            var tray = jpf.appsettings.tray;

            tray.icon = q.getAttribute("tray") || 100;
            tray.tip  = q.getAttribute("tooltip") || "Javeline DeskRun";
            tray.PopupClear();
            tray.PopupItemAdd("Exit", 3);
            tray.PopupItemAdd("SEP", function(){});

            var nodes = ct.childNodes;
            for (var i = nodes.length - 1; i >= 0; i--) {
                if (nodes[i].nodeType != 1)
                    continue;

                if (nodes[i][jpf.TAGNAME] == "divider") {
                    tray.PopupItemAdd("SEP", function(){});
                }
                else {
                    tray.PopupItemAdd(jpf.getXmlValue(nodes[i], "."),
                        nodes[i].getAttribute("href")
                        ? new Function("window.open('" + nodes[i].getAttribute("href") + "')")
                        : new Function(nodes[i].getAttribute("onclick")));
                }
            }
        }

        jdwin.shell.debug = jpf.debug ? 7 : 0;
        jdwin.Show();
        jdwin.SetFocus();
    };

    /**** Focus Internals ****/


    this.$tabList = [];

    this.$addFocus = function(jmlNode, tabindex, isAdmin){
        if (!isAdmin) {
            if (jmlNode.$domHandlers) {
                jmlNode.$domHandlers.reparent.push(moveFocus);
                jmlNode.$domHandlers.remove.push(removeFocus);
            }

            if (jmlNode.isWindowContainer > -1) {
                jmlNode.addEventListener("focus", trackChildFocus);

                if (!jmlNode.$tabList) {
                    jmlNode.$tabList = [jmlNode];
                }

                jmlNode.$focusParent = jmlNode;
                this.$tabList.push(jmlNode);

                return;
            }
        }

        var fParent = findFocusParent(jmlNode);
        var list    = fParent.$tabList;

        if (list[tabindex]) {
            jpf.console.warn("Jml node already exist for tabindex " + tabindex
                             + ". Will insert " + jmlNode.tagName + " ["
                             + (jmlNode.name || "") + "] before existing one");
        }

        jmlNode.$focusParent = fParent;

        if (list[tabindex])
            list.insertIndex(jmlNode, tabindex);
        else
            list.push(jmlNode);
    };

    this.$removeFocus = function(jmlNode){
        if (!jmlNode.$focusParent)
            return;

        jmlNode.$focusParent.$tabList.remove(jmlNode);

        if (!jmlNode.isWindowContainer && jmlNode.$domHandlers) {
            jmlNode.$domHandlers.reparent.remove(moveFocus);
            jmlNode.$domHandlers.remove.remove(removeFocus);
        }

        if (jmlNode.isWindowContainer > -1)
            jmlNode.removeEventListener("focus", trackChildFocus);
    };

    this.$focus = function(jmlNode, e, force){
        if (this.focussed == jmlNode && !force)
            return; //or maybe when force do $focus

        var hadAlreadyFocus = this.focussed == jmlNode;

        this.$settingFocus = jmlNode;

        if (this.focussed && this.focussed != jmlNode) {
            this.focussed.blur(true, e);

        }

        (this.focussed = jmlNode).focus(true, e);

        this.$settingFocus = null;

        jpf.dispatchEvent("movefocus", {
            toElement : this.focussed
        });


        if (!hadAlreadyFocus)
            jpf.console.info("Focus given to " + this.focussed.tagName +
                " [" + (this.focussed.name || "") + "]");

        if (typeof jpf.offline != "undefined" && jpf.offline.state.enabled
          && jpf.offline.state.realtime)
            jpf.offline.state.set(this, "focus", jmlNode.name || jmlNode.uniqueId);
    };

    this.$blur = function(jmlNode){
        if (this.focussed != jmlNode)
            return false;

        jpf.console.info(this.focussed.tagName + " ["
            + (this.focussed.name || "") + "] was blurred.");

        jpf.window.focussed.$focusParent.$lastFocussed = null;
        this.focussed = null;

        jpf.dispatchEvent("movefocus", {
            fromElement : jmlNode
        });

    };
    
    var lastFocusParent;
    this.addEventListener("focus", function(e){
        if (!jpf.window.focussed && lastFocusParent) {
            jpf.window.$focusLast(lastFocusParent);
        }
    });
    this.addEventListener("blur", function(e){
        if (!jpf.window.focussed)
            return;

        jpf.window.focussed.blur(true, {srcElement: this});//, {cancelBubble: true}
        lastFocusParent = jpf.window.focussed.$focusParent;
        jpf.window.focussed = null;
    });

    this.$focusDefault = function(jmlNode, e){
        var fParent = findFocusParent(jmlNode);
        this.$focusLast(fParent, e);
    }

    this.$focusRoot = function(e){
        var docEl = jpf.document.documentElement;
        if (this.$focusLast(docEl, e) === false) {
            //docEl.$lastFocussed = null;
            //this.moveNext(null, jpf.document.documentElement, true, e);
        }
    };

    this.$focusLast = function(jmlNode, e, ignoreVisible){
        var lf = jmlNode.$lastFocussed;
        if (lf && lf.parentNode && lf.$focussable === true
          && (ignoreVisible || lf.oExt.offsetHeight)) {
            this.$focus(lf, e, true);
        }
        else { //Let's find the object to focus first
            var str, x, node = jmlNode;
            while (node) {
                if (node.focussable !== false && node.$focussable === true
                  && (ignoreVisible || node.oExt.offsetHeight)) {
                    this.$focus(node, e, true);
                    break;
                }

                //Walk sub tree
                if (node.firstChild || node.nextSibling) {
                    node = node.firstChild || node.nextSibling;
                }
                else {
                    do {
                        node = node.parentNode;
                    } while (node && !node.nextSibling && node != jmlNode)

                    if (node == jmlNode)
                        return; //do nothing

                    if (node)
                        node = node.nextSibling;
                }
            }

            if (!node)
                this.$focus(jpf.document.documentElement);//return false;//

            /*@todo get this back from SVN
            var node, list = jmlNode.$tabList;
            for (var i = 0; i < list.length; i++) {
                node = list[i];
                if (node.focussable !== false && node.$focussable === true
                  && (ignoreVisible || node.oExt.offsetHeight)) {
                    this.$focus(node, e, true);
                    return;
                }
            }

            this.$focus(jpf.document.documentElement);*/
        }
    };

    function trackChildFocus(e){
        if (e.srcElement == this || e.trackedChild) {
            e.trackedChild = true;
            return;
        }

        this.$lastFocussed = e.srcElement;

        if (this.tagName.indexOf("window") > -1)
            e.trackedChild = true;
    }

    function findFocusParent(jmlNode){
        var node = jmlNode;
        do {
            node = node.parentNode;
        } while(node && !node.isWindowContainer);
        //(!node.$focussable || node.focussable === false)

        return node || jpf.document.documentElement;
    }

    //Dom handler
    //@todo make this look at the dom tree insertion point to determine tabindex
    function moveFocus(){
        if (this.isWindowContainer)
            jpf.window.$tabIndex.push(this);
        else
            jpf.window.$addFocus(this, this.tabindex, true)
    }

    //Dom handler
    function removeFocus(doOnlyAdmin){
        if (this.isWindowContainer) {
            jpf.window.$tabList.remove(this);
            return;
        }

        if (!this.$focusParent)
            return;

        this.$focusParent.$tabList.remove(this);
        //this.$focusParent = null; //@experimental to not execute this
    }

    /**** Focus API ****/

    /**
     * Determines whether a given jml element has the focus.
     * @param {JMLElement} the element to check
     * @returns {Boolean} whether the element has focus.
     */
    this.hasFocus = function(jmlNode){
        return this.focussed == jmlNode;
    };

    /**
     * @private
     */
    this.moveNext = function(shiftKey, relObject, switchWindows, e){
        var dir, start, next;

        if (switchWindows && jpf.window.focussed) {
            var p = jpf.window.focussed.$focusParent;
            if (p.visible && p.modal)
                return false;
        }

        var jmlNode = relObject || jpf.window.focussed;
        var fParent = jmlNode
            ? (switchWindows && jmlNode.isWindowContainer
                ? jpf.window
                : jmlNode.$focusParent)
            : jpf.document.documentElement;
        var list    = fParent.$tabList;

        if (jmlNode && (switchWindows || jmlNode != jpf.document.documentElement)) {
            start   = (list || []).indexOf(jmlNode);
            if (start == -1) {
                jpf.console.warn("Moving focus from element which isn't in the list\
                                  of it's parent. This should never happen.");

                return;
            }
        }
        else
            start = -1;

        if (this.focussed && this.focussed == jmlNode
          && list.length == 1 || list.length == 0)
            return false;

        dir  = (shiftKey ? -1 : 1);
        next = start;
        if (start < 0)
            start = 0;
        do {
            next += dir;

            if (next >= list.length)
                next = 0;
            else if (next < 0)
                next = list.length - 1;

            if (start == next && jmlNode)
                return false; //No visible enabled element was found

            jmlNode = list[next];
        }
        while (!jmlNode
            || jmlNode.disabled
            || jmlNode == jpf.window.focussed
            || (switchWindows ? !jmlNode.visible : jmlNode.oExt && !jmlNode.oExt.offsetHeight)
            || jmlNode.focussable === false
            || switchWindows && !jmlNode.$tabList.length);

        if (fParent == jpf.window)
            this.$focusLast(jmlNode, {mouse:true}, switchWindows);
        else
            this.$focus(jmlNode, e);

    };

    /**
     * @private
     */
    this.focusDefault = function(){
        if (typeof jpf.offline != "undefined" && jpf.offline.state.enabled) {
            var node, id = jpf.offline.state.get(this, "focus");

            if (id == -1)
                return this.$focusRoot();

            if (id)
                node = self[id] || jpf.lookup(id);

            if (node) {
                if (!node.$focussable) {
                    jpf.console.warn("Invalid offline state detected. The \
                                      application was probably changed in \
                                      between sessions. Resetting offline state\
                                      and rebooting.");

                    jpf.offline.clear();
                    jpf.offline.reboot();
                }
                else {
                    this.$focus(node);
                    return;
                }
            }
        }

        if (this.moveNext() === false) {
            this.moveNext(null, jpf.document.documentElement, true)
        }
    };


    /** Set Window Events **/

    window.onbeforeunload = function(){
        return jpf.dispatchEvent("exit");
    };

    if (jpf.isDeskrun)
        window.external.onbeforeunload = window.onbeforeunload;

    window.onunload = function(){
        jpf.window.isExiting = true;
        jpf.window.destroy();
    };


    var timer, state = "", last = "";
    this.$focusfix = function(){
        state += "a";
        clearTimeout(timer);
        setTimeout("window.focus();");
        timer = setTimeout(determineAction);
    }

    this.$focusfix2 = function(){
        state += "b";
        clearTimeout(timer);
        timer = setTimeout(determineAction);
    }

    this.$blurfix = function(){
        state += "c";
        clearTimeout(timer);
        timer = setTimeout(determineAction);
    }

    function determineAction(){
        clearTimeout(timer);

        //jpf.console.info(state);
        if (state == "e" || state == "c"
          || state.charAt(0) == "x" && !state.match(/eb$/)
          || state == "ce" || state == "de") { //|| state == "ae"
            if (last != "blur") {
                last = "blur";
                jpf.window.dispatchEvent("blur");
                //jpf.console.warn("blur");
            }
        }
        else {
            if (last != "focus") {
                last = "focus";
                jpf.window.dispatchEvent("focus");
                //jpf.console.warn("focus");
            }
        }

        state = "";
        timer = null;
    }

    window.onfocus = function(){
        if (jpf.hasFocusBug) {
            state += "d";
            clearTimeout(timer);
            timer = setTimeout(determineAction);
        }
        else {
            clearTimeout(iframeFixTimer)
            iframeFix.newState = "focus";
            //jpf.console.warn("win-focus");
            iframeFixTimer = setTimeout(iframeFix, 10);
        }
    };

    window.onblur = function(){
        if (jpf.hasFocusBug) {
            state += "e";
            clearTimeout(timer);
            timer = setTimeout(determineAction);
        }
        else {
            clearTimeout(iframeFixTimer)
            iframeFix.newState = "blur";
            //jpf.console.warn("win-blur");
            iframeFixTimer = setTimeout(iframeFix, 10);
        }
    };

    var iframeFixTimer;
    function iframeFix(){
        clearTimeout(iframeFixTimer);

        var newState = iframeFix.newState;
        if (last == newState)
            return;

        last = newState;

        jpf.window.dispatchEvent(last);
        //jpf.console.warn(last);
    }

    this.hasFocus = function(){
        return last == "focus";
    }


    /**** Keyboard and Focus Handling ****/

    document.oncontextmenu = function(e){
        if (!e)
            e = event;

        var jmlNode = jpf.findHost(e.srcElement || e.target)
              || jpf.window.focussed
              || jpf.document && jpf.document.documentElement;

        if (jmlNode.tagName == "menu") //The menu is already visible
            return false;

        var pos, ev;

        if (jmlNode && jmlNode.tagName == "menu")
            jmlNode = jmlNode.parentNode;

        if (jpf.contextMenuKeyboard) {
            if (jmlNode) {
                pos = jmlNode.selected
                    ? jpf.getAbsolutePosition(jmlNode.$selected)
                    : jpf.getAbsolutePosition(jmlNode.oExt || jmlNode.pHtmlNode);
            }
            else
                pos = [0, 0];

            var ev = {
                x         : pos[0] + 10 - document.documentElement.scrollLeft,
                y         : pos[1] + 10 - document.documentElement.scrollTop,
                htmlEvent : e
            }
        }
        else {
            if (e.htmlEvent)
                ev = e;
            else
                ev = { //@todo probably have to deduct the border of the window
                    x         : e.clientX - document.documentElement.scrollLeft,
                    y         : e.clientY - document.documentElement.scrollTop,
                    htmlEvent : e
                }
        }

        ev.bubbles = true; //@todo discuss this, are we ok with bubbling?

        jpf.contextMenuKeyboard = null;

        if ((jmlNode || jpf).dispatchEvent("contextmenu", ev) === false
          || ev.returnValue === false)
            return false;

        if (jpf.appsettings.disableRightClick)
            return false;
    };

    var ta = {"INPUT":1, "TEXTAREA":1, "SELECT":1};
    document.onmousedown = function(e){
        if (!e) e = event;
        var jmlNode = jpf.findHost(e.srcElement || e.target);
        
        if (jpf.popup.last && jpf.popup.last != jmlNode.uniqueId)
            jpf.popup.forceHide();

        var p;
        //Make sure modal windows cannot be left
        if ((!jmlNode || !jmlNode.$focussable || jmlNode.focussable === false)
          && jpf.appsettings.allowBlur) {
            lastFocusParent = null;
            if (jpf.window.focussed)
                jpf.window.focussed.blur();
        }
        else if ((p = jpf.window.focussed && jpf.window.focussed.$focusParent || lastFocusParent)
            && p.visible && p.modal && jmlNode.$focusParent != p) {
                jpf.window.$focusLast(p, {mouse: true});
        }
        else if (!jmlNode && jpf.window.focussed) {
            jpf.window.$focusRoot();
        }
        else if (!jmlNode.disabled && jmlNode.focussable !== false) {
            if (jmlNode.$focussable === jpf.KEYBOARD_MOUSE)
                jpf.window.$focus(jmlNode, {mouse: true});
            else if (jmlNode.canHaveChildren == 2) {
                if (!jpf.appsettings.allowBlur || !jpf.window.focussed 
                  || jpf.window.focussed.$focusParent != jmlNode)
                    jpf.window.$focusLast(jmlNode, {mouse: true});
            }
            else
                jpf.window.$focusDefault(jmlNode, {mouse: true});
        }
        else
            jpf.window.$focusDefault(jmlNode, {mouse: true});

        if (jpf.hasFocusBug) {
            var isContentEditable = ta[(e.srcElement || e.target).tagName]
                && !(e.srcElement || e.target).disabled || jmlNode.$isContentEditable
                && jmlNode.$isContentEditable(e) && !jmlNode.disabled;

            if (!jmlNode || !isContentEditable)
                jpf.window.$focusfix();
        }
        else if (!last) {
            window.onfocus();
        }

        jpf.dispatchEvent("mousedown", {
            htmlEvent : e,
            jmlNode   : jmlNode
        });

        //Non IE selection handling
        if (!jpf.isIE && (jpf.JmlParser && !jpf.appsettings.allowSelect
          && (!jpf.isParsingPartial || jmlNode)
          || jpf.dragmode.mode
          ) && !ta[e.target.tagName])
            return false;
    };

    document.onselectstart = function(e){
        if (!e) e = event;

        //IE selection handling
        if (jpf.JmlParser && !jpf.appsettings.allowSelect
          || jpf.dragmode.mode
          || jpf.dragmode.isDragging
          )
            return false;
    };

    // Keyboard forwarding to focussed object
    document.onkeyup = function(e){
        if (!e) e = event;

        if (jpf.window.focussed
          && !jpf.window.focussed.disableKeyboard
          && jpf.window.focussed.dispatchEvent("keyup", {
                keyCode  : e.keyCode,
                ctrlKey  : e.ctrlKey,
                shiftKey : e.shiftKey,
                altKey   : e.altkey,
                htmlEvent: e
            }) === false) {
            return false;
        }

        jpf.dispatchEvent("keyup", null, e);
    };

    function wheel(e) {
        if (!e)
            e = event;

        var delta = null;
        if (e.wheelDelta) {
            delta = e.wheelDelta / 120;
            if (jpf.isOpera)
                delta *= -1;
        }
        else if (e.detail)
            delta = -e.detail / 3;

        if (delta !== null) {
            var ev = {delta: delta};
            var res = jpf.dispatchEvent("mousescroll", ev);
            if (res === false || ev.returnValue === false) {
                if (e.preventDefault)
                    e.preventDefault();

                e.returnValue = false;
            }
        }
    }

    if (document.addEventListener)
        document.addEventListener('DOMMouseScroll', wheel, false);

    window.onmousewheel   =
    document.onmousewheel = wheel; //@todo 2 keer events??

    var keyNames = {
        "32" : "Spacebar",
        "13" : "Enter",
        "9"  : "Tab",
        "27" : "Esc",
        "46" : "Del",
        "36" : "Home",
        "35" : "End",
        "107": "+",
        "37" : "Left Arrow",
        "38" : "Up Arrow",
        "39" : "Right Arrow",
        "40" : "Down Arrow",
        "33" : "Page Up",
        "34" : "Page Down",
        "112": "F1",
        "113": "F2",
        "114": "F3",
        "115": "F4",
        "116": "F5",
        "117": "F6",
        "118": "F7",
        "119": "F8",
        "120": "F9",
        "121": "F10",
        "122": "F11",
        "123": "F12"
    };

    document.onkeydown = function(e){
        if (!e)
            e = event;

        if (e.keyCode == 93)
            jpf.contextMenuKeyboard = true;

        //@todo move this to appsettings and use with_hotkey
        if (jpf.appsettings.useUndoKeys && e.ctrlKey) {
            //Ctrl-Z - Undo
            if (e.keyCode == 90) {
                var o = jpf.window.focussed;
                if (!o || !o.getActionTracker)
                     o = jpf.window;
                o.getActionTracker().undo();
            }
            //Ctrl-Y - Redo
            else if (e.keyCode == 89) {
                var o = jpf.window.focussed;
                if (!o || !o.getActionTracker)
                     o = jpf.window;
                o.getActionTracker().redo();
            }
        }

        var eInfo = {
            ctrlKey   : e.ctrlKey,
            shiftKey  : e.shiftKey,
            altKey    : e.altKey,
            keyCode   : e.keyCode,
            htmlEvent : e,
            bubbles   : true
        };

        //Hotkey
        if (jpf.dispatchEvent("hotkey", eInfo) === false || eInfo.returnValue === false) {
            e.returnValue  = false;
            e.cancelBubble = true;
            if (jpf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch(e) {}
            }
            return false;
        }

        var keys = []; //@todo put this in a lut
        if (e.altKey)
            keys.push("Alt");
        if (e.ctrlKey)
            keys.push("Ctrl");
        if (e.shiftKey)
            keys.push("Shift");
        if (e.metaKey)
            keys.push("Meta");

        if (keyNames[e.keyCode])
            keys.push(keyNames[e.keyCode]);

        if (keys.length) {
            if (e.keyCode > 46)
                keys.push(String.fromCharCode(e.keyCode));
            jpf.setProperty("hotkey", keys.join("-"));
        }


        //Keyboard forwarding to focussed object
        if (jpf.window.focussed && !jpf.window.focussed.disableKeyboard
          && jpf.window.focussed.dispatchEvent("keydown", eInfo) === false) {
            e.returnValue  = false;
            e.cancelBubble = true;
            if (jpf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch(e) {}
            }
            return false;
        }

        //Focus handling
        else if ((!jpf.appsettings.disableTabbing || jpf.window.focussed) && e.keyCode == 9) {
            //Window focus handling
            if (e.ctrlKey && jpf.window.focussed) {
                var w = jpf.window.focussed.$focusParent;
                if (w.modal) {
                    e.returnValue = false;
                    return false;
                }

                jpf.window.moveNext(e.shiftKey,
                    jpf.window.focussed.$focusParent, true);

                var w = jpf.window.focussed.$focusParent;
                if (w && w.bringToFront)
                    w.bringToFront();
            }
            //Element focus handling
            else if(!jpf.window.focussed || jpf.window.focussed.tagName != "menu")
                jpf.window.moveNext(e.shiftKey);

            e.returnValue = false;
            return false;
        }

        //Disable backspace behaviour triggering the backbutton behaviour
        if (jpf.appsettings.disableBackspace
          && (e.keyCode == 8 || e.altKey && (e.keyCode == 37 || e.keyCode == 39))
          && !ta[(e.srcElement || e.target).tagName]) {
            if (jpf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch(e) {}
            }
            e.returnValue = false;
        }

        //Disable space behaviour of scrolling down the page
        /*if(Application.disableSpace && e.keyCode == 32 && e.srcElement.tagName.toLowerCase() != "input"){
            e.keyCode = 0;
            e.returnValue = false;
        }*/

        //Disable F5 refresh behaviour
        if (jpf.appsettings.disableF5 && (e.keyCode == 116 || e.keyCode == 117)) {
            if (jpf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch(e) {}
            }
            else {
                e.preventDefault();
                e.stopPropagation();
            }
            //return false;
        }

        if (e.keyCode == 27) { //or up down right left pageup pagedown home end unless body is selected
            e.returnValue = false;
        }

        if (!jpf.appsettings.allowSelect
          && e.shiftKey && (e.keyCode > 32 && e.keyCode < 41)
          && !ta[(e.explicitOriginalTarget || e.srcElement || e.target).tagName]
          && (!e.srcElement || e.srcElement.contentEditable != "true")) {
                e.returnValue = false;
        }

        //jpf.dispatchEvent("keydown", null, eInfo);

        return e.returnValue;
    };

    //@todo maybe generalize this to pub/sub event system??
    var hotkeys = {}, keyMods = {"ctrl": 1, "alt": 2, "shift": 4, "meta": 8};

    /**
     * Registers a hotkey handler to a key combination.
     * Example:
     * <code>
     *   jpf.registerHotkey('Ctrl-Z', undoHandler);
     * </code>
     * @param {String}   hotkey  the key combination to user. This is a
     * combination of Ctrl, Alt, Shift and a normal key to press. Use + to
     * seperate the keys.
     * @param {Function} handler the code to be executed when the key
     * combination is pressed.
     */
    jpf.registerHotkey = function(hotkey, handler){
        var hashId = 0, key;

        var keys = hotkey.splitSafe("\\-|\\+| ", null, true),
            bHasCtrl = false,
            bHasMeta = false;
        for (var i = 0; i < keys.length; i++) {
            if (keyMods[keys[i]]) {
                hashId = hashId | keyMods[keys[i]];
                if (jpf.isMac) {
                    bHasCtrl = (keyMods[keys[i]] === keyMods["ctrl"]);
                    bHasMeta = (keyMods[keys[i]] === keyMods["meta"]);
                }
            }
            else
                key = keys[i];
        }

        if (bHasCtrl && !bHasMeta) //for improved Mac hotkey support
            hashId = hashId | keyMods["meta"];

        if (!key) {
            throw new Error("missing key for hotkey: " + hotkey);
        }

        (hotkeys[hashId] || (hotkeys[hashId] = {}))[key] = handler;
    };

    /**
     * Removes a registered hotkey.
     * @param {String} hotkey the hotkey combination.
     */
    jpf.removeHotkey = function(hotkey){
        jpf.registerHotkey(hotkey, null);
    };

    jpf.addEventListener("hotkey", function(e){
        // enable meta-hotkey support for macs, like for Apple-Z, Apple-C, etc.
        if (jpf.isMac && e.metaKey)
            e.ctrlKey = true;
        var hashId = 0 | (e.ctrlKey ? 1 : 0)
            | (e.shiftKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);

        var key = keyNames[e.keyCode];
        if (!hashId && !key) //Hotkeys should always have one of the modifiers
            return;

        var handler = (hotkeys[hashId] || {})[(key
            || String.fromCharCode(e.keyCode)).toLowerCase()];
        if (handler) {
            handler();
            e.returnValue = false;
        }
    });

    this.destroy = function(){
        this.$at = null;

        jpf.destroy(this);
        jpf.windowManager.destroy(this);

        jpf           =
        this.win      =
        this.window   =
        this.document = null;

        window.onfocus        =
        window.onerror        =
        window.onunload       =
        window.onbeforeunload =
        window.onbeforeprint  =
        window.onafterprint   =
        window.onmousewheel   =
        window.onblur         = null;

        document.oncontextmenu =
        document.onmousedown   =
        document.onmousemove   =
        document.onmouseup     =
        document.onselectstart =
        document.onmousewheel  =
        document.onkeyup       =
        document.onkeydown     = null

        document.body.onmousedown =
        document.body.onmousemove =
        document.body.onmouseup   = null;

        document.body.innerHTML = "";
    };
};

/**
 * The jml document. This is the jml node with nodeType 9. It is the root of
 * the application and has a refererence to the documentElement.
 *
 * @constructor
 *
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.8
 */
jpf.DocumentImplementation = function(){
    jpf.makeClass(this);

    this.inherit(jpf.JmlDom); /** @inherits jpf.JmlDom */

    this.nodeType   = jpf.NODE_DOCUMENT;
    this.nodeFunc   = jpf.NODE_HIDDEN;
    this.$jmlLoaded = true;

    /**
     * The root element node of the jml application. This is an element with
     * the tagName 'application'. This is similar to the 'html' element
     */
    this.documentElement = {

        uniqueId      : jpf.all.push(this) - 1,
        nodeType      :  1,
        nodeFunc      : jpf.NODE_HIDDEN,
        tagName       : "application",
        parentNode    : this,
        ownerDocument : this,
        pHtmlNode     : document.body,
        $jml          : jpf.JmlParser.$jml,
        $tabList      : [], //Prevents documentElement from being focussed
        $jmlLoaded    : true,
        $focussable   : jpf.KEYBOARD,
        focussable    : true,
        visible       : true,

        isWindowContainer : true,
        canHaveChildren   : true,

        focus : function(){
            this.dispatchEvent("focus");
        },

        blur  : function(){
            this.dispatchEvent("blur");
        }
    };

    this.appendChild  =
    this.insertBefore = function(){
        this.documentElement.insertBefore.apply(this.documentElement, arguments);
    };

    jpf.inherit.call(this.documentElement, jpf.Class);
    jpf.window.$addFocus(this.documentElement);

    jpf.inherit.call(this.documentElement, jpf.JmlDom);

    this.getElementById = function(id){
        return self[id];
    };

    /**
     * Creates a new jml element.
     * @param {mixed} tagName information about the new node to create.
     *   Possible values:
     *   {String}     the tagName of the new element to create
     *   {String}     the jml definition for a single or multiple elements.
     *   {XMLElement} the jml definition for a single or multiple elements.
     * @return {JMLElement} the created jml element.
     */
    this.createElement = function(tagName){
        var x, o;

        //We're supporting the nice IE hack
        if (tagName.nodeType) {
            x = tagName;
        }
        else if (tagName.indexOf("<") > -1) {
            x = jpf.getXml(tagName)
        }
        else {
            var prefix = jpf.findPrefix(jpf.JmlParser.$jml, jpf.ns.jml);
            var doc = jpf.JmlParser.$jml.ownerDocument;

            if(jpf.JmlParser.$jml && doc.createElementNS) {
                x = doc.createElementNS(jpf.ns.jml, prefix + ":" + tagName);
            }
            else {
                x = jpf.getXml("<" + prefix + ":" + tagName + " xmlns:"
                               + prefix + "='" + jpf.ns.jml + "' />", true);
            }
        }

        if (jpf.isIE) {
            if (!prefix)
                prefix = x.prefix;

            x.ownerDocument.setProperty("SelectionNamespaces",
                "xmlns:" + prefix + "='" + jpf.ns.jml + "'");
        }

        tagName = x[jpf.TAGNAME];
        var initId;

        if (typeof jpf[tagName] != "function") { //Call JMLParser??
            o = new jpf.JmlDom(tagName, null, jpf.NODE_HIDDEN, x);
            if (jpf.JmlParser.handler[tagName]) {
                initId = o.$domHandlers["reparent"].push(function(b, pNode){
                    this.$domHandlers.reparent[initId] = null;

                    if (!pNode.$jmlLoaded)
                        return; //the jmlParser will handle the rest

                    o = jpf.JmlParser.handler[tagName](this.$jml,
                        pNode, pNode.oInt);

                    if (o) jpf.extend(this, o); //ruins prototyped things

                    //Add this component to the nameserver
                    if (o && this.name)
                        jpf.nameserver.register(tagName, this.name, o);

                    if (this.name)
                        jpf.setReference(name, o);

                    o.$jmlLoaded = true;
                }) - 1;
            }
        }
        else {
            o = new jpf[tagName](null, tagName, x);
            if (o.loadJml) {
                initId = o.$domHandlers["reparent"].push(function(b, pNode){
                    this.$domHandlers.reparent[initId] = null;

                    if (!pNode.$jmlLoaded) //We're not ready yet
                        return;

                    function loadJml(o, pHtmlNode){
                        if (!o.$jmlLoaded) {
                            //Process JML
                            var length = o.childNodes.length;

                            o.pHtmlNode = pHtmlNode || document.body;
                            o.loadJml(o.$jml);
                            o.$jmlLoaded = false; //small hack

                            if (length) {
                                for (var i = 0, l = o.childNodes.length; i < l; i++) {
                                    if (o.childNodes[i].loadJml) {
                                        loadJml(o.childNodes[i], o.canHaveChildren
                                            ? o.oInt
                                            : document.body);
                                    }
                                    else
                                        o.childNodes[i].$jmlLoaded = true;
                                }
                            }
                        }
                        if (o.$reappendToParent) {
                            o.$reappendToParent();
                        }

                        o.$jmlLoaded = true;
                        o.$reappendToParent = null;
                    }

                    var parsing = jpf.isParsing;
                    jpf.isParsing = true;
                    jpf.JmlParser.parseFirstPass([x]);

                    loadJml(o, pNode && pNode.oInt || document.body);

                    if (pNode && pNode.pData)
                        jpf.layout.compileAlignment(pNode.pData);

                    if (pNode.pData)
                        jpf.layout.activateRules(pNode.oInt || document.body);
                    //jpf.layout.activateRules();//@todo maybe use processQueue

                    jpf.JmlParser.parseLastPass();
                    jpf.isParsing = parsing;
                }) - 1;
            }
        }

        if (o.name)
            jpf.setReference(o.name, o);

        o.$jml = x;

        return o;
    };
    
    this.createDocumentFragment = function(){
        return new jpf.JmlDom(jpf.NODE_DOCUMENT_FRAGMENT)
    }

    /**
     * See W3C evaluate
     */
    this.evaluate = function(sExpr, contextNode, nsResolver, type, x){
        var result = jpf.XPath.selectNodes(sExpr,
            contextNode || this.documentElement);

        return {
            snapshotLength : result.length,
            snapshotItem   : function(i){
                return result[i];
            }
        }
    };

    /**
     * See W3C createNSResolver
     */
    this.createNSResolver = function(contextNode){
        return {};
    };
};


jpf.sanitizeTextbox = function(oTxt){
    oTxt.onfocus = function(){
        jpf.window.$focusfix2();
    };

    oTxt.onblur = function(){
        jpf.window.$blurfix();
    };
}


/*FILEHEAD(/var/lib/jpf/src/core/crypto/rsa.js)SIZE(-1077090856)TIME(1224578765)*/

/**
 * RSA, a suite of routines for performing RSA public-key computations in
 * JavaScript.
 *
 * Requires BigInt.js and Barrett.js.
 *
 * Copyright 1998-2005 David Shapiro.
 *
 * You may use, re-use, abuse, copy, and modify this code to your liking, but
 * please keep this header.
 *
 * Thanks!
 * 
 * @author Dave Shapiro <dave AT ohdave DOT com>
 */


jpf.crypto.RSA = (function() {
    function RSAKeyPair(encryptionExponent, decryptionExponent, modulus) {
        this.e = jpf.crypto.BigInt.fromHex(encryptionExponent);
        this.d = jpf.crypto.BigInt.fromHex(decryptionExponent);
        this.m = jpf.crypto.BigInt.fromHex(modulus);
        /*
         * We can do two bytes per digit, so
         * chunkSize = 2 * (number of digits in modulus - 1).
         * Since biHighIndex returns the high index, not the number of digits, 1 has
         * already been subtracted.
         */
        ////////////////////////////////// TYF
        this.digitSize = 2 * jpf.crypto.BigInt.highIndex(this.m) + 2;
        this.chunkSize = this.digitSize - 11; // maximum, anything lower is fine
        ////////////////////////////////// TYF
        this.radix = 16;
        this.barrett = new jpf.crypto.Barrett(this.m);
    }
    
    function twoDigit(n) {
        return (n < 10 ? "0" : "") + String(n);
    }
    
    function encryptedString(key, s) {
        /*
         * Altered by Rob Saunders (rob@robsaunders.net). New routine pads the
         * string after it has been converted to an array. This fixes an
         * incompatibility with Flash MX's ActionScript.
         * Altered by Tang Yu Feng for interoperability with Microsoft's
         * RSACryptoServiceProvider implementation.
         */
        ////////////////////////////////// TYF
        if (key.chunkSize > key.digitSize - 11) {
            return "Error";
        }
        ////////////////////////////////// TYF
        var a = new Array();
        var sl = s.length;
        
        var i = 0;
        while (i < sl) {
            a[i] = s.charCodeAt(i);
            i++;
        }
        
        var al = a.length;
        var result = "";
        var j, k, block;
        for (i = 0; i < al; i += key.chunkSize) {
            block = new jpf.crypto.BigInt.construct();
            j = 0;
            ////////////////////////////////// TYF
            /*
             * Add PKCS#1 v1.5 padding
             * 0x00 || 0x02 || PseudoRandomNonZeroBytes || 0x00 || Message
             * Variable a before padding must be of at most digitSize-11
             * That is for 3 marker bytes plus at least 8 random non-zero bytes
             */
            var x;
            var msgLength = (i+key.chunkSize)>al ? al%key.chunkSize : key.chunkSize;
            
            // Variable b with 0x00 || 0x02 at the highest index.
            var b = new Array();
            for (x = 0; x < msgLength; x++) {
                b[x] = a[i + msgLength - 1 - x];
            }
            b[msgLength] = 0; // marker
            var paddedSize = Math.max(8, key.digitSize - 3 - msgLength);
            for (x = 0; x < paddedSize; x++) {
                b[msgLength + 1 + x] = Math.floor(Math.random() * 254) + 1; // [1,255]
            }
            // It can be asserted that msgLength+paddedSize == key.digitSize-3
            b[key.digitSize - 2] = 2; // marker
            b[key.digitSize - 1] = 0; // marker
            
            for (k = 0; k < key.digitSize; ++j) 
            {
                block.digits[j] = b[k++];
                block.digits[j] += b[k++] << 8;
            }
            ////////////////////////////////// TYF
            
            var crypt = key.barrett.powMod(block, key.e);
            var text = key.radix == 16 ? jpf.crypto.BigInt.toHex(crypt) : jpf.crypto.BigInt.toString(crypt, key.radix);
            result += text + " ";
        }
        return result.substring(0, result.length - 1); // Remove last space.
    }
    
    function decryptedString(key, s) {
        var blocks = s.split(" ");
        var result = "";
        var i, j, block;
        for (i = 0; i < blocks.length; ++i) {
            var bi;
            if (key.radix == 16) {
                bi = jpf.crypto.BigInt.fromHex(blocks[i]);
            } else {
                bi = jpf.crypto.BigInt.fromString(blocks[i], key.radix);
            }
            block = key.barrett.powMod(bi, key.d);
            for (j = 0; j <= jpf.crypto.BigInt.highIndex(block); ++j) {
                result += String.fromCharCode(block.digits[j] & 255,
                  block.digits[j] >> 8);
            }
        }
        // Remove trailing null, if any.
        if (result.charCodeAt(result.length - 1) == 0) {
            result = result.substring(0, result.length - 1);
        }
        return result;
    }
    
    //publish public functions:
    return {
        F4: "10001",
        E3: "3",
        getKeyPair: RSAKeyPair,
        twoDigit: twoDigit,
        encrypt: encryptedString,
        decrypt: decryptedString
    };
})();




/*FILEHEAD(/var/lib/jpf/src/core/crypto/barrett.js)SIZE(-1077090856)TIME(1225628610)*/

/**
 * Crypt.Barrett, a class for performing Barrett modular reduction computations in
 * JavaScript.
 *
 * Requires BigInt.js.
 *
 * Copyright 2004-2005 David Shapiro.
 *
 * You may use, re-use, abuse, copy, and modify this code to your liking, but
 * please keep this header.
 *
 * Thanks!
 * 
 * @author Dave Shapiro <dave AT ohdave DOT com>
 */


/**
 * A class for performing Barrett modular reduction computations in JavaScript.
 *
 * @param {jpf.crypto.BigInt} m
 */
jpf.crypto.Barrett = function(){this.init.apply(this, arguments);};
jpf.crypto.Barrett.prototype = {
    init: function(m) {
        this.modulus = jpf.crypto.BigInt.copy(m);
        this.k = jpf.crypto.BigInt.highIndex(this.modulus) + 1;
        var b2k = new jpf.crypto.BigInt.construct();
        b2k.digits[2 * this.k] = 1; // b2k = b^(2k)
        this.mu = jpf.crypto.BigInt.divide(b2k, this.modulus);
        this.bkplus1 = new jpf.crypto.BigInt.construct();
        this.bkplus1.digits[this.k + 1] = 1; // bkplus1 = b^(k+1)
    },
    modulo: function(x) {
        var q1 = jpf.crypto.BigInt.divideByRadixPower(x, this.k - 1);
        var q2 = jpf.crypto.BigInt.multiply(q1, this.mu);
        var q3 = jpf.crypto.BigInt.divideByRadixPower(q2, this.k + 1);
        var r1 = jpf.crypto.BigInt.moduloByRadixPower(x, this.k + 1);
        var r2term = jpf.crypto.BigInt.multiply(q3, this.modulus);
        var r2 = jpf.crypto.BigInt.moduloByRadixPower(r2term, this.k + 1);
        var r = jpf.crypto.BigInt.subtract(r1, r2);
        if (r.isNeg) {
            r = jpf.crypto.BigInt.add(r, this.bkplus1);
        }
        var rgtem = jpf.crypto.BigInt.compare(r, this.modulus) >= 0;
        while (rgtem) {
            r = jpf.crypto.BigInt.subtract(r, this.modulus);
            rgtem = jpf.crypto.BigInt.compare(r, this.modulus) >= 0;
        }
        return r;
    },
    multiplyMod: function(x, y) {
        /*
         * x = this.modulo(x);
         * y = this.modulo(y);
         */
        var xy = jpf.crypto.BigInt.multiply(x, y);
        return this.modulo(xy);
    },
    powMod: function(x, y) {
        var result = new jpf.crypto.BigInt.construct();
        result.digits[0] = 1;
        var a = x;
        var k = y;
        while (true) {
            if ((k.digits[0] & 1) != 0) result = this.multiplyMod(result, a);
            k = jpf.crypto.BigInt.shiftRight(k, 1);
            if (k.digits[0] == 0 && jpf.crypto.BigInt.highIndex(k) == 0) break;
            a = this.multiplyMod(a, a);
        }
        return result;
    }
};



/*FILEHEAD(/var/lib/jpf/src/core/crypto/bigint.js)SIZE(-1077090856)TIME(1225628610)*/

/**
 * BigInt, a suite of routines for performing multiple-precision arithmetic in
 * JavaScript.
 *
 * Copyright 1998-2005 David Shapiro.
 *
 * You may use, re-use, abuse,
 * copy, and modify this code to your liking, but please keep this header.
 * Thanks!
 *
 * @author Dave Shapiro <dave AT ohdave DOT com>
 * @author Ian Bunning
 *
 * IMPORTANT THING: Be sure to set maxDigits according to your precision
 * needs. Use the setMaxDigits() function to do this. See comments below.
 *
 * Tweaked by Ian Bunning
 * Alterations:
 * Fix bug in function biFromHex(s) to allow
 * parsing of strings of length != 0 (mod 4)
 *
 * Changes made by Dave Shapiro as of 12/30/2004:
 *
 * The BigInt() constructor doesn't take a string anymore. If you want to
 * create a BigInt from a string, use biFromDecimal() for base-10
 * representations, biFromHex() for base-16 representations, or
 * biFromString() for base-2-to-36 representations.
 *
 * biFromArray() has been removed. Use biCopy() instead, passing a BigInt
 * instead of an array.
 *
 * The BigInt() constructor now only constructs a zeroed-out array.
 * Alternatively, if you pass <true>, it won't construct any array. See the
 * biCopy() method for an example of this.
 *
 * Be sure to set maxDigits depending on your precision needs. The default
 * zeroed-out array ZERO_ARRAY is constructed inside the setMaxDigits()
 * function. So use this function to set the variable. DON'T JUST SET THE
 * VALUE. USE THE FUNCTION.
 *
 * ZERO_ARRAY exists to hopefully speed up construction of BigInts(). By
 * precalculating the zero array, we can just use slice(0) to make copies of
 * it. Presumably this calls faster native code, as opposed to setting the
 * elements one at a time. I have not done any timing tests to verify this
 * claim.
 * Max number = 10^16 - 2 = 9999999999999998;
 *               2^53     = 9007199254740992;
 */


jpf.crypto.BigInt = (function() {
    var biRadixBase = 2;
    var biRadixBits = 16;
    var bitsPerDigit = biRadixBits;
    var biRadix = 1 << 16; // = 2^16 = 65536
    var biHalfRadix = biRadix >>> 1;
    var biRadixSquared = biRadix * biRadix;
    var maxDigitVal = biRadix - 1;
    var maxInteger = 9999999999999998; 
    
    /*
     * maxDigits:
     * Change this to accommodate your largest number size. Use setMaxDigits()
     * to change it!
     *
     * In general, if you're working with numbers of size N bits, you'll need 2*N
     * bits of storage. Each digit holds 16 bits. So, a 1024-bit key will need
     *
     * 1024 * 2 / 16 = 128 digits of storage.
     */
    
    var maxDigits;
    var ZERO_ARRAY;
    var bigZero, bigOne;
    
    function setMaxDigits(value) {
        maxDigits = value;
        ZERO_ARRAY = new Array(maxDigits);
        for (var iza = 0; iza < ZERO_ARRAY.length; iza++) ZERO_ARRAY[iza] = 0;
        bigZero = new BigInt();
        bigOne = new BigInt();
        bigOne.digits[0] = 1;
    }
    
    setMaxDigits(20);
    
    // The maximum number of digits in base 10 you can convert to an
    // integer without JavaScript throwing up on you.
    var dpl10 = 15;
    // lr10 = 10 ^ dpl10
    var lr10 = biFromNumber(1000000000000000);
    
    function BigInt(flag) {
        if (typeof flag == "boolean" && flag == true) {
            this.digits = null;
        } else {
            this.digits = ZERO_ARRAY.slice(0);
        }
        this.isNeg = false;
    }
    
    function biFromDecimal(s) {
        var isNeg = s.charAt(0) == '-';
        var i = isNeg ? 1 : 0;
        var result;
        // Skip leading zeros.
        while (i < s.length && s.charAt(i) == '0') ++i;
        if (i == s.length) {
            result = new BigInt();
        } else {
            var digitCount = s.length - i;
            var fgl = digitCount % dpl10;
            if (fgl == 0) fgl = dpl10;
            result = biFromNumber(Number(s.substr(i, fgl)));
            i += fgl;
            while (i < s.length) {
                result = biAdd(biMultiply(result, lr10),
                               biFromNumber(Number(s.substr(i, dpl10))));
                i += dpl10;
            }
            result.isNeg = isNeg;
        }
        return result;
    }
    
    function biCopy(bi) {
        var result = new BigInt(true);
        result.digits = bi.digits.slice(0);
        result.isNeg = bi.isNeg;
        return result;
    }
    
    function biFromNumber(i) {
        var result = new BigInt();
        result.isNeg = i < 0;
        i = Math.abs(i);
        var j = 0;
        while (i > 0) {
            result.digits[j++] = i & maxDigitVal;
            i = Math.floor(i / biRadix);
        }
        return result;
    }
    
    function reverseStr(s) {
        var result = "";
        for (var i = s.length - 1; i > -1; --i) {
            result += s.charAt(i);
        }
        return result;
    }
    
    var hexatrigesimalToChar = new Array(
     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
     'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
     'u', 'v', 'w', 'x', 'y', 'z'
    );
    
    function biToString(x, radix) {
        // 2 <= radix <= 36
        var b = new BigInt();
        b.digits[0] = radix;
        var qr = biDivideModulo(x, b);
        var result = hexatrigesimalToChar[qr[1].digits[0]];
        while (biCompare(qr[0], bigZero) == 1) {
            qr = biDivideModulo(qr[0], b);
            digit = qr[1].digits[0];
            result += hexatrigesimalToChar[qr[1].digits[0]];
        }
        return (x.isNeg ? "-" : "") + reverseStr(result);
    }
    
    function biToDecimal(x) {
        var b = new BigInt();
        b.digits[0] = 10;
        var qr = biDivideModulo(x, b);
        var result = String(qr[1].digits[0]);
        while (biCompare(qr[0], bigZero) == 1) {
            qr = biDivideModulo(qr[0], b);
            result += String(qr[1].digits[0]);
        }
        return (x.isNeg ? "-" : "") + reverseStr(result);
    }
    
    var hexToChar = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
      'a', 'b', 'c', 'd', 'e', 'f');
    
    function digitToHex(n) {
        var mask = 0xf;
        var result = "";
        for (i = 0; i < 4; ++i) {
            result += hexToChar[n & mask];
            n >>>= 4;
        }
        return reverseStr(result);
    }
    
    function biToHex(x) {
        var result = "";
        var n = biHighIndex(x);
        for (var i = biHighIndex(x); i > -1; --i) {
            result += digitToHex(x.digits[i]);
        }
        return result;
    }
    
    function charToHex(c) {
        var ZERO = 48;
        var NINE = ZERO + 9;
        var littleA = 97;
        var littleZ = littleA + 25;
        var bigA = 65;
        var bigZ = 65 + 25;
        var result;
    
        if (c >= ZERO && c <= NINE) {
            result = c - ZERO;
        } else if (c >= bigA && c <= bigZ) {
            result = 10 + c - bigA;
        } else if (c >= littleA && c <= littleZ) {
            result = 10 + c - littleA;
        } else {
            result = 0;
        }
        return result;
    }
    
    function hexToDigit(s) {
        var result = 0;
        var sl = Math.min(s.length, 4);
        for (var i = 0; i < sl; ++i) {
            result <<= 4;
            result |= charToHex(s.charCodeAt(i))
        }
        return result;
    }
    
    function biFromHex(s) {
        var result = new BigInt();
        var sl = s.length;
        for (var i = sl, j = 0; i > 0; i -= 4, ++j) {
            result.digits[j] = hexToDigit(s.substr(Math.max(i - 4, 0), Math.min(i, 4)));
        }
        return result;
    }
    
    function biFromString(s, radix) {
        var isNeg = s.charAt(0) == '-';
        var istop = isNeg ? 1 : 0;
        var result = new BigInt();
        var place = new BigInt();
        place.digits[0] = 1; // radix^0
        for (var i = s.length - 1; i >= istop; i--) {
            var c = s.charCodeAt(i);
            var digit = charToHex(c);
            var biDigit = biMultiplyDigit(place, digit);
            result = biAdd(result, biDigit);
            place = biMultiplyDigit(place, radix);
        }
        result.isNeg = isNeg;
        return result;
    }
    
    function biDump(b) {
        return (b.isNeg ? "-" : "") + b.digits.join(" ");
    }
    
    function biAdd(x, y) {
        var result;
    
        if (x.isNeg != y.isNeg) {
            y.isNeg = !y.isNeg;
            result = biSubtract(x, y);
            y.isNeg = !y.isNeg;
        } else {
            result = new BigInt();
            var c = 0;
            var n;
            for (var i = 0; i < x.digits.length; ++i) {
                n = x.digits[i] + y.digits[i] + c;
                result.digits[i] = n % biRadix;
                c = Number(n >= biRadix);
            }
            result.isNeg = x.isNeg;
        }
        return result;
    }
    
    function biSubtract(x, y) {
        var result;
        if (x.isNeg != y.isNeg) {
            y.isNeg = !y.isNeg;
            result = biAdd(x, y);
            y.isNeg = !y.isNeg;
        } else {
            result = new BigInt();
            var n, c;
            c = 0;
            for (var i = 0; i < x.digits.length; ++i) {
                n = x.digits[i] - y.digits[i] + c;
                result.digits[i] = n % biRadix;
                // Stupid non-conforming modulus operation.
                if (result.digits[i] < 0) result.digits[i] += biRadix;
                c = 0 - Number(n < 0);
            }
            // Fix up the negative sign, if any.
            if (c == -1) {
                c = 0;
                for (var i = 0; i < x.digits.length; ++i) {
                    n = 0 - result.digits[i] + c;
                    result.digits[i] = n % biRadix;
                    // Stupid non-conforming modulus operation.
                    if (result.digits[i] < 0) result.digits[i] += biRadix;
                    c = 0 - Number(n < 0);
                }
                // Result is opposite sign of arguments.
                result.isNeg = !x.isNeg;
            } else {
                // Result is same sign.
                result.isNeg = x.isNeg;
            }
        }
        return result;
    }
    
    function biHighIndex(x) {
        var result = x.digits.length - 1;
        while (result > 0 && x.digits[result] == 0) --result;
        return result;
    }
    
    function biNumBits(x) {
        var n = biHighIndex(x);
        var d = x.digits[n];
        var m = (n + 1) * bitsPerDigit;
        var result;
        for (result = m; result > m - bitsPerDigit; --result) {
            if ((d & 0x8000) != 0) break;
            d <<= 1;
        }
        return result;
    }
    
    function biMultiply(x, y) {
        var result = new BigInt();
        var c;
        var n = biHighIndex(x);
        var t = biHighIndex(y);
        var u, uv, k;
    
        for (var i = 0; i <= t; ++i) {
            c = 0;
            k = i;
            for (j = 0; j <= n; ++j, ++k) {
                uv = result.digits[k] + x.digits[j] * y.digits[i] + c;
                result.digits[k] = uv & maxDigitVal;
                c = uv >>> biRadixBits;
                //c = Math.floor(uv / biRadix);
            }
            result.digits[i + n + 1] = c;
        }
        // Someone give me a logical xor, please.
        result.isNeg = x.isNeg != y.isNeg;
        return result;
    }
    
    function biMultiplyDigit(x, y) {
        var n, c, uv;
    
        result = new BigInt();
        n = biHighIndex(x);
        c = 0;
        for (var j = 0; j <= n; ++j) {
            uv = result.digits[j] + x.digits[j] * y + c;
            result.digits[j] = uv & maxDigitVal;
            c = uv >>> biRadixBits;
            //c = Math.floor(uv / biRadix);
        }
        result.digits[1 + n] = c;
        return result;
    }
    
    function arrayCopy(src, srcStart, dest, destStart, n) {
        var m = Math.min(srcStart + n, src.length);
        for (var i = srcStart, j = destStart; i < m; ++i, ++j) {
            dest[j] = src[i];
        }
    }
    
    var highBitMasks = new Array(0x0000, 0x8000, 0xC000, 0xE000, 0xF000, 0xF800,
      0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0,
      0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF);
    
    function biShiftLeft(x, n) {
        var digitCount = Math.floor(n / bitsPerDigit);
        var result = new BigInt();
        arrayCopy(x.digits, 0, result.digits, digitCount,
          result.digits.length - digitCount);
        var bits = n % bitsPerDigit;
        var rightBits = bitsPerDigit - bits;
        for (var i = result.digits.length - 1, i1 = i - 1; i > 0; --i, --i1) {
            result.digits[i] = ((result.digits[i] << bits) & maxDigitVal) |
              ((result.digits[i1] & highBitMasks[bits]) >>>
              (rightBits));
        }
        result.digits[0] = ((result.digits[i] << bits) & maxDigitVal);
        result.isNeg = x.isNeg;
        return result;
    }
    
    var lowBitMasks = new Array(0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
      0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF,
      0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF);
    
    function biShiftRight(x, n) {
        var digitCount = Math.floor(n / bitsPerDigit);
        var result = new BigInt();
        arrayCopy(x.digits, digitCount, result.digits, 0,
          x.digits.length - digitCount);
        var bits = n % bitsPerDigit;
        var leftBits = bitsPerDigit - bits;
        for (var i = 0, i1 = i + 1; i < result.digits.length - 1; ++i, ++i1) {
            result.digits[i] = (result.digits[i] >>> bits) |
              ((result.digits[i1] & lowBitMasks[bits]) << leftBits);
        }
        result.digits[result.digits.length - 1] >>>= bits;
        result.isNeg = x.isNeg;
        return result;
    }
    
    function biMultiplyByRadixPower(x, n) {
        var result = new BigInt();
        arrayCopy(x.digits, 0, result.digits, n, result.digits.length - n);
        return result;
    }
    
    function biDivideByRadixPower(x, n) {
        var result = new BigInt();
        arrayCopy(x.digits, n, result.digits, 0, result.digits.length - n);
        return result;
    }
    
    function biModuloByRadixPower(x, n) {
        var result = new BigInt();
        arrayCopy(x.digits, 0, result.digits, 0, n);
        return result;
    }
    
    function biCompare(x, y) {
        if (x.isNeg != y.isNeg) {
            return 1 - 2 * Number(x.isNeg);
        }
        for (var i = x.digits.length - 1; i >= 0; --i) {
            if (x.digits[i] != y.digits[i]) {
                if (x.isNeg) {
                    return 1 - 2 * Number(x.digits[i] > y.digits[i]);
                } else {
                    return 1 - 2 * Number(x.digits[i] < y.digits[i]);
                }
            }
        }
        return 0;
    }
    
    function biDivideModulo(x, y) {
        var nb = biNumBits(x);
        var tb = biNumBits(y);
        var origYIsNeg = y.isNeg;
        var q, r;
        if (nb < tb) {
            // |x| < |y|
            if (x.isNeg) {
                q = biCopy(bigOne);
                q.isNeg = !y.isNeg;
                x.isNeg = false;
                y.isNeg = false;
                r = biSubtract(y, x);
                // Restore signs, 'cause they're references.
                x.isNeg = true;
                y.isNeg = origYIsNeg;
            } else {
                q = new BigInt();
                r = biCopy(x);
            }
            return new Array(q, r);
        }
    
        q = new BigInt();
        r = x;
    
        // Normalize Y.
        var t = Math.ceil(tb / bitsPerDigit) - 1;
        var lambda = 0;
        while (y.digits[t] < biHalfRadix) {
            y = biShiftLeft(y, 1);
            ++lambda;
            ++tb;
            t = Math.ceil(tb / bitsPerDigit) - 1;
        }
        // Shift r over to keep the quotient constant. We'll shift the
        // remainder back at the end.
        r = biShiftLeft(r, lambda);
        nb += lambda; // Update the bit count for x.
        var n = Math.ceil(nb / bitsPerDigit) - 1;
    
        var b = biMultiplyByRadixPower(y, n - t);
        while (biCompare(r, b) != -1) {
            ++q.digits[n - t];
            r = biSubtract(r, b);
        }
        for (var i = n; i > t; --i) {
        var ri = (i >= r.digits.length) ? 0 : r.digits[i];
        var ri1 = (i - 1 >= r.digits.length) ? 0 : r.digits[i - 1];
        var ri2 = (i - 2 >= r.digits.length) ? 0 : r.digits[i - 2];
        var yt = (t >= y.digits.length) ? 0 : y.digits[t];
        var yt1 = (t - 1 >= y.digits.length) ? 0 : y.digits[t - 1];
            if (ri == yt) {
                q.digits[i - t - 1] = maxDigitVal;
            } else {
                q.digits[i - t - 1] = Math.floor((ri * biRadix + ri1) / yt);
            }
    
            var c1 = q.digits[i - t - 1] * ((yt * biRadix) + yt1);
            var c2 = (ri * biRadixSquared) + ((ri1 * biRadix) + ri2);
            while (c1 > c2) {
                --q.digits[i - t - 1];
                c1 = q.digits[i - t - 1] * ((yt * biRadix) | yt1);
                c2 = (ri * biRadix * biRadix) + ((ri1 * biRadix) + ri2);
            }
    
            b = biMultiplyByRadixPower(y, i - t - 1);
            r = biSubtract(r, biMultiplyDigit(b, q.digits[i - t - 1]));
            if (r.isNeg) {
                r = biAdd(r, b);
                --q.digits[i - t - 1];
            }
        }
        r = biShiftRight(r, lambda);
        // Fiddle with the signs and stuff to make sure that 0 <= r < y.
        q.isNeg = x.isNeg != origYIsNeg;
        if (x.isNeg) {
            if (origYIsNeg) {
                q = biAdd(q, bigOne);
            } else {
                q = biSubtract(q, bigOne);
            }
            y = biShiftRight(y, lambda);
            r = biSubtract(y, r);
        }
        // Check for the unbelievably stupid degenerate case of r == -0.
        if (r.digits[0] == 0 && biHighIndex(r) == 0) r.isNeg = false;
    
        return new Array(q, r);
    }
    
    function biDivide(x, y) {
        return biDivideModulo(x, y)[0];
    }
    
    function biModulo(x, y) {
        return biDivideModulo(x, y)[1];
    }
    
    function biMultiplyMod(x, y, m) {
        return biModulo(biMultiply(x, y), m);
    }
    
    function biPow(x, y) {
        var result = bigOne;
        var a = x;
        while (true) {
            if ((y & 1) != 0) result = biMultiply(result, a);
            y >>= 1;
            if (y == 0) break;
            a = biMultiply(a, a);
        }
        return result;
    }
    
    function biPowMod(x, y, m) {
        var result = bigOne;
        var a = x;
        var k = y;
        while (true) {
            if ((k.digits[0] & 1) != 0) result = biMultiplyMod(result, a, m);
            k = biShiftRight(k, 1);
            if (k.digits[0] == 0 && biHighIndex(k) == 0) break;
            a = biMultiplyMod(a, a, m);
        }
        return result;
    }
    
    //publish public methods:
    return {
        construct: BigInt,
        setMaxDigits: setMaxDigits,
        fromDecimal: biFromDecimal,
        copy: biCopy,
        fromNumber: biFromNumber,
        toString: biToString,
        toDecimal: biToDecimal,
        toHex: biToHex,
        fromHex: biFromHex,
        fromString: biFromString,
        dump: biDump,
        add: biAdd,
        subtract: biSubtract,
        highIndex: biHighIndex,
        numBits: biNumBits,
        multiply: biMultiply,
        shiftLeft: biShiftLeft,
        shiftRight: biShiftRight,
        compare: biCompare,
        pow: biPow,
        powMod: biPowMod,
        divide: biDivide,
        divideByRadixPower: biDivideByRadixPower,
        moduloByRadixPower: biModuloByRadixPower
    };
})();



/*FILEHEAD(/var/lib/jpf/src/core/crypto/base64.js)SIZE(-1077090856)TIME(1224578765)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


jpf.crypto.Base64 = (function() {
    
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    
    // public method for encoding
    function encode(input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = jpf.crypto.UTF8.encode(input);

        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
              keyStr.charAt(enc1) + keyStr.charAt(enc2) +
              keyStr.charAt(enc3) + keyStr.charAt(enc4);
        }

        return output;
    }
    
    // public method for decoding
    function decode(input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {
            enc1 = keyStr.indexOf(input.charAt(i++));
            enc2 = keyStr.indexOf(input.charAt(i++));
            enc3 = keyStr.indexOf(input.charAt(i++));
            enc4 = keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }

        output = jpf.crypto.UTF8.decode(output);

        return output;
    }
    
    return {
        decode: decode,
        encode: encode
    };
    
})();

jpf.crypto.UTF8 = {
    // private method for UTF-8 encoding
    encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    },

    // private method for UTF-8 decoding
    decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while (i < utftext.length) {
            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }

        return string;
    }

};



/*FILEHEAD(/var/lib/jpf/src/core/crypto/blowfish.js)SIZE(-1077090856)TIME(1224578765)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/*
 * Copyright (C) 2005 - Stephen Griffin, i-code.co.uk
 * Copyright (C) 2005 - Raphael Derosso Pereira - thyAPI adaptation
 *                      <raphaelpereira@users.sourceforge.net>
 */

(function(global) {

if (typeof jpf.crypt == "undefined") jpf.crypt = {};

function stringtoints(instring){
    var binary = [];
    var i, ch;
    
    for (i = 0; i < instring.length; i++) {
        ch = instring.charCodeAt(i);
        binary[i >> 2] |= ch << (3 - i % 4) * 8;
    }
    return binary;
}

function intstostring(intvalues){
    var outstring = new String();
    
    for (var i = 0; i < intvalues.length; i++) {
        outstring += String.fromCharCode(intvalues[i] >>> 24,
            (intvalues[i] >>> 16) & 0xff, (intvalues[i] >>> 8) & 0xff,
            intvalues[i] & 0xff);
    }
    return outstring;
}

function base64tobin(codestring){
    var temp, i = 0;
    var binary = [];
    
    while (i < codestring.length * 6) {
        temp = codestring.charCodeAt(i / 6);
        
        if (temp > 47 && temp < 58) 
            temp -= 48;
        if (temp > 62 && temp < 91) 
            temp -= 53;
        if (temp > 96 && temp < 123) 
            temp -= 59;
        
        switch (i & 0x1f) { //i%32
            case 0:
                binary[i >> 5] = temp;
                i += 6;
                break;
            case 28:
                binary[i >> 5] = (binary[i >> 5] << 4) | (temp >> 2);
                i += 4;
                binary[i >> 5] = temp & 0x03;
                i += 2;
                break;
            case 30:
                binary[i >> 5] = (binary[i >> 5] << 2) | (temp >> 4);
                i += 2;
                binary[i >> 5] = temp & 0x0f;
                i += 4;
                break;
            default:
                binary[i >> 5] = (binary[i >> 5] << 6) | temp;
                i += 6;
        }
    }
    return binary;
}

function base64encode(binary){
    var temp, bincount = 0;
    var codestring = "";
    var chars = "0123456789?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
    while (bincount < binary.length) {
        for (x = 0; x < 2; x++) {
            temp = binary[bincount] >>> 26;
            codestring += chars.charAt(temp);
            temp = (binary[bincount] >>> 20) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 14) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 8) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 2) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount++] & 0x3) << 4;
            temp = temp | (binary[bincount] >>> 28);
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 22) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 16) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 10) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 4) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount++] & 0xf) << 2;
            temp = temp | (binary[bincount] >>> 30);
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 24) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 18) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 12) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = (binary[bincount] >>> 6) & 0x3f;
            codestring += chars.charAt(temp);
            
            temp = binary[bincount++] & 0x3f;
            codestring += chars.charAt(temp);
        }
    }
    return codestring;
}

/**
 * Class: Blowfish
 *
 *	Blowfish cypher class.
 *
 *	Algorithm gently taken from i-code.co.uk. Thanks Stephen!
 *
 */
jpf.crypto.Blowfish = function(){this.init.apply(this, arguments);};
jpf.crypto.Blowfish.prototype = {
    init: function(m){
        this.P;
        this.S;
        this.previous_xHi = 0;
        this.previous_xLo = 0;
    },
    
    /**
     * Encodes the text passed with key
     *
     * @param {String} str The text to be encoded
     * @param {String} pass The key to be used to encode the text
     * @return Base64 encoded string
     * @type String
     */
    encode: function(str, pass){
        var ciphertext = new String("");
        var IV = new Array();
        
        for (x = 0; x < 4; x++) {
            if (typeof(this.customRand) == 'function') {
                IV[x] = this.customRand();
            } else {
                IV[x] = Math.floor(Math.random() * 0xFFFFFFFF);
            }
        }
        
        var key = this.passtokey(pass, IV[2], IV[3], IV[4]);
        IV[4] = key[14];//hashHi
        IV[5] = key[15];//hashLo
        this.initialise(key, IV);
        
        var binary = stringtoints(str);
        this.encipher_array(binary);
        
        return {
            code: base64encode(binary),
            init: base64encode(IV)
        };
    },
    
    /**
     * Decodes a base64 encoded string to the original one, if key is correct
     *
     * @param {String} code The cyphered code
     * @param {String} pass The key to be used
     * @param {Number} init The base64 initialization int
     */
    decode: function(code, pass, init){
        var initialints = base64tobin(init);
        var key = this.passtokey(pass, initialints[2], initialints[3]);
        
        if ((initialints[4] == key[14]) && (initialints[5] == key[15])) { //check password hash
            this.initialise(key, initialints);
            var bincode = base64tobin(code);
            this.decipher(bincode);
            return intstostring(bincode);
        }
        
        return null;
    },
    
    setConstants: function(){
        var s0 = [0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED,
            0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2,
            0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F,
            0x728EB658, 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539,
            0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0,
            0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA,
            0x55605C60, 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A,
            0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411,
            0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33,
            0x6C24CF5C, 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B,
            0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D,
            0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3,
            0x83F44239, 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842,
            0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728,
            0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D,
            0x39AF0176, 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3,
            0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62,
            0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3,
            0x49F1C09B, 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A,
            0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2,
            0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F,
            0x9B30952C, 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807,
            0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD,
            0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8,
            0xDB3222F8, 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA,
            0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E,
            0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0,
            0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C,
            0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA,
            0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE,
            0x2BF11FB4, 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0,
            0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812,
            0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218,
            0xBE0E1777, 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6,
            0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266,
            0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 
            0xFB9D35CF, 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 
            0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, 
            0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 
            0x6295CFA9, 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 
            0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, 
            0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 
            0xC5855664, 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A];
        
        var s1 = [0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 
            0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
            0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, 
            0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 
            0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 
            0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, 
            0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 
            0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, 
            0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, 
            0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 
            0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908, 
            0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, 
            0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 
            0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 
            0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, 
            0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 
            0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B, 
            0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, 
            0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 
            0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, 
            0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, 
            0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 
            0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 
            0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, 
            0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 
            0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 
            0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, 
            0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 
            0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77, 
            0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, 
            0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 
            0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 
            0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, 
            0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 
            0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 
            0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, 
            0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 
            0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 
            0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, 
            0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 
            0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 
            0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, 
            0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7];
        
        var s2 = [0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7,
            0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, 
            0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, 
            0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 
            0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4, 
            0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, 
            0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 
            0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 
            0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, 
            0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 
            0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58, 
            0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, 
            0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 
            0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, 
            0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, 
            0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 
            0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 
            0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, 
            0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 
            0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 
            0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, 
            0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 
            0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979, 
            0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, 
            0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 
            0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 
            0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, 
            0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 
            0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 
            0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, 
            0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 
            0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, 
            0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, 
            0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 
            0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE, 
            0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, 
            0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 
            0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, 
            0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, 
            0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 
            0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 
            0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, 
            0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0];
        
        var s3 = [0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 
            0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 
            0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, 
            0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 
            0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 
            0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, 
            0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 
            0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 
            0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, 
            0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 
            0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 
            0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, 
            0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 
            0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, 
            0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, 
            0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 
            0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 
            0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, 
            0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 
            0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 
            0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, 
            0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 
            0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 
            0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, 
            0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 
            0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 
            0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, 
            0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 
            0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 
            0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, 
            0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 
            0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 
            0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, 
            0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 
            0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623, 
            0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, 
            0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 
            0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 
            0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, 
            0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 
            0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 
            0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, 
            0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6];
        
        this.S = [s0, s1, s2, s3];
        
        this.P = [0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, 
            0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 
            0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B];
    },
    
    encipher: function(x){
        var xHi = x[0];
        var xLo = x[1];
        var Round = 0;
        
        xHi ^= this.P[0];
        
        while (Round < 16) {
            xLo ^= (((this.S[0][xHi >>> 24] + this.S[1][(xHi >>> 16) & 0x0ff])
                ^ this.S[2][(xHi >>> 8) & 0x0ff]) + this.S[3][xHi & 0x0ff]) ^ this.P[++Round];
            xHi ^= (((this.S[0][xLo >>> 24] + this.S[1][(xLo >>> 16) & 0x0ff])
                ^ this.S[2][(xLo >>> 8) & 0x0ff]) + this.S[3][xLo & 0x0ff]) ^ this.P[++Round];
        }
        
        xLo ^= this.P[16 + 1];
        
        x[0] = xLo;
        x[1] = xHi;
    },
    
    encipher_array: function(x){
        var count, xHi, xLo, Round, temp;
        
        for (i = 0; (i < x.length) || (x.length % 6); i += 2) {
            xHi = x[i];
            xLo = x[i + 1];
            
            xHi ^= this.previous_xHi;
            xLo ^= this.previous_xLo;
            
            xHi ^= this.P[0];
            
            Round = 0;
            
            while (Round < 16) {
                xLo ^= (((this.S[0][xHi >>> 24] + this.S[1][(xHi >>> 16) & 0x0ff])
                    ^ this.S[2][(xHi >>> 8) & 0x0ff]) + this.S[3][xHi & 0x0ff]) ^ this.P[++Round];
                xHi ^= (((this.S[0][xLo >>> 24] + this.S[1][(xLo >>> 16) & 0x0ff])
                    ^ this.S[2][(xLo >>> 8) & 0x0ff]) + this.S[3][xLo & 0x0ff]) ^ this.P[++Round];
            }
            
            xLo ^= this.P[17];
            
            this.previous_xHi = xLo;
            this.previous_xLo = xHi;
            
            x[i + 1] = xHi;
            x[i] = xLo;
        }
    },
    
    decipher: function(x){
        var xHi, xLo, Round, temp;
        
        for (i = 0; i < x.length; i += 2) {
            xHi = x[i];
            xLo = x[i + 1];
            
            xHi ^= this.P[17];
            
            Round = 16;
            
            while (Round > 0) {
                xLo ^= (((this.S[0][xHi >>> 24] + this.S[1][(xHi >>> 16) & 0xff])
                    ^ this.S[2][(xHi >>> 8) & 0xff]) + this.S[3][xHi & 0xff]) ^ this.P[Round--];
                xHi ^= (((this.S[0][xLo >>> 24] + this.S[1][(xLo >>> 16) & 0xff])
                    ^ this.S[2][(xLo >>> 8) & 0xff]) + this.S[3][xLo & 0xff]) ^ this.P[Round--];
            }
            
            xLo ^= this.P[0];
            
            temp = x[i];
            x[i] = xLo ^ this.previous_xHi;
            this.previous_xHi = temp;
            
            temp = x[i + 1];
            x[i + 1] = xHi ^ this.previous_xLo;
            this.previous_xLo = temp;
        }
    },
    
    initialise: function(key, IV){
        var i, j, k, data;
        var block = new Array(2);
        
        this.previous_xHi = IV[0];
        this.previous_xLo = IV[1];
        
        this.setConstants();
        
        for (j = 0, i = 0; i < 16 + 2; ++i) {
            this.P[i] = this.P[i] ^ key[j];
            j = (j + 1) % 14;
        }
        
        for (i = 0; i < 16 + 2; i += 2) {
            this.encipher(block);
            
            this.P[i] = block[0];
            this.P[i + 1] = block[1];
        }
        
        for (i = 0; i < 4; ++i) {
            for (j = 0; j < 256; j += 2) {
                this.encipher(block);
                this.S[i][j] = block[0];
                this.S[i][j + 1] = block[1];
            }
        }
    },
    
    passtokey: function(pass, HiIV, LoIV){
        var binarypassword = stringtoints(pass);
        var block          = new Array(2);
        var key            = new Array(16);
        var i = 0, j;
        
        block[0] = HiIV;
        block[1] = LoIV;
        
        do {
            this.initialise(key, block, 16);
            
            for (j = 0; j < 16;) {
                block[0] ^= binarypassword[i];
                
                this.encipher(block);
                
                key[j++] ^= block[0];
                key[j++] ^= block[1];
            }
        } while (++i < binarypassword.length)
        
        return key;
    }
};

})(this);



/*FILEHEAD(/var/lib/jpf/src/core/crypto/md4.js)SIZE(-1077090856)TIME(1224578765)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


jpf.crypto.MD4 = {
    /*
     * Configurable variables. You may need to tweak these to be compatible with
     * the server-side, but the defaults work in most cases.
     */
    hexcase: 0,  /* hex output format. 0 - lowercase; 1 - uppercase        */
    b64pad : "", /* base-64 pad character. "=" for strict RFC compliance   */
    chrsz  : 8,  /* bits per input character. 8 - ASCII; 16 - Unicode      */

    /*
     * These are the functions you'll usually want to call
     */
    hex_md4: function(s){
        return this.binl2hex(this.core_md4(this.str2binl(s), s.length * this.chrsz));
    },
    
    b64_md4: function(s){
        return this.binl2b64(this.core_md4(this.str2binl(s), s.length * this.chrsz));
    },
    
    str_md4: function(s){
        return this.binl2str(this.core_md4(this.str2binl(s), s.length * this.chrsz));
    },
    
    hex_hmac_md4: function(key, data){
        return this.binl2hex(this.core_hmac_md4(key, data));
    },
    
    b64_hmac_md4: function(key, data){
        return this.binl2b64(this.core_hmac_md4(key, data));
    },
    
    str_hmac_md4: function(key, data){
        return this.binl2str(this.core_hmac_md4(key, data));
    },
    
    /**
     * Perform a simple self-test to see if the VM is working
     */
    md4_vm_test: function(){
        return this.hex_md4("abc") == "a448017aaf21d8525fc10ae87aa6729d";
    },
    
    /**
     * Calculate the MD4 of an array of little-endian words, and a bit length
     */
    core_md4: function(x, len){
        /* append padding */
        x[len >> 5] |= 0x80 << (len % 32);
        x[(((len + 64) >>> 9) << 4) + 14] = len;
        
        var a = 1732584193;
        var b = -271733879;
        var c = -1732584194;
        var d = 271733878;
        
        for (var i = 0; i < x.length; i += 16) {
            var olda = a;
            var oldb = b;
            var oldc = c;
            var oldd = d;
            
            a = this.md4_ff(a, b, c, d, x[i + 0], 3);
            d = this.md4_ff(d, a, b, c, x[i + 1], 7);
            c = this.md4_ff(c, d, a, b, x[i + 2], 11);
            b = this.md4_ff(b, c, d, a, x[i + 3], 19);
            a = this.md4_ff(a, b, c, d, x[i + 4], 3);
            d = this.md4_ff(d, a, b, c, x[i + 5], 7);
            c = this.md4_ff(c, d, a, b, x[i + 6], 11);
            b = this.md4_ff(b, c, d, a, x[i + 7], 19);
            a = this.md4_ff(a, b, c, d, x[i + 8], 3);
            d = this.md4_ff(d, a, b, c, x[i + 9], 7);
            c = this.md4_ff(c, d, a, b, x[i + 10], 11);
            b = this.md4_ff(b, c, d, a, x[i + 11], 19);
            a = this.md4_ff(a, b, c, d, x[i + 12], 3);
            d = this.md4_ff(d, a, b, c, x[i + 13], 7);
            c = this.md4_ff(c, d, a, b, x[i + 14], 11);
            b = this.md4_ff(b, c, d, a, x[i + 15], 19);
            
            a = this.md4_gg(a, b, c, d, x[i + 0], 3);
            d = this.md4_gg(d, a, b, c, x[i + 4], 5);
            c = this.md4_gg(c, d, a, b, x[i + 8], 9);
            b = this.md4_gg(b, c, d, a, x[i + 12], 13);
            a = this.md4_gg(a, b, c, d, x[i + 1], 3);
            d = this.md4_gg(d, a, b, c, x[i + 5], 5);
            c = this.md4_gg(c, d, a, b, x[i + 9], 9);
            b = this.md4_gg(b, c, d, a, x[i + 13], 13);
            a = this.md4_gg(a, b, c, d, x[i + 2], 3);
            d = this.md4_gg(d, a, b, c, x[i + 6], 5);
            c = this.md4_gg(c, d, a, b, x[i + 10], 9);
            b = this.md4_gg(b, c, d, a, x[i + 14], 13);
            a = this.md4_gg(a, b, c, d, x[i + 3], 3);
            d = this.md4_gg(d, a, b, c, x[i + 7], 5);
            c = this.md4_gg(c, d, a, b, x[i + 11], 9);
            b = this.md4_gg(b, c, d, a, x[i + 15], 13);
            
            a = this.md4_hh(a, b, c, d, x[i + 0], 3);
            d = this.md4_hh(d, a, b, c, x[i + 8], 9);
            c = this.md4_hh(c, d, a, b, x[i + 4], 11);
            b = this.md4_hh(b, c, d, a, x[i + 12], 15);
            a = this.md4_hh(a, b, c, d, x[i + 2], 3);
            d = this.md4_hh(d, a, b, c, x[i + 10], 9);
            c = this.md4_hh(c, d, a, b, x[i + 6], 11);
            b = this.md4_hh(b, c, d, a, x[i + 14], 15);
            a = this.md4_hh(a, b, c, d, x[i + 1], 3);
            d = this.md4_hh(d, a, b, c, x[i + 9], 9);
            c = this.md4_hh(c, d, a, b, x[i + 5], 11);
            b = this.md4_hh(b, c, d, a, x[i + 13], 15);
            a = this.md4_hh(a, b, c, d, x[i + 3], 3);
            d = this.md4_hh(d, a, b, c, x[i + 11], 9);
            c = this.md4_hh(c, d, a, b, x[i + 7], 11);
            b = this.md4_hh(b, c, d, a, x[i + 15], 15);
            
            a = this.safe_add(a, olda);
            b = this.safe_add(b, oldb);
            c = this.safe_add(c, oldc);
            d = this.safe_add(d, oldd);
            
        }
        return Array(a, b, c, d);
        
    },
    
    /*
     * These functions implement the basic operation for each round of the
     * algorithm.
     */
    md4_cmn: function(q, a, b, x, s, t){
        return this.safe_add(rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s), b);
    },
    
    md4_ff: function(a, b, c, d, x, s){
        return this.md4_cmn((b & c) | ((~ b) & d), a, 0, x, s, 0);
    },
    
    md4_gg: function(a, b, c, d, x, s){
        return this.md4_cmn((b & c) | (b & d) | (c & d), a, 0, x, s, 1518500249);
    },
    
    md4_hh: function(a, b, c, d, x, s){
        return this.md4_cmn(b ^ c ^ d, a, 0, x, s, 1859775393);
    },
    
    /**
     * Calculate the HMAC-MD4, of a key and some data
     */
    core_hmac_md4: function(key, data){
        var bkey = this.str2binl(key);
        if (bkey.length > 16) 
            bkey = this.core_md4(bkey, key.length * this.chrsz);
        
        var ipad = Array(16), opad = Array(16);
        for (var i = 0; i < 16; i++) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }
        
        var hash = this.core_md4(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz);
        return this.core_md4(opad.concat(hash), 512 + 128);
    },
    
    /**
     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     * to work around bugs in some JS interpreters.
     */
    safe_add: function(x, y){
        var lsw = (x & 0xFFFF) + (y & 0xFFFF);
        var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    },
    
    /**
     * Bitwise rotate a 32-bit number to the left.
     */
    rol: function(num, cnt){
        return (num << cnt) | (num >>> (32 - cnt));
    },
    
    /**
     * Convert a string to an array of little-endian words
     * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
     */
    str2binl: function(str){
        var bin = Array();
        var mask = (1 << this.chrsz) - 1;
        for (var i = 0; i < str.length * this.chrsz; i += this.chrsz) 
            bin[i >> 5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i % 32);
        return bin;
    },
    
    /**
     * Convert an array of little-endian words to a string
     */
    binl2str: function(bin){
        var str = "";
        var mask = (1 << this.chrsz) - 1;
        for (var i = 0; i < bin.length * 32; i += this.chrsz) 
            str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask);
        return str;
    },
    
    /**
     * Convert an array of little-endian words to a hex string.
     */
    binl2hex: function(binarray){
        var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
        var str = "";
        for (var i = 0; i < binarray.length * 4; i++) {
            str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
            hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
        }
        return str;
    },
    
    /**
     * Convert an array of little-endian words to a base-64 string
     */
    binl2b64: function(binarray){
        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var str = "";
        for (var i = 0; i < binarray.length * 4; i += 3) {
            var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) |
            (((binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) |
            ((binarray[i + 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF);
            for (var j = 0; j < 4; j++) {
                if (i * 8 + j * 6 > binarray.length * 32) 
                    str += this.b64pad;
                else 
                    str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
            }
        }
        return str;
    }
};



/*FILEHEAD(/var/lib/jpf/src/core/crypto/md5.js)SIZE(-1077090856)TIME(1224578765)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


jpf.crypto.MD5 = {
    /*
     * Configurable variables. You may need to tweak these to be compatible with
     * the server-side, but the defaults work in most cases.
     */
    hexcase: 0,  /* hex output format. 0 - lowercase; 1 - uppercase        */
    b64pad : "", /* base-64 pad character. "=" for strict RFC compliance   */
    chrsz  : 8,  /* bits per input character. 8 - ASCII; 16 - Unicode      */

    /*
     * These are the functions you'll usually want to call
     * They take string arguments and return either hex or base-64 encoded strings
     */
    hex_md5: function(s) {
        return this.binl2hex(this.core_md5(this.str2binl(s), s.length * this.chrsz));
    },
    b64_md5: function(s) {
        return this.binl2b64(this.core_md5(this.str2binl(s), s.length * this.chrsz));
    },
    str_md5: function(s) {
        return this.binl2str(this.core_md5(this.str2binl(s), s.length * this.chrsz));
    },
    hex_hmac_md5: function(key, data) {
        return this.binl2hex(this.core_hmac_md5(key, data));
    },
    b64_hmac_md5: function(key, data) {
        return this.binl2b64(this.core_hmac_md5(key, data));
    },
    str_hmac_md5: function(key, data) {
        return this.binl2str(this.core_hmac_md5(key, data));
    },
    /**
     * Calculate the MD5 of an array of little-endian words, and a bit length
     */
    core_md5: function(x, len) {
      /* append padding */
      x[len >> 5] |= 0x80 << ((len) % 32);
      x[(((len + 64) >>> 9) << 4) + 14] = len;
    
      var a =  1732584193;
      var b = -271733879;
      var c = -1732584194;
      var d =  271733878;
    
      for(var i = 0; i < x.length; i += 16) {
        var olda = a;
        var oldb = b;
        var oldc = c;
        var oldd = d;
    
        a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
        d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
        c = this.md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
        b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
        a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
        d = this.md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
        c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
        b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
        a = this.md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
        d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
        c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063);
        b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
        a = this.md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
        d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101);
        c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
        b = this.md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
    
        a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
        d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
        c = this.md5_gg(c, d, a, b, x[i+11], 14,  643717713);
        b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
        a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
        d = this.md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
        c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335);
        b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
        a = this.md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
        d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
        c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
        b = this.md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
        a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
        d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
        c = this.md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
        b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
    
        a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
        d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
        c = this.md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
        b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556);
        a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
        d = this.md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
        c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
        b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
        a = this.md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
        d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
        c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
        b = this.md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
        a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
        d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835);
        c = this.md5_hh(c, d, a, b, x[i+15], 16,  530742520);
        b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
    
        a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
        d = this.md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
        c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
        b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
        a = this.md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
        d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
        c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523);
        b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
        a = this.md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
        d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744);
        c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
        b = this.md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
        a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
        d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
        c = this.md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
        b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
    
        a = this.safe_add(a, olda);
        b = this.safe_add(b, oldb);
        c = this.safe_add(c, oldc);
        d = this.safe_add(d, oldd);
      }
      return Array(a, b, c, d);
    },
    /*
     * These functions implement the four basic operations the algorithm uses.
     */
    md5_cmn: function(q, a, b, x, s, t) {
        return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s),b);
    },
    md5_ff: function(a, b, c, d, x, s, t) {
        return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
    },
    md5_gg: function(a, b, c, d, x, s, t) {
        return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
    },
    md5_hh: function(a, b, c, d, x, s, t) {
        return this.md5_cmn(b ^ c ^ d, a, b, x, s, t);
    },
    md5_ii: function(a, b, c, d, x, s, t) {
        return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
    },
    /**
     * Calculate the HMAC-MD5, of a key and some data
     */
    core_hmac_md5: function(key, data) {
        var bkey = this.str2binl(key);
        if(bkey.length > 16) bkey = this.core_md5(bkey, key.length * this.chrsz);
    
        var ipad = Array(16), opad = Array(16);
        for(var i = 0; i < 16; i++) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }
    
        var hash = this.core_md5(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz);
        return this.core_md5(opad.concat(hash), 512 + 128);
    },
    /**
     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     * to work around bugs in some JS interpreters.
     */
    safe_add: function(x, y) {
        var lsw = (x & 0xFFFF) + (y & 0xFFFF);
        var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    },
    /**
     * Bitwise rotate a 32-bit number to the left.
     */
    bit_rol: function(num, cnt) {
        return (num << cnt) | (num >>> (32 - cnt));
    },
    /**
     * Convert a string to an array of little-endian words
     * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
     */
    str2binl: function(str) {
        var bin = Array();
        var mask = (1 << this.chrsz) - 1;
        for(var i = 0; i < str.length * this.chrsz; i += this.chrsz)
            bin[i>>5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i%32);
        return bin;
    },
    /**
     * Convert an array of little-endian words to a string
     */
    binl2str: function(bin) {
        var str = "";
        var mask = (1 << this.chrsz) - 1;
        for(var i = 0; i < bin.length * 32; i += this.chrsz)
            str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
        return str;
    },
    /**
     * Convert an array of little-endian words to a hex string.
     */
    binl2hex: function(binarray) {
        var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
        var str = "";
        for(var i = 0; i < binarray.length * 4; i++) {
            str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
                   hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
        }
        return str;
    },
    /**
     * Convert an array of little-endian words to a base-64 string
     */
    binl2b64: function(binarray) {
        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var str = "";
        for(var i = 0; i < binarray.length * 4; i += 3) {
            var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                    | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                    |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
            for(var j = 0; j < 4; j++) {
                if(i * 8 + j * 6 > binarray.length * 32) str += this.b64pad;
                else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
            }
        }
        return str;
    }
};



/*FILEHEAD(/var/lib/jpf/src/core/crypto/sha1.js)SIZE(-1077090856)TIME(1224578765)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


jpf.crypto.SHA1 = {
    /*
     * Configurable variables. You may need to tweak these to be compatible with
     * the server-side, but the defaults work in most cases.
     */
    hexcase: 0,  /* hex output format. 0 - lowercase; 1 - uppercase        */
    b64pad : "", /* base-64 pad character. "=" for strict RFC compliance   */
    chrsz  : 8,  /* bits per input character. 8 - ASCII; 16 - Unicode      */

    /*
     * These are the functions you'll usually want to call
     * They take string arguments and return either hex or base-64 encoded strings
     */
    hex_sha1: function(s){
        return this.binb2hex(this.core_sha1(this.str2binb(s), s.length * this.chrsz));
    },
    
    b64_sha1: function(s){
        return this.binb2b64(this.core_sha1(this.str2binb(s), s.length * this.chrsz));
    },
    
    str_sha1: function(s){
        return this.binb2str(this.core_sha1(this.str2binb(s), s.length * this.chrsz));
    },
    
    hex_hmac_sha1: function(key, data){
        return this.binb2hex(this.core_hmac_sha1(key, data));
    },
    
    b64_hmac_sha1: function(key, data){
        return this.binb2b64(this.core_hmac_sha1(key, data));
    },
    
    str_hmac_sha1: function(key, data){
        return this.binb2str(this.core_hmac_sha1(key, data));
    },
    
    /**
     * Perform a simple self-test to see if the VM is working
     */
    sha1_vm_test: function(){
        return this.hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
    },
    
    /**
     * Calculate the SHA-1 of an array of big-endian words, and a bit length
     */
    core_sha1: function(x, len){
        /* append padding */
        x[len >> 5] |= 0x80 << (24 - len % 32);
        x[((len + 64 >> 9) << 4) + 15] = len;
        
        var w = Array(80);
        var a = 1732584193;
        var b = -271733879;
        var c = -1732584194;
        var d = 271733878;
        var e = -1009589776;
        
        for (var i = 0; i < x.length; i += 16) {
            var olda = a;
            var oldb = b;
            var oldc = c;
            var oldd = d;
            var olde = e;
            
            for (var j = 0; j < 80; j++) {
                if (j < 16) 
                    w[j] = x[i + j];
                else 
                    w[j] = this.rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
                var t = this.safe_add(this.safe_add(rol(a, 5), this.sha1_ft(j, b, c, d)),
                    this.safe_add(this.safe_add(e, w[j]), this.sha1_kt(j)));
                e = d;
                d = c;
                c = this.rol(b, 30);
                b = a;
                a = t;
            }
            
            a = this.safe_add(a, olda);
            b = this.safe_add(b, oldb);
            c = this.safe_add(c, oldc);
            d = this.safe_add(d, oldd);
            e = this.safe_add(e, olde);
        }
        return Array(a, b, c, d, e);
        
    },
    
    /**
     * Perform the appropriate triplet combination for the current
     * iteration
     */
    sha1_ft: function(t, b, c, d){
        if (t < 20) 
            return (b & c) | ((~ b) & d);
        if (t < 40) 
            return b ^ c ^ d;
        if (t < 60) 
            return (b & c) | (b & d) | (c & d);
        return b ^ c ^ d;
    },
    
    /**
     * Determine the appropriate additive constant for the current iteration
     */
    sha1_kt: function(t){
        return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514;
    },
    
    /**
     * Calculate the HMAC-SHA1 of a key and some data
     */
    core_hmac_sha1: function(key, data){
        var bkey = this.str2binb(key);
        if (bkey.length > 16) 
            bkey = this.core_sha1(bkey, key.length * this.chrsz);
        
        var ipad = Array(16), opad = Array(16);
        for (var i = 0; i < 16; i++) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }
        
        var hash = this.core_sha1(ipad.concat(str2binb(data)), 512 + data.length * this.chrsz);
        return this.core_sha1(opad.concat(hash), 512 + 160);
    },
    
    /**
     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     * to work around bugs in some JS interpreters.
     */
    safe_add: function(x, y){
        var lsw = (x & 0xFFFF) + (y & 0xFFFF);
        var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    },
    
    /**
     * Bitwise rotate a 32-bit number to the left.
     */
    rol: function(num, cnt){
        return (num << cnt) | (num >>> (32 - cnt));
    },
    
    /**
     * Convert an 8-bit or 16-bit string to an array of big-endian words
     * In 8-bit function, characters >255 have their hi-byte silently ignored.
     */
    str2binb: function(str){
        var bin = Array();
        var mask = (1 << this.chrsz) - 1;
        for (var i = 0; i < str.length * this.chrsz; i += this.chrsz) 
            bin[i >> 5] |= (str.charCodeAt(i / this.chrsz) & mask) << (32 - this.chrsz - i % 32);
        return bin;
    },
    
    /**
     * Convert an array of big-endian words to a string
     */
    binb2str: function(bin){
        var str = "";
        var mask = (1 << this.chrsz) - 1;
        for (var i = 0; i < bin.length * 32; i += this.chrsz) 
            str += String.fromCharCode((bin[i >> 5] >>> (32 - this.chrsz - i % 32)) & mask);
        return str;
    },
    
    /**
     * Convert an array of big-endian words to a hex string.
     */
    binb2hex: function(binarray){
        var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
        var str = "";
        for (var i = 0; i < binarray.length * 4; i++) {
            str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
            hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF);
        }
        return str;
    },
    
    /**
     * Convert an array of big-endian words to a base-64 string
     */
    binb2b64: function(binarray){
        var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        var str = "";
        for (var i = 0; i < binarray.length * 4; i += 3) {
            var triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16) |
            (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8) |
            ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF);
            for (var j = 0; j < 4; j++) {
                if (i * 8 + j * 6 > binarray.length * 32) 
                    str += this.b64pad;
                else 
                    str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F);
            }
        }
        return str;
    }
};



/*FILEHEAD(/var/lib/jpf/src/core/node/interactive.js)SIZE(-1077090856)TIME(1238944816)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

var __INTERACTIVE__ = 1 << 21;


/**
 * Baseclass giving interactive features to this element, it makes an
 * element draggable and resizable.
 * Example:
 * <code>
 *  <j:textarea draggable="true" resizable="true" />
 * </code>
 * 
 * @attribute {Boolean} draggable makes an element draggable. The user will 
 * able to move the element around while holding the mouse button down on the 
 * element.
 * Example:
 * <code>
 *  <j:bar draggable="true" />
 * </code>
 * @attribute {Boolean} resizable makes an element resizable. The user will able 
 * to resize the element by grabbing one of the four edges of the element and 
 * pulling it in either direction. Grabbing the corners allow the users the 
 * resize horizontally and vertically at the same time. The right bottom corner 
 * is special because it offers an especially big grab area. The size of this 
 * area can be configured in the skin of the element.
 * Example:
 * <code>
 *  <j:window resizable="true" />
 * </code>
 * @attribute {Number} minwidth  the minimum horizontal size the element can get when resizing.
 * @attribute {Number} minheight the minimum vertical size the element can get when resizing.
 * @attribute {Number} maxwidth  the maximum horizontal size the element can get when resizing.
 * @attribute {Number} maxheight the maximum vertical size the element can get when resizing.
 *
 * @event drag          Fires when the widget has been dragged.
 * @event resizestart   Fires before the widget is resized.
 *   cancellable: Prevents this resize action to start.
 *   object:
 *   {String} type the type of resize. This is a combination of the four directions, n, s, e, w.
 * @event resize        Fires when the widget has been resized.
 *
 * @constructor
 *
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       1.0
 *
 * @see element.appsettings.attribute.outline
 * @see element.appsettings.attribute.resize-outline
 * @see element.appsettings.attribute.drag-outline
 */
jpf.Interactive = function(){
    var nX, nY, rX, rY, startPos, lastCursor = null, l, t, lMax, tMax, 
        w, h, we, no, ea, so, rszborder, rszcorner, marginBox,
        verdiff, hordiff, _self = this, posAbs, oX, oY, overThreshold,
        dragOutline, resizeOutline;

    this.$regbase = this.$regbase | __INTERACTIVE__;

    this.$propHandlers["draggable"] = function(value){
        if (jpf.isFalse(value))
            this.draggable = value = false;
        else if (jpf.isTrue(value))
            this.draggable = value = true;
        
        var o = this.oDrag || this.oExt;
        if (o.interactive & 1) 
            return;

        var mdown = o.onmousedown;
        o.onmousedown = function(){
            if (mdown && mdown.apply(this, arguments) === false)
                return;

            dragStart.apply(this, arguments);
        }
        o.interactive = (o.interactive||0)+1;
        
        //this.oExt.style.position = "absolute";
    };

    this.$propHandlers["resizable"] = function(value){
        if (jpf.isFalse(value))
            this.resizable = value = false;
        else if (jpf.isTrue(value))
            this.resizable = value = true;
        
        var o = this.oResize || this.oExt;
        if (o.interactive & 2) 
            return;
        
        var mdown = o.onmousedown;
        var mmove = o.onmousemove;

        o.onmousedown = function(){
            if (mdown && mdown.apply(this, arguments) === false)
                return;

            resizeStart.apply(this, arguments);
        };

        o.onmousemove = function(){
            if (mmove && mmove.apply(this, arguments) === false)
                return;

            resizeIndicate.apply(this, arguments);
        };
        
        o.interactive = (o.interactive||0)+2;
        
        //this.oExt.style.position = "absolute";
        
        rszborder = this.$getOption && parseInt(this.$getOption("Main", "resize-border")) || 3;
        rszcorner = this.$getOption && parseInt(this.$getOption("Main", "resize-corner")) || 12;
        marginBox = jpf.getBox(jpf.getStyle(this.oExt, jpf.isIE ? "borderWidth" : "border-width"));
    };
    
    /*
    this.$propHandlers["minwidth"]  = 
    this.$propHandlers["maxwidth"]  = 
    this.$propHandlers["minheight"] = 
    this.$propHandlers["maxheight"] = function(value, force, prop){
        if (this.aData)
            this.aData[prop] = parseInt(value);
    }
    if (this.aData) {
        this.aData.minwidth = this.minwidth;
        this.aData.minheight = this.minheight;
    }*/
    
    function dragStart(e){
        if (!e) e = event;

        if (!_self.draggable || jpf.dragmode.isDragging)
            return;
        
        dragOutline = !(_self.dragOutline == false || !jpf.appsettings.dragOutline);
        
        jpf.dragmode.isDragging = true;
        overThreshold           = false;
        
        jpf.popup.forceHide();
        
        posAbs = "absolute|fixed".indexOf(jpf.getStyle(_self.oExt, "position")) > -1;
        if (!posAbs)
            _self.oExt.style.position = "relative";

        //@todo not for docking
        if (posAbs && !_self.aData) {
            jpf.plane.show(dragOutline
                ? oOutline
                : _self.oExt);//, true
        }

        var pos = posAbs 
            ? jpf.getAbsolutePosition(_self.oExt, _self.oExt.offsetParent) 
            : [parseInt(_self.oExt.style.left) || _self.oExt.offsetLeft || 0, 
               parseInt(_self.oExt.style.top) || _self.oExt.offsetTop || 0];
            
        nX = pos[0] - (oX = e.clientX);
        nY = pos[1] - (oY = e.clientY);
        
        if (_self.hasFeature && _self.hasFeature(__ANCHORING__))
            _self.disableAnchoring();

        if (posAbs && dragOutline) {
            oOutline.className     = "drag";
            
            var diffOutline = jpf.getDiff(oOutline);
            _self.oExt.parentNode.appendChild(oOutline);
            oOutline.style.left    = pos[0] + "px";
            oOutline.style.top     = pos[1] + "px";
            oOutline.style.width   = (_self.oExt.offsetWidth - diffOutline[0]) + "px";
            oOutline.style.height  = (_self.oExt.offsetHeight - diffOutline[1]) + "px";
        }

        jpf.dragmode.mode = true;
        
        document.onmousemove = dragMove;
        document.onmouseup   = function(){
            document.onmousemove = document.onmouseup = null;
            
            jpf.dragmode.mode = false;
            
            if (posAbs && !_self.aData)
                jpf.plane.hide();
            
            if (overThreshold) {
                if (_self.setProperty) {
                    if(l) _self.setProperty("left", l);
                    if(t) _self.setProperty("top", t);
                }
                else if (dragOutline) {
                    _self.oExt.style.left = l + "px";
                    _self.oExt.style.top  = t + "px";
                }
            }
            
            if (!posAbs)
                _self.oExt.style.position = "relative";
            
            if (_self.showdragging)
                jpf.setStyleClass(_self.oExt, "", ["dragging"]);
            
            if (posAbs && dragOutline)
                oOutline.style.display = "none";
            
            jpf.dragmode.isDragging = false;
            
            if (_self.dispatchEvent)
                _self.dispatchEvent("drag");
        };
        
        if (jpf.isIE)
            document.onmousedown();

        return false;
    };
    
    function dragMove(e){
        if(!e) e = event;
        
        if (!overThreshold && _self.showdragging)
            jpf.setStyleClass(_self.oExt, "dragging");
        
        // usability rule: start dragging ONLY when mouse pointer has moved delta x pixels
        var dx = e.clientX - oX,
            dy = e.clientY - oY,
            distance; 

        if (!overThreshold 
          && (distance = dx*dx > dy*dy ? dx : dy) * distance < 2)
            return;

        //Drag outline support
        else if (!overThreshold && dragOutline 
          && oOutline.style.display != "block")
            oOutline.style.display = "block";

        var oHtml = dragOutline
            ? oOutline
            : _self.oExt;

        oHtml.style.left = (l = e.clientX + nX) + "px";
        oHtml.style.top  = (t = e.clientY + nY) + "px";

        overThreshold = true;
    };
    
    function resizeStart(e){
        if (!e) e = event;

        if (!_self.resizable)
            return;
        
        resizeOutline = !(_self.resizeOutline == false || !jpf.appsettings.resizeOutline);
        
        if (!resizeOutline) {
            var diff = jpf.getDiff(_self.oExt);
            hordiff  = diff[0];
            verdiff  = diff[1];
        }
        
        //@todo This is probably not gen purpose
        startPos = jpf.getAbsolutePosition(_self.oExt);//, _self.oExt.offsetParent);
        startPos.push(_self.oExt.offsetWidth);
        startPos.push(_self.oExt.offsetHeight);
        
        var sLeft = document.documentElement.scrollLeft;
        var sTop = document.documentElement.scrollTop;
        var x = (oX = e.clientX) - startPos[0] + sLeft;
        var y = (oY = e.clientY) - startPos[1] + sTop;

        var resizeType = getResizeType.call(_self.oExt, x, y);
        rX = x;
        rY = y;

        if (!resizeType)
            return;

        if (_self.dispatchEvent && _self.dispatchEvent("resizestart", {
            type : resizeType
          }) === false)
            return;
        
        jpf.popup.forceHide();

        if (_self.hasFeature && _self.hasFeature(__ANCHORING__))
            _self.disableAnchoring();

        jpf.dragmode.isDragging = true;
        overThreshold           = false;

        var r = "|" + resizeType + "|"
        we = "|w|nw|sw|".indexOf(r) > -1;
        no = "|n|ne|nw|".indexOf(r) > -1;
        ea = "|e|se|ne|".indexOf(r) > -1;
        so = "|s|se|sw|".indexOf(r) > -1;
        
        if (!_self.minwidth)  _self.minwidth  = 0;
        if (!_self.minheight) _self.minheight = 0;
        if (!_self.maxwidth)  _self.maxwidth  = 10000;
        if (!_self.maxheight) _self.maxheight = 10000;

        if (posAbs) {
            lMax = startPos[0] + startPos[2] - _self.minwidth;
            tMax = startPos[1] + startPos[3] - _self.minheight;
            lMin = startPos[0] + startPos[2] - _self.maxwidth;
            tMin = startPos[1] + startPos[3] - _self.maxheight;
        }

        if (posAbs) {
            jpf.plane.show(resizeOutline
                ? oOutline
                : _self.oExt);//, true
        }
        
        if (resizeOutline) {
            oOutline.className     = "resize";
            var diffOutline = jpf.getDiff(oOutline);
            hordiff = diffOutline[0];
            verdiff = diffOutline[1];
            
            //_self.oExt.parentNode.appendChild(oOutline);
            oOutline.style.left    = startPos[0] + "px";
            oOutline.style.top     = startPos[1] + "px";
            oOutline.style.width   = (_self.oExt.offsetWidth - hordiff) + "px";
            oOutline.style.height  = (_self.oExt.offsetHeight - verdiff) + "px";
            oOutline.style.display = "block";
        }
        
        if (lastCursor === null)
            lastCursor = document.body.style.cursor;//jpf.getStyle(document.body, "cursor");
        document.body.style.cursor = resizeType + "-resize";

        jpf.dragmode.mode = true;

        document.onmousemove = resizeMove;
        document.onmouseup   = function(e){
            document.onmousemove = document.onmouseup = null;
            
            jpf.dragmode.mode = false;
            
            if (posAbs)
                jpf.plane.hide();
            
            if (resizeOutline) {
                var diff = jpf.getDiff(_self.oExt);
                hordiff  = diff[0];
                verdiff  = diff[1];
            }
            
            doResize(e || event, true);
            
            if (_self.setProperty) {
                if (posAbs) {
                    if (l) _self.setProperty("left", l);
                    if (t) _self.setProperty("top", t);
                }
                
                if (w) _self.setProperty("width", w + hordiff) 
                if (h) _self.setProperty("height", h + verdiff); 
            }
            
            l = t = w = h = null;

            document.body.style.cursor = lastCursor;
            lastCursor = null;
            
            if (resizeOutline)
                oOutline.style.display = "none";
            
            jpf.dragmode.isDragging = false;
            
            if (_self.dispatchEvent)
                _self.dispatchEvent("resize");
        };
        
        if (jpf.isIE)
            document.onmousedown();
        
        return false;
    };
    
    var min = Math.min, max = Math.max, lastTime;
    function resizeMove(e){
        if(!e) e = event;
        
        //if (!e.button)
            //return this.onmouseup();
        
        // usability rule: start dragging ONLY when mouse pointer has moved delta x pixels
        /*var dx = e.clientX - oX,
            dy = e.clientY - oY,
            distance; 
        
        if (!overThreshold 
          && (distance = dx*dx > dy*dy ? dx : dy) * distance < 4)
            return;*/
        
        if (lastTime && new Date().getTime() 
          - lastTime < (resizeOutline ? 6 : jpf.mouseEventBuffer))
            return;
        lastTime = new Date().getTime();
        
        doResize(e);
        
        //overThreshold = true;
    };
    
    function doResize(e, force){
        var oHtml = resizeOutline && !force
            ? oOutline
            : _self.oExt;

        var sLeft = document.documentElement.scrollLeft;
        var sTop = document.documentElement.scrollTop;
        
        if (we) {
            oHtml.style.left = (l = max(lMin, min(lMax, e.clientX - rX + sLeft))) + "px";
            oHtml.style.width = (w = min(_self.maxwidth - hordiff, 
                max(hordiff, _self.minwidth, 
                    startPos[2] - (e.clientX - startPos[0]) + rX + sLeft
                    ) - hordiff) + sLeft) + "px"; //@todo
        }
        
        if (no) {
            oHtml.style.top = (t = max(tMin, min(tMax, e.clientY - rY + sTop))) + "px";
            oHtml.style.height = (h = min(_self.maxheight - verdiff, 
                max(verdiff, _self.minheight, 
                    startPos[3] - (e.clientY - startPos[1]) + rY + sTop
                    ) - verdiff)) + "px"; //@todo
        }

        if (ea)
            oHtml.style.width  = (w = min(_self.maxwidth - hordiff, 
                max(hordiff, _self.minwidth, 
                    e.clientX - startPos[0] + (startPos[2] - rX) + sLeft)
                    - hordiff)) + "px";

        if (so)
            oHtml.style.height = (h = min(_self.maxheight - verdiff, 
                max(verdiff, _self.minheight, 
                    e.clientY - startPos[1] + (startPos[3] - rY) + sTop)
                    - verdiff)) + "px";

        if (jpf.hasSingleRszEvent)
            jpf.layout.forceResize(_self.oInt);
    }
    
    function getResizeType(x, y){
        var cursor  = "", 
            tcursor = "";
        posAbs = "absolute|fixed".indexOf(jpf.getStyle(_self.oExt, "position")) > -1;

        if (_self.resizable == true || _self.resizable == "vertical") {
            if (y < rszborder + marginBox[0])
                cursor = posAbs ? "n" : "";
            else if (y > this.offsetHeight - rszborder) //marginBox[0] - marginBox[2] - 
                cursor = "s";
            else if (y > this.offsetHeight - rszcorner) //marginBox[0] - marginBox[2] - 
                tcursor = "s";
        }
        
        if (_self.resizable == true || _self.resizable == "horizontal") {
            if (x < (cursor ? rszcorner : rszborder) + marginBox[0])
                cursor += tcursor + (posAbs ? "w" : "");
            else if (x > this.offsetWidth - (cursor || tcursor ? rszcorner : rszborder)) //marginBox[1] - marginBox[3] - 
                cursor += tcursor + "e";
        }
        
        return cursor;
    }
    
    var originalCursor;
    function resizeIndicate(e){
        if(!e) e = event;
        
        if (!_self.resizable || document.onmousemove)
            return;

        //@todo This is probably not gen purpose
        var pos = jpf.getAbsolutePosition(_self.oExt);//, _self.oExt.offsetParent
        var sLeft = document.documentElement.scrollLeft;
        var sTop = document.documentElement.scrollTop;
        var x = e.clientX - pos[0] + sLeft;
        var y = e.clientY - pos[1] + sTop;
        
        if (!originalCursor)
            originalCursor = jpf.getStyle(this, "cursor");
        
        var cursor = getResizeType.call(_self.oExt, x, y);
        this.style.cursor = cursor 
            ? cursor + "-resize" 
            : originalCursor || "default";
    };

    if (!this.pHtmlDoc)
        this.pHtmlDoc = window.document;
    
    var oOutline = this.pHtmlDoc.getElementById("jpf_outline");
    if (!oOutline) {
        oOutline = this.pHtmlDoc.body.appendChild(this.pHtmlDoc.createElement("div"));
        
        oOutline.refCount = 0;
        oOutline.setAttribute("id", "jpf_outline");
        
        oOutline.style.position = "absolute";
        oOutline.style.display  = "none";
        oOutline.style.zIndex   = 100000000;
    }
    oOutline.refCount++;
    
    /*this.$jmlDestroyers.push(function(){
        oOutline.refCount--;
        
        if (!oOutline.refCount) {
            //destroy
        }
    });*/
};



/*FILEHEAD(/var/lib/jpf/src/core/node/media.js)SIZE(-1077090856)TIME(1238933673)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

var __MEDIA__ = 1 << 20;


/**
 * Interface that adds Media node features and dynamics to this Element.
 * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#media7
 *
 * @constructor
 * @baseclass
 * @author      Mike de Boer
 * @version     %I%, %G%
 * @since       1.0
 */
jpf.Media = function(){
    this.$regbase = this.$regbase | __MEDIA__;

    this.muted = false;

    this.$booleanProperties["paused"]     = true;
    this.$booleanProperties["muted"]      = true;
    this.$booleanProperties["seeking"]    = true;
    this.$booleanProperties["autoplay"]   = true;
    this.$booleanProperties["controls"]   = true;
    this.$booleanProperties["ready"]      = true;

    this.$supportedProperties.push("position", "networkState", "readyState",
        "progress", "buffered", "bufferedBytes", "totalBytes", "currentTime",
        "paused", "seeking", "volume", "type", "src", "autoplay", "controls");

    this.mainBind = "src";

    this.$propHandlers["readyState"] = function(value){ //in seconds
        if (this.readyState !== value)
            this.readyState = value;
        if (value == jpf.Media.HAVE_NOTHING) {
            jpf.console.error("Unable to open medium with URL '" + this.src
                + "'. Please check if the URL you entered as src is pointing to \
                   a valid resource.");

            var oError = this.MediaError("Unable to open medium with URL '" + this.src
                + "'. Please check if the URL you entered as src is pointing to \
                   a valid resource.");
            if (this.dispatchEvent("havenothing", {
                error   : oError,
                bubbles : true
              }) === false)
                throw oError;
        }
        else if (value == jpf.Media.HAVE_CURRENT_DATA)
            this.dispatchEvent("havecurrentdata");
        else if (value == jpf.Media.HAVE_FUTURE_DATA)
            this.dispatchEvent("havefuturedata");
        else if (value == jpf.Media.HAVE_ENOUGH_DATA) {
            this.dispatchEvent("haveenoughdata");
            this.setProperty('ready', true);
        }
    };

    this.$propHandlers["bufferedBytes"] = function(value) {
        this.setProperty("progress", this.totalBytes
            ? value.end / this.totalBytes
            : 0);
    };

    this.$propHandlers["position"] = function(value){
        if (this.duration > 0 && this.seek) {
            // first, check if the seek action doesn't go beyond the download progress of the media element.
            if (value >= this.progress)
                value = this.progress - 0.05;

            var isPlaying = !this.paused;
            if (isPlaying)
                this.pause();

            if (value < 0)
                value = 0;
            else if (value > 1)
                value = 1;

            this.seek(Math.round(value * this.duration));

            this.setProperty('paused', !isPlaying);
        }
    };

    this.$propHandlers["currentTime"] = function(value){ //in milliseconds
        if (value >= 0 && this.seek)
            this.seek(value);
    };

    this.$propHandlers["volume"] = function(value){
        if (!this.player) return;
        if (value < 0)
            throw this.MediaError("Attempt to set the volume to a negative volue  '" + value);

        if (value < 1 && value > 0)
            value = value * 100;

        if (this.setVolume)
            this.setVolume(value);
        if (value > 0 && this.muted)
            this.setProperty("muted", false);
    };

    var oldVolume = null;

    this.$propHandlers["muted"] = function(value){
        if (!this.player || !this.setVolume) return;

        if (value) { //mute the player
            oldVolume = this.volume;
            this.setVolume(0);
        }
        else
            this.setVolume(oldVolume || 20);
    };

    this.$propHandlers["paused"] = function(value){
        if (!this.player) return;

        this.paused = jpf.isTrue(value);
        if (this.paused)
            this.player.pause();
        else
            this.player.play();
    };

    var loadTimer = null;

    this.$propHandlers["type"] = function(value){
        if (loadTimer) return;

        var _self = this;
        loadTimer = window.setTimeout(function() {
            reload.call(_self);
        });
    };

    this.$propHandlers["src"] = function(value){
        if (loadTimer || !value) return; //@todo for mike: please check if this is the best behaviour for setting an empty value

        var oUrl = new jpf.url(value);
        this.src = oUrl.uri;

        if (!oUrl.isSameLocation())
            jpf.console.warn("Media player: the medium with URL '" + this.src + "' \
                does not have the same origin as your web application. This can \
                cause the medium to not load and/ or play.", "media");
        if (oUrl.protocol == "file")
            jpf.console.warn("Media player: the medium with URL '" + this.src + "' \
                will be loaded through the 'file://' protocol. This can \
                cause the medium to not load and/ or play.", "media");

        if (this.src != this.currentSrc && this.networkState !== jpf.Media.LOADING) {
            var type = this.$guessType(this.src);
            if (type == this.type) {
                reset.call(this);
                this.loadMedia();
            }
            else {
                this.type = type;
                var _self = this;
                loadTimer = window.setTimeout(function() {
                    reload.call(_self);
                });
            }
        }
    };

    this.$propHandlers["ID3"] = function(value){
        if (!this.player) return;
        // usually this feature is only made available BY media as getters
        if (typeof this.player.setID3 == "function")
            this.player.setID3(value);
    };

    /**** DOM Hooks ****/

    this.$domHandlers["remove"].push(function(doOnlyAdmin){
        jpf.console.log('Media: removing node...');
        reset.call(this);
    });

    this.$domHandlers["reparent"].push(function(beforeNode, pNode, withinParent){
        if (!this.$jmlLoaded)
            return;

        jpf.console.log('Media: reparenting - ' + beforeNode + ', ' + pNode);

        this.$draw();
        reload.call(this, true);
    });

    function reset() {
        this.setProperty('networkState',  jpf.Media.NETWORK_EMPTY);
        //this.setProperty('readyState',   jpf.Media.HAVE_NOTHING);
        this.setProperty('ready',         false);
        //this.setProperty('buffered',      {start: 0, end: 0, length: 0});
        //this.setProperty('bufferedBytes', {start: 0, end: 0, length: 0});
        this.buffered      = {start: 0, end: 0, length: 0};
        this.bufferedBytes = {start: 0, end: 0, length: 0};
        this.totalBytes    = 0;
        this.setProperty('progress',      0);
        //this.setProperty('totalBytes',    0);

        this.setProperty('seeking',  false);
        this.setProperty('paused',   true);
        this.setProperty('position', 0);
        this.currentTime = this.duration = 0;
        this.played = this.seekable = null;
        this.ended  = false;

        this.start = this.end = this.loopStart = this.loopEnd =
            this.playCount = this.currentLoop = 0;
        this.controls = this.muted = false;
    }

    function reload(bNoReset) {
        jpf.console.log('Media: reloading medium with mimetype ', this.type);

        window.clearTimeout(loadTimer);
        loadTimer = null;

        if (!bNoReset)
            reset.call(this);

        this.$destroy(true); //bRuntime = true

        this.playerType = this.$getPlayerType(this.type);

        // sanity checking
        if (!this.playerType || !this.$isSupported()) {
            this.oExt.innerHTML = this.notSupported;
            return;
        }

        this.$initPlayer();
    }

    // error state
    this.MediaError = function(sMsg) {
        return new Error(jpf.formatErrorString(0, this, "Media", sMsg));
    };

    // network state
    this.src = this.currentSrc = null;
    this.networkState       = jpf.Media.NETWORK_EMPTY; //default state
    this.bufferingRate      = 0;
    this.bufferingThrottled = false;
    this.buffered           = {start: 0, end: 0, length: 0}; //TimeRanges container {start: Function(idx):Float, end: Function(idx):Float, length: n}
    this.bufferedBytes      = {start: 0, end: 0, length: 0}; //ByteRanges container {start: Function(idx):Number, end: Function(idx):Number, length: n}
    this.totalBytes         = 0;
    this.volume             = 100;

    this.loadMedia = function() {
        //must be overridden by the component
    };

    // ready state
    this.readyState = jpf.Media.HAVE_NOTHING;
    this.seeking    = false;

    // playback state
    this.currentTime         = this.duration = 0;
    this.paused              = true;
    this.defaultPlaybackRate = this.playbackRate = 0;
    this.played              = null; // TimeRanges container
    this.seekable            = null; // TimeRanges container
    this.ended = this.autoplay = false;

    /**
     * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-navigator-canplaytype
     */
    this.canPlayType = function(sType) {
        if (this.$getPlayerType) {
            var sPlayer = this.$getPlayerType(sType);
            if (!sPlayer || !this.$isSupported(sPlayer))
                return "no";
            if (sPlayer.indexOf("Wmp") != -1)
                return "maybe";
            return "probably"; //we're sooo confident ;)
        }

        return "no";
    };

    this.play = function() {
        this.setProperty('paused', false);
    };

    this.pause = function() {
        this.setProperty('paused', true);
    };

    // looping
    this.start = this.end = this.loopStart = this.loopEnd =
    this.playCount = this.currentLoop = 0;

    // cue ranges
    this.addCueRange = function(sClassName, sId, iStart, iEnd, bPauseOnExit, fEnterCallback, fExitCallback) {
        //to be overridden by the component
    };

    this.removeCueRanges = function(sClassName) {
        //to be overridden by the component
    };

    /**
     * Return a counter as you commonly see in front panels of CD/ DVD players
     *
     * @link  http://php.net/strftime
     * @param {Number}  iMillis  Amount of milliseconds to transform in a counter
     * @param {String}  sFormat  Format of the counter is the form of PHP's strftime function:
     *                             %H - hour as a decimal number using a 24-hour clock (range 00 to 23)
     *                             %M - minute as a decimal number
     *                             %S - second as a decimal number
     *                             %Q - Millisecond as decimal (000-999)
     *                             %n - newline character
     *                             %t - tab character
     *                             %T - current time, equal to %H:%M:%S
     *                             %% - a literal `%' character
     * @param {Boolean} bReverse Show the counter in reverse notation (countdown)
     * @type  {String}
     */
    this.getCounter = function(iMillis, sFormat, bReverse) {
        // for String.pad, 'jpf.PAD_LEFT' is implicit
        if (bReverse)
            iMillis = iMillis - this.duration;
        var iSeconds = Math.round(Math.abs(iMillis / 1000)),
            sHours   = String(Math.round(Math.abs(iSeconds / 60 / 60))).pad(2, "0"),
            sMinutes = String(Math.round(Math.abs(iSeconds / 60))).pad(2, "0"),
            sSeconds = String(iSeconds).pad(2, "0"),
            sMillis  = String(Math.round(Math.abs(iMillis % 1000))).pad(3, "0");
        return (bReverse ? "- " : "") + sFormat.replace(/\%T/g, "%H:%M:%S")
            .replace(/\%[a-zA-Z\%]/g, function(sMatch) {
                switch (sMatch) {
                case "%H":
                    return sHours;
                case "%M":
                    return sMinutes;
                case "%S":
                    return sSeconds;
                case "%Q":
                    return sMillis;
                case "%n":
                    return "\n";
                case "%t":
                    return "\t";
                case "%%":
                    return "%";
                }
            });
    };

    /**
     * Set the source for a media element by going through all the &lt;source&gt;
     * child elements of the &lt;audio&gt; or &lt;video&gt; node, searching for
     * a valid source media file that is playable by one of our plugins.
     * The 'src' and 'type' attributes respectively have precedence over any
     * &lt;source&gt; element.
     * It also parses the &lt;nomedia&gt; tag that specifies what text or HTML to
     * display when a medium is not supported and/ or playable.
     *
     * @param  {XmlDomElement} [jml] Parent Jml node of the player
     * @return {Boolean}             Tells the client that no supported/ playable source file was found
     */
    this.setSource = function(jml) {
        jml = jml || this.$jml;
        // first get the 'Not supported' placeholder...
        var aNodes = $xmlns(jml, "nomedia", jpf.ns.jml);
        if (!aNodes.length) {
            this.notSupported = (jml.firstChild && jml.firstChild.nodeType == 3)
                ? jml.firstChild.nodeValue
                : "Unable to playback, medium not supported.";
        }
        else
            this.notSupported = aNodes[0].innerHTML;

        if (!this.src) { // no direct src-attribute set
            var src, type, oSources = $xmlns(jml, "source", jpf.ns.jml);
            // iterate through all the <source> tags from left to right
            for (var i = 0, j = oSources.length; i < j; i++) {
                src  = oSources[i].getAttribute("src");
                if (!src) continue;
                type = oSources[i].getAttribute("type");
                if (!type) // auto-detect type, based on file extension
                    type = this.$guessType(src);
                if (this.canPlayType(type) != "no") {
                    // yay! we found a type that we can play for the client
                    this.src  = src;
                    this.type = type;
                    break; //escape!
                }
            }
        }
        else if (!this.type) {
            this.type = this.$guessType(this.src);
            if (this.canPlayType(this.type) == "no")
                return false;
        }

        return (this.src && this.type);
    };
};

// network state (.networkState)
jpf.Media.NETWORK_EMPTY   = 0;
jpf.Media.NETWORK_IDLE    = 1;
jpf.Media.NETWORK_LOADING = 2;
jpf.Media.NETWORK_LOADED  = 3;

// ready state (.readyState)
jpf.Media.HAVE_NOTHING      = 0;
jpf.Media.HAVE_METADATA     = 1;
jpf.Media.HAVE_SOME_DATA    = 2; //wtf??
jpf.Media.HAVE_CURRENT_DATA = 3;
jpf.Media.HAVE_FUTURE_DATA  = 4;
jpf.Media.HAVE_ENOUGH_DATA  = 5;



/*FILEHEAD(/var/lib/jpf/src/core/node/delayedrender.js)SIZE(-1077090856)TIME(1238944816)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

var __DELAYEDRENDER__ = 1 << 11


/**
 * Baseclass adding delayed rendering features to this element. Any element
 * that is (partially) hidden at startup has the possibility to delay rendering
 * it's childNodes by setting render="runtime" on the element. These elements
 * include window, tab, pages, form and container.
 * For instance a Tab page in a container is initally hidden and does not
 * need to be rendered. When the tab button is pressed to activate the page
 * the page is rendered and then displayed. This can dramatically decrease
 * the startup time of the application.
 * Example:
 * In this example the button isn't rendered until the advanced tab becomes active.
 * <code>
 *  <j:tab>
 *      <j:page caption="General">
 *          ...
 *      </j:page>
 *      <j:page caption="Advanced" render="runtime">
 *          <j:button>OK</j:button>
 *      </j:page>
 *  </j:tab>
 * </code>
 *
 * @event beforerender  Fires before elements are rendered. Use this event to display a loader.
 *   cancellable: Prevents rendering of the childNodes
 * @event afterrender   Fires after elements are rendered. User this event to hide a loader.
 *
 * @attribute {String}  render           when the contents of this element is rendered.
 *   Possible values:
 *   init     elements are rendered during init of the application.
 *   runtime  elements are rendered when the user requests them.
 * @attribute {Boolean} use-render-delay wether there's a short delay between showing this element and rendering it's contents.
 *   Possible values:
 *   true   The elements are rendered immediately
 *   false  There is a delay between showing this element and the actual rendering, allowing the browsers' render engine to draw (for instance a loader).
 *
 * @constructor
 * @baseclass
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.8.9
 */
jpf.DelayedRender = function(){
    this.$regbase   = this.$regbase | __DELAYEDRENDER__;
    this.isRendered = false;

    var withheld    = false;

    this.$checkDelay = function(x){
        if (x.getAttribute("render") == "runtime") {
            x.setAttribute("render-status", "withheld");
            if (!jpf.JmlParser.renderWithheld)
                jpf.JmlParser.renderWithheld = [];
            jpf.JmlParser.renderWithheld.push(this);

            withheld = true;
            return true;
        }

        this.isRendered = true;
        return false;
    };

    /**
     * Renders the children of this element.
     *
     * @param {Boolean} [usedelay] whether a delay is added between calling this function and the actual rendering. This allows the browsers' render engine to draw (for instance a loader).
     */
    this.$render = function(usedelay){
        if (this.isRendered || this.$jml.getAttribute("render-status") != "withheld")
            return;
        this.dispatchEvent("beforerender");

        if (jpf.isNull(this.usedelay))
            this.usedelay = jpf.xmldb.getInheritedAttribute(this.$jml,
                "use-render-delay") == "true";

        if (this.usedelay || usedelay)
            setTimeout("jpf.lookup(" + this.uniqueId + ").$renderparse()", 10);
        else
            this.$renderparse();
    };

    this.$renderparse = function(){
        if (this.isRendered)
            return;

        jpf.JmlParser.parseMoreJml(this.$jml, this.oInt, this)

        this.$jml.setAttribute("render-status", "done");
        this.$jml.removeAttribute("render"); //Experimental
        this.isRendered = true;
        withheld = false;

        this.dispatchEvent("afterrender");
    };
};



/*FILEHEAD(/var/lib/jpf/src/core/node/databinding.js)SIZE(-1077090856)TIME(1239018215)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

var __DATABINDING__ = 1 << 1;


/**
 * Baseclass adding databinding features to this element. Databinding takes
 * care of automatically going from data to representation and establishing a
 * permanent link between the two. In this way data that is changed will
 * change the representation as well. Furthermore, actions that are executed on
 * the representation will change the underlying data.
 * Example:
 * <code>
 *  <j:list>
 *      <j:bindings>
 *          <j:icon select="@icon" />
 *          <j:caption select="text()" />
 *          <j:traverse select="item" />
 *      </j:bindings>
 *  </j:list>
 * </code>
 *
 * @event error             Fires when a communication error has occured while making a request for this element.
 *   cancellable: Prevents the error from being thrown.
 *   bubbles:
 *   object:
 *   {Error}          error     the error object that is thrown when the event callback doesn't return false.
 *   {Number}         state     the state of the call
 *     Possible values:
 *     jpf.SUCCESS  the request was successfull
 *     jpf.TIMEOUT  the request has timed out.
 *     jpf.ERROR    an error has occurred while making the request.
 *     jpf.OFFLINE  the request was made while the application was offline.
 *   {mixed}          userdata  data that the caller wanted to be available in the callback of the http request.
 *   {XMLHttpRequest} http      the object that executed the actual http request.
 *   {String}         url       the url that was requested.
 *   {Http}           tpModule  the teleport module that is making the request.
 *   {Number}         id        the id of the request.
 *   {String}         message   the error message.
 
 *
 * @constructor
 * @baseclass
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.4
 */
jpf.DataBinding = function(){
    var loadqueue;
    var cXmlOnLoad = [];
    var cXmlSelect = [];
    var cXmlChoice = [];

    this.$regbase  = this.$regbase | __DATABINDING__;
    this.mainBind  = "value";
    var _self      = this;

    /**** Public Methods ****/

    /**
     * Gets the xpath statement from the main bind rule. Each databound
     * element which does not implement jpf.MultiSelect has a main bind rule.
     * This method gets the xpath statement in the select attribute of this rule.
     *
     * @return  {String}  the xpath statement
     * @see  element.smartbinding
     */
    this.getMainBindXpath = function(){
        if (this.hasFeature(__MULTIBINDING__))
            return this.$getMultiBind().getMainBindXpath();
        var m = this.getModel(true);
        return (m && m.connect
            ? m.connect.select + "/"
            : (this.dataParent
                ? this.dataParent.xpath + "/"
                : "")) + this.smartBinding.bindings[this.mainBind][0].getAttribute("select");
    };

    /**
     * Checks whether this element is completely bound.
     *
     * @return  {Boolean}
     */
    this.isBoundComplete = function(){
        if (!this.smartBinding) return true;
        if (!this.xmlRoot) return false;

        if (this.hasFeature(__MULTIBINDING__) && !this.$getMultiBind().xmlRoot)
            return false;
        return true;
    };

    /**
     * Queries the bound data for a string value
     *
     * @param {String} xpath  the xpath statement which queries on the data this element is bound on.
     * @return {String} value of the selected XML Node
     * @todo
     *  lstRev.query('revision/text()', 'selected');
     *  lstRev.query('revision/text()', 'xmlRoot');
     *  lstRev.query('revision/text()', 'indicator');
     */
    this.queryValue = function(xpath, type){
        return jpf.getXmlValue(this[type || 'xmlRoot'], xpath );
    };
	/**
     * Queries the bound data for an array of string values
     *
     * @param {String} xpath the xpath statement which queries on the data this element is bound on.
     * @return {String} value of the selected XML Node
     */
    this.queryValues = function(xpath, type){
        return jpf.getXmlValues(this[type || 'xmlRoot'], xpath );
    };
	
    /**
     * Executes an xpath statement on the data of this model
     *
     * @param  {String}   xpath    the xpath used to select the XMLNode(s).
     * @return  {variant}  XMLNode or NodeList with the result of the selection
     */
    this.queryNode = function(xpath, type){
        var n = this[type||'xmlRoot'];
		return n?n.selectSingleNode(xpath):null;
    };


    /**
     * Executes an xpath statement on the data of this model
     *
     * @param  {String}   xpath    the xpath used to select the XMLNode(s).
     * @return  {variant}  XMLNode or NodeList with the result of the selection
     */
    this.queryNodes = function(xpath, type){
        var n = this[type||'xmlRoot'];
		return n?n.selectNodes(xpath):null;
    };
	
    /**
     * Loads the binding rules from the j:bindings element
     *
     * @param {Array}   rules     the rules array created using {@link core.jpf.method.getrules}
     * @param {XMLElement} [xmlNode] the reference to the j:bindings xml element
     * @see  element.smartbinding
     */
    this.loadBindings = function(rules, node){
        if (this.bindingRules)
            this.unloadBindings();
        this.bindingRules = rules;

        jpf.console.info("Initializing Bindings for " + this.tagName + "[" + (this.name || '') + "]");

        if (this.$loaddatabinding)
            this.$loaddatabinding();

        if (this.bindingRules["traverse"])
            this.parseTraverse(this.bindingRules["traverse"][0]);

        this.$checkLoadQueue();
    };

    this.$checkLoadQueue = function(){
        // Load from queued load request
        if (loadqueue) {
            if (this.load(loadqueue[0], loadqueue[1]) != loadqueue)
                loadqueue = null;
        }
    };

    /**
     * Unloads the binding rules from this element
     *
     * @see  element.smartbinding
     */
    this.unloadBindings = function(){
        if (this.$unloaddatabinding)
            this.$unloaddatabinding();

        var node = this.xmlBindings;//this.$jml.selectSingleNode("Bindings");
        if (!node || !node.getAttribute("connect"))
            return;

        //Fails if parent window is closing...
        try {
            var o = eval(node.getAttribute("connect"));
            o.disconnect(this, node.getAttribute("type"));
        }
        catch(e) {}//ignore that case

        this.bindingRules = null;

        return this.uniqueId;
    };

    /**
     * Loads the action rules from the j:actions element
     *
     * @param {Array}       rules     the rules array created using {@link core.jpf.method.getrules}
     * @param {XMLElement}  [xmlNode] the reference to the j:bindings element
     * @see  element.smartbinding
     */
    this.loadActions = function(rules, node){
        if (this.actionRules)
            this.unloadActions();
        this.actionRules = rules;

        jpf.console.info("Initializing Actions for " + this.tagName
            + "[" + (this.name || '') + "]");

        //@todo revise this
        if (node && (jpf.isTrue(node.getAttribute("transaction"))
          || node.selectSingleNode("add|update"))){
            if (!this.hasFeature(__TRANSACTION__))
                this.inherit(jpf.Transaction); /** @inherits jpf.Transaction */

            //Load ActionTracker & xmldb
            if (!this.$at)
                this.$at = new jpf.actiontracker(this);

            this.$at.realtime = isTrue(node.getAttribute("realtime"));
            this.defaultMode  = node.getAttribute("mode") || "update";

            //Turn caching off, it collides with rendering views on copies of data with the same id's
            this.caching      = false;
        }
    };

    /**
     * Gets the ActionTracker this element communicates with.
     *
     * @return {ActionTracker}
     * @see  element.smartbinding
     */
    this.getActionTracker = function(ignoreMe){
        if (!jpf.JmlDom)
            return jpf.window.$at;

        var pNode = this, tracker = ignoreMe ? null : this.$at;
        if (!tracker && this.connectId)
            tracker = self[this.connectId].$at;

        //jpf.xmldb.getInheritedAttribute(this.$jml, "actiontracker");
        while (!tracker) {
            //if(!pNode.parentNode) throw new Error(jpf.formatErrorString(1055, this, "ActionTracker lookup", "Could not find ActionTracker by traversing upwards"));
            if (!pNode.parentNode)
                return jpf.window.$at;

            tracker = (pNode = pNode.parentNode).$at;
        }
        return tracker;
    };

    /**
     * Unloads the action rules from this element
     *
     * @see  element.smartbinding
     */
    this.unloadActions = function(){
        this.xmlActions  = null;
        this.actionRules = null;

        //Weird, this cannot be correct... (hack?)
        if (this.$at) {
            if (this.$at.undolength)
                jpf.console.warn("Component : "
                    + this.name + " [" + this.tagName + "]\n\
                    Message : ActionTracker still has undo stack filled with "
                    + this.$at.undolength + " items.");

            this.$at = null;
        }
    };

    var lock    = {};
    var actions = {};

    /**
     *  Start the specified action, does optional locking and can be offline aware
     *  - This method can be cancelled by events, offline etc
     *  - This method can execute pessimistic locking calls (<j:name locking="datainstr" /> rule)
     *  - or for optimistic locking it will record the timestamp (a setting <j:appsettings locking="optimistic")
     *  - During offline work, pessimistic locks will always fail
     *  - During offline work, optimistic locks will be handled by taking the timestamp of going offline
     *  - This method is always optional! The server should not expect locking to exist.
     *  Single Client Locking
     *   - Because of the increased complexity of this, when a lock fails (either pessimistic or optimistic)
     *     the developer should handle this by reloading that part of the content for which the lock failed.
     *     It is impossible for JPF to know which part this is and what to update
     * Example:
     * <code>
     *     <j:rename set="..." lock="rpc:comm.lockFile(xpath:@path, unlock)" />
     * </code>
     * Note: I am expecting the status codes specified in RFC4918 for the locking implementation
     *       http://tools.ietf.org/html/rfc4918#section-9.10.6
     *
     * @event locksuccess   Fires when a lock request succeeds
     *   bubbles: yes
     *   object:
     *     {Number} state    the return code of the lock request
     * @event lockfailed    Fires when a lock request failes
     *   bubbles: yes
     *   object:
     *     {Number} state    the return code of the lock request
     * @event unlocksuccess Fires when an unlock request succeeds
     *   bubbles: yes
     *   object:
     *     {Number} state    the return code of the unlock request
     * @event unlockfailed  Fires when an unlock request fails
     *   bubbles: yes
     *   object:
     *     {Number} state    the return code of the unlock request
     */
    this.$startAction = function(name, xmlContext, fRollback){
        if (this.disabled)
            return false;

        var actionRule = this.getNodeFromRule(name, xmlContext, true);
        if (jpf.appsettings.autoDisableActions && !this.actionRules 
          || this.actionRules && !actionRule)
            return false;

        var bHasOffline = typeof jpf.offline != "undefined";
        if (bHasOffline && !jpf.offline.canTransact())
            return false;

        if (this.dispatchEvent(name + "start", {
            xmlContext: xmlContext
        }) === false)
            return false;


        //Requesting a lock, whilst we still have one open
        if (lock[name] && !lock[name].stopped) {
            jpf.console.warn("Starting new action whilst previous \
                action wasn't terminated:" + name);

            this.$stopAction(); //or should we call: fRollback.call(this, xmlContext);
        }

        //Check if we should attain a lock (when offline, we just pretend to get it)
        var lockInstruction = actionRule ? actionRule.getAttribute("lock") : null;
        if ((bHasOffline && (!jpf.offline.enabled || !jpf.offline.onLine)) && lockInstruction) {
            var curLock = lock[name] = {
                start      : bHasOffline && !jpf.offline.onLine
                                ? jpf.offline.offlineTime
                                : new Date().getTime(),
                stopped    : false,
                xmlContext : xmlContext,
                instr      : lockInstruction,
                rollback   : fRollback
            };

            //Execute pessimistic locking request
            jpf.saveData(lockInstruction, xmlContext, null, function(data, state, extra){
                if (state == jpf.TIMEOUT && extra.retries < jpf.maxHttpRetries)
                    return extra.tpModule.retry(extra.id);

                if (state == jpf.SUCCESS) {
                    _self.dispatchEvent("locksuccess", jpf.extend({
                        state   : extra.http.status,
                        bubbles : true
                    }, extra));

                    curLock.retrieved = true; //@todo Record timeout here... think of method

                    //Action was apparently finished before the lock came in, cancelling lock
                    if (curLock.stopped)
                        _self.$stopAction(name, true, curLock);

                    //That's it we're ready to go...
                }
                else {
                    if (curLock.stopped) //If the action has terminated we just let it go
                        return; //Do we want to take away the event from the developer??

                    _self.dispatchEvent("lockfailed", jpf.extend({
                        state   : extra.http.status,
                        bubbles : true
                    }, extra));

                    //Cancel the action, because we didnt get a lock
                    fRollback.call(this, xmlContext);
                }
            });
        }

        actions[name] = xmlContext;

        return true;
    };

    // @todo think about if this is only for rsb
    this.addEventListener("xmlupdate", function(e){
        if (jpf.xmldb.disableRSB != 2)
            return;

        for (var name in actions) {
            if (jpf.xmldb.isChildOf(actions[name], e.xmlNode, true)) {
                //this.$stopAction(name, true);
                actions[name].rollback.call(this, actions[name].xmlContext);
            }
        }
    });

    this.$stopAction = function(name, isCancelled, curLock){
        delete actions[name];

        if (!curLock) curLock = lock[name];

        if (curLock && !curLock.stopped) {
            curLock.stopped = true;

            //The resource needs to unlock when the action is cancelled
            if (isCancelled && curLock.retrieved) {
                //Execute unlocking request
                jpf.saveData(curLock.instr, curLock.xmlContext, {
                        unlock     : true
                    },
                    function(data, state, extra){
                        if (state == jpf.TIMEOUT && extra.retries < jpf.maxHttpRetries)
                            return extra.tpModule.retry(extra.id);

                        //Do we care if an unlock failed/succeeded?
                        _self.dispatchEvent(
                            (state == jpf.SUCCESS
                                ? "unlocksuccess"
                                : "unlockfailed"),
                            jpf.extend({
                                state   : extra.http.status,
                                bubbles : true
                            }, extra));
                    });
            }
        }

        return curLock;
    };

    /**
     * Executes an action using action rules set in the j:actions element
     *
     * @param {String}      atAction      the name of the action to be performed by the ActionTracker.
     *   Possible values:
     *   setTextNode        sets the first text node of an xml element. {@link core.xmldb.method.setTextNode}
     *   setAttribute       sets the attribute of an xml element. {@link core.xmldb.method.setAttribute}
     *   removeAttribute    removes an attribute from an xml element. {@link core.xmldb.method.removeAttribute}
     *   setAttributes      sets multiple attribute on an xml element. Arguments are [xmlNode, Array]
     *   replaceNode        replaces an xml child with another one. {@link core.xmldb.method.replaceNode}
     *   addChildNode       adds a new xml node to a parent node. {@link core.xmldb.method.addChildNode}
     *   appendChild        appends an xml node to a parent node. {@link core.xmldb.method.appendChild}
     *   moveNode           moves an xml node from one parent to another. {@link core.xmldb.method.moveNode}
     *   removeNode         removes a node from it's parent. {@link core.xmldb.method.removeNode}
     *   removeNodeList     removes multiple nodes from their parent. {@link core.xmldb.method.removeNodeList}
     *   setValueByXpath    sets the nodeValue of an xml node whiche is selected by an xpath statement. Arguments are [xmlNode, xpath, value]
     *   multicall          calls multiple of these actions. Arguments is an array of argument arrays for these actions each with a func property which is the name of the action.
     * @param {Array}       args          the arguments to the function specified in <code>atAction</code>.
     * @param {String}      action        the name of the action rule defined in j:actions for this element.
     * @param {XMLElement}  xmlNode       the context for the action rules.
     * @param {Boolean}     [noevent]     whether or not to call events.
     * @param {XMLElement}  [contextNode] the context node for action processing (such as RPC calls). Usually the same as <code>xmlNode</code>
     * @return {Boolean} specifies success or failure
     * @see  element.smartbinding
     */
    this.executeAction = function(atAction, args, action, xmlNode, noevent, contextNode, multiple){
        if (this.disabled) return; //hack

        jpf.console.info("Executing action '" + action + "' for " + this.name
                         + " [" + (this.tagName || "") + "]");

        if(typeof jpf.offline != "undefined" && !jpf.offline.canTransact())
            return false;

        //Get Rules from Array
        var rules = this.actionRules
            ? this.actionRules[action]
            : (!action.match(/change|select/) && jpf.appsettings.autoDisableActions
                ? false
                : []);

        if (!rules)
            return false;

        var curLock = this.$stopAction(action);

        for (var node, i = 0; i < (rules.length || 1); i++) {
            if (!rules[i] || !rules[i].getAttribute("select")
                || xmlNode.selectSingleNode(rules[i].getAttribute("select"))) {

                var ev = new jpf.Event("before" + action.toLowerCase(), {
                    action        : atAction,
                    args          : args,
                    xmlActionNode : rules[i],
                    jmlNode       : this,
                    selNode       : contextNode,
                    multiple      : multiple || false
                    ,timestamp    : curLock
                                      ? curLock.start
                                      : new Date().getTime()
                });

                //Call Event and cancel if it returns false
                if (!noevent) {
                    //Allow the action and arguments to be changed by the event
                    if (this.dispatchEvent(ev.name, null, ev) === false)
                        return false;
                }

                //Call ActionTracker and return ID of Action in Tracker
                var UndoObj = this.getActionTracker().execute(ev);
                ev.xmlNode = UndoObj.xmlNode;
                ev.undoObj = UndoObj;

                //Call After Event
                if (!noevent) {
                    ev.name         = "after" + action.toLowerCase();
                    ev.cancelBubble = false;
                    delete ev.returnValue;
                    this.dispatchEvent(ev.name, null, ev);
                }

                return UndoObj;
            }
        }

        //Action not executed
        return false;
    };

    /**
     * Executes an action based on the set name and the new value
     * @param {String}      atName   the name of the action rule defined in j:actions for this element.
     * @param {String}      setName  the name of the binding rule defined in j:bindings for this element.
     * @param {XMLElement}  xmlNode  the xml element to which the rules are applied
     * @param {String}      value    the new value of the node
     */
    this.executeActionByRuleSet = function(atName, setName, xmlNode, value){
        var xpath, args, selInfo = this.getSelectFromRule(setName, xmlNode);
        var shouldLoad = false, atAction, node = selInfo[1];

        if (node) {
            if (jpf.xmldb.getNodeValue(node) == value) return; // Do nothing if value is unchanged

            atAction = (node.nodeType == 1 || node.nodeType == 3
                || node.nodeType == 4) ? "setTextNode" : "setAttribute";
            args = (node.nodeType == 1)
                ? [node, value]
                : (node.nodeType == 3 || node.nodeType == 4
                    ? [node.parentNode, value]
                    : [node.ownerElement || node.selectSingleNode(".."), node.nodeName, value]);
        }
        else {
            if (!this.createModel)
                return false;

            atAction = "setValueByXpath";
            xpath    = selInfo[0];

            if (!xmlNode) {
                //Assuming this component is connnected to a model
                var model   = this.getModel();
                if (model) {
                    if (model.connect) {
                        xmlNode = model.connect.node.selected || model.connect.node.xmlRoot;
                        xpath = (model.connect.select || ".")
                            + (xpath && xpath != "." ? "/" + xpath : "");
                        shouldLoad = true;
                    }
                    else {
                        if (!model.data)
                            model.load("<data />");
        
                        xpath   = (model.getXpathByJmlNode(this) || ".")
                            + (xpath && xpath != "." ? "/" + xpath : "");
                        xmlNode = model.data;
                    }
                }
                else {
                    if (!this.dataParent)
                        return false;

                    xmlNode = this.dataParent.parent.selected || this.dataParent.parent.xmlRoot;
                    xpath = (this.dataParent.xpath || ".")
                        + (xpath && xpath != "." ? "/" + xpath : "");
                    shouldLoad = true;
                }
            }

            args = [xmlNode, value, xpath];
        }

        //Use Action Tracker
        this.executeAction(atAction, args, atName, xmlNode);
        
        if (shouldLoad)
            this.load(xmlNode.selectSingleNode(xpath));
    };

    /**
     * Connects another element to this element. This connection is used
     * to push data from this element to the other element. Whenever this
     * element loads data, (a selection of) the data is pushed to the other
     * element. For elements inheriting from MultiSelect data is pushed
     * when a selection occurs.
     * Example:
     * This is how it's achieved using the javeline markup language.
     * <code>
     *  <j:list id="lstExample" />
     *  <j:text model="#lstExample:select" />
     * </code>
     *
     * @param {JmlNode} oElement  JmlNode specifying the element which is connected to this element.
     * @param {Boolean} [dataOnly]
     *   Possible values:
     *   true   data is sent only once.
     *   false  real connection is made.
     * @param {String}  [xpath]     the Xpath statement used to select a subset of the data to sent.
     * @param {String}  [type]
     *   Possible values:
     *   select  sents data when a node is selected
     *   choice  sents data when a node is chosen (by double clicking, or pressing enter)
     * @see  element.smartbinding
     * @see  baseclass.databinding.method.disconnect
     */
    this.connect = function(o, dataOnly, xpath, type, noselect){
        if (o.dataParent)
            o.dataParent.parent.disconnect(o);

        if (!this.signalXmlUpdate)
            this.signalXmlUpdate = {};

        o.dataParent = {
            parent: this,
            xpath : xpath
        };

        //Onload - check if problem when doing setConnections to early
        if (dataOnly) {
            if (!xpath && !this.selected) {
                throw new Error(jpf.formatErrorString(1056, null, 
                    "Connecting", 
                    "Illegal XPATH statement specified: '" + xpath + "'"));
            }

            if (cXmlOnLoad)
                return cXmlOnLoad.push([o, xpath]);
            else
                return o.load(xpath ? this.xmlRoot.selectSingleNode(xpath) : this.selected);//(this.selected || this.xmlRoot)
        }

        //jpf.debug Message
        //alert(this.tagName + ":" + o.tagName + " - " + type + "["+dataOnly+"]");

        //User action - Select || Choice
        if (!dataOnly)
            (!type || type == "select")
                ? cXmlSelect.push({o:o,xpath:xpath})
                : cXmlChoice.push({o:o,xpath:xpath});

        //Load Default
        if (type != "choice" && !noselect) {
            if (this.selected || !this.traverse && this.xmlRoot) {
                var xmlNode = this.selected || this.xmlRoot;
                if (xpath) {
                    xmlNode = xmlNode.selectSingleNode(xpath);
                    if (!xmlNode) {
                        //Hack!!
                        this.addEventListener("xmlupdate", function(){
                            this.connect(o, false, xpath, type);
                            this.removeEventListener("xmlupdate", arguments.callee);
                        });
                    }
                }

                if (xmlNode)
                    o.load(xmlNode);
            }
            else {
                if (o.clear && !o.hasFeature(__MULTIBINDING__))
                    o.clear(); //adding o.hasFeature(__MULTIBINDING__) is a quick fix. should be only with the bind="" level
                if (o.disable && o.createModel)
                    o.setProperty("disabled", true);
            }
        }
    };

    /**
     * Disconnects a previously established connection with another element.
     *
     * @param {JmlNode} oElement  the element to be disconnected from this element.
     * @param {String}  [type]
     *   Possible values:
     *   select  disconnects the select connection
     *   choice  disconnects the choice connection
     * @see  element.smartbinding
     * @see  baseclass.databinding.method.connect
     */
    this.disconnect = function(o, type){
        //User action - Select || Choice
        var ar = (!type || type == "select") ? cXmlSelect : cXmlChoice; //This should be both when there is no arg set

        if (this.signalXmlUpdate) {
            this.signalXmlUpdate[o.uniqueId] = null;
            delete this.signalXmlUpdate[o.uniqueId];
        }
        
        o.dataParent = null;

        //CAN BE OPTIMIZED IF NEEDED TO ONLY SET TO null
        for (var i = 0; i < ar.length; i++){
            if (ar[i].o != o) continue;

            for (var j = i; j < ar.length; j++)
                ar[j] = ar[j + 1];
            ar.length--;
            i--;
        }
    };

    /**
     * Pushes data to connected elements
     *
     * @param {XMLElement}  xmlNode  the xml data element to be pushed to the connected elements.
     * @param {String}      [type]
     *   Possible Values:
     *   select  pushes data to the elements registered for selection
     *   choice  pushes data to the elements registered for choice
     * @see  element.smartbinding
     * @see  baseclass.databinding.method.connect
     * @see  baseclass.databinding.method.disconnect
     */
    this.setConnections = function(xmlNode, type){
        var a = type == "both"
            ? cXmlChoice.concat(cXmlSelect)
            : (type == "choice" ? cXmlChoice : cXmlSelect);

        //Call Load of objects
        for (var x, o, i = 0; i < a.length; i++) {
            o     = a[i].o;
            xpath = a[i].xpath;
            o.load((xpath && xmlNode)
                ? xmlNode.selectSingleNode(xpath)
                : xmlNode);
            if (xmlNode && o.disabled && o.createModel)
                o.setProperty("disabled", false);
        }

        //Set Onload Connections only Once
        if (!cXmlOnLoad) return;

        for (var i = 0; i < cXmlOnLoad.length; i++)
            cXmlOnLoad[i][0].load(cXmlOnLoad[i][1]
                ? this.xmlRoot.selectSingleNode(cXmlOnLoad[i][1])
                : this.selected);//(this.selected || this.xmlRoot)

        cXmlOnLoad = null;
    };

    /**
     * @private
     */
    this.importConnections = function(x){
        cXmlSelect = x;
    };

    /**
     * @private
     */
    this.getConnections = function(){
        return cXmlSelect;
    };

    /**
     * @private
     */
    this.removeConnections = function(){
        cXmlSelect = [];
    };

    /**
     * Uses bind rules to convert data into a value string
     *
     * @param {String}      setname  the name of the binding rule set.
     * @param {XMLElement}  cnode    the xml element to which the binding rules are applied.
     * @param {String}      [def]    the default (fallback) value for the query.
     * @return  {String} the calculated value
     * @see  element.smartbinding
     */
    this.applyRuleSetOnNode = function(setname, cnode, def){
        if (!cnode) return "";

        //Get Rules from Array
        var rules = typeof setname == "string"
            ? (this.bindingRules || {})[setname]
            : setname;

        if (!this.$dcache)
            this.$dcache = {};

        if (!rules && !def && !this.$dcache[this.uniqueId + "." + setname]
          && typeof this[setname] != "string") {
            this.$dcache[this.uniqueId + "." + setname] = true;
            jpf.console.info("Could not find a binding rule for '" + setname
                             + "' (" + this.tagName
                             + " [" + (this.name || "") + "])")
        }

        if (!rules) {
            if (setname == "value") setname = "valuerule";
            return typeof this[setname] == "string" && setname != "value"
                    && jpf.getXmlValue(cnode, this[setname])
                    || def && cnode.selectSingleNode(def) || false;
        }

        var node = null, sel, i, o, v, rule;
        for (i = 0; i < rules.length; i++) {
            rule = rules[i];
            sel  = jpf.parseExpression(rule.getAttribute("select")) || ".";
            o    = cnode.selectSingleNode(sel);
            
            if (o) {
                this.lastRule = rule;

                if (v = rule.getAttribute("value")){ //Check for Default Value
                    /**
                     * @todo internationalization for <j:caption value="no value" />
                     */

                    //jpf.language.addElement(q.nodeValue.replace(/^\$(.*)\$$/,
                    //    "$1"), {htmlNode : pHtmlNode});

                    return v;
                }

                //Process XSLT/JSLT Stylesheet if needed
                else if(rule.childNodes.length) {
                    var xsltNode;

                    //Check Cache
                    if (v = rule.getAttribute("cacheId")) {
                        xsltNode = jpf.nameserver.get("xslt", v);
                    }
                    else {
                        //Find Xslt Node
                        var prefix = jpf.findPrefix(rule, jpf.ns.xslt);
                        var xsltNode;

                        if (rule.getElementsByTagNameNS) {
                            xsltNode = rule.getElementsByTagNameNS(jpf.ns.xslt, "*")[0];
                        }
                        else {
                            var prefix = jpf.findPrefix(rule, jpf.ns.xslt, true);
                            if (prefix) {
                                if (!jpf.supportNamespaces)
                                    rule.ownerDocument.setProperty("SelectionNamespaces", "xmlns:"
                                        + prefix + "='" + jpf.ns.xslt + "'");
                                xsltNode = rule.selectSingleNode(prefix + ":*");
                            }
                        }

                        if (xsltNode) {
                            if (xsltNode[jpf.TAGNAME] != "stylesheet") {
                                //Add it
                                var baseXslt = jpf.nameserver.get("base", "xslt");
                                if (!baseXslt) {
                                    baseXslt = jpf.getXmlDom(
                                        '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match="node()"></xsl:template></xsl:stylesheet>')
                                        .documentElement;
                                    jpf.nameserver.register("base", "xslt", xsltNode);
                                }

                                var xsltNode = baseXslt.cloneNode(true);
                                for (var j = rule.childNodes.length; j >= 0; j++)
                                    xsltNode.firstChild.appendChild(rule.childNodes[j]);

                                //Set cache Item
                                rule.setAttribute("cacheId",
                                    jpf.nameserver.add("xslt", xsltNode));
                            }
                        }
                    }

                    //XSLT
                    if (xsltNode) {
                        var x = o.transformNode(xsltNode)
                            .replace(/^<\?xml version="1\.0" encoding="UTF-16"\?>/, "")
                            .replace(/\&lt\;/g, "<").replace(/\&gt\;/g, ">")
                            .replace(/\&amp\;/g, "&");
                    }
                    //JSLT
                    else {
                        var x = jpf.JsltInstance.apply(rule, o);

                        var d = document.createElement("div");
                        var t = window.onerror;
                        window.onerror = function(){
                            window.onerror = t;
                            throw new Error(jpf.formatErrorString(0, this, "JSLT transform", "HTML Error:"+x,rule));
                        }
                        d.innerHTML    = x;
                        d.innerHTML    = '';
                        window.onerror = t;
                    }

                    if (v = rule.getAttribute("method")) {
                        try{
                            eval(v);
                        }
                        catch(e) {
                            jpf.console.warn("Method not available (yet): '" + v + "'");
                            return false;
                        }
                    }

                    return rule.getAttribute("method") ? self[rule.getAttribute("method")](x, this) : x;
                }

                //Execute Callback if any
                else if(v = rule.getAttribute("method")){
                    if(!self[v]){
                        jpf.console.warn("Method not available (yet): '" + v + "'");

                        return false;
                    }

                    return self[v](o, this);
                }
                //Execute Expression
                else if(v = rule.getAttribute("eval")){
                    var func = new Function('xmlNode', 'control', "return " + v);

                    var value = func.call(this, o, this);
                    if (!value) continue;

                    return value;
                }
                //Process XMLElement
                else {
                    var value;
                    if (o.nodeType == 2) {
                        try {
                            value = unescape(decodeURI(o.nodeValue));
                        }
                        catch(e) {
                            value = unescape(o.nodeValue);
                        }
                    }
                    else {
                        if (o.nodeType == 1) {
                            if (!o.firstChild || o.firstChild.nodeType == 1 || o.firstChild.nodeType > 4)
                                return "";
    
                            value = o.firstChild.nodeValue;
                        }
                        else                        
                            value = o.nodeValue;
                    }

                    //Mask Support
                    if (v = rule.getAttribute("mask")) {
                        if (value.match(/^(?:true|false)$/))
                            value = value == "true" ? 1 : 0;
                        return v.split(";")[value];
                    }
                    else
                        return value;
                }
            }
        }

        if (!this.$dcache[this.uniqueId + "." + setname]) {
            this.$dcache[this.uniqueId + "." + setname] = true;
            jpf.console.warn("Could not find a SmartBindings rule for property \
                              '" + setname + "' which matches any data for \
                              component " + this.name + " [" + this.tagName + "].")
        }

        //Applying failed
        return false;
    };

    /**
     * Assigns a smartbinding definition to this element
     *
     * @param {mixed} sb
     *   Possible values:
     *   {SmartBinding}  object to be assigned to this element.
     *   {String}        the name of the SmartBinding.
     * @throws  Error  If no SmartBinding was passed to the method.
     * @see  element.smartbinding
     */
    this.setSmartBinding = function(sb){
        this.$propHandlers["smartbinding"].call(this, sb);
        /*
            this.setProperty && this.setProperty("smartbinding", sb)
            ||
        */
    };

    /**
     * Removes the smartbinding from this element
     *
     * @see  element.smartbinding
     */
    this.removeSmartBinding = function(){
        this.setProperty("smartbinding", null);
    };

    /**
     * Gets the smartbinding of this element
     *
     * @returns  {SmartBinding}  The SmartBinding object of this element
     * @see  element.smartbinding
     */
    this.getSmartBinding = function(){
        return this.smartBinding;
    };

    /**
     * Gets the model to which this element is connected.
     * This is the model which acts as a datasource for this element.
     *
     * @param {Boolean} doRecur whether the model should be searched recursively up the data tree.
     * @returns  {Model}  The model this element is connected to.
     * @see  element.smartbinding
     */
    this.getModel = function(doRecur){
        if(doRecur && !this.$model)
            return this.dataParent ? this.dataParent.parent.getModel(true) : null;

        return this.$model;
    };

    /**
     * Sets the model to which this element is connected.
     * This is the model which acts as datasource for this element.
     *
     * @param {Model}  model   the model this element will be connected to.
     * @param {String} [xpath] the xpath statement used to query a subset of the data presented by the model.
     * @see  element.smartbinding
     */
    this.setModel = function(model, xpath){
        if (this.$model)
            this.$model.unregister(this);
        
        if (typeof model == "string")
            model = jpf.nameserver.get("model", model);

        this.$model = model;
        model.register(this, xpath);
    };

    /**
     * Gets the data element or binding / action rule of a binding set.
     *
     * @param {String}      setname       the name of the binding/action rule set.
     * @param {XMLElement}  cnode         the xml element to which the binding rules are applied.
     * @param {Boolean}     [isAction]    whether search is for an action rule.
     * @param {Boolean}     [getRule]     whether search is for a binding rule.
     * @param {Boolean}     [createNode]  whether the xml data elementis created when it doesn't exist.
     * @returns  {XMLElement}  the requested node.
     * @see  element.smartbinding
     */
    this.getNodeFromRule = function(setname, cnode, isAction, getRule, createNode){
        //Get Rules from Array
        var rules = ((isAction ? this.actionRules : this.bindingRules) || {})[setname];
        if (!rules) {
            if (setname == "value") setname = "valuerule";
            if (!isAction && !getRule && typeof this[setname] == "string") {
                return cnode.selectSingleNode(this[setname]) || (createNode
                    ? jpf.xmldb.createNodeFromXpath(cnode, this[setname])
                    : false);
            }
            return false;
        }

        for(var i = 0; i < rules.length; i++) {
            if (!rules[i]) continue;

            var sel = jpf.parseExpression(rules[i].getAttribute("select")) || ".";

            if (!sel)
                return getRule ? rules[i] : false;
            if (!cnode) return false;

            var o = cnode.selectSingleNode(sel);
            if (o)
                return getRule ? rules[i] : o;

            if (createNode || rules[i].getAttribute("create") == "true") {
                var o = jpf.xmldb.createNodeFromXpath(cnode, sel);
                return getRule ? rules[i] : o;
            }
        }

        //Retrieving Failed
        return false;
    };

    /**
     * Returns the select statement of a binding or action rule
     *
     * @param {String}      setname  the name of the binding/action rule set.
     * @param {XMLElement}  cnode    the xml element to which the binding rules are applied.
     * @returns {String}
     */
    this.getSelectFromRule = function(setname, cnode){
        var rules = this.bindingRules && this.bindingRules[setname];
        if (!rules || !rules.length) {
            if (setname == "value") setname = "valuerule";
            return typeof this[setname] == "string" && [this[setname]] || ["."];

        }

        for (var first, i = 0; i < rules.length; i++) {
            var sel = jpf.parseExpression(rules[i].getAttribute("select")) || ".";

            if (!cnode) return [sel];
            if (i == 0)
                first = sel;

            var o = cnode.selectSingleNode(sel);
            if (o)
                return [sel, o];
        }

        return [first];
    };

    /**
     * Reloads the data in this element.
     *
     */
    this.reload = function(){
        var sb = this.getSmartBinding();
        if (sb && sb.$isMarkedForUpdate(this)) {
            sb.$updateMarkedItems();
        }
        else
            this.load(this.xmlRoot, this.cacheID, true);
    };

    /**
     * Loads data in to this element using binding rules to transform the
     * data in to a presentation.
     * Example:
     * <code>
     *  <j:list id="lstExample">
     *      <j:bindings>
     *          <j:caption select="text()" />
     *          <j:icon select="@icon" />
     *          <j:traverse select="image" />
     *      </j:bindings>
     *  </j:list>
     *
     *  <j:script>
     *      lstExample.load('<images>\
     *          <image icon="icoTest.gif">image 1</image>\
     *          <image icon="icoTest.gif">image 2</image>\
     *          <image icon="icoTest.gif">image 3</image>');
     *  </j:script>
     * </code>
     *
     * @param {mixed}  [xmlRootNode]
     *   Possible Values:
     *   {XMLElement}  an xml element loaded in to this element.
     *   {String}      an xml string which is loaded in this element.
     *   {Null         null clears this element from it's data {@link baseclass.cache.method.clear}.
     * @param {String}  [cacheID]       the xml element to which the binding rules are applied.
     * @param {Boolean} [forceNoCache]  whether cache is checked before loading the data.
     * @event beforeload  Fires before loading data in this element.
     *   cancellable: Prevents the data from being loaded.
     *   object:
     *   {XMLElement} xmlNode the node that is loaded as the root data element.
     * @event afterload   Fires after loading data in this element.
     *   object:
     *   {XMLElement} xmlNode the node that is loaded as the root data element.
     * @see  element.smartbinding
     * @see  baseclass.cache.method.clear
     */
    this.load = function(xmlRootNode, cacheID, forceNoCache, noClearMsg){
        if (jpf.popup.isShowing(this.uniqueId))
            jpf.popup.forceHide(); //This should be put in a more general position

        // If control hasn't loaded databinding yet, queue the call
        if ((!this.bindingRules && this.$jml
            && (!this.smartBinding || jpf.JmlParser.stackHasBindings(this.uniqueId))
            && !this.traverse) || (this.$canLoadData && !this.$canLoadData())) {
            if (!jpf.JmlParser.stackHasBindings(this.uniqueId)) {
                jpf.console.warn("Could not load data yet in " + this.tagName
                    + "[" + (this.name || "") + "]. The loaded data is queued \
                      until smartbinding rules are loaded or set manually.");
            }
            return loadqueue = [xmlRootNode, cacheID];
        }
        // Convert first argument to an xmlNode we can use;
        if (xmlRootNode)
            xmlRootNode = jpf.xmldb.getBindXmlNode(xmlRootNode);

        // If no xmlRootNode is given we clear the control, disable it and return
        if (this.dataParent && this.dataParent.xpath)
            this.dataParent.parent.signalXmlUpdate[this.uniqueId] = !xmlRootNode;

        if (!xmlRootNode && (!cacheID || !this.isCached(cacheID))) {
            jpf.console.warn("No xml root node was given to load in "
                + this.tagName + "[" + (this.name || '') + "]. Clearing any \
                  loaded xml in this component");

            this.clear(noClearMsg, true);

            if (jpf.appsettings.autoDisable && !this.createModel)
                this.setProperty("disabled", true);

            this.setConnections();
            return;
        }

        var disabled = this.disabled;
        this.disabled = false;

        // Remove listen root if available (support for listening to non-available data)
        if (this.$listenRoot) {
            jpf.xmldb.removeNodeListener(this.$listenRoot, this);
            this.$listenRoot = null;
        }

        //Run onload event
        if (this.dispatchEvent("beforeload", {xmlNode : xmlRootNode}) === false)
            return false;

        jpf.console.info("Loading XML data in "
            + this.tagName + "[" + (this.name || '') + "]");

        // If reloading current document, and caching is disabled, exit
        if (this.caching && !forceNoCache && xmlRootNode && xmlRootNode == this.xmlRoot)
            return;

        // retrieve the cacheId
        if (!cacheID) {
            cacheID = xmlRootNode.getAttribute(jpf.xmldb.xmlIdTag) ||
                jpf.xmldb.nodeConnect(jpf.xmldb.getXmlDocId(xmlRootNode), xmlRootNode);
        }

        // Remove message notifying user the control is without data
        if (this.$removeClearMessage)
            this.$removeClearMessage();

        // Retrieve cached version of document if available
        var fromCache;
        if (this.caching && !forceNoCache && (fromCache = this.getCache(cacheID, xmlRootNode))) {
            if (fromCache == -1)
                return;

            if (!this.hasFeature(__MULTISELECT__))
                this.setConnections(this.xmlRoot, "select");
            else {
                var nodes = this.getTraverseNodes();

                //Information needs to be passed to the followers... even when cached...
                if (nodes.length && this.autoselect)
                    this.select(nodes[0], null, null, null, true);
                else
                    this.setConnections();

                if (!nodes.length)
                    this.$setClearMessage(this.emptyMsg, "empty");
            }

            //@todo move this to getCache??
            if (this.hasFeature(__MULTISELECT__) && nodes.length != this.length)
                this.setProperty("length", nodes.length);

            this.dispatchEvent('afterload', {XMLRoot : xmlRootNode});
            return;
        }
        else
            this.clear(true);

        //Set usefull vars
        this.documentId = jpf.xmldb.getXmlDocId(xmlRootNode);
        this.cacheID    = cacheID;
        this.xmlRoot    = xmlRootNode;

        // Draw Content
        this.$load(xmlRootNode);

        // Check if subtree should be loaded
        this.$loadSubData(xmlRootNode);

        if (!this.createModel) {
            this.disabled = true;
            this.setProperty("disabled", false);
        }
        else
            this.disabled = disabled;

        // Check Connections
        if (!this.hasFeature(__MULTISELECT__))
            this.setConnections(this.xmlRoot);

        // Run onafteronload event
        this.dispatchEvent('afterload', {xmlNode : xmlRootNode});
    };

    /**
     * @binding load Determines how new data is loaded data is loaded into this
     * element. Usually this is only the root node containing no children.
     * Example:
     * This example shows a load rule in a text element. It gets its data from
     * a list. When a selection is made on the list the data is loaded into the
     * text element.
     * <code>
     *  <j:list id="lstExample" smartbinding="..." />
     *
     *  <j:text model="#lstExample">
     *      <j:bindings>
     *          <j:load get="url:getMessage.php?id={@id}" />
     *          <j:contents select="message/text()" />
     *      </j:bindings>
     *  </j:text>
     * </code>
     * @attribute {string} get the data instruction on how to load data from the data source into the xmlRoot of this component.
     */
    this.$loadSubData = function(xmlRootNode){
        if (this.hasLoadStatus(xmlRootNode)) return;

        if (typeof jpf.offline != "undefined" && !jpf.offline.onLine) {
            jpf.offline.transactions.actionNotAllowed();
            this.loadedWhenOffline = true;

            if (this.hasFeature(__MULTISELECT__) && !this.getTraverseNodes().length)
                this.$setClearMessage(this.offlineMsg, "offline");

            return;
        }

        //var loadNode = this.applyRuleSetOnNode("load", xmlRootNode);
        var loadNode, rule = this.getNodeFromRule("load", xmlRootNode, false, true);
        var sel = (rule && rule.getAttribute("select"))
            ? rule.getAttribute("select")
            : ".";

        if (rule && (loadNode = xmlRootNode.selectSingleNode(sel))) {
            this.setLoadStatus(xmlRootNode, "loading");

            if (this.$setClearMessage)
                this.$setClearMessage(this.loadingMsg, "loading");

            //||jpf.xmldb.findModel(xmlRootNode)
            var mdl = this.getModel(true);
            if (!mdl)
                throw new Error("Could not find model");

            var jmlNode = this;
            if (mdl.insertFrom(rule.getAttribute("get"), loadNode, {
                    insertPoint : xmlRootNode, //this.xmlRoot,
                    jmlNode     : this
                }, function(){
                    jmlNode.setConnections(xmlRootNode);//jmlNode.xmlRoot);
                }) === false
            ) {
                this.clear(true);
                if (jpf.appsettings.autoDisable)
                    this.setProperty("disabled", true);
                this.setConnections(null, "select"); //causes strange behaviour
            }
        }
    };

    /**
     * @private
     */
    this.setLoadStatus = function(xmlNode, state, remove){
        //remove old status if any
        var ostatus = xmlNode.getAttribute("j_loaded");
        ostatus = ostatus
            ? ostatus.replace(new RegExp("\\|\\w+\\:" + this.uniqueId + "\\|", "g"), "")
            : "";

        if (!remove)
            ostatus += "|" + state + ":" + this.uniqueId + "|";

        xmlNode.setAttribute("j_loaded", ostatus);
    };

    /**
     * @private
     */
    this.removeLoadStatus = function(xmlNode){
        this.setLoadStatus(xmlNode, null, true);
    };

    /**
     * @private
     */
    this.hasLoadStatus = function(xmlNode, state){
        var ostatus = xmlNode.getAttribute("j_loaded");
        if (!ostatus) return false;

        return (ostatus.indexOf((state || "") + ":" + this.uniqueId + "|") != -1)
    };

    /**
     * @event beforeinsert Fires before data is inserted.
     *   cancellable: Prevents the data from being inserted.
     *   object:
     *   {XMLElement} xmlParentNode the parent in which the new data is inserted
     * @event afterinsert Fires after data is inserted.
     */

    /**
     * @private
     */
    this.insert = function(XMLRoot, parentXMLElement, options){
        if (typeof XMLRoot != "object")
            XMLRoot = jpf.getXmlDom(XMLRoot).documentElement;
        if (!parentXMLElement)
            parentXMLElement = this.xmlRoot;

        if (this.dispatchEvent("beforeinsert", {xmlParentNode : parentXMLElement}) === false)
            return false;

        //Integrate XMLTree with parentNode
        var newNode = jpf.xmldb.integrate(XMLRoot, parentXMLElement,
          jpf.extend(options, {copyAttributes: true}));

        //Call __XMLUpdate on all listeners
        jpf.xmldb.applyChanges("insert", parentXMLElement);

        //Select or propagate new data
        if (this.selectable && this.autoselect) {
            if (this.xmlRoot == newNode)
                this.$selectDefault(this.xmlRoot);
        }
        else if (this.xmlRoot == newNode)
            this.setConnections(this.xmlRoot, "select");

        if (this.hasLoadStatus(parentXMLElement, "loading"))
            this.setLoadStatus(parentXMLElement, "loaded");

        this.dispatchEvent("afterinsert");

        //Check Connections
        //this one shouldn't be called because they are listeners anyway...(else they will load twice)
        //if(this.selected) this.setConnections(this.selected, "select");
    };

    this.inherit(this.hasFeature(__MULTISELECT__)
        ? jpf.MultiselectBinding
        : jpf.StandardBinding);

    function findModel(x, isSelection) {
        return x.getAttribute((isSelection
            ? "ref"
            : "") + "model") || jpf.xmldb.getInheritedAttribute(x, null,
              function(xmlNode){
                if (isSelection && x == xmlNode)
                    return false;
                if (xmlNode.getAttribute("model")) {
                    /*
                    var isCompRef = xmlNode.getAttribute("model").substr(0,1) == "#";
                    if (xmlNode.getAttribute("id")
                      && self[xmlNode.getAttribute("id")].hasFeature(__DATABINDING__)) {
                        var data = xmlNode.getAttribute("model").split(":", 3);
                        return "#" + xmlNode.getAttribute("id") + ((isCompRef
                            ? data[2]
                            : data[1]) || "");
                    }
                    */
                    return xmlNode.getAttribute("model");
                }
                if (xmlNode.getAttribute("smartbinding")) {
                    var bclass = x.getAttribute("smartbinding").split(" ")[0];
                    if (jpf.nameserver.get("model", bclass))
                        return bclass + ":select";
                }
                return false
              });
    }

    var initModelId = [];
    this.$addJmlLoader(function(x){
        //, this.ref && this.hasFeature(__MULTISELECT__)
        if (initModelId[0])
            jpf.setModel(initModelId[0], this);
        if (initModelId[1])
            jpf.setModel(initModelId[1], this, true);

        var hasModel = initModelId.length;

        //Set the model for normal smartbinding
        if ((!this.ref || this.hasFeature(__MULTISELECT__)) && !this.xmlRoot) {
            var sb = jpf.JmlParser.sbInit[this.uniqueId]
                && jpf.JmlParser.sbInit[this.uniqueId][0];

            //@todo experimental for traverse="" attributes
            if (this.traverse && (sb && !sb.model
              || !sb && this.hasFeature(__MULTISELECT__))
              || !initModelId[0] && sb) {
                initModelId = findModel(x);

                if (initModelId) {
                    if (!sb)
                        this.smartBinding = true; //@todo experimental for traverse="" attributes

                    jpf.setModel(initModelId, this, 0);
                }
            }
        }

        initModelId = null;

        if (this.hasFeature(__MULTISELECT__) || this.$hasStateMessages) {
            //@todo An optimization might be to loop through the parents once
            var defProps = ["empty-message", "loading-message", "offline-message"];

            for (var i = 0, l = defProps.length; i < l; i++) {
                if (!x.getAttribute(defProps[i]))
                    this.$propHandlers[defProps[i]].call(this);
            }
        }

        if (!x.getAttribute("create-model"))
            this.$propHandlers["create-model"].call(this);

        var hasInitSb = jpf.JmlParser.sbInit[this.uniqueId] ? true : false;
        if ((!hasInitSb || !hasModel) && this.$setClearMessage
          && (!loadqueue && !this.xmlRoot && (this.hasFeature(__MULTISELECT__)
          || this.ref || hasInitSb)))
            this.$setClearMessage(this.emptyMsg, "empty");
    });

    this.$jmlDestroyers.push(function(){
        // Remove data connections - Should be in DataBinding
        if (this.dataParent)
            this.dataParent.parent.disconnect(this);
        if (this.hasFeature(__DATABINDING__)) {
            this.unloadBindings();
            this.unloadActions();
        }
    });

    /**
     * @attribute {Boolean} render-root whether the xml element loaded into this
     * element is rendered as well. Default is false.
     * Example:
     * This example shows a tree which also renders the root element.
     * <code>
     *  <j:tree render-root="true">
     *      <j:model>
     *          <root name="My Computer">
     *              <drive letter="C">
     *                  <folder path="/Program Files" />
     *                  <folder path="/Desktop" />
     *              </drive>
     *          </root>
     *      </j:model>
     *  </j:tree>
     * </code>
     */
    this.$booleanProperties["render-root"] = true;
    this.$supportedProperties.push("empty-message", "loading-message",
        "offline-message", "render-root", "smartbinding", "create-model",
        "bindings", "actions", "dragdrop");

    this.$propHandlers["render-root"] = function(value){
        this.renderRoot = value;
    }
    
    /**
     * @attribute {String} empty-message the message displayed by this element
     * when it contains no data. This property is inherited from parent nodes.
     * When none is found it is looked for on the appsettings element. Otherwise
     * it defaults to the string "No items".
     */
    this.$propHandlers["empty-message"] = function(value){
        this.emptyMsg = value
            || jpf.xmldb.getInheritedAttribute(this.$jml, "empty-message")
            || "No items";

        if (!jpf.isParsing) 
            this.$updateClearMessage(this.emptyMsg, "empty");
    };

    /**
     * @attribute {String} loading-message  the message displayed by this
     * element when it's loading. This property is inherited from parent nodes.
     * When none is found it is looked for on the appsettings element. Otherwise
     * it defaults to the string "Loading...".
     * Example:
     * This example uses property binding to update the loading message. The
     * position of the progressbar should be updated by the script taking care
     * of loading the data.
     * <code>
     *  <j:list loading-message="{'Loading ' + Math.round(progress1.value*100) + '%'}" />
     *  <j:progressbar id="progress1" />
     * </code>
     * Remarks:
     * Usually a static loading message is displayed for only 100 milliseconds
     * or so, whilst loading the data from the server. This is done for instance
     * when the load binding rule is used. In the code example below a list
     * binds on the selection of a tree displaying folders. When the selection
     * changes, the list loads new data by extending the model. During the load
     * of this new data the loading message is displayed.
     * <code>
     *  <j:list model="#trFolders">
     *      <j:bindings>
     *          ...
     *          <j:load load="rpc:comm.getFiles({@path})" />
     *      </j:bindings>
     *  </j:list>
     * </code>
     */
    this.$propHandlers["loading-message"] = function(value){
        this.loadingMsg = value
            || jpf.xmldb.getInheritedAttribute(this.$jml, "loading-message")
            || "Loading...";

        if (!jpf.isParsing)
            this.$updateClearMessage(this.loadingMsg, "loading");
    };

    /**
     * @attribute {String} offline-message  the message displayed by this
     * element when it can't load data because the application is offline.
     * This property is inherited from parent nodes. When none is found it is
     * looked for on the appsettings element. Otherwise it defaults to the
     * string "You are currently offline...".
     */
    this.$propHandlers["offline-message"] = function(value){
        this.offlineMsg = value
            || jpf.xmldb.getInheritedAttribute(this.$jml, "offline-message")
            || "You are currently offline...";

        if (!jpf.isParsing)
            this.$updateClearMessage(this.offlineMsg, "offline");
    };

    /**
     * @attribute {Boolean} create-model whether the model this element connects
     * to is extended when the data pointed to does not exist. Defaults to true.
     * Example:
     * In this example a model is extended when the user enters information in
     * the form elements. Because no model is specified for the form elements
     * the first available model is chosen. At the start it doesn't have any
     * data, this changes when for instance the name is filled in. A root node
     * is created and under that a 'name' element with a textnode containing
     * the entered text.
     * <code>
     *  <j:model id="mdlForm" submission="url:save_form.php" />
     *
     *  <j:bar>
     *      <j:label>Name</j:label>
     *      <j:textbox ref="name" required="true" />
     *
     *      <j:label>Address</j:label>
     *      <j:textarea ref="address" />
     *
     *      <j:label>Country</j:label>
     *      <j:dropdown ref="country" model="url:countries.xml" traverse="country" caption="@name" />
     *
     *      <j:button action="submit">Submit</j:button>
     *  </j:bar>
     * </code>
     */
    this.$propHandlers["create-model"] = function(value){
        this.createModel = !jpf.isFalse(
            jpf.xmldb.getInheritedAttribute(this.$jml, "create-model"));
            
        var mb;
        if (this.getMultibinding && (mb = this.getMultibinding()))
            mb.createModel = this.createModel;
    };

    /**
     * @attribute {String} smartbinding  the name of the SmartBinding for this
     * element. A smartbinding is a collection of rules which define how data
     * is transformed into representation, how actions on the representation are
     * propagated to the data and it's original source, how drag&drop actions
     * change the data and where the data is loaded from. Each of these are
     * optionally defined in the smartbinding set and can exist independently
     * of the smartbinding object.
     * Example:
     * This example shows a fully specified smartbinding. Usually only parts
     * are used. This example shows a tree with files and folders.
     * <code>
     *  <j:tree smartbinding="sbExample" />
     *
     *  <j:smartbinding id="sbExample">
     *      <j:bindings>
     *         <j:caption  select = "@name"/>
     *         <j:icon     select = "self::file"
     *                     value  = "icoFile.gif" />
     *         <j:icon     value  = "icoFolder.gif" />
     *         <j:traverse select = "file|folder|root" />
     *      </j:bindings>
     *      <j:actions>
     *          <j:remove set = "url:remove.php?path={@path}" />
     *          <j:rename set = "url:move.php?from=oldValue&to={@path}" />
     *      </j:actions>
     *      <j:dragdrop>
     *         <j:allow-drag select = "folder|file" />
     *         <j:allow-drop select = "folder" 
     *                       target = "root"
     *                       operation = "tree-append" />
     *         <j:allow-drop select = "folder" 
     *                       target = "folder"
     *                       operation = "insert-before" />
     *         <j:allow-drop select = "file"   
     *                       target = "folder|root" 
     *                       soperation = "tree-append" />
     *         <j:allow-drop select = "file"   
     *                       target = "file"        
     *                       operation = "insert-before" />
     *      </j:dragdrop>
     *      <j:model load="url:get_listing.php" />
     *  </j:smartbinding>
     * </code>
     * Remarks:
     * The smartbinding parts can also be assigned to an element by adding them
     * directly as a child in jml.
     * <code>
     *  <j:tree>
     *      <j:bindings>
     *          ...
     *      </j:bindings>
     *      <j:actions>
     *          ...
     *      </j:actions>
     *      <j:dragdrop>
     *          ...
     *      </j:dragdrop>
     *      <j:model />
     *  </j:tree>
     * </code>
     *
     * See:
     * There are several ways to be less verbose in assigning certain rules.
     * <ul>
     *  <li>{@link baseclass.multiselectbinding.binding.traverse}</li>
     *  <li>{@link baseclass.dragdrop.attribute.dragEnabled}</li>
     *  <li>{@link element.bindings}</li>
     *  <li>{@link element.actions}</li>
     *  <li>{@link element.dragdrop}</li>
     *  <li>{@link baseclass.multiselectbinding.method.loadInlineData}</li>
     * </ul>
     */
    this.$propHandlers["smartbinding"] = function(value, forceInit){
        var sb;

        if (value && typeof value == "string") {
            sb = jpf.JmlParser.getSmartBinding(value);

            if (!sb)
                throw new Error(jpf.formatErrorString(1059, this,
                    "Attaching a smartbinding to " + this.tagName
                    + " [" + this.name + "]",
                    "Smartbinding '" + value + "' was not found."));
        }
        else
            sb = value;

        if (this.smartBinding && this.smartBinding.deinitialize)
            this.smartBinding.deinitialize(this)

        if (jpf.isParsing) {
            if (forceInit === true)
                return (this.smartBinding = sb.initialize(this));

            return jpf.JmlParser.addToSbStack(this.uniqueId, sb);
        }

        return (this.smartBinding = sb.markForUpdate(this));
    };

    /**
     * @attribute {String} bindings the id of the j:bindings element which
     * provides the binding rules for this element.
     * Example:
     * This example shows a set of binding rules that transform data into the
     * representation of a list. In this case it displays the names of
     * several email accounts, with after each account name the number of unread
     * mails in that account. It uses JSLT to transform the caption.
     * <code>
     *  <j:list bindings="bndExample" />
     *
     *  <j:bindings id="bndExample">
     *      <j:caption>{text()} (#'mail[@read=0]')</j:caption>
     *      <j:icon     select = "@icon" />
     *      <j:traverse select = "account" sort="text()" />
     *  </j:bindings>
     * </code>
     * Remarks:
     * Bindings can also be assigned directly by putting the bindings tag as a
     * child of this element.
     *
     * If the rule only contains a select attribute, it can be written in a
     * short way by adding an attribute with the name of the rule to the
     * element itself:
     * <code>
     *  <j:list caption="text()" icon="@icon" traverse="item" />
     * </code>
     */
    this.$propHandlers["bindings"] = function(value){
        var sb = this.smartBinding || (jpf.isParsing
            ? jpf.JmlParser.getFromSbStack(this.uniqueId)
            : this.$propHandlers["smartbinding"].call(this, new jpf.smartbinding()));

        if (!value) {
            //sb.removeBindings();
            throw new Error("Not Implemented"); //@todo
            return;
        }

        if (!jpf.nameserver.get("bindings", value))
            throw new Error(jpf.formatErrorString(1064, this,
                "Connecting bindings",
                "Could not find bindings by name '" + value + "'", this.$jml));

        sb.addBindings(jpf.nameserver.get("bindings", value));
    };

    /**
     * @attribute {String} actions the id of the j:actions element which
     * provides the action rules for this element. Action rules are used to
     * send changes on the bound data to a server.
     * Example:
     * <code>
     *  <j:tree actions="actExample" />
     *
     *  <j:actions id="actExample">
     *      <j:rename set="rpc:comm.update({@id}, {@name})" />
     *      <j:remove set="rpc:comm.remove({@id})" />
     *      <j:add get="rpc:comm.add({../@id})" />
     *  </j:actions>
     * </code>
     */
    this.$propHandlers["actions"] = function(value){
        var sb = this.smartBinding || (jpf.isParsing
            ? jpf.JmlParser.getFromSbStack(this.uniqueId)
            : this.$propHandlers["smartbinding"].call(this, new jpf.smartbinding()));

        if (!value) {
            //sb.removeBindings();
            throw new Error("Not Implemented"); //@todo
            return;
        }

        if (!jpf.nameserver.get("actions", value))
            throw new Error(jpf.formatErrorString(1065, this,
                "Connecting bindings",
                "Could not find actions by name '" + value + "'", this.$jml));

        sb.addActions(jpf.nameserver.get("actions", value));
    };

    function refModelPropSet(strBindRef){
        var isSelection = this.hasFeature(__MULTISELECT__) ? 1 : 0;
        var o = isSelection
            ? this.$getMultiBind()
            : this;

        var sb = hasRefBinding && o.smartBinding || (jpf.isParsing
            ? jpf.JmlParser.getFromSbStack(this.uniqueId, isSelection, true)
            : this.$propHandlers["smartbinding"].call(this, new jpf.smartbinding()))

        //We don't want to change a shared smartbinding
        if (!hasRefBinding) {
            if (o.bindingRules)
                o.unloadBindings();
            o.bindingRules = {};
        }

        //Get or create bind rule
        var bindRule = (o.bindingRules[o.mainBind] ||
            (o.bindingRules[o.mainBind]
                = [jpf.getXml("<" + (o.mainBind || "value") + " />")]))[0];

        //Check if the smartbinding has the rule (We assume all or nothing)
        ((sb.bindings || (sb.bindings = o.bindingRules))[o.mainBind])
            || (sb.bindings[o.mainBind] = [bindRule]);

        // Define model
        var model, modelId;
        if (!jpf.isParsing)
            model = o.getModel();

        if (!model) {
            modelId = o.lastModelId =
                o.model || findModel(this.$jml, isSelection);

            //deprecated bindway: @todo test this!! with a model NOT a component (well that too)

            if (!modelId) {
                if (jpf.globalModel) {
                    jpf.console.warn("Cannot find a model to connect to, will \
                                      try to use default model.");
                    modelId = "@default";
                }
                else
                    throw new Error(jpf.formatErrorString(1062, this, "init",
                        "Could not find model to get data from", o.$jml));
            }
        }

        /*
            We don't want to connect to the root, that would create a rush
            of unnecessary update messages, so we'll find the element that's
            closest to the node that is gonna feed us the value
        */
        strBindRef.match(/^(.*?)((?:\@[\w-_\:]+|text\(\))(\[.*?\])?|[\w-_\:]+\[.*?\])?$/);
        var valuePath   = RegExp.$1;
        var valueSelect = RegExp.$2 || ".";

        if (valuePath === null) {
            throw new Error(jpf.formatErrorString(1063, this,
                "Setting @ref",
                "Could not find xpath to determine XMLRoot: "
                + strBindRef, o.$jml));

            return;
        }

        if (modelId) {
            //Reconstructing modelId with new valuePath
            if (modelId == "@default") {
                valueSelect = strBindRef;
            }
            else if (valuePath) {
                var modelIdParts = modelId.split(":", 3);

                modelId = modelIdParts.shift();
                if (modelId.indexOf("#") == 0) {
                    modelId += (modelIdParts[0]
                        ? ":" + modelIdParts.shift()
                        : ":select")
                }

                modelId += ":" + ((modelIdParts[0] ? modelIdParts[0] + "/" : "")
                    + (valuePath || "."))
                    .replace(/\/$/, "")
                    .replace(/\/\/+/, "/");
            }

            if (jpf.isParsing && initModelId)
                initModelId[isSelection] = modelId
            else
                setModelQueue(modelId, isSelection);
        }
        else {
            var m = (o.lastModelId || "").split(":");
            var modelIdPart = ((m.shift().indexOf("#") == 0
                &&  m.shift() && m.shift() || m.shift()) || "");

            model.$register(this, ((modelIdPart ? modelIdPart + "/" : "")
                + (valuePath || "."))
                .replace(/\/$/, "")
                .replace(/\/\/+/, "/")); //Update model with new info

            //Add this item to the queue to reload
            sb.markForUpdate(this, "model");
        }

        bindRule.setAttribute("select", valueSelect);
    }

    var timer = [];
    function setModelQueue(modelId, isSelection){
        clearTimeout(timer[isSelection ? 1 : 0]);
        timer[isSelection ? 1 : 0] = setTimeout(function(){
            jpf.setModel(modelId, _self, isSelection);
        });
    }

    /**
     * @attribute {String} model the name of the model to load data from or a
     * datainstruction to load data.
     * Example:
     * <code>
     *  <j:tree model="mdlExample" />
     *  <j:model id="mdlExample" load="url:example.xml" />
     * </code>
     * Example:
     * <code>
     *  <j:list model="url:friends.xml" />
     * </code>
     * Example:
     * <code>
     *  <j:tree id="trContacts" model="rpc:comm.getContacts()" />
     *  <j:text model="#trContacts" />
     * </code>
     * Remarks:
     * This attribute is inherited from a parent when not set. You can use this
     * to tell sets of elements to use the same model.
     * <code>
     *  <j:bar model="mdlForm">
     *      <j:label>Name</j:label>
     *      <j:textbox ref="name" />
     *
     *      <j:label>Happiness</j:label>
     *      <j:slider ref="happiness" min="0" max="10"/>
     *  </j:bar>
     *
     *  <j:model id="mdlForm">
     *      <data />
     *  </j:model>
     * </code>
     * When no model is specified the default model is choosen. The default
     * model is the first model that is found without a name, or if all models
     * have a name, the first model found.
     *
     * @attribute {String} select-model the name of the model or a
     * datainstruction to load data that determines the selection of this
     * element.
     * Example:
     * This example shows a dropdown from which the user can select a country.
     * The list of countries is loaded from a model. Usually this would be loaded
     * from a seperate url, but for clarity it's inlined. When the user selects
     * a country in the dropdown the value of the item is stored in the second
     * model (mdlForm) at the position specified by the ref attribute. In this
     * case this is the country element.
     * <code>
     *  <j:label>Name</j:label>
     *  <j:textbox ref="name" model="mdlForm" />
     *
     *  <j:label>Country</j:label>
     *  <j:dropdown
     *      ref          = "country"
     *      model        = "mdlCountries"
     *      select-model = "mdlForm"
     *      traverse     = "country"
     *      value        = "@value"
     *      caption      = "text()">
     *  </j:dropdown>
     *
     *  <j:model id="mdlCountries">
     *      <countries>
     *          <country value="USA">USA</country>
     *          <country value="GB">Great Brittain</country>
     *          <country value="NL">The Netherlands</country>
     *          ...
     *      </countries>
     *  </j:model>
     *
     *  <j:model id="mdlForm">
     *      <data>
     *          <name />
     *          <country />
     *      </data>
     *  </j:model>
     * </code>
     * Remarks:
     * In most cases this attribute isn't used because the model is inherited
     * from a parent element. In a typical form this will happen as follows:
     * <code>
     *  <j:bar model="mdlForm">
     *      <j:label>Name</j:label>
     *      <j:textbox ref="name" />
     *
     *      <j:label>Country</j:label>
     *      <j:dropdown
     *          ref          = "country"
     *          model        = "url:countries.xml"
     *          traverse     = "country"
     *          value        = "@value"
     *          caption      = "text()">
     *      </j:dropdown>
     *  </j:bar>
     *
     *  <j:model id="mdlForm">
     *      <data />
     *  </j:model>
     * </code>
     * @see baseclass.databinding.attribute.model
     */
    this.$propHandlers["model"] = function(value){
        if (this.$modelIgnoreOnce) {
            delete this.$modelIgnoreOnce;
            return;
        }
        
        if (!value) {
            this.clear();
            this.$model.unregister(this);
            this.$model = null;
            this.lastModelId = "";
            return;
        }

        this.lastModelId = value;

        if (!this.hasFeature(__MULTISELECT__)) {
            if (jpf.isParsing && this.$jml.getAttribute("ref")) //@todo setting attribute in script block will go wrong
                return; //Ref will take care of everything

            //We're changing the model, lets do it using the @ref way
            if (this.ref) {
                refModelPropSet.call(this, this.ref);
                return;
            }
        }

        if (jpf.isParsing)
            initModelId[0] = value;
        else
            setModelQueue(value, this.hasFeature(__MULTISELECT__));
    };

    /**
     * @attribute {String} ref  an xpath statement used to select the data xml
     * element to which this element is bound to.
     * Example:
     * <code>
     *  <j:slider ref="@value" model="mdlExample" />
     *
     *  <j:model id="mdlExample">
     *      <data value="0.3" />
     *  </j:model>
     * </code>
     */
    var hasRefBinding;
    this.$propHandlers["ref"] = function(value){
        if (!value) {
            this.unloadBindings();
            hasRefBinding = false;
            return;
        }

        refModelPropSet.call(this, value);

        //if (isSelection && x.getAttribute("selectcaption"))
        //    strBind.push("/><caption select='", x.getAttribute("selectcaption"), "' "); //hack!

        hasRefBinding = value ? true : false;
    };

    /**
     * @attribute {String} viewport the way this element renders its data.
     * Possible values:
     * virtual  this element only renders data that it needs to display.
     * normal   this element renders all data at startup.
     * @experimental
     */
    this.$propHandlers["viewport"] = function(value){
        if (value != "virtual")
            return;

        this.inherit(jpf.VirtualViewport);
    };
};

/**
 * @constructor
 * @private
 * @baseclass
 */
jpf.StandardBinding = function(){
    if (!this.defaultValue) //@todo please use this in a sentence
        this.defaultValue = "";

    /**
     * Load XML into this element
     * @private
     */
    this.$load = function(XMLRoot){
        //Add listener to XMLRoot Node
        jpf.xmldb.addNodeListener(XMLRoot, this);

        //Set Properties

        var lrule, rule;
        for (rule in this.bindingRules) {
            lrule = rule.toLowerCase();
            if (this.$supportedProperties.contains(lrule))
                this.setProperty(lrule, this.applyRuleSetOnNode(rule,
                    this.xmlRoot) || "", null, true);
        }

        //Think should be set in the event by the Validation Class
        if (this.errBox && this.isValid())
            this.clearError();
    };

    /**
     * Set xml based properties of this element
     * @private
     */
    this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj){
        //Clear this component if some ancestor has been detached
        if (action == "redo-remove") {
            var retreatToListenMode = false, model = this.getModel(true);
            if (model) {
                var xpath = model.getXpathByJmlNode(this);
                if (xpath) {
                    var xmlNode = model.data.selectSingleNode(xpath);
                    if (xmlNode != this.xmlRoot)
                        retreatToListenMode = true;
                }
            }
            
            if (retreatToListenMode || this.xmlRoot == xmlNode) {
                //RLD: Disabled because sometimes indeed components do not 
                //have a model when their xmlRoot is removed.
                if (!model) {
                    throw new Error(jpf.formatErrorString(0, this, 
                        "Setting change notifier on component", 
                        "Component without a model is listening for changes", 
                        this.$jml));
                }

                //Set Component in listening state untill data becomes available again.
                return model.loadInJmlNode(this, xpath);
            }
        }

        //Action Tracker Support
        if (UndoObj && !UndoObj.xmlNode)
            UndoObj.xmlNode = this.xmlRoot;

        //Set Properties

        var lrule, rule;
        for (rule in this.bindingRules) {
            lrule = rule.toLowerCase();
            if (this.$supportedProperties.contains(lrule)) {
                var value = this.applyRuleSetOnNode(rule, this.xmlRoot) || "";

                if (this[lrule] != value)
                    this.setProperty(lrule, value, null, true);
            }
        }

        //Think should be set in the event by the Validation Class
        if (this.errBox && this.isValid())
            this.clearError();
    };

    if (!this.clear) {
        this.clear = function(nomsg, do_event){
            this.documentId = this.xmlRoot = this.cacheID = null;

            if (this.$clear)
                this.$clear(nomsg, do_event);
        };
    }
};

/**
 * @constructor
 * @baseclass
 * @private
 */
jpf.MultiselectBinding = function(){
    this.length = 0;

    /**
     * @define bindings
     * @allowchild traverse
     * @binding traverse Determines the list of elements which for which each
     * gets a visual representation within the element. It also can determine
     * the sequence of how the elements are visualized by offering a way to
     * specify the sort order. (N.B. The sorting mechanism is very similar to
     * that of XSLT)
     * Example:
     * This example contains a list that displays elements with the tagName
     * 'mail' that do not have a deleted attribute set to 1.
     * <code>
     *  <j:list>
     *      <j:bindings>
     *          ...
     *          <j:traverse select="mail[not(@deleted='1')]" />
     *      </j:bindings>
     *  </j:list>
     * </code>
     * Example:
     * This example shows how to use the traverse rule to order files based
     * on their modified data.
     * <code>
     *  <j:traverse
     *      select      = "file"
     *      sort        = "@date"
     *      date-format = "DD-MM-YYYY"
     *      order       = "descending" />
     * </code>
     * Example:
     * This example shows how to do complex sorting using a javascript callback function.
     * <code>
     *  <j:traverse select="file|folder" sort="@name" sort-method="compare" />
     *  <j:script>
     *      function compare(value, args, xmlNode) {
     *          //Sort all folders together and all files and then sort on alphabet.
     *          return (xmlNode.tagName == "folder" ? 0 : 1) + value;
     *      }
     *  </j:script>
     * </code>
     * @attribute {String} select       an xpath statement which selects the nodes which will be rendered.
     * @attribute {String} sort         an xpath statement which selects the value which is subject to the sorting algorithm.
     * @attribute {String} data-type    the way sorting is executed. See {@link baseclass.multiselectbinding.binding.traverse.attribute.sort-method} for how to specify a custom sort method.
     *   Possible values:
     *   string  Sorts alphabetically
     *   number  Sorts based on numerical value (i.e. 9 is lower than 10).
     *   date    Sorts based on the date sequence (21-6-1980 is lower than 1-1-2000). See {@link baseclass.multiselectbinding.binding.traverse.attribute.date-format} for how to specify the date format.
     * @attribute {String} date-format  the format of the date on which is sorted.
     *   Possible values:
     *   YYYY   Full year
     *   YY     Short year
     *   DD     Day of month
     *   MM     Month
     *   hh     Hours
     *   mm     Minutes
     *   ss     Seconds
     * Example:
     * <code>
     *  date-format="DD-MM-YYYY"
     * </code>
     * @attribute {String} sort-method  the name of a javascript function to executed to determine the value to sort on.
     * @attribute {String} order        the order of the sorted list.
     *   Possible values:
     *   ascending  Default sorting order
     *   descending Reverses the default sorting order.
     * @attribute {String} case-order   whether upper case characters have preference above lower case characters.
     *   Possible values:
     *   upper-first    Upper case characters are higher.
     *   lower-first    Lower case characters are higher.
     */
    /**
     * @private
     */
    this.parseTraverse = function (xmlNode){
        this.traverse = xmlNode.getAttribute("select");

        this.$sort = xmlNode.getAttribute("sort") ? new jpf.Sort(xmlNode) : null;
    };

    /**
     * Change the sorting order of this element
     *
     * @param {Object}  options  the new sort options. These are applied incrementally. Any property not set is maintained unless the clear parameter is set to true.
     *   Properties:
     *   {String}   order        see {@link baseclass.multiselectbinding.binding.traverse.attribute.order}
     *   {String}   [xpath]      see {@link baseclass.multiselectbinding.binding.traverse.attribute.sort}
     *   {String}   [type]       see {@link baseclass.multiselectbinding.binding.traverse.attribute.data-type}
     *   {String}   [method]     see {@link baseclass.multiselectbinding.binding.traverse.attribute.sort-method}
     *   {Function} [getNodes]   Function that retrieves a list of nodes.
     *   {String}   [dateFormat] see {@link baseclass.multiselectbinding.binding.traverse.attribute.date-format}
     *   {Function} [getValue]   Function that determines the string content based on an xml node as it's first argument.
     * @param {Boolean} clear    removes the current sort options.
     * @param {Boolean} noReload wether to reload the data of this component.
     * @see   baseclass.multiselectbinding.binding.traverse
     */
    this.resort = function(options, clear, noReload){
        if (!this.$sort)
            this.$sort = new jpf.Sort();
 
        this.$sort.set(options, clear);
        this.clearAllCache();

        if (noReload)
            return;

        /*if(this.hasFeature(__VIRTUALVIEWPORT__)){
            jpf.xmldb.clearVirtualDataset(this.xmlRoot);
            this.reload();

            return;
        }*/

        (function sortNodes(xmlNode, htmlParent) {
            var sNodes = _self.$sort.apply(
                jpf.xmldb.getArrayFromNodelist(xmlNode.selectNodes(_self.traverse)));

            for (var i = 0; i < sNodes.length; i++) {
                if (_self.isTreeArch || _self.$withContainer){
                    var htmlNode = jpf.xmldb.findHTMLNode(sNodes[i], _self);

                    if (!_self.$findContainer){
                        throw new Error(jpf.formatErrorString(_self,
                            "Sorting Nodes",
                            "This component does not \
                             implement _self.$findContainer"));
                    }

                    var container = _self.$findContainer(htmlNode);

                    htmlParent.appendChild(htmlNode);
                    if (!jpf.xmldb.isChildOf(htmlNode, container, true))
                        htmlParent.appendChild(container);

                    sortNodes(sNodes[i], container);
                }
                else
                    htmlParent.appendChild(jpf.xmldb.findHTMLNode(sNodes[i], _self));
            }
        })(this.xmlRoot, this.oInt);

        return options;
    };

    /**
     * Change sorting from ascending to descending and vice verse.
     */
    this.toggleSortOrder = function(){
        return this.resort({"ascending" : !this.$sort.get().ascending}).ascending;
    };

    /**
     * Retrieves the current sort options
     *
     * @returns {Object}  the current sort options.
     *   Properties:
     *   {String}   order      see {@link baseclass.multiselectbinding.binding.traverse.attribute.order}
     *   {String}   xpath      see {@link baseclass.multiselectbinding.binding.traverse.attribute.sort}
     *   {String}   type       see {@link baseclass.multiselectbinding.binding.traverse.attribute.data-type}
     *   {String}   method     see {@link baseclass.multiselectbinding.binding.traverse.attribute.sort-method}
     *   {Function} getNodes   Function that retrieves a list of nodes.
     *   {String}   dateFormat see {@link baseclass.multiselectbinding.binding.traverse.attribute.date-format}
     *   {Function} getValue   Function that determines the string content based on an xml node as it's first argument.
     * @see    baseclass.multiselectbinding.binding.traverse
     */
    this.getSortSettings = function(){
        return this.$sort.get();
    };

    /**
     * Gets a nodelist containing the xml data elements which are rendered by
     * this element (aka. traverse nodes, see {@link baseclass.multiselectbinding.binding.traverse}).
     *
     * @param {XMLElement} [xmlNode] the parent element on which the traverse query is applied.
     */
    this.getTraverseNodes = function(xmlNode){
        if (this.$sort) {
            var nodes = jpf.xmldb.getArrayFromNodelist((xmlNode || this.xmlRoot)
                .selectNodes(this.traverse));
            return this.$sort.apply(nodes);
        }

        return (xmlNode || this.xmlRoot).selectNodes(this.traverse);
    };

    /**
     * Gets the first xml data element which gets representation in this element
     * (aka. traverse nodes, see {@link baseclass.multiselectbinding.binding.traverse}).
     *
     * @param {XMLElement} [xmlNode] the parent element on which the traverse query is executed.
     * @return {XMLElement}
     */
    this.getFirstTraverseNode = function(xmlNode){
        if (this.$sort) {
            var nodes = jpf.xmldb.getArrayFromNodelist((xmlNode || this.xmlRoot)
                .selectNodes(this.traverse));
            return this.$sort.apply(nodes)[0];
        }

        return (xmlNode || this.xmlRoot).selectSingleNode(this.traverse);
    };

    /**
     * Gets the last xml data element which gets representation in this element
     * (aka. traverse nodes, see {@link baseclass.multiselectbinding.binding.traverse}).
     *
     * @param {XMLElement} [xmlNode] the parent element on which the traverse query is executed.
     * @return {XMLElement} the last xml data element
     * @see    baseclass.multiselectbinding.binding.traverse
     */
    this.getLastTraverseNode = function(xmlNode){
        var nodes = this.getTraverseNodes(xmlNode || this.xmlRoot);//.selectNodes(this.traverse);
        return nodes[nodes.length-1];
    };

    /**
     * Determines whether an xml data element is a traverse node (see {@link baseclass.multiselectbinding.binding.traverse})
     *
     * @param {XMLElement} [xmlNode] the parent element on which the traverse query is executed.
     * @return  {Boolean}  whether the xml element is a traverse node.
     * @see  baseclass.multiselectbinding.binding.traverse
     */
    this.isTraverseNode = function(xmlNode){
        /*
            Added optimization, only when an object has a tree architecture is it
            important to go up to the traverse parent of the xmlNode, else the node
            should always be based on the xmlroot of this component
        */
        var nodes = this.getTraverseNodes(this.isTreeArch
            ? this.getTraverseParent(xmlNode) || this.xmlRoot
            : this.xmlRoot);
        for (var i = 0; i < nodes.length; i++)
            if (nodes[i] == xmlNode)
                return true;
        return false;
    };

    /**
     * Gets the next traverse node (see {@link baseclass.multiselectbinding.binding.traverse}) to be selected
     * from a given traverse node. The method can do this in either direction and
     * also return the Nth node for this algorithm.
     *
     * @param {XMLElement}  xmlNode  the starting point for determining the next selection.
     * @param {Boolean}     [up]     the direction of the selection. Default is false.
     * @param {Integer}     [count]  the distance in number of nodes. Default is 1.
     * @return  {XMLElement} the xml data element to be selected next.
     * @see  baseclass.multiselectbinding.binding.traverse
     */
    this.getNextTraverseSelected = function(xmlNode, up, count){
        if (!xmlNode)
            xmlNode = this.selected;
        if (!count)
            count = 1;

        var i = 0;
        var nodes = this.getTraverseNodes(this.getTraverseParent(xmlNode) || this.xmlRoot);//.selectNodes(this.traverse);
        while (nodes[i] && nodes[i] != xmlNode)
            i++;

        var node = (up == null)
            ? nodes[i + count] || nodes[i - count]
            : (up ? nodes[i + count] : nodes[i - count]);

        return node || arguments[2] && (i < count || (i + 1) > Math.floor(nodes.length / count) * count)
            ? node
            : (up ? nodes[nodes.length-1] : nodes[0]);
    };

    /**
     * Gets the next traverse node (see {@link baseclass.multiselectbinding.binding.traverse}).
     * The method can do this in either direction and also return the Nth next node.
     *
     * @param {XMLElement}  xmlNode     the starting point for determining the next node.
     * @param {Boolean}     [up]        the direction. Default is false.
     * @param {Integer}     [count]     the distance in number of nodes. Default is 1.
     * @return  {XMLElement} the next traverse node
     * @see  baseclass.multiselectbinding.binding.traverse
     */
    this.getNextTraverse = function(xmlNode, up, count){
        if (!count)
            count = 1;
        if (!xmlNode)
            xmlNode = this.selected;

        var i = 0;
        var nodes = this.getTraverseNodes(this.getTraverseParent(xmlNode) || this.xmlRoot);//.selectNodes(this.traverse);
        while (nodes[i] && nodes[i] != xmlNode)
            i++;

        return nodes[i + (up ? -1 * count : count)];
    };

    /**
     * Gets the parent traverse node (see {@link baseclass.multiselectbinding.binding.traverse}). In some
     * cases the traverse rules has a complex form like 'children/item'. In those
     * cases the generated tree has a different structure from that of the xml
     * data. For these situations the xmlNode.parentNode property won't return
     * the traverse parent, this method will give you the right parent.
     *
     * @param {XMLElement} xmlNode the node for which the parent element will be determined.
     * @return  {XMLElement} the parent node or null if none was found.
     * @see  baseclass.multiselectbinding.binding.traverse
     */
    this.getTraverseParent = function(xmlNode){
        if (!xmlNode.parentNode || xmlNode == this.xmlRoot) return false;

        var x, id = xmlNode.getAttribute(jpf.xmldb.xmlIdTag);
        if (!id) {
            //return false;
            xmlNode.setAttribute(jpf.xmldb.xmlIdTag, "temp");
            id = "temp";
        }

        /*
        do {
            xmlNode = xmlNode.parentNode;
            if (xmlNode == this.xmlRoot)
                return false;
            if (this.isTraverseNode(xmlNode))
                return xmlNode;
        } while (xmlNode.parentNode);
        */

        //This is not 100% correct, but good enough for now

        //temp untill I fixed the XPath implementation
        if (jpf.isSafari) {
            var y = this.traverse.split("\|");
            for (var i = 0; i < y.length; i++) {
                x = xmlNode.selectSingleNode("ancestor::node()[("
                    + y[i] + "/@" + jpf.xmldb.xmlIdTag + "='" + id + "')]");
                break;
            }
        } else {
            x = xmlNode.selectSingleNode("ancestor::node()[(("
                + this.traverse + ")/@" + jpf.xmldb.xmlIdTag + ")='"
                + id + "']");
        }

        if (id == "temp")
            xmlNode.removeAttribute(jpf.xmldb.xmlIdTag);
        return x;
    };

    /**
     * Set listeners, calls HTML creation methods and
     * initializes select and focus states of object.
     */
    this.$load = function(XMLRoot){
        //Add listener to XMLRoot Node
        jpf.xmldb.addNodeListener(XMLRoot, this);

        var length = this.getTraverseNodes(XMLRoot).length;
        if (!this.renderRoot && !length)
            return this.clearAllTraverse();

        //Traverse through XMLTree
        var nodes = this.$addNodes(XMLRoot, null, null, this.renderRoot);

        //Build HTML
        this.$fill(nodes);

        //Select First Child
        if (this.selectable) {
            var sel, bHasOffline = (typeof jpf.offline != "undefined");
            if (!this.firstLoad && bHasOffline && jpf.offline.state.enabled
              && jpf.offline.state.realtime) {
                sel = jpf.offline.state.get(this, "selection");
                this.firstLoad = true;
            }

            if (sel) {
                var selstate = jpf.offline.state.get(this, "selstate");

                if (sel.length == 0) {
                    this.clearSelection();
                }
                else {
                    for (var i = 0; i < sel.length; i++) {
                        sel[i] = jpf.remote.xpathToXml(sel[i],
                            this.xmlRoot);
                    }

                    if (selstate[1]) {
                        var selected = jpf.remote
                            .xpathToXml(selstate[1], this.xmlRoot);
                    }

                    this.selectList(sel, null, selected);
                }

                if (selstate[0]) {
                    this.setIndicator(jpf.remote
                        .xpathToXml(selstate[0], this.xmlRoot));
                }
            }
            else
            if (this.autoselect) {
                if (this.value) {
                    var value = this.value;
                    this.value = !this.value;
                    this.setProperty("value", value);
                }
                
                if (!this.selected) {
                    if (this.renderRoot)
                        this.select(XMLRoot);
                    else if (nodes.length)
                        this.$selectDefault(XMLRoot);
                    else
                        this.setConnections();
                }
            }
            else {
                this.clearSelection(null, true);
                var xmlNode = this.renderRoot
                    ? this.xmlRoot
                    : this.getFirstTraverseNode(); //should this be moved to the clearSelection function?
                if (xmlNode)
                    this.setIndicator(xmlNode);
                this.setConnections(null, "both");
            }
        }

        if (this.focussable)
            jpf.window.focussed == this ? this.$focus() : this.$blur();

        if (length != this.length)
            this.setProperty("length", length);
    };

    var selectTimer = {}, _self = this;
    var actionFeature = {
        "insert"      : 127,//1111111
        "add"         : 123,//1111011
        "remove"      : 47, //0101111
        "redo-remove" : 79, //1001111
        "synchronize" : 127,//1111111
        "move-away"   : 105,//1101001
        "move"        : 77  //1001101
    };

    /**
     * Loops through parents of changed node to find the first
     * connected node. Based on the action it will change, remove
     * or update the representation of the data.
     *
     * @event xmlupdate Fires when xml of this element is updated.
     *   object:
     *   {String}     action   the action that was executed on the xml.
     *      Possible values:
     *      text        a text node is set.
     *      update      an xml node is updated.
     *      insert      xml nodes are inserted.
     *      add         an xml node is added.
     *      remove      an xml node is removed (parent still set).
     *      redo-remove an xml node is removed (parent not set).
     *      synchronize unknown update.
     *      move-away   an xml node is moved (parent not set).
     *      move        an xml node is moved (parent still set).
     *   {XMLElement} xmlNode  the node that is subject to the update.
     *   {Mixed}      result   the result.
     *   {UndoObj}    UndoObj  the undo information.
     */
    this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj, lastParent){
        if (!this.xmlRoot)
            return; //@todo think about purging cache when xmlroot is removed

        var result, startNode = xmlNode, length;
        if (!listenNode)
            listenNode = this.xmlRoot;

        if (action == "redo-remove") {
            lastParent.appendChild(xmlNode); //ahum, i'm not proud of this one
            var traverseNode = this.isTraverseNode(xmlNode);
            lastParent.removeChild(xmlNode);
            
            if (!traverseNode)
                xmlNode = lastParent;
        }

        //Get First ParentNode connected
        do {
            if (action == "add" && this.isTraverseNode(xmlNode)
              && startNode == xmlNode)
                break; //@todo Might want to comment this out for adding nodes under a traversed node

            if (xmlNode.getAttribute(jpf.xmldb.xmlIdTag)) {
                var htmlNode = this.getNodeFromCache(
                    xmlNode.getAttribute(jpf.xmldb.xmlIdTag)
                    + "|" + this.uniqueId);

                if (htmlNode
                  && (startNode != xmlNode || xmlNode == this.xmlRoot)
                  && actionFeature[action] & 1)
                    action = "update";

                if (xmlNode == listenNode) {
                    if (xmlNode == this.xmlRoot && action != "insert")
                        return;
                    break;
                }

                if (htmlNode && actionFeature[action] & 2
                  && !this.isTraverseNode(xmlNode))
                    action = "remove"; //@todo why not break here?

                if (!htmlNode && actionFeature[action] & 4
                  && this.isTraverseNode(xmlNode)){
                    action = "add";
                    break;
                }

                if (htmlNode  || action == "move")
                    break;
            }
            else if (actionFeature[action] & 8 && this.isTraverseNode(xmlNode)){
                action = "add";
                break;
            }

            if (xmlNode == listenNode) break;
            xmlNode = xmlNode.parentNode;
        }
        while(xmlNode && xmlNode.nodeType != 9);

        /**
         * @todo Think about not having this code here
         */
        if (this.hasFeature(__VIRTUALVIEWPORT__)) {
            if(!this.$isInViewport(xmlNode)) //xmlNode is a traversed node
                return;
        }

        //if(xmlNode == listenNode && !action.match(/add|synchronize|insert/))
        //    return; //deleting nodes in parentData of object

        var foundNode = xmlNode;
        if (xmlNode && xmlNode.nodeType == 9)
            xmlNode = startNode;

        if (action == "replacechild"
          && (UndoObj ? UndoObj.args[0] == this.xmlRoot : !this.xmlRoot.parentNode)) {
            return this.load(UndoObj ? UndoObj.args[1] : listenNode); //Highly doubtfull this is exactly right...
        }

        //Action Tracker Support - && xmlNode correct here??? - UndoObj.xmlNode works but fishy....
        if (UndoObj && xmlNode && !UndoObj.xmlNode)
            UndoObj.xmlNode = xmlNode;

        //Check Move -- if value node isn't the node that was moved then only perform a normal update
        if (action == "move" && foundNode == startNode) {
            //if(!htmlNode) alert(xmlNode.getAttribute("id")+"|"+this.uniqueId);
            var isInThis  = jpf.xmldb.isChildOf(this.xmlRoot, xmlNode.parentNode, true);
            var wasInThis = jpf.xmldb.isChildOf(this.xmlRoot, UndoObj.extra.parent, true);

            //Move if both previous and current position is within this object
            if (isInThis && wasInThis)
                this.$moveNode(xmlNode, htmlNode);
            else if (isInThis) //Add if only current position is within this object
                action = "add";
            else if (wasInThis) //Remove if only previous position is within this object
                action = "remove";
        }
        else if (action == "move-away") {
            var goesToThis = jpf.xmldb.isChildOf(this.xmlRoot, UndoObj.extra.parent, true);
            if (!goesToThis)
                action = "remove";
        }

        //Remove loading message
        if (this.$removeClearMessage && this.$setClearMessage) {
            if (this.getFirstTraverseNode())
                this.$removeClearMessage();
            else
                this.$setClearMessage(this.emptyMsg, "empty")
        }

        //Check Insert
        if (action == "insert" && (this.isTreeArch || xmlNode == this.xmlRoot)) {
            if (this.hasLoadStatus(xmlNode) && this.$removeLoading)
                this.$removeLoading(htmlNode);

            if (this.oInt.firstChild && !jpf.xmldb.getNode(this.oInt.firstChild)) {
                //Appearantly the content was cleared
                this.oInt.innerHTML = "";

                if (!this.renderRoot) {
                    length = this.getTraverseNodes().length;
                    if (!length)
                        this.clearAllTraverse();
                }
            }

            result = this.$addNodes(xmlNode, (this.$getParentNode
                ? this.$getParentNode(htmlNode)
                : htmlNode), true, false);//this.isTreeArch??

            this.$fill(result);

            if (this.selectable && !this.xmlRoot.selectSingleNode(this.traverse))
                jpf.console.warn("No traversable nodes were found for "
                                 + this.name + " [" + this.tagName + "]\n\
                                  Traverse Rule : " + this.traverse);

            if (this.selectable && (length === 0 || !this.xmlRoot.selectSingleNode(this.traverse)))
                return;
        }
        else if (action == "add") {// || !htmlNode (Check Add)
            //var parentHTMLNode = this.getCacheItemByHtmlId(xmlNode.getAttribute(jpf.xmldb.xmlIdTag)+"|"+this.uniqueId);
            //xmlNode.parentNode == this.xmlRoot ? this.oInt :
            var parentHTMLNode = xmlNode.parentNode == this.xmlRoot
                ? this.oInt
                : this.getNodeFromCache(xmlNode.parentNode.getAttribute(
                    jpf.xmldb.xmlIdTag) + "|" + this.uniqueId); //This code should use getTraverseParent()

            //this.getCacheItem(xmlNode.parentNode.getAttribute(jpf.xmldb.xmlIdTag))

            //This should be moved into a function (used in setCache as well)
            if (!parentHTMLNode)
                parentHTMLNode = this.getCacheItem(xmlNode.parentNode.getAttribute(jpf.xmldb.xmlIdTag)
                    || (xmlNode.parentNode.getAttribute(jpf.xmldb.xmlDocTag)
                         ? "doc" + xmlNode.parentNode.getAttribute(jpf.xmldb.xmlDocTag)
                         : false))
                    || this.oInt; //This code should use getTraverseParent()

            //Only update if node is in current representation or in cache
            if (parentHTMLNode || jpf.xmldb.isChildOf(this.xmlRoot, xmlNode)) {
                parentHTMLNode = (this.$findContainer && parentHTMLNode
                    ? this.$findContainer(parentHTMLNode)
                    : parentHTMLNode) || this.oInt;

                result = this.$addNodes(xmlNode, parentHTMLNode, true, true,
                    this.getNodeByXml(this.getNextTraverse(xmlNode)));

                if (parentHTMLNode)
                    this.$fill(result);
            }
        }
        else if ((action == "remove") && (!xmlNode || foundNode == xmlNode && xmlNode.parentNode)) { //Check Remove
            if (!xmlNode)
                return;
            
            //Remove HTML Node
            if (htmlNode)
                this.$deInitNode(xmlNode, htmlNode);
            else if (xmlNode == this.xmlRoot) {
                return this.load(null, null, null,
                    !this.dataParent || !this.dataParent.autoselect);
            }
        }
        else if (htmlNode) {
            this.$updateNode(xmlNode, htmlNode);

            //Transaction 'niceties'
            if (action == "replacechild" && this.hasFeature(__MULTISELECT__)
              && this.selected && xmlNode.getAttribute(jpf.xmldb.xmlIdTag)
              == this.selected.getAttribute(jpf.xmldb.xmlIdTag)) {
                this.selected = xmlNode;
            }

            //if(action == "synchronize" && this.autoselect) this.reselect();
        }
        else if (action == "redo-remove") { //Check Remove of the data (some ancestor) that this component is bound on
            var testNode = this.xmlRoot;
            while (testNode && testNode.nodeType != 9)
                testNode = testNode.parentNode;

            if (!testNode) {
                //Set Component in listening state until data becomes available again.
                var model = this.getModel(true);

                if (!model)
                    throw new Error(jpf.formatErrorString(0, this,
                        "Setting change notifier on component",
                        "Component without a model is listening for changes",
                        this.$jml));

                return model.loadInJmlNode(this, model.getXpathByJmlNode(this));
            }
        }

        //For tree based nodes, update all the nodes up
        var pNode = xmlNode ? xmlNode.parentNode : lastParent;
        if (this.isTreeArch && pNode && pNode.nodeType == 1) {
            do {
                var htmlNode = this.getNodeFromCache(pNode.getAttribute(
                    jpf.xmldb.xmlIdTag) + "|" + this.uniqueId);

                if (htmlNode)
                    this.$updateNode(pNode, htmlNode);
            }
            while ((pNode = this.getTraverseParent(pNode)) && pNode.nodeType == 1);
        }

        //Make sure the selection doesn't become corrupted
        if (actionFeature[action] & 32 && this.selectable
          && startNode == xmlNode
          && (action != "insert" || xmlNode == this.xmlRoot)) {

            clearTimeout(selectTimer.timer);
            // Determine next selection
            if (action == "remove" && xmlNode == this.selected
              || xmlNode == selectTimer.nextNode)
                selectTimer.nextNode = this.getDefaultNext(xmlNode);

            //@todo Fix this by putting it after xmlUpdate when its using a timer
            selectTimer.timer = setTimeout(function(){
                _self.$checkSelection(selectTimer.nextNode);
                selectTimer.nextNode = null;
            });
        }

        //Set dynamic properties that relate to the changed content
        if (actionFeature[action] & 64) {
            if (!length)
                length = this.xmlRoot.selectNodes(this.traverse).length;
            if (length != this.length)
                this.setProperty("length", length);
        }

        //Let's signal components that are waiting for xml to appear (@todo what about clearing the signalXmlUpdate)
        if (this.signalXmlUpdate && actionFeature[action] & 16) {
            var uniqueId;
            for (uniqueId in this.signalXmlUpdate) {
                if (parseInt(uniqueId) != uniqueId) continue; //safari_old stuff

                var o = jpf.lookup(uniqueId);
                if (!this.selected) continue;

                var xmlNode = this.selected.selectSingleNode(o.dataParent.xpath);
                if (!xmlNode) continue;

                o.load(xmlNode);
            }
        }

        this.dispatchEvent("xmlupdate", {
            action : action,
            xmlNode: xmlNode,
            result : result,
            UndoObj: UndoObj
        });
    };

    /**
     * Loop through NodeList of selected Traverse Nodes
     * and check if it has representation. If it doesn't
     * representation is created via $add().
     */
    this.$addNodes = function(xmlNode, parent, checkChildren, isChild, insertBefore){
        if (!this.traverse) {
            throw new Error(jpf.formatErrorString(1060, this,
                "adding Nodes for load",
                "No traverse SmartBinding rule was specified. This rule is \
                 required for a " + this.tagName + " component.", this.$jml));
        }

        var htmlNode, lastNode;
        isChild          = (isChild && (this.renderRoot && xmlNode == this.xmlRoot
            || this.isTraverseNode(xmlNode)));
        var nodes        = isChild ? [xmlNode] : this.getTraverseNodes(xmlNode);//.selectNodes(this.traverse);
        var loadChildren = nodes.length && (this.bindingRules || {})["insert"]
            ? this.applyRuleSetOnNode("insert", xmlNode)
            : false;

        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 1) continue;

            if (checkChildren)
                htmlNode = this.getNodeFromCache(nodes[i].getAttribute(jpf.xmldb.xmlIdTag)
                    + "|" + this.uniqueId);

            if (!htmlNode) {
                //Retrieve DataBind ID
                var Lid = jpf.xmldb.nodeConnect(this.documentId, nodes[i], null, this);

                //Add Children
                var beforeNode = isChild ? insertBefore : (lastNode ? lastNode.nextSibling : null);//(parent || this.oInt).firstChild);
                var parentNode = this.$add(nodes[i], Lid, isChild ? xmlNode.parentNode : xmlNode,
                     beforeNode ? parent || this.oInt : parent, beforeNode,
                     !beforeNode && i==nodes.length-1);//Should use getTraverParent

                //Exit if component tells us its done with rendering
                if (parentNode === false) {
                    //Tag all needed xmlNodes for future reference
                    for (var i = i; i < nodes.length; i++)
                        jpf.xmldb.nodeConnect(this.documentId, nodes[i],
                            null, this);
                    break;
                }

                //Parse Children Recursively -> optimize: don't check children that can't exist
                //if(this.isTreeArch) this.$addNodes(nodes[i], parentNode, checkChildren);
            }

            if (checkChildren)
                lastNode = htmlNode;// ? htmlNode.parentNode.parentNode : null;
        }

        return nodes;
    };

    //Trigger Databinding Connections
    this.addEventListener("beforeselect", function(e){
        var combinedvalue = null;

        if (this.indicator == this.selected || e.list && e.list.length > 1
          && this.getConnections().length) {
            //Multiselect databinding handling... [experimental]
            if (e.list && e.list.length > 1 && this.getConnections().length) {
                var oEl  = this.xmlRoot.ownerDocument.createElement(this.selected.tagName);
                var attr = {};

                //Fill basic nodes
                var nodes = e.list[0].attributes;
                for (var j = 0; j < nodes.length; j++)
                    attr[nodes[j].nodeName] = nodes[j].nodeValue;

                //Remove nodes
                for (var i = 1; i < e.list.length; i++) {
                    for (prop in attr) {
                        if (typeof attr[prop] != "string") continue;

                        if (!e.list[i].getAttributeNode(prop))
                            attr[prop] = undefined;
                        else if(e.list[i].getAttribute(prop) != attr[prop])
                            attr[prop] = "";
                    }
                }

                //Set attributes
                for (prop in attr) {
                    if (typeof attr[prop] != "string") continue;
                    oEl.setAttribute(prop, attr[prop]);
                }

                //missing is childnodes... will implement later when needed...

                oEl.setAttribute(jpf.xmldb.xmlIdTag, this.uniqueId);
                jpf.MultiSelectServer.register(oEl.getAttribute(jpf.xmldb.xmlIdTag),
                    oEl, e.list, this);
                jpf.xmldb.addNodeListener(oEl, jpf.MultiSelectServer);

                combinedvalue = oEl;
            }
        }

        this.$chained = true;
        if (!this.dataParent || !this.dataParent.parent.$chained) {
            var _self = this;
            setTimeout(function(){
                _self.setConnections(combinedvalue || _self.selected);
                delete _self.$chained;
            }, 10);
        }
        else {
            this.setConnections(combinedvalue || this.selected);
            delete this.$chained;
        }
    });

    this.addEventListener("afterdeselect", function(){
        var _self = this;
        setTimeout(function(){
            _self.setConnections(null);
        }, 10);
    });

    /**
     * @allowchild  item, choices
     * @define item         xml element which is rendered by this element.
     * @attribute {String} value    the value that the element gets when this element is selected.
     * @attribute {String} icon     the url to the icon used in the representation of this node.
     * @attribute {String} image    the url to the image used in the representation of this node.
     * @allowchild  [cdata], label
     * @define  choices     container for item nodes which receive presentation. This element is part of the XForms specification. It is not necesary for the javeline markup language.
     * @allowchild  item
     */
    this.$loadInlineData = function(x){
        if (!$xmlns(x, "item", jpf.ns.jml).length)
            return jpf.JmlParser.parseChildren(x, null, this);

        var parent = x;

        if (x.getAttribute("model")) {
            throw new Error(jpf.formatErrorString(0, this, 
                "Loading inline data",
                "Found model attribute set. This will conflict with loading\
                the inline data. Please remove it. If you would like to set\
                the model to receive the selection value, please set the \
                select-model attribute.", x));
        }

        var prefix = jpf.findPrefix(x, jpf.ns.jml);

        x.ownerDocument.setProperty("SelectionNamespaces", "xmlns:"
            + prefix + "='" + jpf.ns.jpf + "'");
    
        //@todo think about using setProperty for this, for consistency (at the price of speed)
        this.icon      = "@icon";
        this.image     = "@image";
        this.caption   = "label/text()|text()|@caption";//|@value
        this.valuerule = "value/text()|@value|text()";
        this.traverse  = prefix + ":item";

        this.load(x);
    };

    var timer;
    this.$handleBindingRule = function(value, f, prop){
        if (!value)
            this[prop] = null;

        if (this.xmlRoot && !timer && !jpf.isParsing) {
            timer = setTimeout(function(){
                _self.reload();
                timer = null;
            });
        }
    };

    /**
     * @attribute {String} traverse the xpath statement that determines which
     * xml data elements are rendered by this element. See
     * {@link baseclass.multiselectbinding.binding.traverse} for more information.
     * Example:
     * <code>
     *  <j:label>Country</j:label>
     *  <j:dropdown
     *      model        = "mdlCountries"
     *      traverse     = "country"
     *      value        = "@value"
     *      caption      = "text()">
     *  </j:dropdown>
     *
     *  <j:model id="mdlCountries">
     *      <countries>
     *          <country value="USA">USA</country>
     *          <country value="GB">Great Brittain</country>
     *          <country value="NL">The Netherlands</country>
     *          ...
     *      </countries>
     *  </j:model>
     * </code>
     * @see  baseclass.multiselectbinding.binding.traverse
     */
    this.$propHandlers["traverse"] =

    /**
     * @attribute {String} caption the xpath statement that determines from
     * which xml node the caption is retrieved.
     * Example:
     * <code>
     *  <j:list caption="text()" traverse="item" />
     * </code>
     * @see  binding.caption
     */
    this.$propHandlers["caption"]  =
    
    /**
     * @attribute {String} valuerule the xpath statement that determines from
     * which xml node the value is retrieved.
     * Example:
     * <code>
     *  <j:list valuerule="@value" traverse="item" />
     * </code>
     * @see  binding.value
     */
    this.$propHandlers["valuerule"]  =

    /**
     * @attribute {String} icon the xpath statement that determines from
     * which xml node the icon url is retrieved.
     * Example:
     * <code>
     *  <j:list icon="@icon" traverse="item" />
     * </code>
     * @see  binding.icon
     */
    this.$propHandlers["icon"]     =

    /**
     * @attribute {String} tooltip the xpath statement that determines from
     * which xml node the tooltip text is retrieved.
     * Example:
     * <code>
     *  <j:list tooltip="text()" traverse="item" />
     * </code>
     * @see  binding.tooltip
     */
    this.$propHandlers["tooltip"]  =

    /**
     * @attribute {String} select the xpath statement that determines whether
     * this node is selectable.
     * Example:
     * <code>
     *  <j:list select="self::node()[not(@disabled='1')]" traverse="item" />
     * </code>
     * @see  binding.select
     */
    this.$propHandlers["select"]   = this.$handleBindingRule;
};



/*FILEHEAD(/var/lib/jpf/src/core/node/virtualviewport.js)SIZE(-1077090856)TIME(1238933673)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

var __VIRTUALVIEWPORT__ = 1 << 19;


/**
 * Baseclass adding Virtual Viewport features to this Element.
 *
 * @experimental This code has never been run. 
 * @constructor
 * @baseclass
 * @private
 *
 * @author      Ruben Daniels & Mike de Boer
 * @version     %I%, %G%
 * @since       1.0
 */
jpf.VirtualViewport = function(){
    this.$regbase = this.$regbase | __VIRTUALVIEWPORT__;
    
    jpf.setStyleClass(this.oExt, "virtual");
    
    this.$deInitNode = function(xmlNode, htmlNode){
        /*  
            Not the htmlNode is deleted, but the viewport is rerendered from this node on. 
            If viewport is too high either the render starting point is adjusted and
            a complete rerender is requested, or the last empty elements are hidden
        */
        this.viewport.redraw();//very unoptimized
    };
    
    this.$moveNode = function(xmlNode, htmlNode){
        /*
            Do a remove when removed from current viewport
            Do an add when moved to current viewport
            Do a redraw from the first of either when both in viewport
        */
        this.viewport.redraw();//very unoptimized
    };
    
    this.emptyNode = jpf.xmldb.getXml("<empty />");
    this.$addEmpty = this.$add;
    this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode){
        //find new slot
        var htmlNode = this.$findNode(null, Lid);
        
        if(!htmlNode)
            return;
        
        //execute update
        this.$updateNode(xmlNode, htmlNode);//, noModifier);
    };

    this.$fill = function(){
        
    };
    
    this.clear = function(nomsg, do_event){
        if (this.clearSelection)
            this.clearSelection(null, !do_event);

        if (!nomsg)
            this.$setClearMessage(this.emptyMsg);
        else if(this.$removeClearMessage)
            this.$removeClearMessage();
        
        this.documentId = this.xmlRoot = this.cacheID = null;
        
        this.viewport.cache = [];
        this.viewport.prepare();
        this.viewport.cache = null;
    };

    var _self = this;
    this.viewport = {
        offset : 0,
        limit  : 15,
        length : 0,
        sb     : new jpf.scrollbar(this.pHtmlNode),
        cache  : null,
        
        inited : false,
        draw : function(){
            this.inited = true;
            var limit = this.limit; this.limit = 0;
            this.resize(limit, true);
        },
        
        redraw : function(){
            this.change(this.offset);
        },
        
        // set id's of xml to the viewport
        prepare : function(){
            if (!this.inited)
                this.draw();
            
            var nodes = _self.getTraverseNodes();
            if (!nodes)
                return;
            
            var docId  = jpf.xmldb.getXmlDocId(_self.xmlRoot);
            var hNodes = _self.oInt.childNodes;
            for (var j = 0, i = 0; i < hNodes.length; i++) {
                if (hNodes[i].nodeType != 1) continue;
                
                hNodes[i].style.display = j >= nodes.length ? "none" : "block"; //Will ruin tables & lists
                
                jpf.xmldb.nodeConnect(docId, nodes[j], hNodes[i], _self);
                j++;
            }
        },
        
        /**
         * @note This function only supports single dimension items (also no grid, like thumbnails)
         */
        resize : function(limit, updateScrollbar){
            this.cache = null;

            //Viewport shrinks
            if (limit < this.limit) {
                var nodes = _self.oInt.childNodes;
                for (var i = 0; i < nodes.length; i++) {
                    if (nodes[i].nodeType != 1) continue;
                    _self.oInt.removeChild(nodes[i]);
                    if(--this.limit == limit) break;
                }
            }
            //Viewport grows
            else if (limit > this.limit) {
                for (var i = this.limit; i < limit; i++) {
                    _self.$addEmpty(_self.emptyNode, "", _self.xmlRoot, _self.oInt);
                }
            }
            else return;
            
            this.limit = limit;
            
            if (updateScrollbar)
                this.sb.update();
        },
        
        
        /**
         *  @todo   This method should be optimized by checking if there is
         *          overlap between the new offset and the old one
        */
        change : function(offset, limit, updateScrollbar){
            this.cache  = null;
            var diff = offset - this.offset;
            var oldLimit = this.limit;
            if (diff*diff >= this.limit*this.limit) //there is no overlap
                diff = 0;
            this.offset = offset;
            
            if (diff > 0) { //get last node before resize
                var lastNode = _self.oInt.lastChild;
                if (lastNode.nodeType != 1) lastNode = lastNode.previousSibling;
            }
            
            if (limit && this.limit != limit)
                this.resize(limit, updateScrollbar);
            else if (updateScrollbar)
                this.sb.update();
            
            //this.viewport.prepare();
            
            //Traverse through XMLTree
            //var nodes = this.$addNodes(this.xmlRoot, this.oInt, null, this.renderRoot);
            var nodes = _self.getTraverseNodes();
            if (!nodes)
                return;
            
            var docId  = jpf.xmldb.getXmlDocId(_self.xmlRoot);
            var hNodes = _self.oInt.childNodes;

            //remove nodes from the beginning
            if (diff > 0) {
                var xmlNode, htmlNode, xmlPos = oldLimit - diff, len = hNodes.length;
                for (var j = 0, i = 0; j < diff && i < len; i++) {
                    htmlNode = _self.oInt.firstChild;
                    if (htmlNode.nodeType == 1) {
                        j++;
                        xmlNode = nodes[xmlPos++];
                        //htmlNode.style.display = j >= nodes.length ? "none" : "block"
                        jpf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self);
                        _self.$updateNode(xmlNode, htmlNode);//, noModifier);
                    }
                    
                    _self.oInt.appendChild(htmlNode);
                }
                
                //var lastNode = nodes[oldLimit - diff - 1]
            }
            //remove nodes from the end
            else if (diff < 0) {
                diff = diff * -1;
                var xmlNode, htmlNode, xmlPos = 0; //should be adjusted for changing limit
                for (var j = 0, i = hNodes.length-1; j < diff && i >= 0; i++) {
                    htmlNode = _self.oInt.lastChild;
                    if (htmlNode.nodeType == 1) {
                        j++;
                        xmlNode = nodes[xmlPos++];
                        //htmlNode.style.display = j >= nodes.length ? "none" : "block"
                        jpf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self);
                        _self.$updateNode(xmlNode, htmlNode);//, noModifier);
                    }
                    
                    _self.oInt.insertBefore(htmlNode, _self.oInt.firstChild);
                }
            }
            //Recalc all nodes
            else {
                var xmlNode, htmlNode, len = hNodes.length; 
                for (var j = 0, i = 0; i < len; i++) {
                    htmlNode = hNodes[i];
                    if (htmlNode.nodeType == 1) {
                        xmlNode = nodes[j++];
                        jpf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self);
                        _self.$updateNode(xmlNode, htmlNode);//, noModifier);
                    }
                }
            }
        
            //Build HTML
            //_self.$fill(nodes);
            
            /*if (_self.$selected) {
                _self.$deselect(_self.$selected);
                _self.$selected = null;
            }
            
            if (_self.selected && _self.$isInViewport(_self.selected))
                _self.select(_self.selected);*/
        }
    };
    
    var timer;
    this.viewport.sb.realtime = false;//!jpf.isIE;
    this.viewport.sb.attach(this.oInt, this.viewport, function(timed, pos){
        var vp = _self.viewport;
        
        if (vp.sb.realtime || !timed)
            vp.change(Math.round((vp.length - vp.limit) * pos), vp.limit, false);
        else {
            clearTimeout(timer);
            timer = setTimeout(function(){
                vp.change(Math.round((vp.length - vp.limit) * pos), vp.limit, false);
            }, 300);
        }
    })
    
    this.$isInViewport = function(xmlNode, struct){
        var marker = xmlNode.selectSingleNode("preceding-sibling::j_marker");
        var start = marker ? marker.getAttribute("end") : 0;
        
        if(!struct && this.viewport.offset + this.viewport.limit < start + 1)
            return false;
        
        var position = start;
        var nodes = (marker || xmlNode).selectNodes("following-sibling::"
              + this.traverse.split("|").join("following-sibling::"));
        
        for (var i = 0; i < nodes.length; i++) {
            ++position;
            if (nodes[i] == xmlNode)
                break;
        }
        
        if(struct) struct.position = position;
        
        if(this.viewport.offset > position 
          || this.viewport.offset + this.viewport.limit < position)
            return false;
        
        return true;
    };
    
    this.scrollTo = function(xmlNode, last){
        var sPos = {};
        this.$isInViewport(xmlNode, sPos);
        this.viewport.change(sPos.position + (last ? this.viewport.limit-1 : 0));
    };
    
    /**
     * @todo this one should be optimized
     */
    this.getFirstTraverseNode = function(xmlNode){
        return this.getTraverseNodes(xmlNode)[0];
    };
    
    var xmlUpdate = this.$xmlUpdate;
    this.$xmlUpdate = function(){
        this.viewport.cache = null;
        this.viewport.length = this.xmlRoot.selectNodes(this.traverse).length; //@todo fix this for virtual length
        this.viewport.sb.update();
        xmlUpdate.apply(this, arguments);
    };
    
    this.$load = function(XMLRoot){
        //Add listener to XMLRoot Node
        jpf.xmldb.addNodeListener(XMLRoot, this);

        //Reserve here a set of nodeConnect id's and add them to our initial marker
        //Init virtual dataset here
        
        if (!this.renderRoot && !this.getTraverseNodes(XMLRoot).length)
            return this.clearAllTraverse(this.loadingMsg);
        
        //Initialize virtual dataset if load rule exists
        if (this.bindingRules["load"])
            jpf.xmldb.createVirtualDataset(XMLRoot);
        
        //Prepare viewport
        this.viewport.cache  = null;
        this.viewport.length = this.xmlRoot.selectNodes(this.traverse).length; //@todo fix this for virtual length
        this.viewport.sb.update();
        this.viewport.prepare();
        
        //Traverse through XMLTree
        var nodes = this.$addNodes(XMLRoot, null, null, this.renderRoot);

        //Build HTML
        //this.$fill(nodes);

        //Select First Child
        if (this.selectable) {
            if (this.autoselect) {
                if (nodes.length)
                    this.$selectDefault(XMLRoot);
                else
                    this.setConnections();
            }
            else {
                this.clearSelection(null, true);
                var xmlNode = this.getFirstTraverseNode(); //should this be moved to the clearSelection function?
                if (xmlNode)
                    this.setIndicator(xmlNode);
                this.setConnections(null, "both");
            }
        }

        if (this.$focussable)
            jpf.window.hasFocus(this) ? this.$focus() : this.$blur();
    };
    
    this.$loadSubData = function(){}; //We use the same process for subloading, it shouldn't be done twice
    
    /**
     * @example <j:load get="call:getCategory(start, length, ascending)" total="@total" />
     */
    this.$loadPartialData = function(marker, start, length){
        //We should have a queing system here, disabled the check for now
        //if (this.hasLoadStatus(xmlRootNode)) return;
        
        var loadNode, rule = this.getNodeFromRule("load", xmlRootNode, false, true);
        var sel = (rule && rule.getAttribute("select"))
            ? rule.getAttribute("select")
            : ".";

        if (rule && (loadNode = xmlRootNode.selectSingleNode(sel))) {
            this.setLoadStatus(xmlRootNode, "loading");
            
            var mdl = this.getModel(true);
            if (!mdl)
                throw new Error("Could not find model");
            
            if (!rule.getAttribute("total")) {
                throw new Error(jpf.formatErrorString(this, "Loading data", "Error in load rule. Missing total xpath. Expecting <j:load total='xpath' />"))                
            }

            mdl.insertFrom(rule.getAttribute("get"), loadNode, {
                    documentId  : this.documentId, //or should xmldb find this itself
                    marker      : marker,
                    start       : start,
                    length      : length,
                    insertPoint : this.xmlRoot, 
                    jmlNode     : this
                    ,ascending  : this.$sort ? this.$sort.get().ascending : true
                }, 
                function(xmlNode){
                    _self.setConnections(_self.xmlRoot);
                    
                    var length = parseInt(jpf.getXmlValue(xmlNode, 
                        rule.getAttribute("total")));
                    
                    if (_self.viewport.length != length) {
                        _self.viewport.length = length;
                        
                        jpf.xmldb.createVirtualDataset(_self.xmlRoot, 
                            _self.viewport.length, _self.documentId);
                    }
                });
        }
    };
    
    //Consider moving these functions to the xmldatabase selectByXpath(xpath, from, length);
    function fillList(len, list, from){
        for (var i = 0; i < len; i++) 
            list.push(_self.documentId + "|" + (from+i));
    }
    
    function buildList(markers, markerId, distance, xml) {
        var vlen = this.viewport.limit;
        var marker, nodes, start, list = [];
        
        //Count from 0
        if (markerId == -1) {
            nodes    = xml.selectNodes(_self.traverse);
            start    = 0;
            marker   = markers[0];
            markerid = 0;
        }
        else {
            //Count back from end of marker
            if (distance < 0) {
                fillList(Math.abs(distance), list, 
                    parseInt(marker.getAttribute("reserved")) + parseInt(marker.getAttribute("end"))
                    - parseInt(marker.getAttribute("start")) + distance);
                
                distance = 0;
                _self.$loadPartialData(marker);
                
                if (list.length == vlen)
                    return list;
            }
            
            nodes  = markers[markerId].selectNodes("following-sibling::"
              + this.traverse.split("|").join("following-sibling::"));
            start  = markers[markerId].getAttribute("end");
            marker = markers[++markerId];
        }
        
        do {
            //Add found nodes
            var loop = Math.min(marker.getAttribute("start") - start, vlen);//, nodes.length
            for (var i = distance; i < loop; i++)
                list.push(nodes[i]);
            
            if (list.length == vlen)
                break;
            
            //Add empty nodes
            var mlen = parseInt(marker.getAttribute("end")) - parseInt(marker.getAttribute("start"));
            fillList(Math.min(mlen, vlen - list.length), list, parseInt(marker.getAttribute("reserved")));
            
            //Add code here to trigger download of this missing info
            _self.$loadPartialData(marker);
            
            start    = parseInt(marker.getAttribute("end"));
            marker   = markers[++markerId];
            distance = 0;
        } 
        while (list.length < vlen && marker);
        
        _self.viewport.cache = list;
        return list;
    }
    
    this.getTraverseNodes = function(xmlNode){
        if (!this.xmlRoot)
            return;
        
        if (this.viewport.cache)
            return this.viewport.cache;

        var start = this.viewport.offset;
        var end   = start + this.viewport.limit + 1;
        
        //caching statement here

        var markers = (xmlNode || this.xmlRoot).selectNodes("j_marker");

        //Special case for fully loaded virtual dataset
        if (!markers.length) {
            var list = (xmlNode || this.xmlRoot).selectNodes("("
                + this.traverse + ")[position() >= " + start
                + " and position() < " + (end) + "]");

            return this.$sort ? this.$sort.apply(list) : list;
        }

        for (var i = 0; i < markers.length; i++) {
            //Looking for marker that (partially) exceeds viewport's current position
            if (markers[i].getAttribute("end") < start) {
                //If this is the last marker, count from here
                if (i == markers.length - 1)
                    return buildList(markers, i, start - markers[i].getAttribute("end"), 
                      (xmlNode || this.xmlRoot));

                continue;
            }
            
            //There is overlap AND begin is IN marker
            if (markers[i].getAttribute("start") - end <= 0 
              && start >= markers[i].getAttribute("start"))
                return buildList(markers, i, start - markers[i].getAttribute("end"), 
                  (xmlNode || this.xmlRoot));

            //Marker is after viewport, there is no overlap
            else if (markers[i-1]) //Lets check the previous marker, if there is one
                return buildList(markers, i-1, start - markers[i-1].getAttribute("end"), 
                  (xmlNode || this.xmlRoot));
                
            //We have to count from the beginning
            else
                return buildList(markers, -1, start, (xmlNode || this.xmlRoot));
        }
    };
    
    this.addEventListener("keydown", function(e){
        var key      = e.keyCode;
        var ctrlKey  = e.ctrlKey;
        var shiftKey = e.shiftKey;
        
        if (!this.indicator) return;
        
        function selScroll(xmlNode, down){
            if(!_self.$isInViewport(xmlNode))
                _self.scrollTo(xmlNode, down);
            
            if (ctrlKey)
                _self.setIndicator(xmlNode);
            else
                _self.select(xmlNode, null, shiftKey);
        }

        switch (key) {
            case 13:
                this.choose(this.$selected);
                break;
            case 32:
                this.select(this.indicator, true);
                break;
            case 46:
            //DELETE
                if(this.disableremove) return;
            
                this.remove(null, true);
                break;
            case 38:
            //UP
                var node = this.getNextTraverseSelected(this.indicator
                    || this.selected, false, items);

                if (node) selScroll(node);
                break;
            case 40:
            //DOWN
                var node = this.getNextTraverseSelected(this.indicator
                    || this.selected, true, items);
                    
                if (node) selScroll(node, true);
                break;
            case 33:
            //PGUP
                var node = this.getNextTraverseSelected(this.indicator 
                    || this.selected, false, this.viewport.limit);
                
                if (!node)
                    node = this.getFirstTraverseNode();
                
                if (node) selScroll(node);
                break;
            case 34:
            //PGDN
                var node = this.getNextTraverseSelected(this.indicator
                    || this.selected, true, this.nodeCount-1);

                if (!node)
                    node = this.getLastTraverseNode();
                
                if (node) selScroll(node, true);
                break;
            case 36:
                //HOME
                var node = this.getFirstTraverseNode();
                if (node) selScroll(node);
                break;
            case 35:
                //END
                var node = this.getLastTraverseNode();
                if (node) selScroll(node);
                break;
            default:
                /*if (key == 65 && ctrlKey) {
                    this.selectAll();
                }
                else if(this.bindingRules["caption"]){
                    //this should move to a onkeypress based function
                    if(!this.lookup || new Date().getTime()
                      - this.lookup.date.getTime() > 300)
                        this.lookup = {
                            str  : "",
                            date : new Date()
                        };
                    
                    this.lookup.str += String.fromCharCode(key);
    
                    var nodes = this.getTraverseNodes();
                    for (var i = 0; i < nodes.length; i++) {
                        if(this.applyRuleSetOnNode("caption", nodes[i])
                          .substr(0, this.lookup.str.length).toUpperCase()
                          == this.lookup.str) {
                            this.scrollTo(nodes[i], true);
                            this.select(nodes[i]);
                            return;
                        }
                    }
                    
                    return;
                }*/
                break;
        };
        
        //this.lookup = null;
        return false;
    }, true);
    
    
    //Init
    this.caching = false; //for now, because the implications are unknown
};


/*FILEHEAD(/var/lib/jpf/src/core/node/presentation.js)SIZE(-1077090856)TIME(1238944816)*/

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

var __PRESENTATION__ = 1 << 9;


/**
 * @private
 * @define skin
 * @allowchild  style, presentation
 * @attribute src
 */
jpf.skins = {
    skins  : {},
    css    : [],
    events : ["onmousemove", "onmousedown", "onmouseup", "onmouseout",
        "onclick", "ondragmove", "ondragstart"],

    /* ***********
     Init
     ************/
    Init: function(xmlNode, refNode, path){
        /*
         get data from refNode || xmlNode
         - name
         - icon-path
         - media-path

         all paths of the xmlNode are relative to the src attribute of refNode
         all paths of the refNode are relative to the index.html
         images/ is replaced if there is a refNode to the relative path from index to the skin + /images/
         */
        var name      = (refNode ? refNode.getAttribute("id") : null)
            || xmlNode.getAttribute("id");
        var base      = (refNode ? refNode.getAttribute("src").match(/\//) || path : "")
            ? (path || refNode.getAttribute("src")).replace(/\/[^\/]*$/, "") + "/"
            : "";
        var mediaPath = (xmlNode.getAttribute("media-path")
            ? jpf.getAbsolutePath(base || jpf.hostPath, xmlNode.getAttribute("media-path"))
            : (refNode ? refNode.getAttribute("media-path") : null));
        var iconPath  = (xmlNode.getAttribute("icon-path")
            ? jpf.getAbsolutePath(base || jpf.hostPath, xmlNode.getAttribute("icon-path"))
            : (refNode ? refNode.getAttribute("icon-path") : null));
        if (!name)
            name = "default";

        if (xmlNode.getAttribute("id"))
            document.body.className += " " + xmlNode.getAttribute("id");

        if (!this.skins[name] || name == "default") {
            this.skins[name] = {
                base     : base,
                name     : name,
                iconPath : (iconPath === null)  ? "icons/" : iconPath,
                mediaPath: (mediaPath === null) ? "images/" : mediaPath,
                templates: {},
                originals: {},
                xml      : xmlNode
            }
        }
        if (!this.skins["default"])
            this.skins["default"] = this.skins[name];

        var nodes = xmlNode.childNodes;
        for (var i = nodes.length - 1; i >= 0; i--) {
            if (nodes[i].nodeType != 1)
                continue;

            //this.templates[nodes[i].tagName] = nodes[i];
            this.skins[name].templates[nodes[i].getAttribute("name")] = nodes[i];
            if (nodes[i].ownerDocument)
                this.importSkinDef(nodes[i], base, name);
        }

        this.purgeCss(mediaPath || base + "images/", iconPath || base + "icons/");
    },

    /**
     * This method loads a stylesheet from a url
     * @param {String}    filename Required The url to load the stylesheet from
     * @param {String}    title Optional Title of the stylesheet to load
     * @method
     */
    loadStylesheet: function(filename, title){
        with (o = document.getElementsByTagName("head")[0].appendChild(document.createElement("LINK"))) {
            rel   = "stylesheet";
            type  = "text/css";
            href  = filename;
            title = title;
        }

        return o;
    },

    /* ***********
     Import
     ************/
    importSkinDef: function(xmlNode, basepath, name){
        var i, l, nodes = $xmlns(xmlNode, "style", jpf.ns.jml), tnode, node;
        for (i = 0, l = nodes.length; i < l; i++) {
            node = nodes[i];

            if (node.getAttribute("src"))
                this.loadStylesheet(node.getAttribute("src").replace(/src/, basepath + "/src"));
            else {
                var test = true;
                if (node.getAttribute("condition")) {
                    try {
                        test = eval(node.getAttribute("condition"));
                    }
                    catch (e) {
                        test = false;
                    }
                }

                if (test) {
                    //#-ifndef __PROCESSED
                    tnode = node.firstChild;
                    while (tnode) {
                        this.css.push(tnode.nodeValue);
                        tnode = tnode.nextSibling;
                    }
                    /*#-else
                    this.css.push(nodes[i].firstChild.nodeValue);
                    #-endif*/
                }
            }
        }

        nodes = $xmlns(xmlNode, "alias", jpf.ns.jpf);
        for (i = 0; i < nodes.length; i++) {
            if (!nodes[i].firstChild)
                continue;
            this.skins[name].templates[nodes[i].firstChild.nodeValue.toLowerCase()] = xmlNode;
        }
    },

    loadedCss : "",
    purgeCss: function(imagepath, iconpath){
        if (!this.css.length)
            return;

        var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath);
        jpf.importCssString(document, cssString);

        this.loadedCss += cssString;

        this.css = [];
    },

    loadCssInWindow : function(skinName, win, imagepath, iconpath){
        this.css = [];
        var name = skinName.split(":");
        var skin = this.skins[name[0]];
        var template = skin.templates[name[1]];
        this.importSkinDef(template, skin.base, skin.name);
        var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath);
        jpf.importCssString(win.document, cssString);

        this.css = [];
    },

    /* ***********
     Retrieve
     ************/
    setSkinPaths: function(skinName, jmlNode){
        skinName = skinName.split(":");
        var name = skinName[0];
        var type = skinName[1];

        if (!this.skins[name]) {
            throw new Error(jpf.formatErrorString(1076, null,
                "Retrieving Skin",
                "Could not find skin '" + name + "'", jmlNode.$jml));
        }

        jmlNode.iconPath  = this.skins[name].iconPath;
        jmlNode.mediaPath = this.skins[name].mediaPath;
    },

    getTemplate: function(skinName, cJml){
        skinName = skinName.split(":");
        var name = skinName[0];
        var type = skinName[1];

        if (!this.skins[name]) {
            throw new Error(jpf.formatErrorString(1077, null,
                "Retrieving Template",
                "Could not find skin '" + name + "'", cJml));
        }

        if (!this.skins[name].templates[type])
            return false;

        var skin      = this.skins[name].templates[type];
        var originals = this.skins[name].originals[type];
        if (!originals) {
            originals = this.skins[name].originals[type] = {};

            if (!$xmlns(skin, "presentation", jpf.ns.jml)[0]) {
                throw new Error(jpf.formatErrorString(1078, null,
                    "Retrieving Template",
                    "Missing presentation tag in '" + name + "'", cJml));
            }

            var nodes = $xmlns(skin, "presentation", jpf.ns.jml)[0].childNodes;
            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i].nodeType != 1) continue;
                originals[nodes[i].baseName || nodes[i][jpf.TAGNAME]] = nodes[i];
            }
        }

        /*for (var item in originals) {
            pNodes[item] = originals[item];
        }*/

        return originals;
    },

    getCssString : function(skinName){
        return jpf.getXmlValue($xmlns(this.skins[skinName.split(":")[0]].xml,
            "style", jpf.ns.jml)[0], "text()");
    },

    changeSkinset : function(value){
        var node = jpf.document.documentElement;
        while (node) {
            if (node && node.nodeFunc == jpf.NODE_VISIBLE
              && node.hasFeature(__PRESENTATION__) && !node.skinset) {
                node.$propHandlers["skinset"].call(node, value);//$forceSkinChange
                node.skinset = null;
            }

            //Walk tree
            if (node.firstChild || node.nextSibling) {
                node = node.firstChild || node.nextSibling;
            }
            else {
                do {
                    node = node.parentNode;
                } while (node && !node.nextSibling)

                if (node)
                    node = node.nextSibling;
            }
        }
    },

    iconMaps : {},
    addIconMap : function(options){
        this.iconMaps[options.name] = options;
        if (options.size)
            options.width = options.height = options.size;
        else {
            if (!options.width)
                options.width = 1;
            if (!options.height)
                options.height = 1;
        }
    },

    setIcon : function(oHtml, strQuery, iconPath){
        if (!strQuery) {
            oHtml.style.backgroundImage = "";
            return;
        }

        if (oHtml.tagName.toLowerCase() == "img") {
            oHtml.setAttribute("src", strQuery
                ? (iconPath || "") + strQuery
                : "");
            return;
        }

        var parts = strQuery.split(":");
        var map = this.iconMaps[parts[0]];

        if (map) {
            var left, top, coords = parts[1].split(",");
            if (map.type == "vertical") {
                left = (coords[1] || 0) * map.width;
                top  = (coords[0] || 0) * map.height;
            }
            else {
                left = (coords[0] || 0) * map.width;
                top  = (coords[1] || 0) * map.height;
            }

            oHtml.style.backgroundImage = "url(" + (iconPath || "")
                + map.src + ")";
            oHtml.style.backgroundPosition = ((-1 * left) - map.offset[0])
                + "px " + ((-1 * top) - map.offset[1]) + "px";
        }
        else

        //Assuming image url
        {
            //@todo check here if it is really a url

            oHtml.style.backgroundImage = "url(" + (iconPath || "")
                + strQuery + ")";
        }
    }
};

/**
 * Baseclass adding skinning features to this element. A skin is a description
 * of how the element is rendered. In the web browser this is done using html
 * elements and css.
 * Remarks:
 * The skin is set using the skin attribute. The skin of each element can be
 * changed at run time. Other than just changing the look of an element, a skin
 * change can help the user to perceive information in a different way. For 
 * example a list element has a default skin, but can also use the thumbnail 
 * skin to display thumbnails of the data elements.
 *
 * A skin for an element is always build up out of a standard set of parts.
 * <code>
 *   <j:textbox name="textbox">
 *      <j:alias>
 *          ...
 *      </j:alias>
 *      <j:style><![CDATA[
 *          ...
 *      ]]></j:style>
 *  
 *      <j:presentation>
 *          <j:main>
 *              ...
 *          </j:main>
 *          ...
 *      </j:presentation>
 *   </j:textbox>
 * </code>
 * The alias contains a name that contains alternative names for the skin. The
 * style tags contain the css. The main tag contains the html elements that are
 * created when the component is created. Any other skin items are used to render
 * other elements of the widget. In this reference guide you will find these
 * skin items described on the pages of each widget.
 *
 * @constructor
 * @baseclass
 * @author      Ruben Daniels
 * @version     %I%, %G%
 * @since       0.5
 */
jpf.Presentation = function(){
    var pNodes, originalNodes;

    this.$regbase = this.$regbase | __PRESENTATION__;
    this.skinName = null;
    var _self     = this;
    var skinTimer;

    /**** Properties and Attributes ****/

    this.$supportedProperties.push("skin");
    /**
     * @attribute {string} skinset Specifies the skinset where the skin for
     * this element is found. If none is specified the skinset attribute
     * of <j:appsettings /> is used. When not defined the default skinset
     * is accessed.
     * Example:
     * <code>
     *  <j:list skinset="perspex" />
     * </code>
     */
    this.$propHandlers["skinset"] =

    /**
     * @attribute {string} skin Specifies the skin that defines the rendering
     * of this element. When a skin is changed the full state of the
     * element is kept including it's selection, all the
     * jml attributes, loaded data, focus and disabled state.
     * Example:
     * <code>
     *  <j:list id="lstExample" skin="thumbnails" />
     * </code>
     * Example:
     * <code>
     *  lstExample.setAttribute("skin", "list");
     * </code>
     */
    this.$propHandlers["skin"] = function(value){
        if (!this.$jmlLoaded) //If we didn't load a skin yet, this will be done when we attach to a parent
            return;

        if (!skinTimer) {
            clearTimeout(skinTimer);
            skinTimer = setTimeout(function(){
                changeSkin.call(_self);
                skinTimer = null;
            });
        }
    }

    /**
     * @attribute {String} style css style properties applied to the this element.
     */
    this.$propHandlers["style"] = function(value){
        if (!jpf.isParsing)
            this.oExt.setAttribute("style", value);
    }

    var oldClass;
    /**
     * @attribute {String} class css style class applied to the this element.
     */
    this.$propHandlers["class"] = function(value){
        this.$setStyleClass(this.oExt, value, [oldClass || ""])
    }

    this.$forceSkinChange = function(skin, skinset){
        changeSkin.call(this, skin, skinset);
    }

    function changeSkin(skin, skinset){
        clearTimeout(skinTimer);

        var skinName = (skinset || this.skinset || jpf.appsettings.skinset)
            + ":" + (skin || this.skin || this.tagName);

        //Store selection
        if (this.selectable)
            var valueList = this.getSelection();//valueList;

        //Store needed state information
        var oExt = this.oExt;
        var beforeNode = oExt.nextSibling;
        var id         = this.oExt.getAttribute("id");
        var oldBase    = this.baseCSSname;

        if (oExt.parentNode)
            oExt.parentNode.removeChild(oExt);

        //@todo changing skin will leak A LOT, should call $destroy here, with some extra magic
        if (this.$destroy)
            this.$destroy(true);

        //Load the new skin
        this.$loadSkin(skinName);

        //Draw
        this.$draw();

        if (id)
            this.oExt.setAttribute("id", id);

        if (beforeNode)
            this.oExt.parentNode.insertBefore(this.oExt, beforeNode);

        //Copy classes
        var i, l, newclasses = [],
               classes    = (oExt.className || "").splitSafe("\s+");
        for (i = 0; i < classes; i++) {
            if (classes[i] && classes[i] != oldBase)
                newclasses.push(classes[i].replace(oldBase, this.baseCSSname));
        }
        jpf.setStyleClass(this.oExt, newclasses.join(" "));

        //Copy events
        var en, ev = jpf.skins.events;
        for (i = 0, l = ev.length; i < l; i++) {
            en = ev[i];
            if (typeof oExt[en] == "function" && !this.oExt[en])
                this.oExt[en] = oExt[en];
        }

        //Copy css state (dunno if this is best)
        this.oExt.style.left     = oExt.style.left;
        this.oExt.style.top      = oExt.style.top;
        this.oExt.style.width    = oExt.style.width;
        this.oExt.style.height   = oExt.style.height;
        this.oExt.style.right    = oExt.style.right;
        this.oExt.style.bottom   = oExt.style.bottom;
        this.oExt.style.zIndex   = oExt.style.zIndex;
        this.oExt.style.position = oExt.style.position;
        this.oExt.style.display  = oExt.style.display;

        //Widget specific
        if (this.$loadJml)
            this.$loadJml(this.$jml);

        //DragDrop
        if (this.hasFeature(__DRAGDROP__)) {
            if (document.elementFromPointAdd) {
                document.elementFromPointRemove(oExt);
                document.elementFromPointAdd(this.oExt);
            }
        }

        //Check disabled state
        if (this.disabled)
            this.$disable();

        //Check focussed state
        if (this.$focussable && jpf.window.focussed == this)
            this.$focus();

        //Reload data
        if (this.hasFeature(__DATABINDING__) && this.xmlRoot)
            this.reload();
        else
        if (this.value)
            this.$propHandlers["value"].call(this, this.value);

        //Set Selection
        if (this.hasFeature(__MULTISELECT__)) {
            if (this.selectable)
                this.selectList(valueList, true);
        }

        if (this.hasFeature(__ALIGNMENT__)) {
            if (this.aData)
                this.aData.oHtml = this.oExt;

            if (this.pData) {
                this.pData.oHtml = this.oExt;
                this.pData.pHtml = this.oInt;

                var nodes = this.pData.childNodes;
                for (i = 0; i < nodes.length; i++) {
                    nodes[i].pHtml = this.oInt; //Should this be recursive??
                }
            }
        }

        if (this.draggable)
            this.$propHandlers["draggable"].call(this, this.draggable);
        if (this.resizable)
            this.$propHandlers["resizable"].call(this, this.resizable);

        if (this.$skinchange)
            this.$skinchange();

        //Dispatch event
        //this.dispatchEvent("skinchange");
    };

    /**** Private methods ****/

    this.$setStyleClass = jpf.setStyleClass;

    /**
     * Initializes the skin for this element when none has been set up.
     *
     * @param  {String}  skinName  required  Identifier for the new skin (for example: 'default:List' or 'win').
     */
    this.$loadSkin = function(skinName){
        this.baseSkin = skinName || this.skinName || (this.skinset || this.$jml
            && this.$jml.getAttribute("skinset") || jpf.appsettings.skinset)
            + ":" + (this.skin || this.$jml
            && this.$jml.getAttribute("skin") || this.tagName);

        if (this.skinName) {
            this.$blur();
            this.baseCSSname = null;
        }

        this.skinName = this.baseSkin; //Why??

        pNodes = {}; //reset the pNodes collection
        originalNodes = jpf.skins.getTemplate(this.skinName, this.$jml);

        if (!originalNodes) {
            this.baseName = this.skinName = "default:" + this.tagName;
            originalNodes = jpf.skins.getTemplate(this.skinName, this.$jml);

            if (!originalNodes) {
                throw new Error(jpf.formatErrorString(1077, this,
                    "Presentation",
                    "Could not load skin: " + this.skinName, this.$jml));
            }
        }

        if (originalNodes)
            jpf.skins.setSkinPaths(this.skinName, this);
    };

    this.$getNewContext = function(type, jmlNode){
        if (type != type.toLowerCase()) {
            throw new Error("Invalid layout node name ('" + type + "'). lowercase required");
        }

        if (!originalNodes[type]) {
            throw new Error(jpf.formatErrorString(0, this,
                "Getting new skin item",
                "Missing node in skin description '" + type + "'"));
        }

        pNodes[type] = originalNodes[type].cloneNode(true);
    };

    this.$hasLayoutNode = function(type){
        if (type != type.toLowerCase()) {
            throw new Error("Invalid layout node name ('" + type + "'). lowercase required");
        }

        return originalNodes[type] ? true : false;
    };

    this.$getLayoutNode = function(type, section, htmlNode){
        if (type != type.toLowerCase()) {
            throw new Error("Invalid layout node name ('" + type + "'). lowercase required");
        }

        var node = pNodes[type] || originalNodes[type];
        if (!node) {
            if (!this.$dcache)
                this.$dcache = {}

            if (!this.$dcache[type + "." + this.skinName]) {
                this.$dcache[type + "." + this.skinName] = true;
                jpf.console.info("Could not find node '" + type
                                 + "' in '" + this.skinName + "'", "skin");
            }
            return false;
        }

        if (!section)
            return jpf.getFirstElement(node);

        var textNode = node.selectSingleNode("@" + section);
        if (!textNode) {
            if (!this.$dcache)
                this.$dcache = {}

            if (!this.$dcache[section + "." + this.skinName]) {
                this.$dcache[section + "." + this.skinName] = true;
                jpf.console.info("Could not find textnode '" + section
                                 + "' in '" + this.skinName + "'", "skin");
            }
            return null;
        }

        return (htmlNode
            ? jpf.xmldb.selectSingleNode(textNode.nodeValue, htmlNode)
            : jpf.getFirstElement(node).selectSingleNode(textNode.nodeValue));
    };

    this.$getOption = function(type, section){
        type = type.toLowerCase(); //HACK: lowercasing should be solved in the comps.

        //var node = pNodes[type];
        var node = pNodes[type] || originalNodes[type];
        if (!section)
            return node;//jpf.getFirstElement(node);
        var option = node.selectSingleNode("@" + section);

        return option ? option.nodeValue : "";
    };

    this.$getExternal = function(tag, pNode, func, jml){
        if (!pNode)
            pNode = this.pHtmlNode;
        if (!tag)
            tag = "main";
        if (!jml)
            jml = this.$jml;

        tag = tag.toLowerCase(); //HACK: make components case-insensitive

        this.$getNewContext(tag);
        var oExt = this.$getLayoutNode(tag);
        if (jml && jml.getAttributeNode("style"))
            oExt.setAttribute("style", jml.getAttribute("style"));

        if (jml && (jml.getAttributeNode("class") || jml.className)) {
            this.$setStyleClass(oExt,
                (oldClass = jml.getAttribute("class") || jml.className));
        }

        if (func)
            func.call(this, oExt);

        oExt = jpf.xmldb.htmlImport(oExt, pNode);
        oExt.host = this;
        if (jml && jml.getAttribute("bgimage"))
            oExt.style.backgroundImage = "url(" + jpf.getAbsolutePath(
                this.mediaPath, jml.getAttribute("bgimage")) + ")";


        if (!this.baseCSSname)
            this.baseCSSname = oExt.className.trim().split(" ")[0];

        return oExt;
    };

    /**** Focus ****/
    this.$focus = function(){
        if (!this.oExt)
            return;

        this.$setStyleClass(this.oFocus || this.oExt, this.baseCSSname + "Focus");
    };

    this.$blur = function(){
        if (this.renaming)
            this.stopRename(null, true);

        if (!this.oExt)
            return;

        this.$setStyleClass(this.oFocus || this.oExt, "", [this.baseCSSname + "Focus"]);
    };

    if (jpf.hasCssUpdateScrollbarBug) {
        this.$fixScrollBug = function(){
            if (this.oInt != this.oExt)
     
