/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Create kukit namespace */

if (typeof(kukit) == 'undefined') {
    var kukit = {};
}

    /*  ----------------------------------------------------------------
     *  Lines starting with the ;;; are only cooked in development mode.
     *  ----------------------------------------------------------------
     */

/*
 * kukit.E is a proxy variable used globally for error and info messages.
 * This assure the following code can be used:
 *    
 *     ;;; kukit.E = 'This is the error message';
 *     throw kukit.E;
 *
 * or:
 *
 *     ;;; kukit.E = 'The event' + event + ' caused problems';
 *     method_with_info(x, kukit.E);
 *
 * or even:
 *
 *     ;;; kukit.E = 'The event' + event + ' caused problems ';
 *     ;;; kukit.E += 'and this is a very long line ';
 *     ;;; kukit.E += 'so we split it to parts for better readibility';
 *     ;;; kukit.logWarning(kukit.E);
 *
 */
kukit.E = 'Unknown message (kss optimized for production mode)';

// Browser identification. We need these switches only at the moment.
try {
    kukit.HAVE_SAFARI = navigator.vendor && 
        navigator.vendor.indexOf('Apple') != -1;
    kukit.HAVE_IE =  eval("_SARISSA_IS_IE");
} catch (e) {}

    // Activation of extra logging panel: if necessary
    // this allows to start the logging panel from the browser with
    //    javascript:kukit.showLog();
    kukit.showLog = function() {
        var msg = 'Logging is on the console: request to show logging pane';
        msg += ' ignored.';
        kukit.logWarning(msg);
    };

/*
 * Cookie handling code taken from: 
 * http://www.quirksmode.org/js/cookies.html
 * Cookie handling is in dom.js, but this method
 * is needed right here for log handling.
 */

kukit.readCookie = function(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

// a null function that is used for logging
kukit._null = function() {};

kukit._COOKIE_LOGLEVEL = '__kss_loglevel';

// an empty logger
kukit._logger = new function() {

    this.updateLogLevel = function() {
        // set default level
        this.loglevel = 0;
        // read the cookie
        var cookie = kukit.readCookie(kukit._COOKIE_LOGLEVEL);
        if (cookie) {
            // decode it to a numeric level
            cookie = cookie.toLowerCase();
            // Cookies are quoted in Zope, for some reason (???)
            // ie we get '"VALUE"' here. Let's compensate this.
            if (cookie.substr(0, 1) == '"') {
                cookie = cookie.substr(1, cookie.length - 2);
            }
            if (cookie == 'debug') this.loglevel = 0;
            if (cookie == 'info') this.loglevel = 1;
            if (cookie == 'warning') this.loglevel = 2;
            if (cookie == 'error') this.loglevel = 3;
        };
        // Call the function that sets up the handlers
        this._setupHandlers();
        // Wrap the just set up handlers, to include wrapping
        this.logDebug = this._logFilter(this.logDebug, 0);
        this.log = this._logFilter(this.log, 1);
        this.logWarning = this._logFilter(this.logWarning, 2);
        this.logError = this._logFilter(this.logError, 3);
        this.logFatal = this._logFilter(this.logFatal, 3);
    };

    // Log filter, for use from the handlers.
    this._logFilter = function(f, currentlevel) {
        return (currentlevel >= this.loglevel) ? f : kukit._null;
    };

    // This sets up the handlers and allows to set them
    // up again with a different cookie setting.
    // Will be overwritten by different loggers.
    this._setupHandlers = function() {
        this.logDebug = kukit._null;
        this.log = kukit._null;
        this.logWarning = kukit._null;
        this.logError = kukit._null;
        this.logFatal = kukit._null;
    };
}();

// Stub functions that can be used for logging
kukit.logDebug = function(message) {kukit._logger.logDebug(message);};
kukit.log = function(message) {kukit._logger.log(message);};
kukit.logWarning = function(message) {kukit._logger.logWarning(message);};
kukit.logError = function(message) {kukit._logger.logError(message);};
kukit.logFatal = function(message) {kukit._logger.logFatal(message);};

// Function to change the log level from javascript
// level must be one of "DEBUG", "INFO", "WARNING", "ERROR".
// (Small caps are tolerated as well)
kukit.setLogLevel = function(level) {
        // Store it in the cookie so that it persists through requests.
        kukit.dom.createCookie(kukit._COOKIE_LOGLEVEL, level);
        // re-establish the log handlers, based on this cookie setting
        kukit._logger.updateLogLevel();
}

// We want a way of knowing if Firebug is available :
// it is very convenient to log a node in Firebug;
// you get a clickable result that brings you to Firebug inspector.
// The pattern is the following :
//  if (kukit.hasFirebug) {
//     kukit.log(node);
//  }

    // check whether the logging stuff of Firebug is available
    kukit.hasFirebug = function() {
       var result = typeof console != 'undefined';
       result = result && typeof console.log != 'undefined';
       result = result && typeof console.debug != 'undefined';
       result = result && typeof console.error != 'undefined';
       result = result && typeof console.warn != 'undefined';
       return result;
    }();

    // Set up logging for FireBug
    if (kukit.hasFirebug) {
        kukit._logger._setupHandlers = function() {
            // for debug level we also log as 'info', because we do
            // not want FireBug to display line information.
            this.logDebug = console.log;
            this.log = console.log;
            this.logWarning = console.warn;
            this.logError = console.error;
            this.logFatal = console.error;
        }
    }

    // check whether the logging stuff of MochiKit is available
    kukit.hasMochiKit = function() {
       var result = typeof MochiKit != 'undefined';
       result = result && typeof MochiKit.Logging != 'undefined';
       result = result && typeof MochiKit.Logging.log != 'undefined';
       return result;
    }();

    // Set up logging for MochiKit
    if (! kukit.hasFirebug && kukit.hasMochiKit) {
        kukit._logger._setupHandlers = function() {
            this.logDebug = MochiKit.Logging.logDebug;
            this.log = MochiKit.Logging.log;
            this.logWarning = MochiKit.Logging.logWarning;
            this.logError = MochiKit.Logging.logError;
            this.logFatal = MochiKit.Logging.logFatal;
        }
        // make convenience url
        //    javascript:kukit.showLog();
        // instead of the need to say
        //    javascript:void(createLoggingPane(true));
        kukit.showLog = function() {
            createLoggingPane(true);
        };
    }

    // check whether the logging stuff of Safari is available
    kukit.hasSafari = function() {
       var result = typeof console != 'undefined';
       result = result && typeof console.log != 'undefined';
       return result;
    }();

    // Set up logging for Safari
    if (! kukit.hasFirebug && ! kukit.hasMochiKit && kukit.hasSafari) {
        kukit._logger._setupHandlers = function() {
            this.logDebug = function(str) { console.log('DEBUG: '+str); };
            this.log = function(str) { console.log('INFO: '+str); };
            this.logWarning = function(str) { console.log('WARNING: '+str); };
            this.logError = function(str) { console.log('ERROR: '+str); };
            this.logFatal = function(str) { console.log('FATAL: '+str); };
        }
    }

// Initialize the logger with the solution we've just detected
kukit._logger.updateLogLevel();

// log a startup message
    kukit.log('Loading KSS engine.');

/* utilities */

kukit.ut = {};


/* 
* class FifoQueue
*/
kukit.ut.FifoQueue = function () {
    this.reset();
};

kukit.ut.FifoQueue.prototype.reset = function() {
    this.elements = new Array();
};

kukit.ut.FifoQueue.prototype.push = function(obj) {
    this.elements.push(obj);
};

kukit.ut.FifoQueue.prototype.pop = function() {
    return this.elements.shift();
};

kukit.ut.FifoQueue.prototype.empty = function() {
    return ! this.elements.length;
};

kukit.ut.FifoQueue.prototype.size = function() {
    return this.elements.length;
};

kukit.ut.FifoQueue.prototype.front = function() {
    return this.elements[0];
};

/*
* class SortedQueue
*/
kukit.ut.SortedQueue = function (comparefunc) {
    // comparefunc(left, right) determines the order by returning 
    // -1 if left should occur before right,
    // +1 if left should occur after right or 
    //  0 if left and right  have no preference as to order.
    // If comparefunc is not specified or is undefined,
    // the default order specified by < used.
    if (comparefunc) {
        this.comparefunc = comparefunc;
    }
    this.reset();
};

kukit.ut.SortedQueue.prototype.comparefunc = function(a, b) {
    if (a < b) {
        return -1;
    } else if (a > b) {
        return +1;
    } else {
        return 0;
    }
};

kukit.ut.SortedQueue.prototype.reset = function() {
    this.elements = new Array();
};

kukit.ut.SortedQueue.prototype.push = function(obj) {
    // Find the position of the object.
    var i = 0;
    var length = this.elements.length;
    while (i < length && this.comparefunc(this.elements[i], obj) == -1) {
        i ++;
    }
    // and insert it there
    this.elements.splice(i, 0, obj);
};

kukit.ut.SortedQueue.prototype.pop = function() {
    // takes minimal element
    return this.elements.shift();
};

kukit.ut.SortedQueue.prototype.popn = function(n) {
    // takes first n minimal element
    return this.elements.splice(0, n);
};

kukit.ut.SortedQueue.prototype.empty = function() {
    return ! this.elements.length;
};

kukit.ut.SortedQueue.prototype.size = function() {
    return this.elements.length;
};

kukit.ut.SortedQueue.prototype.get = function(n) {
    return this.elements[n];
};

kukit.ut.SortedQueue.prototype.front = function() {
    return this.elements[0];
};

kukit.ut.evalBool = function(value, errname) {
    if (value == 'true' || value == 'True' || value == '1') {
        value = true;
    } else if (value == 'false' || value == 'False' || value == '0'
        || value == '') {
        value = false;
        } else {
            throw 'Bad boolean value "' + value + '" ' + errname;
    }
    return value;
};

kukit.ut.evalInt = function(value, errname) {
        try {
        value = parseInt(value);
        } catch(e) {
            throw 'Bad integer value "' + value + '" ' + errname;
        }
    return value;
};

kukit.ut.evalList = function(value, errname) {
        try {
        // remove whitespace from beginning, end
        value = value.replace(/^ +/, '');
        //while (value && value.charAt(0) == ' ') {
        //    value = value.substr(1);
        //}
        value = value.replace(/ +$/, '');
        // do the splitting
        value = value.split(/ *, */);
        } catch(e) {
            throw 'Bad list value "' + value + '" ' + errname;
        }
    return value;
};



/* 
* class TimerCounter
*
* for repeating or one time timing
*/
kukit.ut.TimerCounter = function(delay, func, restart) {
    this.delay = delay;
    this.func = func;
    if (typeof(restart) == 'undefined') {
        restart = false;
    }
    this.restart = restart;
    this.timer = null;
};

kukit.ut.TimerCounter.prototype.start = function() {
    if (this.timer) {
        kukit.E = 'Timer already started.';

        throw kukit.E;
    }
    var self = this;
    var func = function() {
        self.timeout();
    };
    this.timer = setTimeout(func, this.delay);
};

kukit.ut.TimerCounter.prototype.timeout = function() {
    // Call the event action
    this.func();
    // Restart the timer
    if (this.restart) {
        this.timer = null;
        this.start();
    }
};

kukit.ut.TimerCounter.prototype.clear = function() {
    if (this.timer) {
        window.clearTimeout(this.timer);
        this.timer = null;
    }
    this.restart = false;
};

/*
* class Scheduler
*/
kukit.ut.Scheduler = function(func) {
    this.func = func;
    this.timer = null;
    this.nextWake = null;
};

kukit.ut.Scheduler.prototype.setNextWake = function(ts) {
    // Sets wakeup time, null clears
    if (this.nextWake) {
        this.clear();
    }
    if (! ts) {
        return;
    }
    var now = (new Date()).valueOf();
    if (ts > now) {
        this.nextWake = ts;
        var self = this;
        var func = function() {
            self.timeout();
        };
        this.timer = setTimeout(func, ts - now);
    } else {
        // if in the past, run immediately
        this.func();
    }
};

kukit.ut.Scheduler.prototype.setNextWakeAtLeast = function(ts) {
    // Sets wakeup time, unless it would wake up later than the
    // currently set timeout. Null clears the timer.
    if (! ts || ! this.nextWake || ts < this.nextWake) {
        this.setNextWake(ts);
    } else {
        var now = (new Date()).valueOf();
        // XXX why compute now and not use it ?
    }
};

kukit.ut.Scheduler.prototype.timeout = function() {
    // clear the timer
    this.timer = null;
    this.nextWake = null;
    // Call the event action
    this.func();
};


kukit.ut.Scheduler.prototype.clear = function() {
    if (this.nextWake) {
        window.clearTimeout(this.timer);
        this.timer = null;
        this.nextWake = null;
    }
};

/* 
* class SerializeScheduler
*
* Scheduler for serializing bind and load procedures
*/
kukit.ut.SerializeScheduler = function() {
    this.items = [];
    this.lock = false;
};

kukit.ut.SerializeScheduler.prototype.addPre = function(func, remark) {
    this.items.push({func: func, remark: remark});
    this.execute();
};

kukit.ut.SerializeScheduler.prototype.addPost = function(func, remark) {
    this.items.unshift({func: func, remark: remark});
    this.execute();
};

kukit.ut.SerializeScheduler.prototype.execute = function() {
    if (! this.lock) {
        this.lock = true;
        while (true) {
            var item = this.items.pop();
            if (! item) {
                break;
            }
            kukit.log(item.remark + ' starts.');
            var ts_start = (new Date()).valueOf();
            try {
                item.func();
            } catch(e) {
                this.lock = false;
                throw e;
            }
            var ts_end = (new Date()).valueOf();
            var msg = item.remark + ' finished in ';
            msg += (ts_end - ts_start) + ' ms.';
            kukit.log(msg);
        }
        this.lock = false;
    }
};

/* Browser event binding */

/* extracted from Plone */
// cross browser function for registering event handlers
kukit.ut.registerEventListener = function(elem, event, func) {
    if (elem.addEventListener) {
        elem.addEventListener(event, func, false);
        return true;
    } else if (elem.attachEvent) {
        var result = elem.attachEvent("on"+event, func);
        return result;
    }
    // maybe we could implement something with an array
    return false;
};


/* collecting keys-values into a dict or into a tuple list */

kukit.ut.DictCollector = function() {
    this.result = {};
};

kukit.ut.DictCollector.prototype.add = function(key, value) {
    this.result[key] = value;
};

kukit.ut.TupleCollector = function() {
    this.result = [];
};

kukit.ut.TupleCollector.prototype.add = function(key, value) {
    this.result.push([key, value]);
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.err = {};

/* 
* Exception factory 
*
* Create exception types:
*
* myError = kukit.err.exceptionFactory("myError", "My special error: ");
*
* Throwing:
*
* throw new myError("There was an error in my program.");
*
* Catching example:
*
*   ...
*    } catch(e) {
*        if (e.name == 'JSONRPCError') {
*            ...
*        } else {
*            throw(e);
*        }
*    }
*
* XXX TODO what about IE or other browsers?
* - on IE6, the error text is not printed.
*/


kukit.err.exceptionFactory = function(name) {
    var exc = function (arg1, arg2, arg3, arg4, arg5) {
        var kw = this.__init__(name, arg1, arg2, arg3, arg4, arg5);
        var err = new Error(kw.message);
        for (var key in kw) {
            err[key] = kw[key];
        }
        // number is an IE-only property
        if (typeof err.number == 'number') {
            // show sensible error on IE
            err.toString = function () {
                return this.name + ': ' + this.message;
            };
        }
        return err;
    };
    exc.prototype.__init__ = function(name, message) {
        var kw = {};
        kw.name = name;
        kw.message = message;
        return kw;
    };
    return exc;
};

// this should be thrown with the error command as parameter
kukit.err.ExplicitError = kukit.err.exceptionFactory('ExplicitError');
kukit.err.ee = kukit.err.ExplicitError;
kukit.err.ee.prototype.__superinit__ = kukit.err.ee.prototype.__init__;
kukit.err.ee.prototype.__init__ = function(name, errorcommand) {
    var message = 'Explicit error';
    var kw = this.__superinit__(name, message);
    kw.errorcommand = errorcommand;
    return kw;
};

var name = 'ResponseParsingError';
kukit.err.ResponseParsingError = kukit.err.exceptionFactory(name);

    var name = 'CommandExecutionError';
    kukit.err.CommandExecutionError = kukit.err.exceptionFactory(name);
    kukit.err.cex = kukit.err.CommandExecutionError;
    kukit.err.cex.prototype.__superinit__ = kukit.err.cex.prototype.__init__; 
    kukit.err.cex.prototype.__init__ = function(name, e, command) {
        var kw = this.__superinit__(name, '');
        kw.message = 'Command [' + command.name + '] ' + e.toString();
        return kw;
    };

kukit.err.rd = {};
    kukit.err.rd.RuleMergeError = kukit.err.exceptionFactory('RuleMergeError');

    var name = 'KssSelectorError';
    kukit.err.rd.KssSelectorError = kukit.err.exceptionFactory(name);

    var name = 'EventBindError';
    kukit.err.rd.EventBindError = kukit.err.exceptionFactory(name);
    kukit.err.rd.ebe = kukit.err.rd.EventBindError;
    kukit.err.rd.ebe.prototype.__superinit__ = kukit.err.rd.ebe.prototype.__init__; 
    kukit.err.rd.ebe.prototype.__init__ = function(name, message, eventName, eventNamespace) {
        var kw = this.__superinit__(name, message);
        kw.eventName = eventName;
        kw.eventNamespace = eventNamespace;
        kw.message += ' When binding event name [' + eventName;
        kw.message += '] in namespace [' + eventNamespace + '].';
        return kw;
    };

kukit.err.tk = {};
    kukit.err.tk.ParsingError = kukit.err.exceptionFactory('ParsingError');
    kukit.err.tk.pe = kukit.err.tk.ParsingError; 
    kukit.err.tk.pe.prototype.__superinit__ = kukit.err.tk.pe.prototype.__init__; 
    kukit.err.tk.pe.prototype.__init__ = function(name, message, cursor) {
        var kw = this.__superinit__(name, message);
        if (cursor) {
            kw.errpos = cursor.pos;
            kw.errrow = cursor.row;
            kw.errcol = cursor.col;
            kw.message += ' at row ' + kw.errrow + ', column ' + kw.errcol;
        } else {
            kw.errpos = null;
            kw.errrow = null;
            kw.errcol = null;
        }
        return kw;
    };


/*
* Copyright (c) 2006-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/



kukit.op = {};

/*
* class Oper
*
*  This is a single parameter that contains a collection
*  of operation objects to pass by, at various
*  operations.
*
*  Node and parms are the one to be accessed really, but the rest
*  is also accessible to read for special event implementations.
*
*
*  The members are:
*
*  node: the node in focus, to which the event triggered
*
*  parms: a dictionary that holds the parms to the function.
*      All parms are named ones.
*
*  eventRule: The eventRule associated by the trigger.
*
*  binderInstance: The event binder instance that holds the event state
*       and on which all events are executed.
*
*  orignode: in case when a command has returned from a server action, 
*      this holds the original node that triggered the event first.
*
*  browserevent: the original browser event.
*/
kukit.op.Oper = function (dict) {
    this.node = null;
    this.parms = {};
    this.eventRule = null;
    this.binderInstance = null;
    this.orignode = null;
    this.action = null;
    this.browserevent = null;
    // update from the dict
    this.unrestrictedUpdate(dict);
};

kukit.op.Oper.prototype.clone = function(dict, restricted) {
    var newoper = new kukit.op.Oper(this);
    newoper.unrestrictedUpdate(dict, restricted);
    return newoper;
};

kukit.op.Oper.prototype.update = function(dict) {
    // restricted attrs must not be changed on existing oper.
    this.unrestrictedUpdate(dict, true);
};

kukit.op.Oper.prototype.unrestrictedUpdate = function(dict, restricted) {
    if (typeof(dict) == 'undefined') {
        return;
    }
    for (var key in dict) {
        if (typeof(checkKey) == 'undefined') {
            var checkKey = function(key) {
                var isNode = key == 'node';
                var isParameters = key == 'parms';
                var isEventRule = key == 'eventRule';
                var isBinder = key == 'binderInstance';
                var isOrig = key == 'orignode';
            return isNode || isParameters || isEventRule || isBinder || isOrig;
            };
        }
        if (restricted && checkKey(key)) {
            kukit.E = 'Illegal update on oper object, protected attribute [';
            kukit.E += key + '].';
            throw kukit.E;
        }
        var value = dict[key];
        if (typeof(value) != 'function') {
            this[key] = value;
        }
    }
};

kukit.op.Oper.prototype.logDebug = function() {
        var result = [];
        for (var key in this){
            if (key == 'parms') {
                var res2 = [];
                for (var k2 in this.parms){
                    res2.push(k2 + '="' + this.parms[k2] + '"');
                }
                result.push('parms={' + res2.join(',') + '}');
            } else if (typeof(kukit.op.Oper.prototype[key]) == 'undefined') {
                result.push(key + '=' + this[key]);
            }
        }
        kukit.logDebug('Oper values: ' + result.join(', '));
};

kukit.op.Oper.prototype.executeClientAction = function(name) {
    // Check kss action parms
    var nodes = null;
    // XXX TODO this should be refactored with parms constraint checking
    for (key in this.kssParms) {
        switch (key) {
            case 'kssSelector': {
                // The value already contains the results
                nodes = this.kssParms[key];
            } break;
            default: {
               kukit.E = 'Wrong parameter : [' + key + '] starts with ';
               kukit.E += '"kss"; normal parms (that do not start with';
               kukit.E += ' "kss") only are allowed in action-client keys.';
               throw kukit.E;
            } break;
        }
    }
    // XXX TODO refactor this with commands execution (or the other way)
    var nodetext = function(node) {
        if (node) {
            return node.tagName.toLowerCase();
        } else {
            return 'DOCUMENT';
        }
    };
    var executeActions = kukit.actionsGlobalRegistry.get(name);
    if (nodes != null) { 
        var msg = nodes.length + ' nodes found for action [' + name + '].';
        kukit.logDebug(msg);
        if (!nodes || nodes.length == 0) {
            kukit.logWarning('Action selector found no nodes.');
        }
        for (var i=0; i < nodes.length; i++) {
            this.node = nodes[i];
            //XXX error handling for wrong command name
            var msg = '[' + name + '] action executes on target (' + (i+1);
            msg += '/' + nodes.length +  ') ';
            msg += '[' + nodetext(this.node) + '].';
            kukit.logDebug(msg);
            executeActions(this);
        }
    } else {
        // single node
        var msg = '[' + name + '] action executes on single node ';
        msg += '[' + nodetext(this.node) + '].';
        kukit.logDebug(msg);
        executeActions(this);
    }
};

kukit.op.Oper.prototype.executeDefaultAction = function(name, optional) {
    // Check kss action parms
    for (key in this.kssParms) {
        kukit.E = 'Wrong parameter : [' + key + '] starts with "kss";';
        kukit.E += ' normal parms (that do not start with kss)';
        kukit.E += ' only are allowed in action-default keys.';
        throw kukit.E;
    }
    //
    var namespace = this.binderInstance.__eventNamespace__;
    var kssevent = kukit.eventsGlobalRegistry.get(namespace, name);
    var methodName = kssevent.defaultActionMethodName;
    var success = false;
    if (! methodName) {
        if (! optional) {
            kukit.E = 'Could not trigger event [' + name;
            kukit.E += '] from namespace [' + namespace + '], because this';
            kukit.E += ' event has no default method registered.';
            throw kukit.E;
        }
    } else {
        // Put defaultParameters to parms!
        // This makes sure, that for implicit events 
        // you do not need to specify pass(key)
        // for making the parms arrive to the action.
        if (typeof(this.defaultParameters) != 'undefined') {
            this.parms = this.defaultParameters;
        } else {
            this.parms = {};
        }
        this.binderInstance._EventBinder_callMethod(
            namespace, name, this, methodName);
        success = true;
    }
    return success;
};

kukit.op.Oper.prototype.executeServerAction = function(name) {
    for (key in this.kssParms) {
        if (key == 'kssUrl') {
            // Value will be evaluated.
        } else if (key == 'kssSubmitForm') {
            // Value will be evaluated.
        } else {
           kukit.E = 'Wrong parameter : [' + key + '] starts with "kss";';
            kukit.E += ' normal parms (that do not start with kss)';
            kukit.E += ' only are allowed in action-server keys.';
            throw kukit.E;
        }
    }
    // oper will be accessible to some commands that execute in return
    var sa = new kukit.sa.ServerAction(name, this);
};

/* Helpers the serve binding */

kukit.op.Oper.prototype.getEventName = function () {
    // Gets event name
    return this.eventRule.kssSelector.name;
};

kukit.op.Oper.prototype.getEventNamespace = function () {
    // Gets event name
    return this.eventRule.kssSelector.namespace;
};

kukit.op.Oper.prototype.hasExecuteActions = function () {
    // Decide if there are any actions (or a default action)
    // to execute. This can speed up execution if in case
    // we have nothing to do, there is no reason to bind
    // the actions hook.
    if (this.eventRule) {  
        // if it has actions, the answer is yes
        if (this.eventRule.actions.hasActions())
            return true
        // if we have a default action, we will return true in any case
        // because we may want to call it.
        // The reason for this check is, that a default action is also
        // valid, even if it received no parms in the eventRule,
        // in which case it is not present as an action.
        var kssevent = kukit.eventsGlobalRegistry.get(
            this.getEventNamespace(), this.getEventName())
        var methodName = kssevent.defaultActionMethodName;
        return (typeof methodName != 'undefined');
    } else
        return false;
};

kukit.op.Oper.prototype.makeExecuteActionsHook = function (filter) {
    // Factory that creates the function that executes the actions.
    // The function may take a dict that is updated on the oper 
    // If filter is specified, it will we called with a function and
    // the event will only be triggered if the filter returned true.
    // THe return value of func_to_bind will show if the event
    // has executed or not.
    //
    // Speedup.
    if (! this.hasExecuteActions()) {
        return function() {};
    }
    var eventName = this.getEventName();
    var self = this;
    var func_to_bind = function(dict) {
        // (XXX XXX TODO it should happen here, that we change to a different
        // oper class. This is for the future when we separate the BindOper
        // from the ActionOper.)

        var newoper = self.clone(dict, true);
        // call the filter and if it says skip it, we are done
        if (filter && ! filter(newoper)) return false;
        // execute the event's actions
        newoper.binderInstance._EventBinder_triggerEvent(eventName, newoper);
        // show that the event's actions have been executed
        return true;
    };
    return func_to_bind;
};

/* Utility for parameter checking */

kukit.op.Oper.prototype.evaluateParameters =
    function(mandatory, defaults, errname, allow_excess) {
    // Checks if mandatory params are supplied and there are no excess params
    // also fill up default values
    // Parms are cloned and returned.
    // Example: 
    // oper.evaluateParameters(['mand1', 'mand2'], {'key1': 'defval'},
    //      'event X');
    if (typeof(allow_excess) == 'undefined') {
        allow_excess = false;
   }
    var newParameters = {};
    for (var i=0; i<mandatory.length; i++) {
        var next = mandatory[i];
        if (typeof(this.parms[next]) == 'undefined') {
            kukit.E = 'Missing mandatory parameter [' + next;
            kukit.E += '] in [' + errname + '].';
            throw kukit.E;
        }
        newParameters[next] = this.parms[next];
    }
    for (var key in defaults){
        var val = this.parms[key];
        if (typeof(val) == 'undefined') {
            newParameters[key] = defaults[key];
        } else {
            newParameters[key] = val;
        }
    }
    for (var key in this.parms){
        if (typeof(newParameters[key]) == 'undefined') {
            if (allow_excess) {
                newParameters[key] = this.parms[key];
            } else {
                throw 'Excess parameter [' + key + '] in [' + errname + '].';
            }
        }
    }
    this.parms = newParameters;
};

kukit.op.Oper.prototype.completeParms =
    function(mandatory, defaults, errname, allow_excess) {
    var msg = 'Deprecated [Oper.completeParms],';
    msg += 'use [Oper.evaluateParameters] instead !';
    kukit.logWarning(msg);
    this.evaluateParameters(mandatory, defaults, errname, allow_excess);
};

kukit.op.Oper.prototype.evalBool = function(key, errname) {
    var value = this.parms[key];
    kukit.E = 'for key [' + key + '] in [' + errname + '].';
    this.parms[key] = kukit.ut.evalBool(value, kukit.E);
};

kukit.op.Oper.prototype.evalInt = function(key, errname) {
    var value = this.parms[key];
    kukit.E = 'for key [' + key + '] in [';
    kukit.E += errname || this.componentName + '].';
    this.parms[key] = kukit.ut.evalInt(value, kukit.E);
};

kukit.op.Oper.prototype.evalList = function(key, errname) {
    var value = this.parms[key];
    kukit.E = 'for key [' + key + '] in [';
    kukit.E += errname || this.componentName + '].';
    this.parms[key] = kukit.ut.evalList(value, kukit.E);
};

    kukit.op.Oper.prototype.debugInformation = function() {
    if (this.eventRule) {
        var eventRule = this.eventRule;
        var node = this.node;
        var nodeName = '<DOCUMENT>';
        if (node != null) {
            nodeName = node.nodeName;
        }
        var message = ', event [' + eventRule.kssSelector.name;
        message += '], rule #' + eventRule.getIndex() + ', node [';
        message += nodeName + '].'; 
        return message;
    }
    return '';
    };

/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/



/*
* class Engine
*/
kukit.Engine = function() {
    this.baseUrl = this.calculateBase();
    this.documentRules = new kukit.rd.MethodTable();
    // table from res_type to rule processor
    this._ruleProcessorClasses = {};
    // register processor for type kss
    this._ruleProcessorClasses['kss'] = kukit.kssp.KssRuleProcessor;
    this._ruleProcessors = new Array();
    this.bindScheduler = new kukit.ut.SerializeScheduler();
    // State vars storage. This can be used from kss via a method.
    this.stateVariables = {};
    // instantiate request manager
    this.requestManager = new kukit.rm.RequestManager();
    this.binderInfoRegistry = new kukit.er.BinderInfoRegistry();
    // instantiate a load scheduler
    this.loadScheduler = new kukit.rd.LoadActions();
    this.initializedOnDOMLoad = false;
    // setup events queuing, collect them at the end of commands
    this.setupEventsQueue = [];
    this.setupEventsInProgress = false;

};

kukit.Engine.prototype.calculateBase = function() {
    var base = '';
    try {
        var _dummy = document;
        _dummy = window;
    } catch (e) {
        // testing or what
        return base;
    }
    var nodes = document.getElementsByTagName("link");
    if (nodes.length > 0) {
        for (var i=0; i<nodes.length; i++) {
            var link = nodes[i];
            if (link.rel == 'kss-base-url') {
                return link.href;
            }
        }
    }
    nodes = document.getElementsByTagName("base");
    if (nodes.length == 0) {
        var base = window.location.href;
        var pieces = base.split('/');
        pieces.pop();
        base = pieces.join('/');
    } else {
        base = nodes[0].href;
        // take off trailing slash
        var baselen = base.length;
        if (baselen > 0 && base.substring(baselen - 1) == '/') {
            base = base.substring(0, baselen - 1);
        }
    }
    return base;
};

kukit.Engine.prototype.getRuleSheetLinks = function() {
    var nodes = document.getElementsByTagName("link");
    var results = [];
    for (var i=0; i<nodes.length; i++) {
        if (kukit.isKineticStylesheet(nodes[i])) {
            var res_type = null;
            // Resource syntax is decided on type attribute.
            if((nodes[i].type == 'text/css') || (nodes[i].type == 'text/kss')) {
                res_type = 'kss';
            }
            var newRuleLink = new kukit.RuleSheetLink(nodes[i].href, res_type);
            results[results.length] = newRuleLink;
        }
    }
    return results;
};

kukit.Engine.prototype.createRuleProcessor = function(rulelink) {
    var ruleProcessorClass = this._ruleProcessorClasses[rulelink.res_type];
    if (ruleProcessorClass) {
        var msg = "Start loading and processing " + rulelink.href;
        msg = msg + " of type " + rulelink.res_type;
        kukit.log(msg);
        var ruleprocessor = new ruleProcessorClass(rulelink.href);
        this._ruleProcessors[this._ruleProcessors.length] = ruleprocessor;
        return ruleprocessor;
    } else {
        var msg = "Ignore rulesheet " + rulelink.href;
        msg = msg + " of type " + rulelink.res_type;
        kukit.log(msg);
    }
    return null;
};


kukit.Engine.prototype.getRules = function() {
    var rules = new Array();
    var ruleProcessors = this._ruleProcessors
    for (var j=0; j<ruleProcessors.length; j++) {
        var ruleProcessor = ruleProcessors[j];
        for (var i=0; i<ruleProcessor.rules.length; i++) {
            rules.push(ruleProcessor.rules[i]);
        }
    }
    return rules;
}

kukit.Engine.prototype.getRuleProcessors = function() {
    return this._ruleProcessors
}

kukit.isKineticStylesheet = function(node) {
    var rel = node.rel;
    if (rel=="kinetic-stylesheet") {
        return true;
    }
    // BBB to be removed after 2008-02-17
    if (rel=="kukit" || rel=="k-stylesheet") {
        var msg = node.href + ': rel "' + rel +'" is deprecated;';
        msg = msg + ' use "kinetic-stylesheet" instead.';
        kukit.logWarning(msg);
        return true;
    }
    return false;
};

kukit.Engine.prototype.setupEvents = function(inNodes) {
    if (this.setupEventsInProgress) {
        // remember them
        this.setupEventsQueue = this.setupEventsQueue.concat(inNodes);
    } else {
        // do it
        this.doSetupEvents(inNodes);
    }
};

kukit.Engine.prototype.beginSetupEventsCollection = function() {
    this.setupEventsInProgress = true;
};

kukit.Engine.prototype.finishSetupEventsCollection = function() {
    this.setupEventsInProgress = false;
    var setupEventsQueue = this.setupEventsQueue;
    this.setupEventsQueue = [];
    this.doSetupEvents(setupEventsQueue);
};

kukit.Engine.prototype.doSetupEvents = function(inNodes) {
    var self = this;
    var deferredEventsSetup = function() {
        self._setupEvents(inNodes);
    };
    var targetMsg;
    var found = false;
    if ( ! inNodes) {
        targetMsg = 'document';
        found = true;
    } else {
        targetMsg = 'nodes subtrees ';
        for (var i=0; i<inNodes.length; i++) {
            var node = inNodes[i];
            if (node.nodeType == 1) {
                if (! found) {
                    found = true;
                } else {
                    targetMsg += ', '; 
                }
                targetMsg += '[' + node.tagName.toLowerCase() + ']';
            }
        }
    }
    if (found) {
        var remark = '';
        remark += 'Setup of events for ' + targetMsg;
        this.bindScheduler.addPre(deferredEventsSetup, remark);
    }
};

kukit.Engine.prototype._setupEvents = function(inNodes) {
    // Decide phase. 1=initial, 2=insertion.
    var phase;
    if (typeof(inNodes) == 'undefined') {
        phase = 1;
    } else {
        phase = 2;
    }
    this.binderInfoRegistry.startBindingPhase();
    var rules = this.getRules();
    var ruletable = new kukit.rd.RuleTable(this.loadScheduler);
    for (var y=0; y < rules.length; y++) {
        rules[y].mergeForSelectedNodes(ruletable, phase, inNodes);
    }
    // bind special selectors first
    if (phase == 1) {
        this.documentRules.bindall(phase);
    }
    // finally bind the merged events
    ruletable.bindall(phase);

    // ... and do the actual binding. 
    this.binderInfoRegistry.processBindingEvents();
};




kukit.Engine.prototype.initializeRules = function() {
    if (window.kukitRulesInitializing || window.kukitRulesInitialized) {
        // Refuse to initialize a second time.
        kukit.log('[initializeRules] is called twice.');
        return;
    }
    kukit.log('Initializing rule sheets.');
    // Succesful initialization. At the moment the engine is kept
    // as a global variable, but this needs refinement in the future.
    kukit.engine = this;
    window.kukitRulesInitializing = true;
    // load the rulesheets
    var rulelinks = this.getRuleSheetLinks();
    kukit.log("Count of KSS links: " + rulelinks.length);
    for (var i=0; i<rulelinks.length; i++) {
        var rulelink = rulelinks[i];
        var ruleprocessor = this.createRuleProcessor(rulelink);
        if (ruleprocessor) {
            var ts_start = (new Date()).valueOf();
            ruleprocessor.load();
            var ts_loaded = (new Date()).valueOf();
            ruleprocessor.parse();
            var ts_end = (new Date()).valueOf();
            var msg = "Finished loading and processing " + rulelink.href;
            msg += " resource type " + rulelink.res_type;
            msg += ' in ' + (ts_loaded - ts_start) + ' + ';
            msg += (ts_end - ts_loaded) + ' ms.';
            kukit.log(msg);
        }
    }
    try {
        this.setupEvents();
    } catch(e) {
        // Event setup errors are logged.
        if (e.name == 'RuleMergeError' || e.name == 'EventBindError') {
           var msg = 'Events setup - ' + e.toString();
            // Log the message
            kukit.logFatal(msg);
            // and throw it...
            throw msg;
        } else {
            throw e;
        }
    }
    window.kukitRulesInitializing = false;
    window.kukitRulesInitialized = true;
};


/* XXX deprecated methods, to be removed asap 
 * (this was used from the plone plugin only, 
 * to allow the event-registration.js hook)
 */

kukit.initializeRules = function() {
    var msg = '[kukit.initializeRules] is deprecated,';
    msg += 'use [kukit.bootstrap] instead !';
    kukit.logWarning(msg);
    kukit.bootstrap();
};

/*
* class RuleSheetLink
*/
kukit.RuleSheetLink = function(href, res_type) {
    this.href = href;
    this.res_type = res_type;
};

kukit.bootstrap = function() {
    kukit.log('Engine instantiated.');
    var engine = new kukit.Engine();
    // Successful initializeRules will store the engine as kukit.engine. 
    // Subsequent activations will not delete the already set up engine.
    // Subsequent activations may happen, if more event handlers are set up,
    // and the first one will do the job, the later ones are ignored.
    engine.initializeRules();
};

kukit.bootstrapFromDOMLoad = function() {
    kukit.log('Engine instantiated in [DOMLoad].');
    var engine = new kukit.Engine();
    // Successful initializeRules will store the engine as kukit.engine. 
    // Subsequent activations will not delete the already set up engine.
    // Subsequent activations may happen, if more event handlers are set up,
    // and the first one will do the job, the later ones are ignored.
    engine.initializedOnDOMLoad = true;
    engine.initializeRules();
};

if (typeof(window) != 'undefined') {
    kukit.ut.registerEventListener(window, "load", kukit.bootstrap);
}


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Simple but effective tokenizing parser engine */

kukit.tk = {};

/*
* class _TokenBase
*/
kukit.tk._TokenBase = function() {
};

kukit.tk._TokenBase.prototype.emitError = function(msg) {
    // Use the start position of the token for the error report.
    var marker = this.src.makeMarker(this.startpos);
    kukit.E = new kukit.err.tk.ParsingError(msg, marker);
    throw kukit.E;
};

kukit.tk._TokenBase.prototype.setSrcStatus = function(eofOk) {
    if (! this.finished && this.src.text.length == this.src.pos) {
        if (eofOk) {
            this.finished = true;
        } else {
            kukit.E = 'Unexpected EOF.';
            this.emitError(kukit.E);
        }
    }
};


/*
* class _ParserBase
*/
kukit.tk._ParserBase = function() {
};

kukit.tk._ParserBase.prototype = new kukit.tk._TokenBase;

kukit.tk._ParserBase.prototype.emitAndReturn = function(token) {
    // handle return to the next level
    this.finished = true;
    return token;
};

kukit.tk._ParserBase.prototype.nextStep = function(table) {
    var src = this.src;
    // Search for symbol according to table.
    var best_pos = src.text.length;
    var best_symbol = null;
    for (var symbol in table) {
        var pos = src.text.indexOf(symbol, src.pos);
        if (pos != -1 && pos < best_pos) {
            best_pos = pos;
            best_symbol = symbol;
        }
    }
    // eat up till the symbol found (of EOF)
    if (best_pos > src.pos) {
        this.result.push(new kukit.tk.Fraction(src, best_pos));
        src.pos = best_pos;
    }
    // handle cursor point
    if (best_symbol) {
        // found a symbol, handle that
        // make the token and push it
        var tokens = eval(table[best_symbol]);
        if (typeof(tokens) != 'undefined') {
            if (typeof(tokens.length) == 'undefined') {
                tokens = [tokens];
            }
            for (var i=0; i<tokens.length; i++) {
                this.result.push(tokens[i]);
            }
        }
    }
};

/* token postprocess support */

kukit.tk._ParserBase.prototype.process = function() {
    // default process after tokenization
    this.txt = '';
    for (var i=0; i<this.result.length; i++) {
        this.txt += this.result[i].txt;
    }
};

kukit.tk._ParserBase.prototype.expectToken = function(cursor, token) {
    var i = cursor.next;
    if (token) {
        var symbol = token.prototype.symbol;
        if (i >= this.result.length) {
            kukit.E = 'Missing token : [' + symbol + '].';
            this.emitError(kukit.E);
        } else if (this.result[i].symbol != symbol) {
            kukit.E = 'Unexpected token : [' + this.result[i].symbol;
            kukit.E += '] found, [' + symbol + '] was expected.';
            this.emitError(kukit.E);
        }
    } else {
        if (i >= this.result.length) {
            kukit.E = 'Missing token.';
            this.emitError(kukit.E);
        }
    }
    cursor.token = this.result[i];
    cursor.next += 1;
};

kukit.tk._ParserBase.prototype.ifToken = 
    function(cursor, token1, token2, token3, token4) {
    var i = cursor.next;
    return (! (i >= this.result.length ||
        this.result[i].symbol != token1.prototype.symbol
        && (!token2 || this.result[i].symbol != token2.prototype.symbol
        && (!token3 || this.result[i].symbol != token3.prototype.symbol
        && (!token4 || this.result[i].symbol != token4.prototype.symbol)))));
};

kukit.tk._ParserBase.prototype.digestTxt =
    function(cursor, token1, token2, token3, token4) {
    // digests the txt from the tokens, ignores given token
    // plus whitespace removal
    this.digestExactTxt(cursor, token1, token2, token3, token4);
    cursor.txt = this.dewhitespaceAndTrim(cursor.txt);
};

kukit.tk._ParserBase.prototype.digestExactTxt =
    function(cursor, token1, token2, token3, token4) {
    // digests the txt from the tokens, ignores given token
    // exact value: no whitespace removal
    var result = '';
    while (this.ifToken(cursor, token1, token2, token3, token4)) {
        result += this.result[cursor.next].txt;
        cursor.next ++;
        }
    cursor.txt = result;
};


kukit.tk._ParserBase.prototype.dewhitespace = function(txt) {
    // removes ws but leaves leading and trailing one
    if (txt != ' ') { //speedup only
        txt = txt.replace(/[\r\n\t ]+/g, ' ');
    }
    return txt;
};
    
kukit.tk._ParserBase.prototype.dewhitespaceAndTrim = function(txt) {
    txt = this.dewhitespace(txt);
    // XXX Strange thing is: following replace works from
    // tests and the original demo, but with kukitportlet demo
    // it breaks. Someone stinks!
    //txt = txt.replace(/^ /, '');
    if (txt && txt.charAt(0) == ' ') {
        txt = txt.substr(1);
    }
    txt = txt.replace(/ $/, '');
    return txt;
};

/*
* class Fraction
*/
kukit.tk.Fraction = function(src, endpos) {
    this.txt = src.text.substring(src.pos, endpos);
    this.startpos = src.pos;
    this.endpos = src.pos;
    this.finished = true;
};
kukit.tk.Fraction.prototype.symbol = 'fraction';


/* Factories to make tokens and parsers */

kukit.tk.mkToken = function(symbol, txt) {
    // Poor man's subclassing.
    f = function(src) {
        this.src = src;
        this.startpos = src.pos;
        if (src.text.substr(src.pos, txt.length) != txt) {
            kukit.E = 'Unexpected token : [';
            kukit.E += src.text.substr(src.pos, txt.length) + '] found,';
            kukit.E += ' [' + txt + '] was expected.';
            this.emitError(kukit.E);
        } else {
            src.pos += txt.length;
            this.finished = true;
        }
        this.endpos = src.pos;
        //this.src = null;
    };
    f.prototype = new kukit.tk._TokenBase;
    f.prototype.symbol = symbol;
    f.prototype.txt = txt;
    return f;
};

kukit.tk.mkParser = function(symbol, table) {
    // Poor man's subclassing.
    f = function(src, tokenClass, eofOk) {
        this.src = src;
        this.startpos = src.pos;
        this.finished = false;
        this.result = [];
        if (tokenClass) {
            // Reentry with starting token propagated.
            this.result.push(new tokenClass(this.src));
        }
        this.setSrcStatus(eofOk);
        while (!this.finished) {
            this.nextStep(table);
            this.setSrcStatus(eofOk);
        }
        this.endpos = src.pos;
        // post processing
        this.process();
        
        //this.src = null;
    };
    f.prototype = new kukit.tk._ParserBase;
    f.prototype.symbol = symbol;
    return f;
};

kukit.tk.Cursor = function(txt) {
    this.text = txt;
    this.pos = 0;
};

kukit.tk.Cursor.prototype.makeMarker = function(pos) {
    // create a cursor to mark this position
    var cursor = new kukit.tk.Cursor();
    cursor.text = this.text;
    cursor.pos = pos;
    // Calculate the row and column information on the cursor
    cursor.calcRowCol();
    return cursor;
};

kukit.tk.Cursor.prototype.getRowCol = function(pos) {
    // Gets the row, col information for the position.
    if (typeof(pos) == 'undefined') {
        pos = this.pos;
    }
    var index = 0;
    var row = 1;
    var next = 0;
    while (true) {
        next = this.text.indexOf('\n', index);
        if (next == -1 || next >= pos) {
            break;
        }
        index = next + 1;
        row += 1;
    }
    var col = pos - index + 1;
    return {'row': row, 'col': col};
};

kukit.tk.Cursor.prototype.calcRowCol = function(pos) {
    // Calculates row and column information on the cursor.
    var rowcol = this.getRowCol();
    this.row = rowcol.row;
    this.col = rowcol.col;
};

/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.pr = {};

/*
*  class ValueProviderRegistry
* 
*  The parameter providers need to be registered here.
*/

kukit.pr.ValueProviderRegistry = function () {
    this.content = {};
};

kukit.pr.ValueProviderRegistry.prototype.register = function(name, func) {
    if (typeof(func) == 'undefined') {
        kukit.E = 'func argument is mandatory when registering a parameter'
        kukit.E += ' provider [ValueProviderRegistry.register].';
        throw kukit.E;
    }
    if (this.content[name]) {
       // Do not allow redefinition
       var msg = 'Error : parameter provider [' + name;
       msg += '] already registered.';
       kukit.logError(msg);
       return;
    }
    this.content[name] = func;
};

kukit.pr.ValueProviderRegistry.prototype.exists = function(name) {
    var entry = this.content[name];
    return (typeof(entry) != 'undefined');
};

kukit.pr.ValueProviderRegistry.prototype.get = function(name) {
    var func = this.content[name];
    if (! func) {
        // not found
        if (name == '') {
            // default provider for the strings
            return kukit.pr.IdentityPP;
         } else {
    kukit.E = 'Error : undefined parameter provider [' + name + '].';
            throw kukit.E;
        }
    }
    return func;
};

kukit.pprovidersGlobalRegistry = new kukit.pr.ValueProviderRegistry();

/*
* Register the core parameter providers
*
* A parameter provider is a class that needs to implement the 
* check and the eval methods.
* Check is executed at parsing time, eval is doing the real job
* of providing the requested parameter result.
* Check throws an exception if the parameters are not as expected.
* The parameters are coming in the input array [args]. The current node is
* passed in [node]. The output value should be returned.
*
* There is a third parameter that contains the default parameters
* dict (for input only). This is only used by the [pass()] parameter
* provider. The default parameters are used if an action is called
* programmatically but in this case the parameters to be propagated
* must be explicitely declared using the provider [pass()].
*
* The special key '' is held for the parameter provider that just returns
* the string itself. This is by default defined as the identity function, but
* can be overwritten to do something with the string value. The usage is that
* this provider expects a single parameter, the string.
*/

kukit.pr.IdentityPP = function() {};
kukit.pr.IdentityPP.prototype = {
    check: function(args) {
        // check does not need to be used here actually.
        if (args.length != 1) {
            throw 'internal error, IdentityPP needs 1 argument';
        }
    },
    eval: function(args, node) {
        return args[0];
    }
};
kukit.pprovidersGlobalRegistry.register('', kukit.pr.IdentityPP);

kukit.pr.FormVarPP = function() {};
kukit.pr.FormVarPP.prototype = {
    check: function(args) {
        if (args.length != 2) {
            throw 'formVar method needs 2 arguments [formname, varname]';
        }
    },
    eval: function(args, node) {
        return kukit.fo.getFormVar(new kukit.fo.NamedFormLocator(args[0]),
            args[1]);
    }
};
kukit.pprovidersGlobalRegistry.register('formVar', kukit.pr.FormVarPP);

kukit.pr.CurrentFormVarPP = function() {};
kukit.pr.CurrentFormVarPP.prototype = {
    check: function(args) {
        if (args.length != 0 && args.length != 1) {
            throw 'currentFormVar method needs 0 or 1 argument [varname]';
        }
    },
    eval: function(args, node) {
        if (args.length == 1) {
            return kukit.fo.getFormVar(new kukit.fo.CurrentFormLocator(node),
                args[0]);
        } else {
            // no form var name, just get the value of the node.
            return kukit.fo.getValueOfFormElement(node);
        }
    }
};
kukit.pprovidersGlobalRegistry.register('currentFormVar',
    kukit.pr.CurrentFormVarPP);

kukit.pr.CurrentFormVarFromKssAttrPP = function() {};
kukit.pr.CurrentFormVarFromKssAttrPP.prototype = {
    check: function(args) {
        if (args.length != 1 && args.length != 2) {
            kukit.E = 'currentFormVarFromKssAttr method needs 1 or 2 argument';
            kukit.E += ' [attrname, [recurseParents]]';
            throw kukit.E;
        }
    },
    eval: function(args, node) {
        var argname =  args[0];
        var recurseParents = false;
        if (args.length == 2) {
            kukit.E = '2nd attribute of currentFormVarForKssAttr must be a';
            kukit.E += ' boolean';
            kukit.ut.evalBool(args[1], kukit.E);
            recurseParents = args[1];
        }
        var formvarname = kukit.dom.getRecursiveAttribute(node, argname,
            recurseParents, kukit.dom.getKssAttribute);
        return kukit.fo.getFormVar(new kukit.fo.CurrentFormLocator(node),
            formvarname);
    }
};
kukit.pprovidersGlobalRegistry.register('currentFormVarFromKssAttr',
    kukit.pr.CurrentFormVarFromKssAttrPP);


/* BBB. To be deprecated at 2007-08-15 */
kukit.pr.FormPP = function() {};
kukit.pr.FormPP.prototype = {
    check: function(args) {
        if (args.length != 1) {
            throw 'form method needs 1 arguments [formname]';
        }
        var msg = 'Deprecated the [form(formname)] parameter provider,';
        msg += ' use [xxx-kssSubmitForm: form(formname)] instead !';
        kukit.logWarning(msg);
    },
    eval: function(args, node) {
        return kukit.fo.getAllFormVars(new kukit.fo.NamedFormLocator(args[0]),
            new kukit.ut.DictCollector());
    }
};
kukit.pprovidersGlobalRegistry.register('form', kukit.pr.FormPP);

/* BBB. To be deprecated at 2007-08-15 */
kukit.pr.CurrentFormPP = function() {};
kukit.pr.CurrentFormPP.prototype = {
    check: function(args) {
        if (args.length != 0) {
            throw 'currentForm method needs no argument';
        }
        var msg = 'Deprecated the [currentForm()] parameter provider,';
        msg += ' use [xxx-kssSubmitForm: currentForm()] instead !';
        kukit.logWarning(msg);
    },
    eval: function(args, node) {
        return kukit.fo.getAllFormVars(new kukit.fo.CurrentFormLocator(node),
            new kukit.ut.DictCollector());
    }
};
kukit.pprovidersGlobalRegistry.register('currentForm', kukit.pr.CurrentFormPP);

kukit.pr.NodeAttrPP = function() {};
kukit.pr.NodeAttrPP.prototype = {
    check: function(args) {
        if (args.length != 1 && args.length != 2) {
            kukit.E = 'nodeAttr method needs 1 or 2 argument (attrname,';
            kukit.E += ' [recurseParents]).';
            throw kukit.E;
        }
        if (args[0].toLowerCase() == 'style') {
            throw 'nodeAttr method does not accept [style] as attrname.';
        }
        if (args[0].match(/[ ]/)) {
            throw 'attrname parameter in nodeAttr method cannot contain space.';
        }
    },
    eval: function(args, node) {
        var argname = args[0];
        var recurseParents = false;
        if (args.length == 2) {
            recurseParents = args[1];
            kukit.E = '2nd attribute of nodeAttr must be a boolean.';
            kukit.ut.evalBool(recurseParents, kukit.E);
        }
        return kukit.dom.getRecursiveAttribute(node, argname, recurseParents,
            kukit.dom.getAttribute);
    }
};
kukit.pprovidersGlobalRegistry.register('nodeAttr', kukit.pr.NodeAttrPP);

kukit.pr.KssAttrPP = function() {};
kukit.pr.KssAttrPP.prototype = {
    check: function(args) {
        if (args.length != 1 && args.length != 2) {
            kukit.E = 'kssAttr method needs 1 or 2 argument (attrname,';
            kukit.E += ' [recurseParents]).';
            throw kukit.E;
        }
        if (args[0].match(/[ -]/)) {
            kukit.E = 'attrname parameter in kssAttr method cannot contain';
            kukit.E += ' dashes or spaces.';
            throw kukit.E;
        }
    },
    eval: function(args, node) {
        var argname =  args[0];
        var recurseParents = false;
        if (args.length == 2) {
            recurseParents = args[1];
            kukit.E = '2nd attribute of kssAttr must be a boolean.';
            kukit.ut.evalBool(recurseParents, kukit.E);
        }
        return kukit.dom.getRecursiveAttribute(node, argname, recurseParents,
            kukit.dom.getKssAttribute);
    }
};
kukit.pprovidersGlobalRegistry.register('kssAttr', kukit.pr.KssAttrPP);

kukit.pr.NodeContentPP = function() {};
kukit.pr.NodeContentPP.prototype = {
    check: function(args) {
        if (args.length != 0 && args.length != 1) {
            throw 'nodeContent method needs 0 or 1 argument [recursive].';
        }
    },
    eval: function(args, node) {
        var recursive = false;
        if (args.length == 1) {
            recursive = args[0];
        }
        return kukit.dom.textContent(node, recursive);
    }
};
kukit.pprovidersGlobalRegistry.register('nodeContent', kukit.pr.NodeContentPP);

kukit.pr.StateVarPP = function() {};
kukit.pr.StateVarPP.prototype = {
    check: function(args) {
        if (args.length != 1) {
            throw 'stateVar method needs 1 argument [varname].';
        }
    },
    eval: function(args, node) {
        var key = args[0];
        var value = kukit.engine.stateVariables[key];
        if (typeof(value) == 'undefined') {
            // notfound arguments will get null
            kukit.E = 'Nonexistent statevar ['+ key +'].';
            throw kukit.E;
        }
        return value;
    }
};
kukit.pprovidersGlobalRegistry.register('stateVar', kukit.pr.StateVarPP);

kukit.pr.PassPP = function() {};
kukit.pr.PassPP.prototype = {
    check: function(args) {
        if (args.length != 1) {
            throw 'pass method needs 1 argument [attrname].';
        }
    },
    eval: function(args, node, defaultParameters) {
        var key = args[0];
        var value = defaultParameters[key];
        if (typeof(value) == 'undefined') {
            // notfound arguments will get null
            kukit.E = 'Nonexistent default parm ['+ key +'].';
            throw kukit.E;
        }
        return value;
    }
};
kukit.pprovidersGlobalRegistry.register('pass', kukit.pr.PassPP);


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Supplemental data that the parser builds up */

kukit.rd = {};

kukit.rd.makeId = function(namespace, name) {
    if (namespace == null) {
        namespace = '';
    }
    return '@' + namespace + '@' + name;
};

kukit.rd.makeMergeId = function(id, namespace, name) {
    if (namespace == null) {
        namespace = '';
    }
    return id + '@' + namespace + '@' + name;
};

/*
*  class KssSelector
*/
kukit.rd.KssSelector = function(isEvent, css, name, namespace, id) {
    this.isEventSelector = isEvent;
    this.isMethodSelector = ! isEvent;
    if (! name) {
        throw 'KssSelector must have name';
    }
    if (name.indexOf('@') != -1) {
        var msg = 'Kss selector name must not contain @: [' + name + '].';
        throw new kukit.err.rd.KssSelectorError(msg);
        }
    if (id && id.indexOf('@') != -1) {
        var msg = 'Kss selector id must not contain @: [' + id + '].';
        throw new kukit.err.rd.KssSelectorError(msg);
        }
    if (namespace && namespace.indexOf('@') != -1) {
        var msg = 'Kss selector namespace must not contain @: [' + namespace;
        msg = msg + '].';
        throw new kukit.err.rd.KssSelectorError(msg);
       }
    if (! isEvent) {
        // method rule
        if (css != 'document' && css != 'behaviour') {
            var msg = 'KssSpecialSelector [' + name;
            msg = msg + '] must have one of the allowed names';
            throw new kukit.err.rd.KssSelectorError(msg);
       }
    }
    this.css = css;
    this.name = name;
    this.namespace = namespace;
    this.className = null;
    this.id = id;
};

kukit.rd.KssSelector.prototype.setIdAndClass = function() {
    // Sets up id and class on the selector, based on registration info
    this.className = kukit.eventsGlobalRegistry.get(
        this.namespace, this.name).className;
    if (this.id == null) {
        // singleton for class
        this.id = kukit.rd.makeId(this.namespace, this.className);
    }
    // Also set the merge id. The rules with the same merge
    // id should be merged on the same node.
    this.mergeId = kukit.rd.makeMergeId(this.id, this.namespace, this.name);
};

/*
* Kss parameter values. There are two kinds: text and method.
*
* They are evaluated in two phases: check is invoked at parsing,
* allowing the early detection of errors. Evaluate is called
* when the action is to be called. This allows a kss method
* to add any parameter to the action.
*/

/*
*  class KssTextValue
*/
kukit.rd.KssTextValue = function(txt) {
    // A text parameter in the format 
    //      key: value;
    this.txt = txt;
};

kukit.rd.KssTextValue.prototype.isMethod = false;

kukit.rd.KssTextValue.prototype.check = function(registry) {
    // use the IdentityPP provider.
    this.pprovider = new (registry.get(''))();
};

kukit.rd.KssTextValue.prototype.evaluate =
    function(parms, key, node, defaultParameters) {

    // For normal string parms, this would return the string itself.
    // In other execution contexts (like kssSelector, for example) this can
    // do something else.
    parms[key] = this.pprovider.eval([this.txt], node, defaultParameters);
};

/*
*  class KssTextValue
*/
kukit.rd.KssMethodValue = function(methodName, args) {
    // A method parameter in the format 
    //      key: methodName(v1, v2, ... vn);
    this.methodName = methodName;
    this.args = args;
};

kukit.rd.KssMethodValue.prototype.isMethod = true;

kukit.rd.KssMethodValue.prototype.check = function(registry) {
    // Check syntax
    var f = registry.get(this.methodName);
    this.pprovider = new f();
    this.pprovider.check(this.args);
};

kukit.rd.KssMethodValue.prototype.evaluate =
    function(parms, key, node, defaultParameters) {

    // Evaluate into parms.
    parms[key] = this.pprovider.eval(this.args, node, defaultParameters);
};

/*
*  class KssPseudoValue
*/
kukit.rd.KssPseudoValue = function(methodName, args) {
    // A method parameter in the format 
    //      methodName(v1)
    this.methodName = methodName;
    this.args = args;
};

kukit.rd.KssPseudoValue.prototype.isMethod = true;

kukit.rd.KssPseudoValue.prototype.check = function() {
};

kukit.rd.EventRuleNr = 0;            // just a counter

/*
*  class EventRule
*/
kukit.rd.EventRule = function(kssSelector, parms, actions) {
    if (typeof(parms) == 'undefined') {
        // called for merging clone
        this.kssSelector = kssSelector;
    } else {
        this.index = kukit.rd.EventRuleNr;
        this.mergedIndex = null;
        kukit.rd.EventRuleNr = this.index + 1;
        var namestr;
        if (kssSelector.namespace) {
            namestr = kssSelector.namespace + '-' + kssSelector.name;
        } else {
            namestr = kssSelector.name;
        }
        var msg = 'EventRule #' + this.getIndex() + ': ';
        msg = msg + kssSelector.css + ' EVENT=' + namestr;
        kukit.logDebug(msg);
        this.kssSelector = kssSelector;
        this.parms = parms;
        this.actions = actions;
    }
};

kukit.rd.EventRule.prototype.getIndex = function() {
    if (this.mergedIndex) {
        return this.mergedIndex;
    } else {
        return this.index;
    }
};

kukit.rd.EventRule.prototype.mergeForSelectedNodes = 
    function(ruletable, phase, inNodes) {

    // Select all nodes within the inNodes for phase==2.
    // (or undefined on initial node, phase==1)
    // Merge itself to the selected nodes.
    if (this.kssSelector.isEventSelector) {
        var nodes = kukit.dom.cssQuery(this.kssSelector.css, inNodes);
        var counter = 0;
        for (var y=0; y < nodes.length; y++)
        {
            var node = nodes[y];
            // XXX never rebind to any node again!
            // this compensates that cssQuery is returning
            // results out of the subtree
            if (typeof(node._kukitmark) == 'undefined') {
                ruletable.add(node, this);
                counter += 1;
                }
        }
        if (counter > 0) {
            var msg = 'EventRule [#' + this.getIndex();
            msg = msg + '-' + this.kssSelector.mergeId;
            msg = msg + '] selected ' + counter + ' nodes.';
            kukit.logDebug(msg);
        }
    } else if (typeof(inNodes) == 'undefined') {
        // Method selector. They only need to be handled on the initial
        // pageload, when the inNodes parameter is ommitted.
        kukit.engine.documentRules.add(this);
    }
};

kukit.rd.EventRule.prototype.getBinderInfo = function() {
    // Gets the event instance for the rule.
    return kukit.engine.binderInfoRegistry.getOrCreateBinderInfo(
        this.kssSelector.id, this.kssSelector.className, 
        this.kssSelector.namespace);
};

/*
* bind(node) : calls binder hook on event instance.
*  These hooks are tried in order, if succeeds it must return true:
*
* __bind__(name, parms, func_to_bind, node, eventRule)
* __bind_<name>__(parms, func_to_bind, node, eventRule)
*
* If none succeeds is an error.
*
*/

kukit.rd.EventRule.prototype.bind = function(node) {
    this.store(node);
    // Creation of the binding oper
    var oper = new kukit.op.Oper();
    var binderInfo = this.getBinderInfo();
    oper.node = node;
    oper.eventRule = this;
    oper.binderInstance = binderInfo.binderInstance;
    oper.parms = this.parms;
    // mark on the instance as bound
    binderInfo.bindOper(oper); 
};
 
kukit.rd.EventRule.prototype.store = function(node) {
    if (node == null) {
        // node == null is *always* valid, it means "document".
        return;
    }
    if (typeof(node.kukitEventRules) == 'undefined') {
        var rules = [];
        node.kukitEventRules = rules;
    }
    node.kukitEventRules.push(this);
};


/*
* Merging event rules
*/

kukit.rd.EventRule.prototype.isMerged = function() {
    return (this.mergedIndex != null);
};

kukit.rd.EventRule.prototype.cloneForMerge = function() {
    // Do not touch ourselves, make a new copy for the merge.
    var merged = new kukit.rd.EventRule(this.kssSelector);
    merged.actions = new kukit.rd.ActionSet();
    merged.parms = {};
    merged.mergedIndex = 'X';
    merged.merge(this);
    merged.mergedIndex = this.getIndex();
    return merged;
};

kukit.rd.EventRule.prototype.merge = function(other) {
    if (! this.isMerged()) {
        throw 'Cannot merge into a genuine event rule';
    }
    if (this.kssSelector.isEventSelector) {
        if (this.kssSelector.id != other.kssSelector.id) {
            throw 'Differing kss selector ids in event rule merge';
        }
        if (this.kssSelector.className != other.kssSelector.className) {
            throw 'Differing kss selector classes in event rule merge';
        }
    }
    if (this.kssSelector.name != other.kssSelector.name) {
        throw 'Differing kss selector names in event rule merge';
    }
    this.mergedIndex = this.mergedIndex + ',' + other.getIndex();
    for (var key in other.parms) {
        this.parms[key] = other.parms[key];
    }
    this.actions.merge(other.actions);
    if (this.mergedIndex.substr(0, 1) != 'X') {
        // ignore initial clone-merge
        var msg = 'Merged rule [' + this.mergedIndex;
        msg = msg + '-' + this.kssSelector.mergeId + '].';
        kukit.logDebug(msg);
    }
};

kukit.rd.EventRule.prototype.mergeIntoDict = function(dict, key, eventRule) {
    // Merge into the given dictionary by given key.
    // If possible, store the genuine rule first - if not,
    // clone it and do a merge. Never destroy the genuine
    // rules, clone first. This is for efficiency.
    var mergedRule = dict[key];
    if (typeof(mergedRule) == 'undefined') {
        // there was no rule
        dict[key] = eventRule;
    } else {
        // we have to merge the rule
        if (! mergedRule.isMerged()) {
            // Make sure genuine instances are replaced
            mergedRule = mergedRule.cloneForMerge();
            dict[key] = mergedRule;
        }
        mergedRule.merge(eventRule);
    }
};

/*
*  class ActionSet
*/
kukit.rd.ActionSet = function() {
    this.content = {};
};

kukit.rd.ActionSet.prototype.hasActions = function() {
    for (var name in this.content) {
        return true;
    }
    return false;
};

kukit.rd.ActionSet.prototype.merge = function(other) {
    for (var key in other.content) {
        var action = this.content[key];
        var action2 = other.content[key];
        if (typeof(action) == 'undefined') {
            if (action2.type != 'X') {
                // new action
                action = new kukit.rd.Action();
                this.content[key] = action;
            } else {
                var msg = 'Cannot action-delete unexisting action, [';
                msg = msg + key + '].';
                kukit.E = new kukit.err.rd.RuleMergeError(msg);
                throw kukit.E;
            }
        }
        if (action2.type != 'X') {
            // merge the action
            action.merge(action2);
        } else {
            // Delete the action
            this.deleteAction(key);
        }
    }
};

kukit.rd.ActionSet.prototype.execute = function(oper) {
    for (var key in this.content) {
        var action = this.content[key];
        // do not execute error actions!
        if (action.type != 'E') {
            action.execute(oper);
        }
    }
    // Execute the default action in case of there is one but there were no
    // parms so it was actually not entered as an action object
    // otherwise, it would have been executed from action.execute already
    if (typeof(this.content['default']) == 'undefined') {
        // this is conditional: if there is no default method, it's skipped.
        var name = oper.eventRule.kssSelector.name;
        // Execution with no parms. (XXX ?)
        oper = oper.clone({'parms': {}});
        oper.executeDefaultAction(name, true);
    }
};

kukit.rd.ActionSet.prototype.getOrCreateAction = function(name) {
    var action = this.content[name];
    if (typeof(action) == 'undefined') {
        action = new kukit.rd.Action();
        action.setName(name);
        this.content[name] = action;
    }
    return action;
};

kukit.rd.ActionSet.prototype.getActionOrNull = function(name) {
    var action = this.content[name];
    if (typeof(action) == 'undefined') {
        action = null;
    }
    return action;
};

kukit.rd.ActionSet.prototype.deleteAction = function(name) {
    var action = this.content[name];
    if (typeof(action) == 'undefined') {
        throw('Action [' + name + '] does not exist and cannot be deleted.');
    }
    delete this.content[name];

};

kukit.rd.ActionSet.prototype.getDefaultAction = function() {
    return this.getActionOrNull('default');
};

kukit.rd.ActionSet.prototype.getErrorActionFor = function(action) {
    // Get the error action of a given action: or null,
    // if the action does not define an error handler.
    return this.getActionOrNull(action.error);
};

/*
*  class Action
*/
kukit.rd.Action = function() {
    this.name = null;
    this.error = null;
    this.parms = {};
    this.type = null;
};

kukit.rd.Action.prototype.setName = function(name) {
    if (this.name != null && this.name != name) {
        var msg = 'Error overriding action name [' + this.name;
        msg = msg + '] to [' + name + '] (Unmatching action names at merge?)';
        throw new kukit.err.rd.RuleMergeError(msg);
    }
    this.name = name;
    if (name == 'default') {
        if (this.type != null && this.type != 'D') {
            var msg = 'Error setting action to default on action [' + this.name;
            msg = msg + '], current type [' + this.type + '].';
            throw new kukit.err.rd.RuleMergeError(msg);
        }
        this.setType('D');
    }
};

kukit.rd.Action.prototype.setType = function(type) {
    // Allowed types:
    //
    // S = server
    // C = client
    // E = error / client
    // D = default (unsettable)
    // X = cancel action
    var checkType = function(type) {
        var isNotServer = type != 'S';
        var isNotClient = type != 'C';
        var isNotError = type != 'E';
        var isNotCancel = type != 'X';
        return isNotServer && isNotClient && isNotError && isNotCancel;
    };
    if (checkType(type) || (this.type != null && this.type != type)) {
        var msg = 'Error setting action type on action [' + this.name;
        msg = msg + '] from [' + this.type + '] to [' + type;
        msg = msg + '] (Attempt to merge client, server or error actions ?)';
        throw new kukit.err.rd.RuleMergeError(msg);
    }
    if (this.error != null && this.type != 'S') {
        var msg = 'Error setting action error handler on action [' + this.name;
        msg = msg + '], this is only allowed on server actions.';
        throw new kukit.err.rd.RuleMergeError(msg);
    }
    this.type = type;  
};

kukit.rd.Action.prototype.setError = function(error) {
    if (this.type != null && this.type != 'S') {
        var msg = 'Error setting action error handler on action [' + this.name;
        msg =  msg + '], this is only allowed on server actions.';
        throw new kukit.err.rd.RuleMergeError(msg);
    }
    this.error = error;  
};

kukit.rd.Action.prototype.merge = function(other) {
    // Merge to the instance.
    if (other.name != null) { 
        this.setName(other.name);
    }
    if (other.type != null) { 
        this.setType(other.type);
    }
    if (other.error != null) { 
        this.setError(other.error);
    }
    // These are simply overwritten.
    for (var key in other.parms) {
        this.parms[key] = other.parms[key];
    }
};

kukit.rd.Action.prototype.makeActionOper = function(oper) {
    // Fill the completed action parms, based on the node
    // The kssXxx parms, reserved for the action, are 
    // handled as appropriate.
    // A cloned oper is returned.
    var parms = {};
    var kssParms = {};
    // Make sure we have defaultParameters on oper
    if (typeof(oper.defaultParameters) == 'undefined') {
        oper.defaultParameters = {};
    }
    for (var key in this.parms) {
        var kssvalue = this.parms[key]; 
        if (key.match(/^kss/)) {
            // kssXxx parms are separated to kssParms.
            kssvalue.evaluate(kssParms, key, oper.node,
                oper.defaultParameters); 
        } else {
            // evaluate the method parms into parms
            kssvalue.evaluate(parms, key, oper.node,
                oper.defaultParameters); 
        }
    }
    var anOper = oper.clone({
            'parms': parms,
            'kssParms': kssParms,
            'action': this
        });
    return anOper;
};

kukit.rd.Action.prototype.execute = function(oper) {
    oper = this.makeActionOper(oper);
    switch (this.type) {
        case 'D': {
            // Default action.
            var name = oper.eventRule.kssSelector.name;
            oper.executeDefaultAction(name);
        } break;
        case 'S': {
            // Server action.
            oper.executeServerAction(this.name);
        } break;
        case 'C': {
            // Client action.
            oper.executeClientAction(this.name);
        } break;
        case 'E': {
            // Error action (= client action)
            oper.executeClientAction(this.name);
        } break;
    }
};


/*
*  class LoadActions
*/
kukit.rd.LoadActions = function() {
    this.items = [];
};

kukit.rd.LoadActions.prototype.empty = function() {
    return (this.size() == 0);
};

kukit.rd.LoadActions.prototype.size = function() {
    return this.items.length;
};

kukit.rd.LoadActions.prototype.push = function(f) {
    if (this.items.length >= 100) {
        throw ('Infinite recursion, stack full');
    }
    this.items.push(f);
};

kukit.rd.LoadActions.prototype.execute = function() {
    var f = this.items.shift();
    if (f) {
        f();
        return true;
    } else {
        return false;
    }
};

kukit.rd.LoadActions.prototype.executeAll = function() {
    var i = 0;
    while(true) {
        var success = this.execute();
        if (! success) {
            break;
        }
        i++;
    }
    return i;
};


/*
*  class RuleTable
*
*   Used for binding rules to nodes, and handling the merges.
*   It is a two level dictionary.
*
*   There are more rules that match a given node and event id. 
*   They will be merged appropriately. The event id is also
*   important. The event class must be the same with merge
*   rules (within the id).
*
*   To summarize the procedure, each eventRule is added with
*   all the nodes that are selected by it. Nothing is executed,
*   only merges are done at this time. Finally, all binds are
*   done in the second path.
*
*   Event with the same merge id are merged. The merge id is
*   a concatenation of the event id and the event name.
* 
*   XXX TODO this has to be refactored, since it's all global now
*
*/

kukit.rd.RuleTable = function(loadScheduler) {
    this.loadScheduler = loadScheduler;
    this.nodes = {};
};

kukit.rd.RuleTable.prototype.add = function(node, eventRule) {
    // look up node
    var nodehash = kukit.rd.hashNode(node);
    var nodeval = this.nodes[nodehash];
    if (typeof(nodeval) == 'undefined') {
        nodeval = {'node': node, 'val': {}};
        this.nodes[nodehash] = nodeval;
    }
    // Merge into the dict
    eventRule.mergeIntoDict(
        nodeval.val, eventRule.kssSelector.mergeId, eventRule);
};

kukit.rd.RuleTable.prototype.bindall = function(phase) {
    // Bind all nodes
    var counter = 0;
    for (var nodehash in this.nodes) {
        var nodeval = this.nodes[nodehash];
        // XXX Mark the node, disabling rebinding in a second round
        nodeval.node._kukitmark = phase;
        for (var id in nodeval.val) {
            var eventRule = nodeval.val[id];
            eventRule.bind(nodeval.node);            
        }
        counter += 1;
    }
    kukit.logDebug(counter + ' nodes bound in grand total.');
    // Execute the load actions in a deferred manner
    var loadactions = this.loadScheduler;
    if (! loadactions.empty()) {
        kukit.logDebug('Delayed load actions execution starts.');
        var count = loadactions.executeAll();
        kukit.logDebug(count + ' load actions executed.');
    }
};

kukit.rd.uid = 0;

kukit.rd.hashNode = function(node) {
    // It is, generally, not possible to use a node as a key.
    // However we try to set this right.
    // We generate an uniqueID on the node. This does not work
    // on MSIE but it already has an uniqueID.
    if (node == null) {
        // null represents the document
        return '<<DOCUMENT>>';
    }
    var id = node.uniqueID;
    if (typeof(id) == 'undefined') {
        id = kukit.rd.uid;
        node.uniqueID = id;
        kukit.rd.uid ++;
    }
    return id;
};

/*
*  class MethodTable
*
* stores the method rules.
*
* Unlike the rule table that is specific for each binding,
* this is unique to the page.
*/
kukit.rd.MethodTable = function() {
    this.content = {};
    this.content['document'] = {};
    this.content['behaviour'] = {};
};

kukit.rd.MethodTable.prototype.add = function(eventRule) {
    // Get the entry by the type which is now at css
    var category = eventRule.kssSelector.css;
    var dict = this.content[category];
    if (typeof(dict) == 'undefined') {
        throw 'Unknown method rule category [' + category + '].';
    }
    // Merge into the corresponding category
    eventRule.mergeIntoDict(dict, eventRule.kssSelector.mergeId, eventRule);
};

kukit.rd.MethodTable.prototype.getMergedRule =
    function(category, name, binderInstance) {

    // Returns the rule for a given event instance, 
    // Get the entry by category (= document or behaviour)
    var dict = this.content[category];
    if (typeof(dict) == 'undefined') {
        throw 'Unknown method rule category [' + category + '].';
    }
    // look up the rule
    var namespace = binderInstance.__eventNamespace__;
    var id = binderInstance.__binderId__;
    var mergeId = kukit.rd.makeMergeId(id, namespace, name);
    var mergedRule = dict[mergeId];
    if (typeof(mergedRule) == 'undefined') {
        // no error, just return null.
        mergedRule = null;
    }
    return mergedRule;
};

kukit.rd.MethodTable.prototype.bindall = function() {
    // bind document events
    var documentRules = this.content['document'];
    var counter = 0;
    for (var mergeId in documentRules) {
        // bind to null as a node
        documentRules[mergeId].bind(null);
        counter += 1;
    }
    kukit.logDebug(counter + ' special rules bound in grand total.');
};

/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Tokens of the KSS parser */

kukit.kssp = {};

/* Tokens */

kukit.kssp.commentbegin = kukit.tk.mkToken('commentbegin', "\/\*");
kukit.kssp.commentend = kukit.tk.mkToken('commentend', "\*\/");
kukit.kssp.openbrace = kukit.tk.mkToken('openbrace', "{");
kukit.kssp.closebrace = kukit.tk.mkToken('closebrace', "}");
kukit.kssp.openbracket = kukit.tk.mkToken('openbracket', "[");
kukit.kssp.closebracket = kukit.tk.mkToken('closebracket', "]");
kukit.kssp.openparent = kukit.tk.mkToken('openparent', "(");
kukit.kssp.closeparent = kukit.tk.mkToken('closeparent', ")");
kukit.kssp.semicolon = kukit.tk.mkToken('semicolon', ";");
kukit.kssp.colon = kukit.tk.mkToken('colon', ":");
kukit.kssp.quote = kukit.tk.mkToken('quote', "'");
kukit.kssp.dquote = kukit.tk.mkToken('dquote', '"');
kukit.kssp.backslash = kukit.tk.mkToken('backslash', '\x5c'); 
kukit.kssp.comma = kukit.tk.mkToken('comma', ",");
kukit.kssp.equals = kukit.tk.mkToken('equals', "=");

/* Parsers */

/*
* class Document 
*/
kukit.kssp.Document = kukit.tk.mkParser('document', {
    "\/\*": 'new kukit.kssp.Comment(this.src, kukit.kssp.commentbegin)',
    "{": 'new kukit.kssp.Block(this.src, kukit.kssp.openbrace)'
    });
kukit.kssp.Document.prototype.process = function() {
    this.eventRules = [];
    var cursor = {'next': 0};
    while (cursor.next < this.result.length) {
        this.digestTxt(cursor, kukit.tk.Fraction, kukit.kssp.Comment);
        var key = cursor.txt;
        if (! key) {
            break;
        }
        this.expectToken(cursor, kukit.kssp.Block);
        this.addBlock(key, cursor.token);
    }
    this.result = [];
    this.txt = '';
}; 
kukit.kssp.Document.prototype.addBlock = function(key, block) {
    // Parse the part in an embedded parser
    var src = new kukit.tk.Cursor(key + ' ');
    var parser = new kukit.kssp.KssSelector(src, null, true);
    // check the event name and namespace use in evt- rules
    // equals the event name and namespace set in the KSS selector.
    if (block.evt_name != null) {
        // We have evt- parms in the rule.
        if (block.evt_name != parser.kssSelector.name
            || block.evt_namespace != parser.kssSelector.namespace) {
            // XXX this should be done in another way,
            //so that we can see where the error happened.
            kukit.E = 'Wrong prefix : we have "' + block.evt_namespace;
            kukit.E += '-' + block.evt_name + '" instead of "' + key;
            kukit.E += '". KSS prefix ';
            kukit.E += '"evt-[<NAMESPACE>-]<EVENTNAME>-<NAME>" ';
            kukit.E += 'must not have different [namespace and] ';
            kukit.E += 'name than the KSS selector at the top of the ';
            kukit.E += 'rule.';
            block.emitError(kukit.E);
        }
    }
    // Create the event rule. (one action only)
    var eventRule = new kukit.rd.EventRule(parser.kssSelector,
        block.eventParameters, block.actions);
    // Store the rule
    this.eventRules.push(eventRule);
};

/*
* class Comment 
*/
kukit.kssp.Comment = kukit.tk.mkParser('comment', {
    // it's not 100% good, but will do
    "\*\/": 'this.emitAndReturn(new kukit.kssp.commentend(this.src))'
    });
kukit.kssp.Comment.prototype.process = function() {
    this.result = [];
    this.txt = ' ';
};

/*
* class Block 
*/
kukit.kssp.Block = kukit.tk.mkParser('block', {
    ";": 'new kukit.kssp.semicolon(this.src)',
    ":": '[new kukit.kssp.colon(this.src), new kukit.kssp.PropValue(this.src)]',
    "}": 'this.emitAndReturn(new kukit.kssp.closebrace(this.src))'
    });
kukit.kssp.Block.prototype.process = function() {
    //this.parms = {};
    this.eventParameters = {};
    this.evt_name = null; // we don't know at this point
    this.evt_namespace = null; // we don't know at this point
    this.actions = new kukit.rd.ActionSet();
    var cursor = {'next': 1};
    while (cursor.next < this.result.length-1) {
        this.digestTxt(cursor, kukit.tk.Fraction, kukit.kssp.Comment);
        var key = cursor.txt;
        if (! key) {
            break;
        }
        this.expectToken(cursor, kukit.kssp.colon);
        this.expectToken(cursor, kukit.kssp.PropValue);
        // store the wrapped prop
        this.addDeclaration(key, cursor.token.value);
        if (cursor.next == this.result.length-1) break;
        this.expectToken(cursor, kukit.kssp.semicolon);
    }
    this.result = [];
    this.txt = '';
};
kukit.kssp.Block.prototype.addDeclaration = function(key, value) {

    var ppRegistries = {
        '': kukit.pprovidersGlobalRegistry,
        'kssSelector': kukit.sr.pproviderSelRegistry,
        'kssSubmitForm': kukit.fo.pproviderFormRegistry
    };

    // p.s. value is here a KssXxParm. In most cases we check and unwrap it.
    // the keys look like this:
    //
    // evt-<EVTNAME>-<KEY>: <VALUE>
    // evt-<NAMESPACE>-<EVTNAME>-<KEY>: <VALUE>
    //
    // action-server: <ACTIONNAME>
    // action-client: <ACTIONNAME>
    // action-client: <NAMESPACE>-<ACTIONNAME>
    // action-cancel: <ACTIONNAME>
    // action-cancel: <NAMESPACE>-<ACTIONNAME>
    //
    // <ACTIONNAME>-<KEY>: <VALUE>
    // <NAMESPACE>-<ACTIONNAME>-<KEY>: <VALUE>
    // <ACTIONNAME>-error: <VALUE>
    // <NAMESPACE>-<ACTIONNAME>-error: <VALUE>
    //
    // default-<KEY>: <VALUE>
    // default-error: <VALUE>
    //
    var splitkey = key.split('-');
    if (splitkey.length < 2 || splitkey.length > 4) {
        kukit.E = 'Wrong rule key : "' + key + '". ';
        kukit.E += 'KSS rule key must be "<ACTIONNAME>-<PARAMETER>" or ';
        kukit.E += '"<NAMESPACE>-<ACTIONNAME>-<PARAMETER>" or ';
        kukit.E += '"evt-<EVENTNAME>-<PARAMETER>" or ';
        kukit.E += '"evt-<NAMESPACE>-<EVENTNAME>-<PARAMETER>".';
        this.emitError(kukit.E);
    }
    var name = splitkey[0];
    if (name == 'evt') {
        // evt-<EVTNAME>-<PARAMETER>: <VALUE>
        // evt-<NAMESPACE>-<EVTNAME>-<PARAMETER>: <VALUE>
        if (splitkey.length < 3) {
            kukit.E = 'Wrong rule key : "' + key + '". ';
            kukit.E += 'KSS rule key must be "<ACTIONNAME>-<PARAMETER>"';
            kukit.E += ' or "<NAMESPACE>-<ACTIONNAME>-<PARAMETER>" or ';
            kukit.E += '"evt-<EVENTNAME>-<PARAMETER>" or ';
            kukit.E += '"evt-<NAMESPACE>-<EVENTNAME>-<PARAMETER>".';
            this.emitError(kukit.E);
        }
        var enamespace;
        var ename;
        var ekey;
        if (splitkey.length == 3) {
            // evt-<EVENTNAME>-<PARAMETER>: <VALUE>
            ename =  splitkey[1];
            ekey = splitkey[2];
        } else {
            // evt-<NAMESPACE>-<EVENTNAME>-<PARAMETER>: <VALUE>
            enamespace = splitkey[1];
            ename = splitkey[2];
            ekey = splitkey[3];
        }
        if (this.evt_name == null) {
            // This is the first evt- rule, so we set it up
            // so that we can check it stays the same within the block.
            this.evt_name = ename;
            this.evt_namespace = enamespace;
        } else {
            if (ename != this.evt_name || enamespace != this.evt_namespace) {
                // Do not allow deviation from the previous event names.
                kukit.E = 'Wrong key [' + key + '] : ';
                kukit.E += 'evt-[<NAMESPACE>-]<EVENTNAME>-<PARAMETER> ';
                kukit.E += 'keys cannot have different [namespace and] ';
                kukit.E += 'event name than in the event selector, ';
                kukit.E += 'it should have been [' + this.evt_namespace;
                kukit.E += '-' + this.evt_name + '].';
                this.emitError(kukit.E);
            }
        }
        if (value.isMethod != false) {
            kukit.E = 'Wrong value for key [' + key + '] : ';
            kukit.E += 'value providers are not ';
            kukit.E += 'allowed as value for ';
            kukit.E += 'evt-[<NAMESPACE>-]<EVENTNAME>-<PARAMETER> keys.';
            this.emitError(kukit.E);
        }
        // set it
        this.eventParameters[ekey] = value.txt;
    } else if (name == 'action') {
        // action-server: <ACTIONNAME>
        // action-client: <ACTIONNAME>
        // action-client: <NAMESPACE>-<ACTIONNAME>
        // action-cancel: <ACTIONNAME>
        // action-cancel: <NAMESPACE>-<ACTIONNAME>
        if (splitkey.length != 2) {
            kukit.E = 'Wrong key [' + key + '] : ';
            kukit.E += 'action-<QUALIFIER> keys can have only one dash.';
            this.emitError(kukit.E);
            }
        if (value.isMethod != false) {
            kukit.E = 'Wrong value for key [' + key + '] : ';
            kukit.E += 'value providers are not ';
            kukit.E += 'allowed for action-<QUALIFIER> keys.';
            this.emitError(kukit.E);
            }
        var atab = {'server': 'S', 'client': 'C', 'cancel': 'X'};
        var actionType = atab[splitkey[1]];
        if (! actionType) {
            kukit.E = 'Wrong key [' + key + '] : ';
            kukit.E += 'qualifier in action-<QUALIFIER> keys must be ';
            kukit.E += '"server" or "client" or "cancel".'; 
            this.emitError(kukit.E);
            }    
        // force value to be <ACTIONNAME> or <NAMESPACE>-<ACTIONNAME>
        var splitvalue = value.txt.split('-');
        if (splitvalue.length > 2) {
            kukit.E = 'Wrong value for key [' + key + '] : ';
            kukit.E += 'value must be <ACTIONNAME> or <NAMESPACE>';
            kukit.E += '-<ACTIONNAME> for action-<QUALIFIER> keys.';
            this.emitError(kukit.E);
            }
        // set it
        var action = this.actions.getOrCreateAction(value.txt);
        if (actionType != 'X' || action.type == null) {
            action.setType(actionType);
        } else {
            this.actions.deleteAction(value.txt);
        }
    } else {
        // <ACTIONNAME>-<KEY>: <VALUE>
        // <NAMESPACE>-<ACTIONNAME>-<KEY>: <VALUE>
        // <ACTIONNAME>-error: <VALUE>
        // <NAMESPACE>-<ACTIONNAME>-error: <VALUE>
        // default-<KEY>: <VALUE>
        // default-error: <VALUE>
        var aname;
        var akey;
        if (splitkey.length == 2) {
            // <ACTIONNAME>-<KEY>: <VALUE>
            // <ACTIONNAME>-error: <VALUE>
            // default-<KEY>: <VALUE>
            // default-error: <VALUE>
            aname =  splitkey[0];
            akey = splitkey[1];
        } else {
            // <NAMESPACE>-<ACTIONNAME>-<KEY>: <VALUE>
            // <NAMESPACE>-<ACTIONNAME>-error: <VALUE>
            aname = splitkey[0] + '-' + splitkey[1];
            akey = splitkey[2];
        }
        // set it
        var action = this.actions.getOrCreateAction(aname);
        switch (akey) {
            case 'error': {
                    // <ACTIONNAME>-error: <VALUE>
                    // default-error: <VALUE>
                    if (value.isMethod != false) {
                        kukit.E = 'Wrong value for key [' + key + '] : ';
                        kukit.E += 'value providers are not ';
                        kukit.E += 'allowed for <ACTIONNAME>-error keys.';
                        this.emitError(kukit.E);
                        }
                    action.setError(value.txt);
                    // also create the action for the error itself.
                    var err_action = this.actions.getOrCreateAction(value.txt);
                    err_action.setType('E');
                } break;
            default: {
                    // <ACTIONNAME>-<KEY>: <VALUE>
                    // default-<KEY>: <VALUE>
                    // 
                    // value may be either txt or method parms, 
                    // and they get stored with the wrapper.
                    action.parms[akey] = value;
                    // 
                    // Check the syntax of the value at this point.
                    // This will also set the value providers on the value
                    // (from check).
                    //
                    // Figure out which registry to use.
                    var registry = ppRegistries[akey];
                    if (typeof(registry) == 'undefined') {
                        // use default pproviders
                        registry = ppRegistries[''];
                    }
                    //
                    try {
                        // Check also sets the value provider on the value.
                        value.check(registry);
                    } catch(e) {
                        kukit.E = 'Error in value : ' + e + '.';
                        this.emitError(kukit.E);
                    }
                } break;
        }
    }
};

/*
* class PropValue
*/
kukit.kssp.PropValue = kukit.tk.mkParser('propvalue', {
    ";": 'this.emitAndReturn()',
    "}": 'this.emitAndReturn()',
    ")": 'this.emitAndReturn()',
    ",": 'this.emitAndReturn()',
    "'": 'new kukit.kssp.String(this.src, kukit.kssp.quote)',
    '"': 'new kukit.kssp.String2(this.src, kukit.kssp.dquote)',
    "\/\*": 'new kukit.kssp.Comment(this.src, kukit.kssp.commentbegin)',
    "(": 'new kukit.kssp.MethodArgs(this.src, kukit.kssp.openparent)'
    });
kukit.kssp.PropValue.prototype.process = function() {
    var cursor = {'next': 0};
    this.digestTxt(cursor, kukit.tk.Fraction, kukit.kssp.Comment);
    this.txt = '';
    var txt = cursor.txt;
    if (this.ifToken(cursor, kukit.kssp.String)) {
        // The previous txt must be all whitespace.
        if (txt) {
            kukit.E = 'Wrong value : unallowed characters [' + txt + ']';
            kukit.E += ' before a string.';
            this.emitError(kukit.E);
        }
        // the next one must be a string.
        this.expectToken(cursor, kukit.kssp.String);
        this.produceTxt(cursor.token.txt);
    } else if (this.ifToken(cursor, kukit.kssp.MethodArgs)) {
        // see if not empty and has no spaces in it 
        if (! txt || txt.indexOf(' ') != -1) {
            kukit.E = 'Wrong value : method name [' + txt + '] cannot ';
            kukit.E += 'have spaces.';
            this.emitError(kukit.E);
        }
        // the next one must be the rules
        this.expectToken(cursor, kukit.kssp.MethodArgs);
        this.value = new this.valueClass(txt, cursor.token.args);
    } else {
        // not a string or method: check if we allowed multiword.
        if (! this.multiword_allowed && txt.indexOf(' ') != -1) {
            kukit.E = 'Wrong value : [' + txt + '] cannot have spaces.';
            this.emitError(kukit.E);
        }
        this.produceTxt(txt);
    }
    // see what's after
    if (cursor.next < this.result.length) {
        this.digestTxt(cursor, kukit.tk.Fraction, kukit.kssp.Comment);
        // we have to be at the end and have no text after
        if (cursor.next < this.result.length || cursor.txt) {
            kukit.E = 'Wrong value : unallowed characters after ';
            kukit.E += 'the property.';
            this.emitError(kukit.E);
        }
    }
    this.result = [];
};
kukit.kssp.PropValue.prototype.multiword_allowed = true;
kukit.kssp.PropValue.prototype.valueClass = kukit.rd.KssMethodValue;
kukit.kssp.PropValue.prototype.produceTxt = function(txt) {
    // txt parms are returned embedded
    this.value = new kukit.rd.KssTextValue(txt);
};

/*
* class PropValueInMethod
*
* PropValue in method cannot have method-style vars.
*/
kukit.kssp.PropValueInMethod = kukit.tk.mkParser('propvalue', {
    ";": 'this.emitAndReturn()',
    "}": 'this.emitAndReturn()',
    ")": 'this.emitAndReturn()',
    "]": 'this.emitAndReturn()',
    ",": 'this.emitAndReturn()',
    "'": 'new kukit.kssp.String(this.src, kukit.kssp.quote)',
    '"': 'new kukit.kssp.String2(this.src, kukit.kssp.dquote)',
    "\/\*": 'new kukit.kssp.Comment(this.src, kukit.kssp.commentbegin)'
    });
kukit.kssp.PropValueInMethod.prototype.multiword_allowed = false;
kukit.kssp.PropValueInMethod.prototype.process =
    kukit.kssp.PropValue.prototype.process;
kukit.kssp.PropValueInMethod.prototype.produceTxt = function(txt) {
    // txt parms are returned unwrapped
    this.txt = txt;
};

/*
* class PropValueInPseudo
*
* PropValue in pseudo must ba single word with no spaces around.
*/
kukit.kssp.PropValueInPseudo = kukit.tk.mkParser('propvalue', {
    "{": 'this.emitAndReturn()',
    " ": 'this.emitAndReturn()',
    "\t": 'this.emitAndReturn()',
    "\n": 'this.emitAndReturn()',
    "\r": 'this.emitAndReturn()',
    "\/\*": 'this.emitAndReturn()',
    ":": 'this.emitAndReturn()',
    "(": 'this.emitAndReturn(new kukit.kssp.MethodArgs(this.src,' +
        'kukit.kssp.openparent))'
    });
kukit.kssp.PropValueInPseudo.prototype.multiword_allowed = false;
kukit.kssp.PropValueInPseudo.prototype.process = 
    kukit.kssp.PropValue.prototype.process;
kukit.kssp.PropValueInPseudo.prototype.valueClass = kukit.rd.KssPseudoValue;
kukit.kssp.PropValueInPseudo.prototype.produceTxt = function(txt) {
    // txt parms are returned embedded
    this.value = new kukit.rd.KssPseudoValue(txt, []);
};

/*
* class String
*/
kukit.kssp.String = kukit.tk.mkParser('string', {
    "'": 'this.emitAndReturn(new kukit.kssp.quote(this.src))',
    '\x5c': 'new kukit.kssp.Backslashed(this.src, kukit.kssp.backslash)'
    });
kukit.kssp.String.prototype.process = function() {
    // collect up the value of the string, omitting the quotes
    this.txt = '';
    for (var i=1; i<this.result.length-1; i++) {
        this.txt += this.result[i].txt;
    }
};

/*
* class String2
*/
kukit.kssp.String2 = kukit.tk.mkParser('string', {
    '"': 'this.emitAndReturn(new kukit.kssp.dquote(this.src))',
    '\x5c': 'new kukit.kssp.Backslashed(this.src, kukit.kssp.backslash)'
    });
kukit.kssp.String2.prototype.process = kukit.kssp.String.prototype.process; 


/*
* class Backslashed
*/
kukit.kssp.Backslashed = kukit.tk.mkParser('backslashed', {});
kukit.kssp.Backslashed.prototype.nextStep = function(table) {
    // digest the next character and store it as txt
    var src = this.src;
    var length = src.text.length;
    if (length < src.pos + 1) {
        kukit.E = 'Missing character after backslash.';
        this.emitError(kukit.E);
    } else { 
        this.result.push(new kukit.tk.Fraction(src, src.pos+1));
        this.src.pos += 1;
        this.finished = true;
    }
};
kukit.kssp.Backslashed.prototype.process = function() {
    this.txt = this.result[1].txt;
};

/*
* class MethodArgs
*
* methodargs are (a, b, c) lists.
*/
kukit.kssp.MethodArgs = kukit.tk.mkParser('methodargs', {
    "'": 'new kukit.kssp.String(this.src, kukit.kssp.quote)',
    '"': 'new kukit.kssp.String2(this.src, kukit.kssp.dquote)',
    ",": 'new kukit.kssp.comma(this.src)',
    ")": 'this.emitAndReturn(new kukit.kssp.closeparent(this.src))',
    "\/\*": 'new kukit.kssp.Comment(this.src, kukit.kssp.commentbegin)'
    });
kukit.kssp.MethodArgs.prototype.process = function() {
    this.args = [];
    var cursor = {'next': 1};
    while (cursor.next < this.result.length-1) {
        this.digestTxt(cursor, kukit.tk.Fraction, kukit.kssp.Comment);
        var value = cursor.txt;
        if (! value) {
            // allow to bail out after widow ,
            if (cursor.next == this.result.length-1) break;
            // here be a string then.
            this.expectToken(cursor, kukit.kssp.String);
            value = cursor.token.txt;
        } else {
            // Just a value, must be one word then.
            if (value.indexOf(' ') != -1) {
                kukit.E = 'Wrong method argument [' + value;
                kukit.E += '] : value cannot have spaces (if needed,';
                kukit.E += ' quote it as a string).';
                this.emitError(kukit.E);
            }
        }
        this.args.push(value);
        if (cursor.next == this.result.length-1) break;
        this.expectToken(cursor, kukit.kssp.comma);
    }
    this.result = [];
    this.txt = '';
};

/*
* class KssSelector
*
* embedded parser to parse the selector
* KSS event selector: (has spaces in it)
*      <css selector> selector:name(id)
* KSS method selector: (has no spaces in it)
*      document:name(id) or behaviour:name(id)
*/
kukit.kssp.KssSelector = kukit.tk.mkParser('kssselector', {
    ":": '[new kukit.kssp.colon(this.src), new ' + 
        'kukit.kssp.PropValueInPseudo(this.src)]',
    "{": 'this.emitAndReturn()',
    "\/\*": 'new kukit.kssp.Comment(this.src, kukit.kssp.commentbegin)'
    });
kukit.kssp.KssSelector.prototype.process = function() {
    var name;
    var namespace = null;
    var id = null;
    var tokenindex = this.result.length - 1;
    // Find the method parms and calculate the end of css parms. (RL)
    var cycle = true;
    while (cycle && tokenindex >= 0) {
        var token = this.result[tokenindex];
        switch (token.symbol) {
            case kukit.tk.Fraction.prototype.symbol: {
                // if all spaces, go to previous one
                if (token.txt.match(/^[\r\n\t ]*$/) != null) {
                    tokenindex -= 1;
                } else {
                    kukit.E = 'Wrong event selector : missing event ';
                    kukit.E += 'qualifier :<EVENTNAME> ';
                    kukit.E += 'or :<EVENTNAME>(<ID>).';
                    this.emitError(kukit.E);
                }
            } break;
            case kukit.kssp.Comment.prototype.symbol: {
                tokenindex -= 1;
            } break;
            default: {
                cycle = false;
            } break;
        }
    }
    // Now we found the token that must be <fraction> <colon> <propvalue>.
    tokenindex -= 2;
    if (tokenindex < 0
         || (this.result[tokenindex+2].symbol !=
                kukit.kssp.PropValueInPseudo.prototype.symbol)
         || (this.result[tokenindex+1].symbol != 
                kukit.kssp.colon.prototype.symbol)
         || (this.result[tokenindex].symbol !=
                kukit.tk.Fraction.prototype.symbol)) {
        kukit.E = 'Wrong event selector : missing event qualifier ';
        kukit.E += ':<EVENTNAME> or :<EVENTNAME>(<ID>).';
        this.emitError(kukit.E);
    }
    // See that the last fraction does not end with space.
    var lasttoken = this.result[tokenindex];
    var commatoken = this.result[tokenindex+1];
    var pseudotoken = this.result[tokenindex+2];
    var txt = lasttoken.txt;
    if (txt.match(/[\r\n\t ]$/) != null) {
        kukit.E = 'Wrong event selector :';
        kukit.E += ' space before the colon.';
        this.emitError(kukit.E);
    }
    if (! pseudotoken.value.methodName) {
        kukit.E = 'Wrong event selector :';
        kukit.E += ' event name cannot have spaces.';
        this.emitError(kukit.E);
    }
    if (pseudotoken.value.args.length > 1) {
        kukit.E = 'Wrong event selector :';
        kukit.E += ':<EVENTNAME>(<ID>) can have only one parameter.';
        this.emitError(kukit.E);
    }
    css = this.src.text.substring(this.startpos, commatoken.startpos);
    // Decide if we have an event or a method selector.
    // We have a method selector if a single word "document" or "behaviour".
    var singleword = css.replace(/[\r\n\t ]/g, ' ');
    if (singleword && singleword.charAt(0) == ' ') {
        singleword = singleword.substring(1);
    }
    var isEvent = (singleword != 'document' && singleword != 'behaviour');
    if (! isEvent) {
        // just store the single word, in case of event selectors
        css = singleword;
    }
    // create the selector.
    var id = null;
    if (pseudotoken.value.args.length == 1) {
        id = pseudotoken.value.args[0];
    }
    var name = pseudotoken.value.methodName;
    var splitname = name.split('-');
    var namespace = null;
    if (splitname.length > 2) {
        kukit.E = 'Wrong event selector [' + name + '] : ';
        kukit.E += 'qualifier should be :<EVENTNAME> or ';
        kukit.E += ':<NAMESPACE>-<EVENTNAME>.';
        this.emitError(kukit.E);
    } else if (splitname.length == 2) { 
        name = splitname[1];
        namespace = splitname[0];
    }
    // Protect the error for better logging
    try {
        this.kssSelector = new kukit.rd.KssSelector(isEvent, css, name,
            namespace, id);
    } catch(e) {
        if (e.name == 'KssSelectorError') {
            // Log the message
            this.emitError(e.toString());
        } else {
            throw e;
        }
    }
    this.txt = '';
    this.result = [];
};

/*
* class KssRuleProcessor
*
* Rule processor that interfaces with kukit core
*/
kukit.kssp.KssRuleProcessor = function(href) {
    this.href = href;
    this.loaded = false;
    this.rules = [];
};

kukit.kssp.KssRuleProcessor.prototype.load = function() {
      // Opera does not support getDomDocument.load, so we use XMLHttpRequest
      var domDoc = new XMLHttpRequest();
      domDoc.open("GET", this.href, false);
      domDoc.send(null);
      this.txt = domDoc.responseText;
      this.loaded = true;
};

kukit.kssp.KssRuleProcessor.prototype.parse = function() {
    try {
        //Build a parser and parse the text into it
        var src = new kukit.tk.Cursor(this.txt);
        var parser = new kukit.kssp.Document(src, null, true);
        // Store event rules in the common list
        for (var i=0; i<parser.eventRules.length; i++) {
            var rule = parser.eventRules[i];
            // finish up the KSS on it
            try {
            rule.kssSelector.setIdAndClass();
            } catch(e) {
                // foolishly, we don't know the position at this point
                kukit.E = 'Undefined event : [';
                kukit.E += rule.kssSelector.namespace;
                kukit.E += ':' + rule.kssSelector.name + '].';
                throw new kukit.err.tk.ParsingError(kukit.E);
            }
            this.rules.push(rule);
        }
    } catch(e) {
       // ParsingError are logged.
       if (e.name == 'ParsingError') {
           var msg = 'Error parsing KSS at ' + this.href;
           msg += ' : ' + e.toString();
           kukit.logFatal(msg);
           throw msg;
       } else {
           throw e;
       }
    }
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.er = {};


kukit.er.eventClassCounter = 0;

/*
*
* class CommandRegistry
*
* available for plugin registration
*
* usage:
*
*  kukit.eventsGlobalRegistry.register(namespace, eventName, func, 
*    bindMethodName, defaultActionMethodName);
*  
*     namespace = null: means global namespace
*     defaultActionMethodName = null: if there is no default action implemented
*     func must be a class (constructor) function, this is the class that
*           implements the binder.
*/
kukit.er.EventRegistry = function () {
    this.content = {};
    this.classes = {};
    this.eventSets = [];
};

/* binder registration */

kukit.er.EventRegistry.prototype.registerBinder = function(className, func) {
    if (typeof(func) == 'undefined') {
        kukit.E = 'func argument is mandatory when registering an event';
        kukit.E += ' binder (EventRegistry.registerBinder).';
        throw kukit.E;
    }
    if (this.classes[className]) {
        // Do not allow redefinition
        var msg = 'Error : event class [' + className + '] already registered.';
        kukit.logError(msg);
        return;
    
    }
    // Decorate and store the class
    kukit.er.decorateEventBinderClass(func);
    this.classes[className] = func;
};

kukit.er.EventRegistry.prototype.existsBinder = function(className) {
    var func = this.classes[className];
    return (typeof(func) != 'undefined');
};

kukit.er.EventRegistry.prototype.getBinder = function(className) {
    var func = this.classes[className];
    if (! func) {
        // not found
        kukit.E = 'Error : undefined event setup type [' + className + '].';
        throw kukit.E;
        }
    return func;
};

/* events (methods) registration  helpers (not to be called directly) */

kukit.er.EventRegistry.prototype._register = 
    function(namespace, eventName, klass,
        bindMethodName, defaultActionMethodName, iterName) {
    if (typeof(defaultActionMethodName) == 'undefined') {
        kukit.E = 'Missing arguments when calling [EventRegistry.register].';
        throw kukit.E;
    }
    // Find out the class name. (Not specified now.)
    var className = klass.prototype.__className__;
    if (typeof(className) == 'undefined') {
        // Create a className, and register it too.
        className = '' + kukit.er.eventClassCounter;
        kukit.er.eventClassCounter += 1;
        this.registerBinder(className, klass);
        klass.prototype.__className__ = className;
    }
    if (!eventName) {
        kukit.E = '[eventName] argument cannot be empty when registering';
        kukit.E += ' an event with [EventRegistry.register].';
        throw kukit.E;
    }
    var key = this._getKey(namespace, eventName);
    var entry = this.content[key];
    if (typeof(entry) != 'undefined') {
        if (key[0] == '-') {
            key = key.substring(1);
        }
        kukit.E = 'Attempt to register key [' + key;
        kukit.E += '] twice when registering';
        kukit.E += ' an event with [EventRegistry.register].';
        throw kukit.E;
    }
    // check bindMethodName and defaultActionMethodName
    if (bindMethodName && ! klass.prototype[bindMethodName]) {
        kukit.E = 'In EventRegistry.register bind method [' + bindMethodName;
        kukit.E += '] is undefined for event [' + eventName;
        kukit.E += '] namespace [' + namespace + '].';
        throw kukit.E;
    }
    if (defaultActionMethodName && ! klass.prototype[defaultActionMethodName]) {
        kukit.E = 'In EventRegistry.register default action method [';
        kukit.E += defaultActionMethodName + '] is undefined for event [';
        kukit.E += eventName + '] namespace [' + namespace + '].';
        throw kukit.E;
    }
    // check the iterator.
    if  (! kukit.er.getBindIterator(iterName)) {
        kukit.E = 'In EventRegistry.register unknown bind iterator [';
        kukit.E += iterName + '].';
        throw kukit.E;
    }
    // register it
    this.content[key] = {
        'className': className,
        'bindMethodName': bindMethodName,
        'defaultActionMethodName': defaultActionMethodName,
        'iterName': iterName
        };
};

/* events (methods) binding [ForAll] registration */

kukit.er.EventRegistry.prototype._registerEventSet =
    function(namespace, names, iterName, bindMethodName) {
    // At this name the values should be checked already. so this should
    // be called _after_ _register.
    this.eventSets.push({
        'namespace': namespace, 
        'names': names,
        'iterName': iterName,
        'bindMethodName': bindMethodName
        });
};

/* there are the actual registration methods, to be called from plugins */

kukit.er.EventRegistry.prototype.register =
    function(namespace, eventName, klass, bindMethodName,
        defaultActionMethodName) {
    this._register(namespace, eventName, klass, bindMethodName,
        defaultActionMethodName, 'EachLegacy');
    this._registerEventSet(namespace, [eventName], 'EachLegacy',
        bindMethodName);
};

kukit.er.EventRegistry.prototype.registerForAllEvents =
    function(namespace, eventNames, klass,
        bindMethodName, defaultActionMethodName, iterName) {
    if (typeof(eventNames) == 'string') {
        eventNames = [eventNames];
        }
    for (var i=0; i<eventNames.length; i++) {
        var eventName = eventNames[i];
        this._register(namespace, eventName, klass, bindMethodName, 
            defaultActionMethodName, iterName);
    }
    this._registerEventSet(namespace, eventNames, iterName, bindMethodName);
};

kukit.er.EventRegistry.prototype._getKey = function(namespace, eventName) {
    if (namespace == null) {
        namespace = '';
    } else if (namespace.split('-') > 1) {
        kukit.E = 'In [EventRegistry.register], [namespace] cannot have';
        kukit.E += 'dashes.';
        throw kukit.E;
    }
    return namespace + '-' + eventName;
};

kukit.er.EventRegistry.prototype.exists = function(namespace, eventName) {
    var key = this._getKey(namespace, eventName);
    var entry = this.content[key];
    return (typeof(entry) != 'undefined');
};

kukit.er.EventRegistry.prototype.get = function(namespace, eventName) {
    var key = this._getKey(namespace, eventName);
    var entry = this.content[key];
    if (typeof(entry) == 'undefined') {
        if (key.substr(0, 1) == '-') {
            key = key.substring(1);
            kukit.E = 'Error : undefined global event key ';
            kukit.E += key + ' (or maybe namespace is missing ?).';
        } else {
            kukit.E = 'Error : undefined event key [' + key + '].';
        }
        throw kukit.E;
    } 
    return entry;
};

kukit.eventsGlobalRegistry = new kukit.er.EventRegistry();


/* XXX deprecated methods, to be removed asap */

kukit.er.eventRegistry = {};
kukit.er.eventRegistry.register = function(namespace, eventName, klass,
        bindMethodName, defaultActionMethodName) {
    var msg = 'Deprecated kukit.er.eventRegistry.register,';
    msg += ' use kukit.eventsGlobalRegistry.register instead ! [';
    msg += namespace + '-' + eventName + '].';
    kukit.logWarning(msg);
    kukit.eventsGlobalRegistry.register(namespace, eventName, klass,
        bindMethodName, defaultActionMethodName);
};

/* Event class decoration 
*
* poor man's subclassing
* This is called automatically on registration, to dress
* up the event class with the necessary methods
*
*/

/* Provide callins on the state instance that execute a given
*  continuation event.
*  Parameters will be the ones specified in the call + 
*  those defined in the rule will be added too. (Parameters can
*  be accessed with the [pass] kss parameter provider.)
*
* Call examples: 
*
* trigger an event bound to a given state instance, same node
*
*     binderInstance.__continueEvent__('doit', oper.node, {'extravalue': '5'});
*
*   with kss rule:
*
*     node.selector:doit {
*         action-client: log;
*         log-message: pass(extravalue);
*     }
*
*  or
*
*     behaviour.selector:doit {
*         action-client: log;
*         log-message: pass(extravalue);
*     }
*
* trigger an event bound to a given state instance, and the document
* (different from current scope)
*
*     binderInstance.__continueEvent__('doit', null, {'extravalue': '5'});
*
*   with kss rule:
*
*     document:doit {
*         action-client: log;
*         log-message: pass(extravalue);
*     }
*
*  or
*
*     behaviour.selector:doit {
*         action-client: log;
*         log-message: pass(extravalue);
*     }
*
* trigger an event on all the nodes + document bound to a given state instance
*
*     binderInstance.__continueEvent_allNodes__('doit', {'extravalue': '5'});
*
*   with kss rule:
*
*     node.selector:doit {
*         action-client: log;
*         log-message: pass(extravalue);
*     }
*
* p.s. oper is not required to make it easy to adapt existing code
* so we create a new oper below
*/

kukit.er.EventBinder__continueEvent__ =
    function(name, node, defaultParameters) {
    // Trigger a continuation event bound to a given state instance, given node
    // (or on document, if node = null)
    //
    var oper = new kukit.op.Oper();
    oper.node = node;
    if (node) {
        // if we found the binding, just use that
        var info = kukit.engine.binderInfoRegistry.getBinderInfoById(
            this.__binderId__);
        var newOper = info.bound.getBoundOperForNode(name, node);
        if (newOper) {
            oper = newOper;
        }
    } else {
        oper.eventRule =  kukit.engine.documentRules.getMergedRule(
            'document', name, this);
    }
    // Look up the behaviour rule, if any.
    var behav_eventRule =  kukit.engine.documentRules.getMergedRule(
        'behaviour', name, this);
    if (behav_eventRule) {
        if (! oper.eventRule) {
            // There was no node matching for the rule, use behaviour rule
            // this allows to set up parametrized actions in general.
            oper.eventRule = behav_eventRule;
        } else {
            // XXX this case should go away, as we should check
            // this already from binding time
            // and signal the appropriate error.
            // Also note that behaviour roles will only be allowed
            // for "non-binding" events.
            var msg = 'Behaviour rule for continuation event [' + name;
            msg += '] will be ignored, because we found an explicit rule.';
            kukit.logError(msg);
        }
    }
    // If parms are specified in the call, use them.
    if (typeof(defaultParameters) != 'undefined') {
        oper.defaultParameters = defaultParameters;
    } else {
        oper.defaultParameters = {};
    }
    // if eventRule is null here, we can yet have the default method, so go on.
    this._EventBinder_triggerEvent(name, oper);
    kukit.logDebug('Continuation event [' + name + '] executed on same node.');
};

kukit.er.EventBinder__continueEvent_allNodes__ =
    function(name, defaultParameters) {
    // Trigger an event bound to a given state instance, on all nodes.
    // (or on document, if node = null)
    // if no other nodes execute.
    var executed = 0;
    // Normal rules. If any of those match, execute them too
    // each on the node that it selects - not on the original node.
    var oper = new kukit.op.Oper();
    var info = kukit.engine.binderInfoRegistry.getBinderInfoById(
        this.__binderId__);
    var opers = info.bound.getBoundOpers(name);
    for (var i=0; i<opers.length; i++) {
        var oper = opers[i];
        var newOper = oper.clone();
        if (typeof(defaultParameters) != 'undefined') {
            newOper.defaultParameters = defaultParameters;
        } else {
            newOper.defaultParameters = {};
        }
        this._EventBinder_triggerEvent(name, newOper);
        executed += 1;
    }
    kukit.logDebug('Event [' + name + '] executed on ' + executed + ' nodes.');
};

kukit.er.EventBinder_makeFuncToBind = function(name, node) {
   var executor = new kukit.er.LateBinder(this, name, node);
   return function() {
       executor.executeActions();
   };
};

kukit.er.LateBinder = function(binderInstance, name, node) {
    this.binderInstance = binderInstance;
    this.name = name;
    this.node = node;
    this.bound = null;
};

kukit.er.LateBinder.prototype.executeActions = function() {
    if (! this.bound) {
           var msg = 'Attempt of late binding for event [' + this.name;
           msg += '], node [' + this.node.nodeName + '].';
           kukit.log(msg);
        if (kukit.hasFirebug) {
            kukit.log(this.node);
        }
        var info = kukit.engine.binderInfoRegistry.getBinderInfoById(
            this.binderInstance.__binderId__);
        var oper = info.bound.getBoundOperForNode(this.name, this.node);
        if (oper) {
            // (if eventRule is null here, we could still have the default
            // method, so go on.)
            oper.parms = {};
            this.bound = function() {
                this.binderInstance._EventBinder_triggerEvent(this.name, oper);
            };
            kukit.log('Node bound.');
        } else {
            kukit.logWarning('No node bound.');
            this.bound = function() {};
        }
    }
    this.bound();
};        

kukit.er.EventBinder_triggerEvent = function(name, oper) {
    // Private. Called from __continueEvent__ or from main event execution.
    oper.binderInstance = this;
    if (oper.eventRule) {
        // Call the actions, if we had an event rule.
        // This includes calling the default action.
        oper.eventRule.actions.execute(oper);
    } else {
        // In case there is no event rule, just call the default event action.
        var namespace = this.__eventNamespace__;
        var msg = 'Calling implicit event [' + name + '] on namespace [';
        msg += namespace + '].';
        kukit.logDebug(msg);
        var success = oper.executeDefaultAction(name, true);
        if (! success) {
            // instead of the standard message give more specific reason:
            // either way we should have executed something...
            kukit.E = 'Could not trigger event name [' + name;
            kukit.E += '] on namespace [' + namespace;
            kukit.E += '], because there is neither an explicit KSS rule,';
            kukit.E += ' nor a default method';
            throw kukit.E;
        }
    }
};

/* (default) method call handling */

kukit.er.EventBinder_callMethod = function(namespace, name, oper, methodName) {
    // hidden method for calling just a method and checking that is exists.
    // (called from oper)
    var method = this[methodName];
    if (! method) {
        kukit.E = 'Could not trigger event name [' + name;
        kukit.E += '] on namespace [' + namespace;
        kukit.E += '], because the method [' + methodName + '] does not exist.';
        throw kukit.E;
    }
    // call it
    oper.binderInstance = this;
    method.call(this, name, oper);
};

kukit.er.decorateEventBinderClass = function(cls) {
    cls.prototype.__continueEvent__ = kukit.er.EventBinder__continueEvent__;
    cls.prototype.__continueEvent_allNodes__ =
        kukit.er.EventBinder__continueEvent_allNodes__;
    cls.prototype._EventBinder_triggerEvent = kukit.er.EventBinder_triggerEvent;
    cls.prototype._EventBinder_callMethod = kukit.er.EventBinder_callMethod;
    cls.prototype.__makeFuncToBind__ = kukit.er.EventBinder_makeFuncToBind;
};

/* Event instance registry 
*
* class BinderInfoRegistry
*
*  used in run-time to keep track of the event instances
*
*/

kukit.er.BinderInfoRegistry = function () {
    this.info = {};
};

kukit.er.BinderInfoRegistry.prototype.getOrCreateBinderInfo =
    function (id, className, namespace) {
    // Get or create the event.
    var binderInfo = this.info[id];
    if (typeof(binderInfo) == 'undefined') {
        // Create a new event.
        var msg = 'Instantiating event id [' + id + '], className [';
        msg += className + '], namespace [' + namespace + '].';
        kukit.logDebug(msg);
        var binder = kukit.eventsGlobalRegistry.getBinder(className);
        var binderInstance = new binder();
        
        binderInfo = this.info[id] = new kukit.er.BinderInfo(binderInstance);

        // decorate it with id and class
        binderInstance.__binderId__ = id;
        binderInstance.__binderClassName__ = className;
        binderInstance.__eventNamespace__ = namespace;
        // store the bound rules
        //binderInstance.__bound_rules__ = [];
    } else if (binderInfo.getBinderInstance().__binderClassName__ != 
        className) {
        // just paranoia
        kukit.E = 'Conflicting class for event id [' + id + '], [';
        kukit.E += binderInfo.getBinderInstance().__binderClassName__;
        kukit.E += '] != [' + className + '].';
        throw kukit.E;
    }
    return binderInfo;
};

kukit.er.BinderInfoRegistry.prototype.getBinderInfoById = function (id) {
    // Get an event.
    var binderInfo = this.info[id];
    if (typeof(binderInfo) == 'undefined') {
        kukit.E = 'Event with id [' + id + '] not found.';
        throw kukit.E;
    }
    return binderInfo;
};

kukit.er.BinderInfoRegistry.prototype.getSingletonBinderInfoByName =
    function (namespace, name) {
    //Get className
    var className = kukit.eventsGlobalRegistry.get(namespace, name).className;
    // Get an event.
    var id = kukit.rd.makeId(namespace, className);
    var binderInfo = this.info[id];
    if (typeof(binderInfo) == 'undefined') {
        kukit.E = 'Singleton event with namespace [' + namespace;
        kukit.E += '] and (event) name [' + name + '] not found.';
        throw kukit.E;
    }
    return binderInfo;
};

kukit.er.BinderInfoRegistry.prototype.startBindingPhase = function () {
    // At the end of the binding phase, we want to process our events. This
    // must include all the binder instances we bound in this phase.
    for (var id in this.info) {
        var binderInfo = this.info[id];
        // process binding on this instance.
        binderInfo.startBindingPhase();
    }
};

kukit.er.BinderInfoRegistry.prototype.processBindingEvents = function () {
    // At the end of the binding phase, we want to process our events. This
    // must include all the binder instances we bound in this phase.
    for (var id in this.info) {
        var binderInfo = this.info[id];
        // process binding on this instance.
        binderInfo.processBindingEvents();
    }
};

/*
* class BinderInfo
*
* Information about the given binder instance. This contains the instance and
* various binding info. Follows the workflow of the binding in different stages.
*
*/

kukit.er.BinderInfo = function (binderInstance) {
    this.binderInstance = binderInstance;
    this.bound = new kukit.er.OperRegistry();
    this.startBindingPhase();
};

kukit.er.BinderInfo.prototype.getBinderInstance = function () {
    return this.binderInstance;
};

kukit.er.BinderInfo.prototype.startBindingPhase = function () {
    // The bindind phase starts and it has the information for
    // the currently on-bound events.
    this.binding = new kukit.er.OperRegistry();
};

kukit.er.BinderInfo.prototype.bindOper = function (oper) {
    // We mark a given oper. This means a binding on the binderInstance 
    // for given event, node and eventRule (containing event namespace,
    // name, and evt- parms.)
    //
    // first see if it can go to already bound ones
    this.bound.checkOperBindable(oper);
    // then register it properly to the binding events
    this.binding.bindOper(oper);
};

kukit.er.BinderInfo.prototype.processBindingEvents = function () {
    // We came to the end of the binding phase. Now we process all our binding
    // events, This will do the actual binding on the browser side.
    this.binding.processBindingEvents(this.binderInstance);
    // Now we to add these to the new ones.
    this.binding.propagateTo(this.bound);
    // Delete them from the registry, to protect against accidents.
    this.binding = null;
};


/*
*  class OperRegistry
*
*  OperRegistry is associated with a binder instance in the 
*  BinderInfoRegistry, and remembers bounding information.
*  This is used both to remember all the bindings for a given
*  instance, but also just to remember the bindings done during
*  a given event setup phase.
*/

kukit.er.OperRegistry = function () {
    this.infoPerName = {};
    this.infoPerNode = {};
};

// XXX XXX XXX we can do this without full cloning, more efficiently.
kukit.er.OperRegistry.prototype.propagateTo = function (newreg) {
    for (var key in this.infoPerName) {
        var rulesPerName = this.infoPerName[key];
        for (var name in rulesPerName) {
            var oper = rulesPerName[name];
            newreg.bindOper(oper);
        }
    }
};

kukit.er.OperRegistry.prototype.checkOperBindable =
    function (oper, name, nodeHash) {
    // Check if the binding with this oper could be done.
    // Throw exception otherwise.
    //
    // Remark. We need  different check and bind method,
    // because we need to bind to the currently
    // processed nodes, but we need to check duplication 
    // in all the previously bound nodes.
    var info = this.infoPerName;
    // name and nodeHash are for speedup.
    if (typeof(nodeHash) == 'undefined') {
        name = oper.eventRule.kssSelector.name;
        nodeHash = kukit.rd.hashNode(oper.node);
    }
    var rulesPerName = info[name];
    if (typeof(rulesPerName) == 'undefined') {
        // Create an empty list.
        rulesPerName = info[name] = {};
    } else if (typeof(rulesPerName[nodeHash]) != 'undefined') {
        kukit.E = 'Mismatch in bind registry,[ ' + name;
        kukit.E += '] already bound to node in this instance.'; 
        throw kukit.E;
    }
    return rulesPerName;
};
    
kukit.er.OperRegistry.prototype.bindOper = function (oper) {
    // Marks binding between binderInstance, eventName, node.
    var name = oper.eventRule.kssSelector.name;
    var nodeHash = kukit.rd.hashNode(oper.node);
    var rulesPerName = this.checkOperBindable(oper, name, nodeHash);
    rulesPerName[nodeHash] = oper;
    // also store per node info
    var rulesPerNode = this.infoPerNode[nodeHash];
    if (typeof(rulesPerNode) == 'undefined') {
        // Create an empty list.
        rulesPerNode = this.infoPerNode[nodeHash] = {};
    }
    rulesPerNode[name] = oper;
};

// XXX This will need refactoring.
/// We would only want to lookup from our registry and not the other way around.
kukit.er.OperRegistry.prototype.processBindingEvents = 
    function (binderInstance) {
    var eventRegistry = kukit.eventsGlobalRegistry;
    for (var i=0; i < eventRegistry.eventSets.length; i++) {
        var eventSet = eventRegistry.eventSets[i];
        // Only process binding events (and ignore non-binding ones)
        if (eventSet.bindMethodName) {
            if (binderInstance.__eventNamespace__ == eventSet.namespace) {
                // Process the binding event set.
                // This will call the actual bindmethods
                // according to the specified iterator.
                var iterator = kukit.er.getBindIterator(eventSet.iterName);
                iterator.call(this, eventSet, binderInstance);
            }
        }
    }
};

// XXX The following methods will probably disappear as iterators 
// replace their functionality.

kukit.er.OperRegistry.prototype.getBoundOperForNode = function (name, node) {
    // Get the oper that is bound to a given eventName
    // to a node in this binderInstance
    // returns null, if there is no such oper.
    var rulesPerName = this.infoPerName[name];
    if (typeof(rulesPerName) == 'undefined') {
        return null;
    }
    var nodeHash = kukit.rd.hashNode(node);
    var oper = rulesPerName[nodeHash];
    if (typeof(oper) == 'undefined') {
        return null;
    }
    // Return it
    return oper;
};

kukit.er.OperRegistry.prototype.getBoundOpers = function (name) {
    // Get the opers bound to a given eventName (to any node)
    // in this binderInstance
    var opers = [];
    var rulesPerName = this.infoPerName[name];
    if (typeof(rulesPerName) != 'undefined') {
        // take the values as a list
        for (var nodeHash in rulesPerName) {
            opers.push(rulesPerName[nodeHash]);
        }
    }
    // Return it
    return opers;
};

// Iterators
// The getBindIterator returns a function that gets executed on
// the oper registry.
//
// Iterators receive the eventSet as a parameter
// plus a binderInstance and a method. They need to iterate by calling this
// as method.call(binderInstance, ...); where ... can be any parms this
// given iteration specifies.
//

kukit.er.getBindIterator = function(iterName) {
    // attempt to find canonical version of string
    // and shout if it does not match.
    // String must start uppercase.
    var canonical = iterName.substring(0, 1).toUpperCase() + 
            iterName.substring(1);
    if (iterName != canonical) {
        // BBB 2007.12.31, this will turn into an exception.
        var msg = 'Deprecated the lowercase iterator names in last ';
        msg += 'parameters of ';
        msg += 'kukit.eventsGlobalRegistry.registerForAllEvents, use [';
        msg += canonical + '] instead of [' + iterName + '] (2007-12-31)';
        kukit.logWarning(msg);
        iterName = canonical;
        }
    return kukit.er.OperRegistry.prototype['_iterate' + iterName];
};

kukit.er.OperRegistry.prototype.callBindMethod = 
    function (eventSet, binderInstance, p1, p2, p3, p4, p5, p6) {
    var method = binderInstance[eventSet.bindMethodName];
    // Protect the binding for better logging
    try {
        method.call(binderInstance, p1, p2, p3, p4, p5, p6);
    } catch(e) {
        var msg = e;
        var names = eventSet.names;
        var namespace = eventSet.namespace;
        kukit.E = new kukit.err.rd.EventBindError(msg, names, namespace);
        throw kukit.E;
    }
};

// This calls the bind method by each bound oper one by one.
// Eventname and funcToBind are passed too.
// this is the legacy ([EachLegacy]) way
kukit.er.OperRegistry.prototype._iterateEachLegacy =
    function (eventSet, binderInstance) {
    for (var i=0; i<eventSet.names.length; i++) {
        var rulesPerName = this.infoPerName[eventSet.names[i]];
        if (typeof(rulesPerName) != 'undefined') {
            for (var nodeHash in rulesPerName) {
                var oper = rulesPerName[nodeHash];
                var eventName = oper.getEventName();
                var funcToBind = oper.makeExecuteActionsHook();
                this.callBindMethod(eventSet, binderInstance, eventName,
                    funcToBind, oper);
            }
        }
    }
};


// This calls the bind method by each bound oper one by one.
// Eventname and funcToBind are passed too.
// this is the preferred ([Each]) way. Parameters are different from EachLegacy.
kukit.er.OperRegistry.prototype._iterateEach =
    function (eventSet, binderInstance) {
    for (var i=0; i<eventSet.names.length; i++) {
        var rulesPerName = this.infoPerName[eventSet.names[i]];
        if (typeof(rulesPerName) != 'undefined') {
            for (var nodeHash in rulesPerName) {
                var oper = rulesPerName[nodeHash];
                this.callBindMethod(eventSet, binderInstance, oper);
            }
        }
    }
};

// This calls the bind method by the list of bound opers
kukit.er.OperRegistry.prototype._iterateOpers =
    function (eventSet, binderInstance) {
    var opers = [];
    for (var i=0; i<eventSet.names.length; i++) {
        var rulesPerName = this.infoPerName[eventSet.names[i]];
        if (typeof(rulesPerName) != 'undefined') {
            for (var nodeHash in rulesPerName) {
                opers.push(rulesPerName[nodeHash]);
            }
        }
    }
    this.callBindMethod(eventSet, binderInstance, opers);
};

// This calls the bind method by a mapping eventName:oper
// per each bound node individually
kukit.er.OperRegistry.prototype._iterateNode =
    function (eventSet, binderInstance) {
    for (var nodeHash in this.infoPerNode) {
        var rulesPerNode = this.infoPerNode[nodeHash];
        // filter only the events we are interested in
        var filteredRules = {};
        var operFound = false;
        for (var i=0; i<eventSet.names.length; i++) {
            var name = eventSet.names[i];
            var oper = rulesPerNode[name];
            if (typeof(oper) != 'undefined') {
                filteredRules[name] = oper;
                operFound = oper;
            }
        }
        // call it
        // All opers have the same node, the last one is yet in operFound, so
        // we use it as a second parameter to the call.
        // The method may or may not want to use this.
        if (operFound) {
            this.callBindMethod(eventSet, binderInstance, filteredRules,
                operFound.node);
        }
    }
};

// This calls the bind method once per instance, by a list of
// items, where item.node is the node and item.opersByEventName nodeHash:item
// in item there is item.node and item.opersByEventName
kukit.er.OperRegistry.prototype._iterateAllNodes = 
    function (eventSet, binderInstance) {
    var items = [];
    var hasResult = false;
    for (var nodeHash in this.infoPerNode) {
        var rulesPerNode = this.infoPerNode[nodeHash];
        // filter only the events we are interested in
        var filteredRules = {};
        var operFound = false;
        for (var i=0; i<eventSet.names.length; i++) {
            var name = eventSet.names[i];
            var oper = rulesPerNode[name];
            if (typeof(oper) != 'undefined') {
                filteredRules[name] = oper;
                operFound = oper;
            }
        }
        if (operFound) {
            var item = {node: operFound.node, 
                opersByEventName: filteredRules};
            items.push(item);
            hasResult = true;
        }
    }
    // call the binder method
    if (hasResult) {
        this.callBindMethod(eventSet, binderInstance, items);
    }
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.ar = {};

/*
*  class ActionRegistry
* 
*  The local event actions need to be registered here.
*/
kukit.ar.ActionRegistry = function () {
    this.content = {};
};

kukit.ar.ActionRegistry.prototype.register = function(name, func) {
    if (typeof(func) == 'undefined') {
        kukit.E = '[func] argument is mandatory when registering an action';
        kukit.E += ' [ActionRegistry.register].';
        throw kukit.E;
    }
    if (this.content[name]) {
        // Do not allow redefinition
        kukit.logError('Error : action [' + name + '] already registered.');
        return;
        }
    this.content[name] = func;
};

kukit.ar.ActionRegistry.prototype.exists = function(name) {
    var entry = this.content[name];
    return (typeof(entry) != 'undefined');
};

kukit.ar.ActionRegistry.prototype.get = function(name) {
    var func = this.content[name];
    if (! func) {
        // not found
        kukit.E = 'Error : undefined local action [' + name + '].';
        throw kukit.E;
        }
    return func;
};

kukit.actionsGlobalRegistry = new kukit.ar.ActionRegistry();


/* XXX deprecated methods, to be removed asap */

kukit.ar.actionRegistry = {};
kukit.ar.actionRegistry.register = function(name, func) {
    var msg='Deprecated kukit.ar.actionRegistry.register, use ';
    msg += 'kukit.actionsGlobalRegistry.register instead !';
    kukit.logWarning(msg);
    kukit.actionsGlobalRegistry.register(name, func);
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Generic dom helpers */

kukit.dom = {};

kukit.dom.getPreviousSiblingTag = function(node) {
    var toNode = node.previousSibling;
    while ((toNode != null) && (toNode.nodeType != 1)) {
        toNode = toNode.previousSibling;
    }
    return toNode;
};

kukit.dom.getNextSiblingTag = function(node) {
    var toNode = node.nextSibling;
    while ((toNode != null) && (toNode.nodeType != 1)) {
        toNode = toNode.nextSibling;
    }
    return toNode;
};

kukit.dom.insertBefore = function(nodeFrom, parentNode, nodeTo) {
    var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ?
        nodeTo : nodeTo.ownerDocument;
    var nodes = nodeFrom.childNodes;
    var result = new Array();
    if(ownerDoc.importNode && (!kukit.HAVE_IE)) {
        for(var i=0;i < nodes.length;i++) {
            var imported = ownerDoc.importNode(nodes[i], true);
            result[i] = parentNode.insertBefore(imported, nodeTo);
        }
    } else {
        for(var i=0;i < nodes.length;i++) {
            var cloned = nodes[i].cloneNode(true);
            result[i] = parentNode.insertBefore(cloned, nodeTo);
        }
    }
    return result;
};

kukit.dom.appendChildren = function(nodes, toNode) {
    var ownerDoc = toNode.nodeType == Node.DOCUMENT_NODE ?
        toNode : toNode.ownerDocument;
    var result = new Array();
    if(ownerDoc.importNode && (!kukit.HAVE_IE)) {
        for(var i=0;i < nodes.length;i++) {
            result[i] = toNode.appendChild(ownerDoc.importNode(nodes[i], true));
        }
    }else{
        for(var i=0;i < nodes.length;i++) {
            result[i] = toNode.appendChild(nodes[i].cloneNode(true));
        }
    }
    return result;
};

kukit.dom.clearChildNodes = function(node) {
    //Maybe we want to get rid of sarissa once?
    Sarissa.clearChildNodes(node);
};

kukit.dom.forceToDom = function(param) {
    // This is a dirty helper to avoid rewriting existing stuff.
    // If param is not a dom, it converts to it.
    // This is to assure that all methods that accept a dom can accept a string
    // instead.
    if (typeof(param) == 'string') {
//
// now convert to dom
//

// param
// <option>test_xmlentity</option><option>tic</option>
// <option>tac&#32;toe</option>

// ***BROKEN*** param
// <option>test_htmlentity</option><option>tic</option>
// <option>tac&nbsp;toe</option>

// param
// <option>test_utf8</option><option>tic</option><option>tacűtoe</option>

// This is a good solution since it does not do magic to
// our html - BUT html is parsed as xml
// so we currently preprocess it on the server
// and remove html named entities from it.
//
        var rootText = '<html xmlns="http://www.w3.org/1999/xhtml"><div>';
        rootText += param + '</div></html>';
        var doc = (new DOMParser()).parseFromString(rootText, "text/xml");
        var root = doc.getElementsByTagName('div')[0];

        // XXX Sarissa bug; html docs would not have a
        // working serialize, and so importNodes would fail on them.
        // XXX Fixed in: Revision 1.23  - Sun Jul 10 18:53:53 2005 UTC
        // use at least 0.9.6.1
        
        param = root;
    }
    // Need to do this or else IE fails miserably.
    // importNode acts strangely.
    // on FF, you can execute it several times but the next condition
    //        always evaluated to False.
    // on IE, it is a big problem to execute this for the second time
    //        but it needs to be executed once, thus the condition
    if (param.ownerDocument != document) {
        param = document.importNode(param, true);
    }
    //alert(Sarissa.serialize(param));
    return param;
};

/*
*  really the query should start from the document root, but
*  limited to inNodes subtrees!
*/


kukit.dom.cssQuery = function(selector, inNodes) {
    // to eliminate possible errors
    if (typeof(inNodes) != 'undefined' && inNodes == null) {
        kukit.E = 'Selection error in kukit.dom.cssQuery';
        throw kukit.E;
    }
    return kukit.dom._cssQuery(selector, inNodes);
};

/*
 * Decide which query to use
 */

kukit.dom._cssQuery = function(selector, inNodes) {
    var USE_BASE2 = (typeof(base2) != 'undefined');
    if (USE_BASE2) {
        kukit.log('Using cssQuery from base2.');
        kukit.dom._cssQuery = kukit.dom._cssQuery_base2
    } else {
        kukit.log('Using original cssQuery.');
        kukit.dom._cssQuery = kukit.dom._cssQuery_orig
    }
    return kukit.dom._cssQuery(selector, inNodes);
};

kukit.dom._cssQuery_base2 = function(selector, inNodes) {
    // global scope, always.
    // This is very bad. However the binding makes sure that
    // nodes once bound will never be bound again
    // (also, noticed the following issue: cssQuery, when called
    // on an element, does not check the element itself.)
    var results = base2.DOM.Document.matchAll(document, selector);
    var nodes = [];
    for(var i = 0; i < results.length; i++) {
        nodes.push(results.item(i));
    }
    return nodes;
};

kukit.dom._cssQuery_orig = function(selector, inNodes) {
    // global scope, always.
    // This is very bad. However the binding makes sure that
    // nodes once bound will never be bound again
    // (also, noticed the following issue: cssQuery, when called
    // on an element, does not check the element itself.)
    var results = cssQuery(selector);
    return results;
};

kukit.dom.focus = function(node) {
    tagName = node.tagName.toLowerCase();
    if ((tagName == 'input') || (tagName == 'select')
       || (tagName == 'textarea')) {
        node.focus();
    } else {
        kukit.logWarning('Focus on node that cannot have focus !');
    }
};

/*
*  Gets the textual content of the node
*  if recursive=false (default), does not descend into sub nodes
*/
kukit.dom.textContent = function(node, recursive) {
    var value = kukit.dom._textContent(node, recursive);
    // replace newline with spaces
    value = value.replace(/\r\n/g, ' ');
    value = value.replace(/[\r\n]/g, ' ');
    return value;
};

kukit.dom._textContent = function(node, recursive) {
    if (typeof(recursive) == 'undefined') {
        recursive = false;
    }
    var value = '';
    var childnodes = node.childNodes;
    for (var i=0; i<childnodes.length; i++) {
        var child = childnodes[i];
        if (child.nodeType == 3) {
            // Only process text nodes
            value += child.nodeValue;
        } else if (recursive && child.nodeType == 1) {
            // recurr into element nodes
            value += kukit.dom.textContent(child, true);
        }
    }
    return value;
};

/* Getting and setting node attibutes 
   We need to provide workarounds for IE.
*/

kukit.dom.getAttribute = function(node, attrname) {
    if (attrname.toLowerCase() == 'style') {
        throw 'Style attribute is not allowed with getAttribute';
    }
    if (typeof(attrname) != 'string') {
        throw 'value error : attrname must be string';
    }
    // The code hereunder does not work for kssattr:xxx args
    // var value = node[argname];

    // try catch is needed in some cases on IE
    try {
        var value = node.getAttribute(attrname);
    }
    catch(e) {
        var value = null;
    }
    if (! value) {
        // Workarounds, in case we have not found above
        if (attrname.toLowerCase() == 'class') {
            // for IE
            value = node.className;
        } else if (attrname.toLowerCase() == 'for') {
            // for IE
            value = node.htmlFor;
        }
    }
    return value;
    // XXX We cannot distinguish between notfound and '', unfortunately
};

kukit.dom.setAttribute = function(node, attrname, value) {
    if (attrname.toLowerCase() == 'style') {
        throw 'Style attribute is not allowed with setAttribute';
    }
    else if (attrname.toLowerCase() == 'class') {
        // The class attribute cannot be set on IE, instead
        // className must be used. However node.className = x
        // works on both IE and FF.
        node.className = value;
    } else if (attrname.toLowerCase() == 'for') {
        // On IE, workaround is needed. Since I am not sure, I use both methods.
        node.htmlFor = value;
        node.setAttribute(attrname, value);
    } else if (attrname.toLowerCase() == 'checked') {
        // we need to convert this to boolean.
        value = ! (value == '' || value == 'false' || value == 'False');
        node.checked = value;
    } else {
        node.setAttribute(attrname, value);
    }
};


/* KSS attributes: a workaround to provide attributes
   in our own namespace.
   Since namespaced attributes (kss:name="value") are not allowed
   even in transitional XHTML, we must provide a way to
   substitute them. This is achieved by putting kssattr-name-value
   identifiers in the class attribute, separated by spaces.
   We only read these attributes, writing happens
   always in the kss namespace.
   XXX at the moment, deletion can be achieved with setting with
   a value ''. This is consistent with DOM behaviour as we seem to
   be getting '' for nonexistent values anyway.
*/

kukit.dom.kssAttrNamespace = 'kssattr';

kukit.dom.getKssClassAttribute = function(node, attrname) {
    // Gets a given kss attribute from the class
    var klass = kukit.dom.getAttribute(node, 'class');
    var result = null;
    if (klass) {
        var splitclass = klass.split(/ +/);
        for (var i=0; i<splitclass.length; i++) {
            var elem = splitclass[i];
            var splitelem = elem.split('-', 3);
            if (splitelem.length == 3 &&
                splitelem[0] == kukit.dom.kssAttrNamespace
                    && splitelem[1] == attrname) {
                // Found it. (The last one will be valid,
                // in case of duplication)
                var index = splitelem[0].length + splitelem[1].length + 2;
                result = elem.substr(index);
            }

        }
    }
    return result;
};

kukit.dom.getKssAttribute = function(node, attrname) {
    // Gets a given kss attribute 
    // first from the namespace, then from the class
    var fullName = kukit.dom.kssAttrNamespace + ':' + attrname;
    var result = kukit.dom.getAttribute(node, fullName);
    // XXX if this was '' it is the same as notfound,
    // so it shadows the class attribute!
    // This means setting an attribute to '' is the same as deleting it - 
    // at least at the moment
    if (! result) {
        result = kukit.dom.getKssClassAttribute(node, attrname);
    }
    return result;
};

kukit.dom.setKssAttribute = function(node, attrname, value) {
    // Sets a given kss attribute on the namespace
    var fullName = kukit.dom.kssAttrNamespace + ':' + attrname;
    kukit.dom.setAttribute(node, fullName);
};

/* Recursive getting of node attributes
   getter is a function that gets the value from the node.
*/

kukit.dom.getRecursiveAttribute =
    function(node, attrname, recurseParents, getter) {
    var value = getter(node, attrname);
    if (recurseParents) {
        var element = node;
        // need to recurse even if value="" !
        // We cannot figure out if there exists
        // and attribute in a crossbrowser way, or it is set to "".
        while (! value) {
            element = element.parentNode;
            if (! element || ! element.getAttribute) {
                break;
            }
            value = getter(element, attrname);
        }
    } 
    if (typeof(value) == 'undefined') {
        // notfound arguments will get null
        value = null;
    }
    return value;
};



/*
*  class EmbeddedContentLoadedScheduler
*
*  Scheduler for embedded window content loaded
*/
kukit.dom.EmbeddedContentLoadedScheduler =
    function(framename, func, autodetect) {
    this.framename = framename;
    this.func = func;
    this.autodetect = autodetect;
    var self = this;
    var f = function() {
        self.check();
    };
    this.counter = new kukit.ut.TimerCounter(250, f, true);
    // check immediately.
    //this.counter.timeout();
    // XXX can't execute immediately, it fails on IE.
    this.counter.start();
};

/*
* From http://xkr.us/articles/dom/iframe-document/
* Note it's not necessary for the iframe to have the name
* attribute since we don't access it from window.frames by name.
*/
kukit.dom.getIframeDocument = function(framename) {
    var iframe = document.getElementById(framename);
    var doc = iframe.contentWindow || iframe.contentDocument;
    if (doc.document) {
        doc = doc.document;
    }
    return doc;
};

kukit.dom.EmbeddedContentLoadedScheduler.prototype.check = function() {
    
    kukit.logDebug('Is iframe loaded ?');
    
    var doc = kukit.dom.getIframeDocument(this.framename);

    // quit if the init function has already been called
    // XXX I believe we want to call the function too, then
    // XXX attribute access starting with _ breaks full compression,
    // even in strings
    //if (doc._embeddedContentLoadedInitDone) {
    if (doc['_' + 'embeddedContentLoadedInitDone']) {
        var msg = 'Iframe already initialized, but we execute the action';
        msg += ' anyway, as requested.';
        kukit.logWarning(msg);
        this.counter.restart = false;
    }

    // autodetect=false implements a more reliable detection method
    // that involves cooperation from the internal document. In this
    // case the internal document sets the _kssReadyForLoadEvent attribute
    // on the document, when loaded. It is safe to check for this in any 
    // case, however if this option is selected, we rely only on this, 
    // and skip the otherwise problematic default checking.
    // XXX attribute access starting with _ breaks full compression,
    // even in strings
    //if (typeof doc._kssReadyForLoadEvent != 'undefined') {
    if (typeof doc['_' + 'kssReadyForLoadEvent'] != 'undefined') {
        this.counter.restart = false;
    } 

    if (this.autodetect && this.counter.restart) {

        // obviously we are not there... this happens on FF
        if (doc.location.href == 'about:blank') {
            return;
        } /* */
        
        // First check for Safari or
        // if DOM methods are supported, and the body element exists
        // (using a double-check including document.body,
        // for the benefit of older moz builds [eg ns7.1] 
        // in which getElementsByTagName('body')[0] is undefined,
        // unless this script is in the body section)
        
        if(/KHTML|WebKit/i.test(navigator.userAgent)) {
            if(/loaded|complete/.test(doc.readyState)) {
                this.counter.restart = false;
            }
        } else if(typeof doc.getElementsByTagName != 'undefined'
            && (doc.getElementsByTagName('body')[0] != null ||
                doc.body != null)) {
            this.counter.restart = false;
        } /* */

    }

    if ( ! this.counter.restart) {
        kukit.logDebug('Yes, iframe is loaded.');
        // XXX attribute access starting with _ breaks full compression,
        // even in strings
        // doc._embeddedContentLoadedInitDone = true;
        doc['_' + 'embeddedContentLoadedInitDone'] = true;
        this.func();
    }
};

kukit.dom.getNsTags = function(dom, tagName) {
    if (dom.getElementsByTagNameNS) { 
        tags = dom.getElementsByTagNameNS('http://www.kukit.org/commands/1.0',
            tagName);
    } else {
        //IE does not know DOM2
        tags = dom.getElementsByTagName('kukit:' + tagName);
    }
    return tags;
};

/*
 * Cookie handling code taken from: 
 * http://www.quirksmode.org/js/cookies.html
 */

kukit.dom.createCookie = function(name, value, days) {
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        var expires = "; expires=" + date.toGMTString();
    }
    else var expires = "";
    document.cookie = name + "=" + value + expires + "; path=/";
};

// we get this from kukit utils.js. We needed an early
// definition there, because logging is needed from the
// very beginning.
kukit.dom.readCookie = kukit.readCookie;

kukit.dom.eraseCookie = function(name) {
    createCookie(name, "", -1);
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Command registration */

kukit.cr = {};

/*
*  class CommandRegistry
*/
kukit.cr.CommandRegistry = function () {
    this.commands = {};
};

/* 
* This is the proposed way of registration, as we like all commands to be
*  client actions first.
* 
*  Examples:
* 
*  kukit.actionsGlobalRegistry.register('log', f1);
*  kukit.commandsGlobalRegistry.registerFromAction('log',
*       kukit.cr.makeGlobalCommand);
* 
*  kukit.actionsGlobalRegistry.register('replaceInnerHTML', f2);
*  kukit.commandsGlobalRegistry.registerFromAction('replaceInnerHTML',
*       kukit.cr.makeSelectorCommand);
*/
kukit.cr.CommandRegistry.prototype.registerFromAction =
    function(srcname, factory, name) {
    if (typeof(name) == 'undefined') {
        // allows to set a different name as the action name,
        // usable for backward
        // compatibility setups
        name = srcname;
    }
    // register a given action as a command, using the given vactor
    var f = kukit.actionsGlobalRegistry.get(srcname);
    factory(name, f);
};

kukit.cr.CommandRegistry.prototype.register = function(name, klass) {
    if (this.commands[name]) {
        // Do not allow redefinition
        var msg = 'ValueError : command [' + name + '] is already registered.';
        kukit.logError(msg);
        return;
        }
    this.commands[name] = klass;
};

kukit.cr.CommandRegistry.prototype.get = function(name) {
    var klass = this.commands[name];
    if (! klass) {
        // not found
        var msg = 'ValueError : no command registered under [' + name + '].';
        kukit.logError(msg);
       }
    return klass;
};

kukit.commandsGlobalRegistry = new kukit.cr.CommandRegistry();


/* XXX deprecated methods, to be removed asap */

kukit.cr.commandRegistry = {};
kukit.cr.commandRegistry.registerFromAction = function(srcname, factory, name) {
    var msg = 'Deprecated kukit.cr.commandRegistry.registerFromAction,';
    msg += ' use kukit.commandsGlobalRegistry.registerFromAction instead! (';
    msg += srcname + ')';
    kukit.logWarning(msg);
    kukit.commandsGlobalRegistry.registerFromAction(srcname, factory, name);
};

/* Command factories */

kukit.cr.makeCommand = function(selector, name, type, parms, transport) {
    var commandClass = kukit.commandsGlobalRegistry.get(name);
    var command = new commandClass();
    command.selector = selector;
    command.name = name;
    command.selectorType = type;
    command.parms = parms;
    command.transport = transport;
    return command;
};

kukit.cr._Command_execute = function(oper) {
    var newoper = oper.clone({
        'parms': this.parms,
        'orignode': oper.node,
        'node': null
        });
    this.executeOnScope(newoper);
};

kukit.cr._Command_execute_selector = function(oper) {
    var selfunc = kukit.selectorTypesGlobalRegistry.get(this.selectorType);
    // When applying the selection, the original event target will be used
    // as a starting point for the selection.
    var nodes = selfunc(this.selector, oper.orignode, {});
    var printType;
    if (this.selectorType) {
        printType = this.selectorType;
    } else {
        printType = 'default (';
        printType += kukit.selectorTypesGlobalRegistry.defaultSelectorType;
        printType += ')';
    }
    var msg = 'Selector type [' + printType + '], selector [';
    msg += this.selector + '], selected nodes [' + nodes.length + '].';
    kukit.logDebug(msg);
    if (!nodes || nodes.length == 0) {
        kukit.logWarning('Selector found no nodes.');
    }
    for (var i=0;i < nodes.length;i++) {
        oper.node = nodes[i];
        //XXX error handling for wrong command name
        kukit.logDebug('[' + this.name + '] execution.');
        this.executeOnSingleNode(oper);
    }
};

kukit.cr.makeSelectorCommand = function(name, executeOnSingleNode) {
    var commandClass = function() {};
    commandClass.prototype = {
        execute: kukit.cr._Command_execute,
        executeOnScope: kukit.cr._Command_execute_selector,
        executeOnSingleNode: executeOnSingleNode
    };
    kukit.commandsGlobalRegistry.register(name, commandClass); 
};

kukit.cr.makeGlobalCommand = function(name, executeOnce) {
    var commandClass = function() {};
    commandClass.prototype = {
        execute: kukit.cr._Command_execute,
        executeOnScope: executeOnce,
        executeOnSingleNode: executeOnce
    };
    kukit.commandsGlobalRegistry.register(name, commandClass); 
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.sa = {};

kukit.sa.ServerAction = function(name, oper) {
    this.url = oper.kssParms.kssUrl;
    if (typeof(this.url) == 'undefined') {
        this.url = name;
    }
    this.url = this.calculateAbsoluteURL(this.url);
    this.oper = oper;
    this.notifyServer();
};

kukit.sa.ServerAction.prototype.calculateAbsoluteURL = function(url) {
    //
    // If the url is an absolute path, it is used
    //
    // If the url is not an absolute path, it is put at the end of the context
    // url.
    //
    // example: url='@theview/getName',
    //          context='http://your.site.com/portal/folder/object'
    //
    //     result='http://your.site.com/portal/folder/object/@@theview/getName'
    //
    if (url.match(RegExp('/^https?:\/\//'))) {
        return url;
    } else {
        var result = kukit.engine.baseUrl + '/' + url;
        return result;
    }
};

// Backparameters can be used on command execution.
kukit.sa.ServerAction.prototype.notifyServer = function() {
    var self = this;
    var sendHook = function(queueItem) {
        // store the queue reception on the oper
        self.oper.queueItem = queueItem;
        self.reallyNotifyServer();
    };
    var timeoutHook = function(queueItem) {
        // store the queue reception on the oper
        self.oper.queueItem = queueItem;
        self.processError('timeout');
    };
    kukit.engine.requestManager.notifyServer(sendHook, this.url, timeoutHook);
};

kukit.sa.ServerAction.prototype.reallyNotifyServer = function() {
    // make a deferred callback
    var domDoc = new XMLHttpRequest();
    var self = this;
    var notifyServer_done  = function() {
        self.notifyServer_done(domDoc);
    };
    // convert params
    var query = new kukit.fo.FormQuery();
    for (var key in this.oper.parms) {
        query.appendElem(key, this.oper.parms[key]);
    }
    // also add the parms that result from submitting an entire form.
    // This is, unlike the normal parms, is a list. Keys and values are
    // added at the end of the query, without mangling names.
    var submitForm = this.oper.kssParms.kssSubmitForm;
    if (submitForm) {
        for (var i=0; i<submitForm.length; i++) {
            var item = submitForm[i];
            query.appendElem(item[0], item[1]);
        }
    }
    // encode the query
    var encoded = query.encode();
    // sending form
    var ts = new Date().getTime();
    //kukit.logDebug('TS: '+ts);
    var tsurl = this.url + "?kukitTimeStamp=" + ts;
    domDoc.open("POST", tsurl, true);
    domDoc.onreadystatechange = notifyServer_done;
    domDoc.setRequestHeader("Content-Type",
        "application/x-www-form-urlencoded");
    domDoc.send(encoded);
};

kukit.sa.ServerAction.prototype.notifyServer_done = function(domDoc) {
    var msg = 'Request readyState = ' + domDoc.readyState + '.';
    kukit.logDebug(msg);
    if (domDoc.readyState == 4) {
        // notify the queue that we are done
        var success = this.oper.queueItem.receivedResult();
        // We only process if the response has not been timed
        // out by the queue in the meantime.
        if (success) {
            // catch the errors otherwise won't get logged.
            // In FF they seem to get swallowed silently.
            // We need these both in production and development mode,
            // since the erorr fallbacks are activated from processError.
            try {
                // process the results
                this.processResult(domDoc);
            } catch(e) {
                if (e.name == 'RuleMergeError' || e.name == 'EventBindError') {
                    // Log the message
                    var msg = 'Error setting up events: ' + e.toString();
                    kukit.logFatal(msg);
                    // just throw it too...
                    throw msg;
                }
                if (e.name == 'ResponseParsingError') {
                    kukit.E = 'Response parsing error: ' + e;
                    this.processError(kukit.E);
                } else if (e.name == 'ExplicitError') {
                    this.processError(e.errorcommand);
                } else {
                    kukit.E = 'Unhandled error during command execution: ' + e;
                    kukit.logError(kukit.E);
                    // also IE acts foul on thrown errors
                    // but at least mumbles something
                    // XXX TODO We should also signal the problem to the user
                    // in production mode, in this case, see the other comment
                    // in this file.
                    throw e;
                }
            }
        }
    }
};

kukit.sa.ServerAction.prototype.processResult = function(domDoc) {
    // checking various dom process errors, and get the commands part
    var dom;
    var commandstags = [];
    // Let's process xml payload first:
    if (domDoc.responseXML) {
        dom = domDoc.responseXML;
        commandstags = kukit.dom.getNsTags(dom, 'commands');
        if (commandstags.length != 1) {
            // no good, maybe better luck with it as html payload
            dom = null;
        }
    }
    // Check for html too, this enables setting the kss error command on the 
    // error response.
    if (dom == null) {
        // Read the header and load it as xml, if defined.
        var payload = domDoc.getResponseHeader('X-KSSCOMMANDS');
        if (payload) {
            try {
                dom = (new DOMParser()).parseFromString(payload, "text/xml");
            } catch(e) {
                kukit.E = 'Error parsing X-KSSCOMMANDS header.';
                throw new kukit.err.ResponseParsingError(msg);
            }
            commandstags = kukit.dom.getNsTags(dom, 'commands');
            if (commandstags.length != 1) {
                // no good
                dom = null;
            }
        } else {
            // Ok. we have not found it either in the headers.
            // Check if there was a parsing error in the xml, 
            // and log it as reported from the dom
            // Opera <= 8.5 does not have the parseError attribute,
            // so check for it first
            dom = domDoc.responseXML;
            kukit.E = 'Unknown server error (invalid KSS response, no error';
            kukit.E += ' info received)';
            if (dom && dom.parseError && (dom.parseError != 0)) {
                kukit.E += ' : ' + Sarissa.getParseErrorText(dom);
                }
            throw new kukit.err.ResponseParsingError(kukit.E);
        }
    }
    if (dom == null) {
        // this should not happen
        kukit.E = 'Neither xml nor html payload.';
        throw new kukit.err.ResponseParsingError(msg);
    }
    // find the commands (atm we don't limit ourselves inside the commandstag)
    var commands = kukit.dom.getNsTags(dom, 'command');
    // Warning, if there is a valid response containing 0 commands.
    if (commands.length == 0) {
        kukit.log('No commands in kukit response');
        return;
    }
    // One or more valid commands to parse
    var command_processor = new kukit.cp.CommandProcessor();
    command_processor.parseCommands(commands, domDoc);
    command_processor.executeCommands(this.oper);
};

kukit.sa.ServerAction.prototype.processError = function(errorcommand) {
    var error_action = null;
    if (this.oper.eventRule) {
        var error_action = this.oper.eventRule.actions.getErrorActionFor(
            this.oper.action);
        }
    var reason = '';
    if (typeof(errorcommand) == 'string') {
        // not a command, just a string
        reason = ', client_reason="' + errorcommand + '" ';
    } else if (typeof(errorcommand) != 'undefined') {
        // a real error command, sent by the server
        reason = ', server_reason="' + errorcommand.parms.message + '" ';
    }
    if (error_action) {
        kukit.E = 'Request failed at url ' + this.oper.queueItem.url;
        kukit.E += ', rid=' + this.oper.queueItem.rid + reason;
        kukit.E += ', will be handled by action "' + error_action.name + '"';
        kukit.logWarning(kukit.E);
        // Individual error handler was defined. Execute it!
        error_action.execute(this.oper);
    } else {
        // Unhandled: just log it...
        kukit.E = 'Request failed at url ' + this.oper.queueItem.url;
        kukit.E += ', rid=' + this.oper.queueItem.rid + reason;
        kukit.logError(kukit.E);
        return;
        // in case of no logging, we would like to throw an error.
        // This means user will see something went wrong.
        // XXX But: throwing an error on Firefox
        // _seems to be ineffective__
        // and throwing the error from IE
        // _throws an ugly window, "Uncaught exception"
        // TODO figure out something?
    }
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Request manager */

kukit.rm = {};

/* Generation of an integer uid on request objects
*/

kukit.rm._rid = 0;

/*
* class RequestItem
*
* Request item. Encapsulates the sendout function and data.
*/
kukit.rm.RequestItem = function(sendHook, url, timeoutHook, timeout, now) {
    if (typeof(now) == 'undefined') {
        now = (new Date()).valueOf();
    }
    this.sent = now;
    this.expire = now + timeout;
    this.handled = false;
    this.sendHook = sendHook;
    this.url = url;
    this.timeoutHook = timeoutHook;
    // Generate a RID. Due to timeouting, we have enough
    // of these for not to overlap ever.
    this.rid = kukit.rm._rid;
    kukit.rm._rid ++;
    if (kukit.rm._rid >= 10000000000) {
        kukit.rm._rid = 0;
    }
};

kukit.rm.RequestItem.prototype.callTimeoutHook = function() {
    // Calls the timeout hook for this item
    if (this.timeoutHook) {
        this.timeoutHook(this);
    }
};

kukit.rm.RequestItem.prototype.setReceivedCallback = function(func) {
    // Sets the received callback function. It will be
    // called with the item as first parameter.
    this._receivedCallback = func;
};

kukit.rm.RequestItem.prototype.receivedResult = function(now) {
    // This is called when the result response has arrived. It
    // returns a booolean value, if this is false, the caller
    // must give up processing the result that has been timed
    // out earlier.
    var result = this._receivedCallback(this, now);
    this._receivedCallback = null;
    return result;
}; 

/* 
* class TimerQueue
*
* the send queue. This handles timeouts, and executes
* a callback for timed out items.
* Callback is called with the request item as parameter.
*/

kukit.rm.TimerQueue = function(callback) {
    this.callback = callback;
    this.queue = new kukit.ut.SortedQueue(this._sentSort);
    this.count = 0;
};

kukit.rm.TimerQueue.prototype._sentSort = function(a, b) {
    // sorting of the sent queue, by expiration
    if (a.expire < b.expire) return -1;
    else if (a.expire > b.expire) return +1;
    else return 0;
};

kukit.rm.TimerQueue.prototype.push = function(item) {
    // push a given slot
    this.queue.push(item);
    this.count += 1;
};

kukit.rm.TimerQueue.prototype.pop = function(item) {
    // pop a given slot, return true if it was valid,
    // return false if it was already handled by timeout.
    // An object can be popped more times!
    if (typeof(item) == 'undefined' || item.handled) {
        return false;
    } else {
        item.handled = true;
        this.count -= 1;
        return true;
    }
};

kukit.rm.TimerQueue.prototype.handleExpiration = function(now) {
    if (typeof(now) == 'undefined') {
        now = (new Date()).valueOf();
    }
    var to;
    for (to=0; to<this.queue.size(); to++) {
        var item = this.queue.get(to);
        if (! item.handled) {
            if (item.expire > now) {
                break;
            } else {
                // call the callback for this element
                item.handled = true;
                this.count -= 1;
                this.callback(item);
            }
        }
    }
    // remove the elements from the queue
    this.queue.popn(to);
    // Returns when the next element will expire.
    var front = this.queue.front();
    var next_expire = null;
    if (front) {
        next_expire = front.expire;
    }
    return next_expire;
};

/* 
* class RequestManager
*/
kukit.rm.RequestManager = function (name, maxNr, schedulerClass) {
    // schedulerClass is mainly provided for debugging...
    this.waitingQueue = new kukit.ut.FifoQueue();
    this.sentNr = 0;
    var self = this;
    var timeoutItem = function(item) {
       self.timeoutItem(item);
    };
    this.timerQueue = new kukit.rm.TimerQueue(timeoutItem);
    if (typeof(name) == 'undefined') {
        name = null;
    }
    this.name = name;
    var nameString = '';
    if (name != null) {
        nameString = '[' + name + '] ';
        }
    this.nameString = nameString;
    if (typeof(maxNr) != 'undefined' && maxNr != null) {
        this.maxNr = maxNr;
    }
    // sets the timeout scheduler
    var checkTimeout = function() {
       self.checkTimeout();
    };
    if (typeof(schedulerClass) == 'undefined') {
        schedulerClass = kukit.ut.Scheduler;
    }
    this.timeoutScheduler = new schedulerClass(checkTimeout);
    this.spinnerEvents = {'off': [], 'on': []};
    this.spinnerState = false;
};

// sending timeout in millisecs
kukit.rm.RequestManager.prototype.sendingTimeout = 8000;

// max request number
kukit.rm.RequestManager.prototype.maxNr = 4;

    kukit.rm.RequestManager.prototype.getInfo = function() {
        var msg = '(RQ: ' + this.sentNr + ' OUT, ' + this.waitingQueue.size();
        msg += ' WAI)';
        return msg;
    };

    kukit.rm.RequestManager.prototype.log = function(txt) {
        var msg = 'RequestManager ' + this.nameString + txt + ' ';
        msg += this.getInfo() + '.';
        kukit.logDebug(msg);
    };

kukit.rm.RequestManager.prototype.setSpinnerState = function(newState) {
    if (this.spinnerState != newState) {
        this.spinnerState = newState;
        // Call the registered spinner events for this state
        var events = this.spinnerEvents[newState ? 'on' : 'off'];
        for (var i=0; i<events.length; i++) {
            events[i]();
        }
    }
};

kukit.rm.RequestManager.prototype.pushWaitingRequest = function(item, now) {
    this.waitingQueue.push(item);
    // Set the timeout
    this.checkTimeout(now);
};

kukit.rm.RequestManager.prototype.popWaitingRequest = function() {
    var q = this.waitingQueue;
    // pop handled elements, we don't send them out at all
    while (! q.empty() && q.front().handled) {
        q.pop();
    }
    // return the element, or null if no more waiting!
    if (! q.empty()) {
        return q.pop();
    } else {
        return null;
    }
};

kukit.rm.RequestManager.prototype.pushSentRequest = function(item, now) {
    this.sentNr += 1;
    this.log('notifies server ' + item.url + ', rid=' + item.rid);
    // Set the spinner state
    this.setSpinnerState(true);
    // Set the timeout
    this.checkTimeout(now);
    // Wrap up the callback func. It will be called
    // with the item as first parameter.
    var self = this;
    var func = function(item, now) {
        return self.receiveItem(item, now);
    };
    item.setReceivedCallback(func);
    // Call the function
    item.sendHook(item);
};

kukit.rm.RequestManager.prototype.checkTimeout = function(now) {
    var nextWake = this.timerQueue.handleExpiration(now);
    if (nextWake) {
        // To make sure, add 50ms to the nextwake
        nextWake += 50;
        // do the logging
        //var now = (new Date()).valueOf();
        //this.log('Next timeout check in: ' + (nextWake - now));
    } else {
        this.log('suspends checking timeout until the next requests');
        // Set the spinner state
        this.setSpinnerState(false);
    }
    // do the scheduling
    this.timeoutScheduler.setNextWakeAtLeast(nextWake);
};

kukit.rm.RequestManager.prototype.popSentRequest = function(item) {
    var success = this.timerQueue.pop(item);
    // We remove both to be processed, and timed out requests from the queue.
    // This means: possibly more physical requests are out, but this
    // is a better strategy in order not to hog the queue infinitely.
    this.sentNr -= 1;
    return success;
};

kukit.rm.RequestManager.prototype.isSentRequestQueueFull = function() {
    return (this.sentNr >= this.maxNr);
};

kukit.rm.RequestManager.prototype.receivedResult = function(item, now) {
    // called automatically when the result gets processed.
    // Mark that we have one less request out.
    var success = this.popSentRequest(item);
    // Independently of the success, this is the moment when we may
    // want to send out another item.
    var waiting = this.popWaitingRequest();
    if (waiting != null) {
        // see if we can send another request in place of the received one
        // request is waiting, send it.
        var msg = 'dequeues server notification at [' + waiting.url;
        msg += '], rid [' + waiting.rid + '].';
        this.log(msg);
        this.pushSentRequest(waiting, now);
    } else {
    //    this.log("Request queue empty.");
        // Set the spinner state
        this.setSpinnerState(false);
    }
    return success;
};


kukit.rm.RequestManager.prototype.receiveItem = function(item, now) {
    // calls result processing
    var success = this.receivedResult(item, now);
    if (success) {
        this.log('received result with rid [' + item.rid + ']');
    } else {
        var msg = 'received timed out result rid [' + item.rid;
        msg += '], to be ignored';
        this.log(msg);
    }
    return success;
};

kukit.rm.RequestManager.prototype.timeoutItem = function(item) {
    /* Time out this item. */
    this.log('timed out request rid [' + item.rid + ']');
    // Call the timeout hook on the item
    item.callTimeoutHook();
};

/* request manager notification API */

kukit.rm.RequestManager.prototype.notifyServer =
    function(sendHook, url, timeoutHook, timeout, now) {
    // url is only for the logging
    // sendHook is the function that actually sends out the request.
    // sendHook will be called with one parameter: the 'item' array.
    // The sender mechanism must make sure to call item.receivedResult()
    // when it received the response.
    // Based on the return value of receivedResult(), the result processing
    // may go on or must be broken. If the return value is false, the
    // results must NOT be processed: this means that we have already
    // timed out the request by that time.
    // timeoutHook: can specify the timeouthook for this request.
    // Setting it to null
    // disables it. This will be called with the 'item' as a parameter as well.
    if (typeof(timeout) == 'undefined') {
        // Default value of timeout
        timeout = this.sendingTimeout;
    }
    var item = new kukit.rm.RequestItem(sendHook, url, timeoutHook, timeout,
        now);
    // Start timing the item immediately
    this.timerQueue.push(item);
    if (! this.isSentRequestQueueFull()) {
        // can be sent if we are not over the limit.
        this.pushSentRequest(item, now);
    } else {
        this.pushWaitingRequest(item, now);
        var msg = 'queues server notification at [' + item.url;
        msg += '], rid [' + item.rid + ']';
        this.log(msg);
    }
};

kukit.rm.RequestManager.prototype.registerSpinnerEvent = function(func, state) {
    this.spinnerEvents[state ? 'on' : 'off'].push(func);
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.cp = {};

/*
* class CommandProcessor
*/
kukit.cp.CommandProcessor = function() {
      this.commands = new Array();
};

kukit.cp.CommandProcessor.prototype.parseCommands =
    function(commands, transport) {
    kukit.log('Parsing commands.');
    kukit.logDebug('Number of commands : ' + commands.length + '.');
    for (var y=0;y < commands.length;y++) {
        var command = commands[y];
        this.parseCommand(command, transport);
        // If we receive an error command, we handle that separately.
        // We abort immediately and let the processError handler do its job.
        // This means that although no other commands should be in commands,
        // we make sure we execute none of them.
        var lastcommand = this.commands[this.commands.length-1];
        if (lastcommand.name == 'error') {
            // We have to throw an explicitError always, since we want
            // error fallbacks work both in production and development mode.
            throw new kukit.err.ExplicitError(lastcommand);
        }
    }
};

kukit.cp.CommandProcessor.prototype.parseCommand =
    function(command, transport) {
    var selector = "";
    var params = {};
    var name = "";

    selector = command.getAttribute("selector");
    name = command.getAttribute("name");
    type = command.getAttribute("selectorType");
    if (name == null)
        name = "";
    var childNodes = command.childNodes;
    for (var n=0;n < childNodes.length;n++) {
        var childNode = childNodes[n];
        if (childNode.nodeType != 1) 
            continue;
        if (childNode.localName) {
            // (here tolerate both cases)
            if (childNode.localName.toLowerCase() != "param"
                && childNode.nodeName.toLowerCase() != "kukit:param") {
                throw 'Bad payload, expected param';
            }
        } else {
            //IE does not know DOM2
            if (childNode.nodeName.toLowerCase() != "kukit:param") {
                throw 'Bad payload, expected kukit:param';
            }
        }        
        data = childNode.getAttribute('name');
        if (data != null) { 
            // Decide if we have a string or a dom parameter
            var childCount = childNode.childNodes.length;
            var result;
            if (childCount == 0) {
                // We take this a string (although this could be dom)
                result = '';
            } else if (childCount == 1 && childNode.firstChild.nodeType == 3) {
                // we have a single text node
                result = childNode.firstChild.nodeValue;
            } else {
                // dom
                result = childNode;
            }
            params[data] = result;
        } else {
            throw 'Bad payload, expected attribute "name"';
        }
    }
    var command = new kukit.cr.makeCommand(selector, name, type, params,
        transport);
    this.addCommand(command);
}; 

kukit.cp.CommandProcessor.prototype.addCommand = function(command) {
    this.commands[this.commands.length] = command;
};

kukit.cp.CommandProcessor.prototype.executeCommands = function(oper) {
    kukit.engine.beginSetupEventsCollection();
    // node, eventRule, binderInstance are given on oper, in case
    // the command was called up from an event
    if (typeof(oper) == 'undefined' || oper == null) {
        oper = new kukit.op.Oper();
    }
    var commands = this.commands;
    for (var y=0;y < commands.length;y++) {
        var command = commands[y];
        try {
            command.execute(oper); 
        } catch (e) {
            if (e.name == 'RuleMergeError' || e.name == 'EventBindError') {
                throw(e);
            } else {
                // augment the error message
                throw new kukit.err.CommandExecutionError(e, command); 
            }
        }
    }
    kukit.engine.finishSetupEventsCollection();
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

kukit.sr = {};

// Registry of the pprovider functions for selecting

kukit.sr.pproviderSelRegistry = new kukit.pr.ValueProviderRegistry();


// this will provide an arbitrary selector, and is designed to
// be used with the makeAnyPP factory function.
kukit.sr.AnyPP = function() {};
kukit.sr.AnyPP.prototype = {
    check: function(args) {
        // check does not need to be used here actually.
        if (args.length != 1) {
            throw 'internal error, xxxselector() needs 1 argument';
        }
    },
    eval: function(args, node, defaultParameters) {
        var f = kukit.selectorTypesGlobalRegistry.get(this.selector_type);
        // We don't have orignode if we evaluate from here, consequently
        // the orignode parameter cannot be used from selectors. We pass
        // node just to be sure...
        return f(args[0], node, defaultParameters, node);
    }
};

kukit.sr.pproviderSelRegistry.register('', kukit.sr.AnyPP);

kukit.sr.makeAnyPP = function(selector_type) {
    var pp = function () {};
    pp.prototype.eval = kukit.sr.AnyPP.prototype.eval;
    pp.prototype.check = kukit.sr.AnyPP.prototype.check;
    pp.prototype.selector_type = selector_type;
    return pp;
};

// this can be used to pass a node programmatically
kukit.sr.PassnodePP = function() {};
kukit.sr.PassnodePP.prototype = {
    check: function(args) {
        if (args.length != 1) {
            throw 'passnode selector method needs 1 argument';
        }
    },
    eval: function(args, node, defaultParameters) {
        var value = defaultParameters[args[0]];
        if (typeof(value) == 'undefined') {
            // notfound arguments will get null
            kukit.E = 'Nonexistent default parm "'+ key +'"';
            throw kukit.E;
        }
        nodes = [value];
        return nodes;
    }
};
kukit.sr.pproviderSelRegistry.register('passnode', kukit.sr.PassnodePP);


/* 
* class SelectorTypeRegistry 
*
*  available for plugin registration
*
*  usage:
*
*  kukit.selectorTypesGlobalRegistry.register(name, func);
*
*/
kukit.sr.SelectorTypeRegistry = function () {
    this.mapping = {};
};

kukit.sr.SelectorTypeRegistry.prototype.defaultSelectorType = 'css';

kukit.sr.SelectorTypeRegistry.prototype.register = function(name, func) {
    if (typeof(func) == 'undefined') {
        throw 'Func is mandatory.';
    }
    if (this.mapping[name]) {
       // Do not allow redefinition
       kukit.logError('Error : redefinition attempt of selector ' + name);
       return;
    }
    this.mapping[name] = func;
    // Also register the selector param provider
    var pp = kukit.sr.makeAnyPP(name);
    kukit.sr.pproviderSelRegistry.register(name, pp);
};

kukit.sr.SelectorTypeRegistry.prototype.get = function(name) {
    if (! name) {
        // if name is null or undefined or '',
        // we use the default type.
        name = this.defaultSelectorType;
    }
    var result = this.mapping[name];
    if (typeof(result) == 'undefined') {
       throw 'Unknown selector type "' + name + '"';
    }
    return result;
};

kukit.selectorTypesGlobalRegistry = new kukit.sr.SelectorTypeRegistry();

kukit.selectorTypesGlobalRegistry.register('htmlid', function(expr, node) {
    var nodes = [];
    var node = document.getElementById(expr);
    if (node) {
        nodes.push(node);
        }
    return nodes;
});

kukit.selectorTypesGlobalRegistry.register('css', function(expr, node) {
    // Always search globally
    var nodes = kukit.dom.cssQuery(expr);
    return nodes;
});

kukit.selectorTypesGlobalRegistry.register('samenode', function(expr, node) {
    nodes = [node];
    return nodes;
});

// Return a list of all nodes that match the css expression in the parent chain
kukit.selectorTypesGlobalRegistry.register('parentnode', function(expr, node) {
    var selectednodes = kukit.dom.cssQuery(expr);
    var parentnodes = [];
    var parentnode = node.parentNode;
    while(parentnode.parentNode) {
        parentnodes.push(parentnode);
        parentnode = parentnode.parentNode;
    }

    // Filter the nodes so that only the ones in the parent chain remain
    var results = [];
    for(var i=0; i<selectednodes.length; i++){
        var inchain = false;
        for(var j=0; j<parentnodes.length; j++){
            if(selectednodes[i] === parentnodes[j]){
                inchain = true;
            }
        }
        if(inchain){
            results.push(selectednodes[i]);
        }
    }
    return results;
});

/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Form handling utilities */

kukit.fo = {};

/* form query assembler */

// Prefix constants for dict marshalling, 
//     pattern: %s(dictprefix)%(name)s%(dictseparator)s%(key)s%(dictpostfix)s
// XXX this should be settable
kukit.fo.dictprefix = '';
kukit.fo.dictseparator = '.';
kukit.fo.dictpostfix = ':record';

/*
* class FormQueryElem
*/
kukit.fo.FormQueryElem = function(name, value) {
    this.name = name;
    this.value = value;
};
    
kukit.fo.FormQueryElem.prototype.encode = function() {
    return this.name+ "=" + encodeURIComponent(this.value);
};
    
/*
* class FormQuery
*/
kukit.fo.FormQuery = function() {
    this.l = [];
};

kukit.fo.FormQuery.prototype.appendElem = function(name, value) {
    if (value == null) {
        // do not marshall nulls
        var msg = "Parameter '" + name + "' is null,";
        msg += " it is not marshalled.";
        kukit.logDebug(msg);
        }
    else if (typeof(value) == 'string') {
        var elem = new kukit.fo.FormQueryElem(name, value);
        this.l.push(elem);
    }
    // value.length is for detection of an Array.
    // In addition we also check that value.pop is a function
    else if (typeof(value) == 'object' && 
        typeof(value.length) == 'number' &&
        typeof(value.pop) == 'function') {
        // Special marshalling of arrays
        for (var i=0; i < value.length; i++) {
            var elem = new kukit.fo.FormQueryElem(name, value[i]);
            this.l.push(elem);
        }
    }
    else if (typeof(value) == 'object') {
        // Special marshalling of dicts
        for (var key in value) {
            var qkey = kukit.fo.dictprefix + name + kukit.fo.dictseparator;
            qkey += key + kukit.fo.dictpostfix;
            var elem = new kukit.fo.FormQueryElem(qkey, value[key]);
            this.l.push(elem);
        }
    }    
};

kukit.fo.FormQuery.prototype.encode = function() {
    var poster = [];
      for (var i=0;i < this.l.length;i++) {
        poster[poster.length] = this.l[i].encode();
    }
    return poster.join("&");
};

kukit.fo.FormQuery.prototype.toDict = function() {
    var d = {};
      for (var i=0;i < this.l.length;i++) {
        var elem = this.l[i];
        d[elem.name] = elem.value;
    }
    return d;
};

/* Form data extraction, helpers */

kukit.fo.findContainer = function(node, func) {
    // Starting with the given node, find the nearest containing element
    // for which the given function returns true.
    while (node != null) {
        if (func(node)) {
            return node;
        }
        node = node.parentNode;
    }
    return null;
};

/*
 * class CurrentFormLocator: gets the current form of a target
 *
 */

kukit.fo.CurrentFormLocator = function(target) {
    this.target = target;
};

kukit.fo.CurrentFormLocator.prototype.queryForm = function() {
    // Find the form that contains the target node.
    return kukit.fo.findContainer(this.target, function(node) {
        if (!node.nodeName) {
            return false;
        }
        if (node.nodeName.toLowerCase() == "form") {
            return true;
        } else {
            return false;
        }
    });
};

kukit.fo.CurrentFormLocator.prototype.getForm = function() {
    var form = this.queryForm();
    if (!form) {
        kukit.logWarning("No form found");
        return null;
    }
    return form;
};

/*
 * class NamedFormLocator: gets the form with a given name
 *
 */

kukit.fo.NamedFormLocator = function(formname) {
    this.formname = formname;
};

kukit.fo.NamedFormLocator.prototype.queryForm = function() {
    // Find the form with the given name.
    return document.forms[this.formname];
};

kukit.fo.NamedFormLocator.prototype.getForm = 
    kukit.fo.CurrentFormLocator.prototype.getForm;

/* methods to take the desired value(s) from the form */

kukit.fo.getValueOfFormElement = function(element) {
    // Returns the value of the form element / or null
    // First: update the field in case an editor is lurking
    // in the background
    kukit.fo.fieldUpdateRegistry.doUpdate(element);
    // Collect the data
    if (element.selectedIndex != undefined) {
        // handle single selects first
        if(!element.multiple) {
                if (element.selectedIndex < 0) {
                    value="";
                } else {
                    var option = element.options[element.selectedIndex];
                    // on FF and safari, option.value has the value
                    // on IE, option.text needs to be used
                    value = option.value || option.text;
                } 
        // Now process selects with the multiple option set
        } else {
            var value = [];
            for(i=0; i<element.options.length; i++) {
                var option = element.options[i];
                if(option.selected) {
                    // on FF and safari, option.value has the value
                    // on IE, option.text needs to be used
                    value.push(option.value || option.text);
                }
            }
        }
    } else if (typeof element.length != 'undefined' && 
        typeof element.item != 'undefined' && 
        element.item(0).type == "radio") {
        var radioList = element;
        value = null;
        for (var i=0; i < radioList.length; i++) {
            var radio = radioList.item(i);
            if (radio.checked) {
                value = radio.value;
            }
        }
    } else if (element.type == "radio" || element.type == "checkbox") {
        if (element.checked) {
           value = element.value;
        } else {
            value = null;
        }   
    } else if ((element.tagName.toLowerCase() == 'textarea')
               || (element.tagName.toLowerCase() == 'input' && 
                    element.type != 'submit' && element.type != 'reset')
              ) {
        value = element.value;
    } else {
        value = null;
    }
    return value;
};

kukit.fo.getFormVar = function(locator, name) {
    var form = locator.getForm();
    if (! form)
        return null;
    // Extract the value of a formvar
    var value = null;
    var element = form[name];
    if (element) {
        var value = kukit.fo.getValueOfFormElement(element);
        if (value != null) {
            var msg = "Form element [" + element.tagName + "] : name = ";
            msg += element.name + ", value = " + value + '.';
            kukit.logDebug(msg);
        }
    } else {
        kukit.logWarning('Form element [' + name + '] not found in form.');
    }
    return value;
};

kukit.fo.getAllFormVars = function(locator, collector) {
    var form = locator.getForm();
    if (! form)
        return collector.result;
    // extracts all elements of a given form
    // the collect_hook will be called wih the name, value parameters to add it
    var elements = form.elements;
    for (var y=0; y<elements.length; y++) {
        var element = elements[y];
        var value = kukit.fo.getValueOfFormElement(element);
        if (value != null) {
            var msg = "Form element [" + element.tagName + "] : name = ";
            msg += element.name + ", value = " + value + '.';
            kukit.logDebug(msg);
            collector.add(element.name, value);
        }
    }
    return collector.result;
};


/* With editors, there are two main points of handling:

   1. we need to load them after injected dynamically
   2. we need to update the form before we accces the form variables

    Any editor has to register the field on their custody.
    The update handler will be called automatically, when a form
    value is about to be fetched.
*/

/*
* class FieldUpdateRegistry
*/
kukit.fo.FieldUpdateRegistry = function() {
    this.editors = {};
};

kukit.fo.FieldUpdateRegistry.prototype.register = function(node, editor) {
    var hash = kukit.rd.hashNode(node);
    if (typeof(this.editors[hash]) != 'undefined') {
        kukit.E = 'Double registration of editor update on node.';
        throw kukit.E;
    }
    this.editors[hash] = editor;
    //kukit.logDebug('Registered '+node.name + ' hash=' + hash);
    //Initialize the editor
    editor.doInit();
};

kukit.fo.FieldUpdateRegistry.prototype.doUpdate = function(node) {
    var hash = kukit.rd.hashNode(node);
    var editor = this.editors[hash];
    if (typeof(editor) != 'undefined') {
        editor.doUpdate(node);
        //kukit.logDebug('Updated '+node.name + ' hash=' + hash);
    }
};

kukit.fo.fieldUpdateRegistry = new kukit.fo.FieldUpdateRegistry();


// Registry of the pprovider functions for kssSubmitForm

kukit.fo.pproviderFormRegistry = new kukit.pr.ValueProviderRegistry();

// form, currentForm will provide identical functions to those 
// in normal parameters
// except they return a tuple list, not a dictionary.
// This is needed because duplications and order must be preserved.

kukit.fo.FormPP = function() {};
kukit.fo.FormPP.prototype = {
    check: function(args) {
        if (args.length != 1) {
            throw 'form method needs 1 arguments (formname)';
        }
    },
    eval: function(args, node) {
        var locator = new kukit.fo.NamedFormLocator(args[0]);
        var collector = new kukit.ut.TupleCollector();
        return kukit.fo.getAllFormVars(locator, collector);
    }
};
kukit.fo.pproviderFormRegistry.register('form', kukit.fo.FormPP);

kukit.fo.CurrentFormPP = function() {};
kukit.fo.CurrentFormPP.prototype = {
    check: function(args) {
        if (args.length != 0) {
            throw 'currentForm method needs no argument';
        }
    },
    eval: function(args, node) {
        var locator = new kukit.fo.CurrentFormLocator(node);
        var collector = new kukit.ut.TupleCollector();
        return kukit.fo.getAllFormVars(locator, collector);
    }
};
kukit.fo.pproviderFormRegistry.register('currentForm', kukit.fo.CurrentFormPP);

// If a string is given, that will look like a form lookup,
// ie. identical to form
kukit.fo.pproviderFormRegistry.register('', kukit.fo.FormPP);


/* BBB. To be deprecated on 2008-06-15 */

kukit.fo.getCurrentForm = function(target) {
    var msg = 'Deprecated kukit.fo.getCurrentForm(target), use new ';
    msg += 'kukit.fo.CurrentFormLocator(target).getForm() instead!';
    kukit.logWarning(msg);
    return new kukit.fo.CurrentFormLocator(target).getForm();
};

kukit.fo.getFormVarFromCurrentForm = function(target, name) {
    var msg = 'Deprecated kukit.fo.getFormVarFromCurrentForm(target, name),';
    msg += ' use kukit.fo.getFormVar(new kukit.fo.CurrentFormLocator(target),';
    msg += ' name) instead!';
    kukit.logWarning(msg);
    return kukit.fo.getFormVar(new kukit.fo.CurrentFormLocator(target), name);
};

kukit.fo.getFormVarFromNamedForm = function(formname, name) {
    var msg = 'Deprecated kukit.fo.getFormVarFromNamedForm(formname, name),';
    msg += ' use kukit.fo.getFormVar(new kukit.fo.NamedFormLocator(formname),';
    msg += ' name) instead!';
    kukit.logWarning(msg);
    return kukit.fo.getFormVar(new kukit.fo.NamedFormLocator(formname), name);
};

kukit.fo.getAllFormVarsFromCurrentForm = function(target) {
    var msg = 'Deprecated kukit.fo.getAllFormVarsFromCurrentForm(target),';
    msg += ' use kukit.fo.getAllFormVars(new kukit.fo.CurrentFormLocator';
    msg += '(target), new kukit.ut.DictCollector()) instead!';
    kukit.logWarning(msg);
    return kukit.fo.getAllFormVars(new kukit.fo.CurrentFormLocator(target),
        new kukit.ut.DictCollector());
};

kukit.fo.getAllFormVarsFromNamedForm = function(formname) {
    var msg = 'Deprecated kukit.fo.getAllFormVarsFromNamedtForm(formname), ';
    msg += 'use kukit.fo.getAllFormVars(new kukit.fo.NamedFormLocator';
    msg += '(formname), new kukit.ut.DictCollector()) instead!';
    kukit.logWarning(msg);
    return kukit.fo.getAllFormVars(new kukit.fo.NamedFormLocator(formname),
        new kukit.ut.DictCollector());
};


/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Core plugins and utilities */

kukit.pl = {};

/* 
* Event plugins 
* 
* __trigger_event__(name, parms, node)
* is a method bound to each class, so methods can call
* it up to call an event action bound through kss.
*
* The event binder hooks
* __bind__(name, parms, func_to_bind)
* should be defined to make binding of event to the given function.
*
* The event action hooks
* __exec__(name, parms, node)
* can be defined to specify a default event action.
*/

kukit.pl.getTargetForBrowserEvent = function(e) {
    // this prevents the handler to be called on wrong elements, which
    // can happen because of propagation or bubbling
    // XXX this needs to be tested in all browsers
    if (!e) var e=window.event;
    var target = null;
    if (e.target) {
        target = e.target;
    } else if (e.srcElement) {
        target = e.srcElement;
    }
    /* ???
    if (e.currentTarget)
        if (target != e.currentTarget)
            target = null;*/
    return target;
};

/*
* function registerBrowserEvent
*
* This can be used to register native events in a way that
* they handle allowbubbling, preventdefault and preventbubbling as needed.
* (THe handling of these parms are optional, it is allowed not to have them
* in the oper.parms.)
*
* THe register function can also take a filter function as parameter. 
* This function needs to receive oper as a parameter,
* where 'browserevent' will be set on oper too as the native browser event.
* The function must return true if it wants the event to execute,
* false otherwise.
* If it returns false, the event will not be prevented and counts as if
* were not called.
* This allows for certain event binder like key handlers, to put an extra
* condition on the triggering of event.
*
* The eventName parameter is entirely optional and
* can be used to set up a different
* event from the desired one.
*/

kukit.pl.registerBrowserEvent = function(oper, filter, eventName) {
    var func_to_bind = oper.makeExecuteActionsHook(filter);
    if (! eventName)
        eventName = oper.getEventName();
    var func = function(e) {
        var target = kukit.pl.getTargetForBrowserEvent(e);
        if (oper.parms.allowbubbling || target == oper.node) {
            // Execute the action, provide browserevent on oper
            // ... however, do it protected. We want the preventdefault
            // in any case!
            var exc;
            var success;
            try {
                success = func_to_bind({'browserevent': e});
            } catch(exc1) {
                exc = exc1;    
            }
            if (success || exc) {
                // This should only be skipped, if the filter told
                // us that we don't need this event to be executed.
                // If an exception happened during the event execution,
                // we do yet want to proceed with the prevents.
                //
                // Cancel default event ?
                if (oper.parms.preventdefault) {
                    // W3C style
                    if (e.preventDefault)
                        e.preventDefault();
                    // MS style
                    try { e.returnValue = false; } catch (exc2) {}
                }
                // Prevent bubbling to other kss events ?
                if (oper.parms.preventbubbling) {
                    if (!e) var e = window.event;
                    e.cancelBubble = true;
                    if (e.stopPropagation) e.stopPropagation();
                }
            }
            //
            if (exc != null) {
                // throw the original exception
                throw exc;
            }
        } else {
            var msg = 'Ignored bubbling event for event [' + eventName;
            msg += '], target [' + target.tagName + '], EventRule #';
            msg += oper.eventRule.getIndex() + ' mergeId [';
            msg += oper.eventRule.kssSelector.mergeId + '].'; 
            kukit.log(msg);
        }
    };
    kukit.ut.registerEventListener(oper.node, eventName, func);
};

/*
* class NativeEventBinder
*/
kukit.pl.NativeEventBinder = function() {
};

kukit.pl.NativeEventBinder.prototype.__bind__node =
    function(name, func_to_bind, oper) {
    if (oper.node == null) {
        throw 'Native event [' + name + '] must be bound to a node.';
    }
    this.__bind__(name, func_to_bind, oper);
};

kukit.pl.NativeEventBinder.prototype.__bind__nodeorwindow = 
    function(name, func_to_bind, oper) {
    if (oper.node == null) {
        oper.node = window;
    }
    this.__bind__(name, func_to_bind, oper);
};

kukit.pl.NativeEventBinder.prototype.__bind__window =
    function(name, func_to_bind, oper) {
    if (oper.node != null) {
        throw 'Native event [' + name + '] must not be bound to a node.';
    }
    oper.node = window;
    this.__bind__(name, func_to_bind, oper);
};

kukit.pl.NativeEventBinder.prototype.__bind__nodeordocument = 
    function(name, func_to_bind, oper) {
    if (oper.node == null) {
        oper.node = document;
    }
    this.__bind__(name, func_to_bind, oper);
};

kukit.pl.NativeEventBinder.prototype.__bind__ = 
    function(name, func_to_bind, oper) {
    oper.componentName = 'native event binding';
    oper.evaluateParameters([], 
        {'preventdefault': '', 'allowbubbling': '', 'preventbubbling': ''});
    oper.evalBool('preventdefault');
    oper.evalBool('allowbubbling');
    oper.evalBool('preventbubbling');
    if (oper.parms.preventdefault) {
        if (name != 'click') {
            kukit.E = 'In native events only the click event can have';
            kukit.E += ' [preventdefault] parameter.';
            throw kukit.E;
        }
    }
    kukit.pl.registerBrowserEvent(oper);
    //
    // XXX Safari hack
    // necessary since Safari does not prevent the <a href...> following
    // (in case of allowbubbling we have to apply it to all clicks, as there
    // might be a link inside that we cannot detect on the current node)
    //
    // XXX not needed since we have the legacy name parameter:
    // var name = oper.getEventName();
    if (oper.parms.preventdefault && kukit.HAVE_SAFARI 
            && (oper.parms.allowbubbling || name == 'click'
            && oper.node.tagName.toLowerCase() == 'a')) {
        oper.node.onclick = function cancelClickSafari() {
            return false;
        };
    }
};

kukit.pl.NativeEventBinder.prototype.__bind_key__ =
    function(name, func_to_bind, oper) {
    oper.componentName = 'native key event binding';
    oper.evaluateParameters([],
        {'preventdefault': 'true', 'allowbubbling': '',
         'preventbubbling': '', 'keycodes': ''});
    oper.evalList('keycodes');
    oper.evalBool('preventdefault');
    oper.evalBool('allowbubbling');
    var filter;
    if (oper.parms.keycodes.length >= 0) {
        // Convert keyCode to dict
        var keycodes = {};
        for (var i=0; i<oper.parms.keycodes.length; i++) {
            keyCode = oper.parms.keycodes[i];
            keycodes[keyCode] = true;
        }
        // Set filter so that only the specified keys should trigger.
        filter = function(oper) {
            var keyCode = oper.browserevent.keyCode.toString();
            return keycodes[keyCode];
        };
    }
    kukit.pl.registerBrowserEvent(oper, filter);
};

/*
* Registration of all the native events that can bound
* to a node or to document 
*  (= document or window, depending on the event specs)
*  Unsupported are those with absolute no hope to work in a cross browser way
*  Preventdefault is only allowed for click and key events, currently
*/
kukit.eventsGlobalRegistry.register(null, 'blur', kukit.pl.NativeEventBinder,
    '__bind__nodeorwindow', null);
kukit.eventsGlobalRegistry.register(null, 'focus', kukit.pl.NativeEventBinder,
    '__bind__nodeorwindow', null);
kukit.eventsGlobalRegistry.register(null, 'resize', kukit.pl.NativeEventBinder,
    '__bind__nodeorwindow', null);
kukit.eventsGlobalRegistry.register(null, 'click', kukit.pl.NativeEventBinder,
    '__bind__nodeordocument', null);
kukit.eventsGlobalRegistry.register(null, 'dblclick',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'mousedown',
    kukit.pl.NativeEventBinder, '__bind__nodeordocument', null);
kukit.eventsGlobalRegistry.register(null, 'mouseup',
    kukit.pl.NativeEventBinder, '__bind__nodeordocument', null);
kukit.eventsGlobalRegistry.register(null, 'mousemove',
    kukit.pl.NativeEventBinder, '__bind__nodeordocument', null);
kukit.eventsGlobalRegistry.register(null, 'mouseover',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'mouseout',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'change',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'reset',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'select',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'submit',
    kukit.pl.NativeEventBinder, '__bind__node', null);
kukit.eventsGlobalRegistry.register(null, 'keydown',
    kukit.pl.NativeEventBinder, '__bind_key__', null);
kukit.eventsGlobalRegistry.register(null, 'keypress',
    kukit.pl.NativeEventBinder, '__bind_key__', null);
kukit.eventsGlobalRegistry.register(null, 'keyup',
    kukit.pl.NativeEventBinder, '__bind_key__', null);
//kukit.eventsGlobalRegistry.register(null, 'unload',
//    kukit.pl.NativeEventBinder, '__bind__window', null);

/*
* class TimeoutEventBinder
*
*  Timer events. The binding of this event will start one counter
*  per event rule. No matter how many nodes matched it. 
*  The timer will tick for ever,
*  unless the binding node has been deleted, in which case it stops,
*  or it runs only once if repeat=false is given.
*/
kukit.pl.TimeoutEventBinder = function() {
    this.counters = {};
};

kukit.pl.TimeoutEventBinder.prototype.__bind__ =
    function(name, func_to_bind, oper) {
    oper.componentName = 'timeout event binding';
    oper.evaluateParameters(['delay'], {'repeat': 'true'});
    oper.evalBool('repeat');
    var key = oper.eventRule.getIndex();
    if (! (oper.parms.repeat && this.counters[key])) {
        var msg = 'Timer event key entered for actionEvent #' + key;
        msg += ', selector [' + oper.eventRule.kssSelector.css + '].';
        kukit.logDebug(msg);
        var f = function() {
            // check if the node has been deleted
            // and weed it out if so
            if (oper.node != null && ! oper.node.parentNode) {
            var msg = 'Timer event key deleted for actionEvent #' + key;
            msg += ', selector [' + oper.eventRule.kssSelector.css + '].';
            kukit.logDebug(msg);
                this.clear();
            } else {
                func_to_bind();
            }
        };
        var delay = oper.parms.delay;
        var repeat = oper.parms.repeat;
        var counter = new kukit.ut.TimerCounter(delay, f, repeat); 
        this.counters[key] = counter;
        // Start the counter
        counter.start();
    } else {   
        // Don't bind the counter if we matched this eventRule already
        // (this is only checked if this event is repeating)
        var msg = 'Timer event key ignored for actionEvent #' + key;
        msg += ', selector [' + oper.eventRule.kssSelector.css + '].';
        kukit.logDebug(msg);
    }
};

kukit.eventsGlobalRegistry.register(null, 'timeout',
    kukit.pl.TimeoutEventBinder, '__bind__', null);

/*
* class LoadEventBinder
*/
kukit.pl.LoadEventBinder = function() {
};

kukit.pl.LoadEventBinder.prototype.processParameters =
    function(oper, iload) {
    if (! oper) {
        return;
    }
    if (iload) {
        oper.componentName = '[iload] event binding';
        oper.evaluateParameters(['autodetect'],
            {'initial': 'true', 'insert': 'true'});
        // autodetect=false changes the iload autosense method to one
        // that requires that code in the iframe set the _kssReadyForLoadEvent
        // attribute on the document. Setting this attribute is explicitely 
        // required if autodetect is off, since we would never notice
        // if the document has arrived in this case.
        oper.evalBool('autodetect');
    } else {
        oper.componentName = '[load] event binding';
        oper.evaluateParameters([], {'initial': 'true', 'insert': 'true'});
    }
    oper.evalBool('initial');
    oper.evalBool('insert');
    var phase;
    if (oper.node == null) {
        // if the event is bound to a document node,
        // we are in phase 1.
        phase = 1;
    } else {
        // get the phase from the node
        phase = oper.node._kukitmark;
    }
    if (phase == 1 && ! oper.parms.initial) {
        var msg = 'EventRule #' + oper.eventRule.getIndex() + ' mergeId [';
        msg += oper.eventRule.kssSelector.mergeId + '] event ignored,';
        msg += ' oninitial=false.';
        kukit.logDebug(msg);
        return;
    }
    if (phase == 2 && ! oper.parms.insert) {
        var msg = 'EventRule #' + oper.eventRule.getIndex() + ' mergeId [';
        msg += oper.eventRule.kssSelector.mergeId + '] event ignored,';
        msg += ' oninsert=false.';
        kukit.logDebug(msg);
        return;
    }
    return oper;
};

kukit.pl.LoadEventBinder.prototype.__bind__ = function(opers_by_eventName) {
    // This bind method handles load and iload events together, and 
    // opers_by_eventName is
    // a dictionary of opers which can contain a load and an iload key,
    // either one or both.
    var loadoper = opers_by_eventName.load;
    var iloadoper = opers_by_eventName.iload;
    loadoper = this.processParameters(loadoper);
    iloadoper = this.processParameters(iloadoper, true);
    var anyoper = loadoper || iloadoper;
    if (! anyoper) {
        return;
    }
    if (anyoper.node != null && 
        anyoper.node.tagName.toLowerCase() == 'iframe') {
        // In an iframe.
        //
        // BBB If there is only a load (and no iload) event bound to this node, 
        // we interpret it as an iload event, but issue deprecation warning.
        // This conserves legacy behaviour when the load event was actually
        // doing an iload, when it was bound to an iframe node.
        // The deprecation tells that the event should be changed 
        // from load to iload.
        if (loadoper && ! iloadoper) {
            iloadoper = loadoper;
            loadoper = null;
            // with the legacy loads we suppose autodetect=false
            iloadoper.parms.autodetect = false;
            var msg = 'Deprecated the use of [load] event for iframes. It';
            msg += ' will behave differently in the future. Use the';
            msg += ' [iload] event (maybe with [evt-iload-autodetect:';
            msg += ' false]) instead !';
            kukit.logWarning(msg);
        } 
    } else {
        // Not an iframe. So iload is not usable.
        if (iloadoper) {
            kukit.E = '[iload] event can only be bound to an iframe node.';
            throw kukit.E;
        }
    }
    // Now, bind the events.
    if (loadoper) {
        var msg = 'EventRule #' + loadoper.eventRule.getIndex() + ' mergeId [';
        msg += loadoper.eventRule.kssSelector.mergeId;
        msg += '] selected normal postponed execution.';
        kukit.logDebug(msg);
        // for any other node than iframe, or even for iframe in phase1,
        // we need to execute immediately.
        var func_to_bind = loadoper.makeExecuteActionsHook();
        var remark = '';
        remark += '[load] event execution for ';
        // loadoper can execute on document!
        // Is this the case? 
        if (loadoper.node == null) {
            // document:load
            remark += '[document]';
        } else {
            // <node>:load
            remark += 'node [';
            remark += loadoper.node.tagName.toLowerCase();
            remark += ']';
        }
        kukit.engine.bindScheduler.addPost(func_to_bind, remark);
    }
    if (iloadoper) {
        var phase = iloadoper.node._kukitmark;
        // For phase 2 we need to execute posponed, for phase1 immediately.
        // XXX it would be better not need this and do always postponed.
        if (phase == 2 || (phase == 1 && kukit.engine.initializedOnDOMLoad)) {
            var msg = 'EventRule #' + iloadoper.eventRule.getIndex();
            msg += ' mergeId [' + iloadoper.eventRule.kssSelector.mergeId;
            msg += ' event selected delayed execution (when iframe loaded)';
            kukit.logDebug(msg);
            // We want the event execute once the iframe is loaded.
            // In a somewhat tricky way, we start the scheduler only from
            // the normal delayed execution. This will enable that in
            // case we had a load event on the same node, it could modify
            // the name and id parms and we only start
            // the autosense loop (which is based on name and id) after the
            // load event's action executed. 
            // (Note, oper.node.id may lie in the log then and show the 
            // original, unchanged id but we ignore this for the time.)
            var g = function() {
                var f = function() {
                    var func_to_bind = iloadoper.makeExecuteActionsHook();
                    var remark = '';
                    remark += '[iload] event execution for iframe [';
                    remark += iloadoper.node.name + ']';
                    kukit.engine.bindScheduler.addPost(func_to_bind, remark);
                };
                new kukit.dom.EmbeddedContentLoadedScheduler(iloadoper.node.id,
                    f, iloadoper.parms.autodetect);
            };
            var remark = '';
            remark += 'Schedule [iload] event for iframe ';
            remark += iloadoper.node.name + ']';
            kukit.engine.bindScheduler.addPost(g, remark);
        } else {
            var msg = 'EventRule #' + iloadoper.eventRule.getIndex();
            msg += ' mergeId [';
            msg += iloadoper.eventRule.kssSelector.mergeId;
            msg += '] event selected normal postponed execution.';
            kukit.logDebug(msg);
            var func_to_bind = iloadoper.makeExecuteActionsHook();
            var remark = '';
            remark += 'Execute [iload] event for iframe ';
            remark += iloadoper.node.name + ']';
            kukit.engine.bindScheduler.addPost(func_to_bind, remark);
        }
    }
};

// Use the [node] iterator to provide expected invocation
// and call signature of the bind method.
kukit.eventsGlobalRegistry.registerForAllEvents(null, ['load', 'iload'],
    kukit.pl.LoadEventBinder, '__bind__', null, 'Node');


/*
* class SpinnerEventBinder
*
* Spinner support. Besides the event itself we use some utility
* classes to introduce lazyness (delay) for the spinner.
*/
kukit.pl.SpinnerEventBinder = function() {
    this.state = false;
    var self = this;
    var timeoutSetState = function(spinnerevent) {
       self.timeoutSetState(spinnerevent);
    };
    this.scheduler = new kukit.ut.Scheduler(timeoutSetState);
};

kukit.pl.SpinnerEventBinder.prototype.__bind__ = function(name, func_to_bind,
    oper) {
    oper.componentName = '[spinner] event binding';
    oper.evaluateParameters([], {'laziness': 0});
    oper.evalInt('laziness');
    // Register the function with the global queue manager
    var state_to_bind = (name == 'spinneron');
    var self = this;
    var func = function() {
        self.setState(func_to_bind, state_to_bind, oper.parms.laziness);
    };
    kukit.engine.requestManager.registerSpinnerEvent(func, state_to_bind);
};

kukit.pl.SpinnerEventBinder.prototype.setState = function(func_to_bind, state,
    laziness) {
    // This is called when state changes. We introduce laziness
    // before calling the func.
    this.func_to_bind = func_to_bind;
    this.state = state;
    var now = (new Date()).valueOf();
    var wakeUp = now + laziness;
    this.scheduler.setNextWakeAtLeast(wakeUp);
};

kukit.pl.SpinnerEventBinder.prototype.timeoutSetState = function() {
    // really call the bound actions which should set the spinner
    this.func_to_bind();
};

kukit.eventsGlobalRegistry.register(null, 'spinneron',
    kukit.pl.SpinnerEventBinder, '__bind__', null);
kukit.eventsGlobalRegistry.register(null, 'spinneroff',
    kukit.pl.SpinnerEventBinder, '__bind__', null);


/* Core actions
*
* The core client actions that can be executed on the client
* side.
*
* They also get registered as commands
*/
kukit.actionsGlobalRegistry.register('error', function (oper) {
    throw 'The builtin error action should never execute.';
    }
);
kukit.commandsGlobalRegistry.registerFromAction('error',
    kukit.cr.makeGlobalCommand);

kukit.actionsGlobalRegistry.register('logDebug', function (oper) {
    var name = '[logDebug] action';
    oper.evaluateParameters([], {'message': '[logDebug] action'}, name);
    var message = oper.parms.message;
    message += oper.debugInformation();    
    kukit.logDebug(message); 
    if (kukit.hasFirebug) {
        kukit.logDebug(oper.node);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('logDebug',
    kukit.cr.makeGlobalCommand);

kukit.actionsGlobalRegistry.register('log', function (oper) {
    oper.evaluateParameters([], {'message': 'Log action'}, 'log action');
    var message = oper.parms.message;
    message += oper.debugInformation();    
    kukit.log(message);
});
kukit.commandsGlobalRegistry.registerFromAction('log',
    kukit.cr.makeGlobalCommand);

kukit.actionsGlobalRegistry.register('alert', function (oper) {
    oper.evaluateParameters([], {'message': 'Alert action'}, 'alert action');
    var message = oper.parms.message;
    message += oper.debugInformation();    
    alert(message);
});
kukit.commandsGlobalRegistry.registerFromAction('alert', 
    kukit.cr.makeGlobalCommand);

/* Core commands 
*
* All the commands are also client actions.
*/

kukit.actionsGlobalRegistry.register('replaceInnerHTML', function(oper) {
/*
*  accepts both string and dom.
*/
    oper.componentName = '[replaceInnerHTML] action';
    oper.evaluateParameters(['html'], {'withKssSetup': true});
    oper.evalBool('withKssSetup');
    var node = oper.node;
    var insertedNodes;
    if (typeof(oper.parms.html) == 'string') {
        node.innerHTML = oper.parms.html;
        insertedNodes = [];
        for (var i=0; i<node.childNodes.length; i++) {
            insertedNodes.push(node.childNodes[i]);
        }
    } else {
        oper.parms.html = kukit.dom.forceToDom(oper.parms.html);
        kukit.dom.clearChildNodes(node);
        insertedNodes = kukit.dom.appendChildren(
            oper.parms.html.childNodes, node);
    }
    kukit.logDebug(insertedNodes.length + ' nodes inserted.');
    if (oper.parms.withKssSetup) {
        kukit.engine.setupEvents(insertedNodes);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('replaceInnerHTML',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('replaceHTML', function(oper) {
/*
*  accepts both string and dom.
*/
    oper.componentName = '[replaceHTML] action';
    oper.evaluateParameters(['html'], {'withKssSetup':true});
    oper.evalBool('withKssSetup');
    var node = oper.node;
    oper.parms.html = kukit.dom.forceToDom(oper.parms.html);
    var elements = oper.parms.html.childNodes;
    var length = elements.length;
    kukit.logDebug(length + ' nodes inserted.');
    if (length > 0) {
        var parentNode = node.parentNode;
        var insertedNodes = [];
        // insert the last node
        var next = elements[length-1];
        parentNode.replaceChild(next, node);
        insertedNodes.push(next);
        // then we go backwards with the rest of the nodes
        for (var i=length-2; i>=0; i--) {
            var inserted = parentNode.insertBefore(elements[i], next);
            insertedNodes.push(inserted);
            next = inserted;
        }
        if (oper.parms.withKssSetup) {
            kukit.engine.setupEvents(insertedNodes);
        }
    }
});
kukit.commandsGlobalRegistry.registerFromAction('replaceHTML',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('setAttribute', function(oper) {
    oper.componentName = '[setAttribute] action';
    oper.evaluateParameters(['name', 'value'], {});
    if (oper.parms.name.toLowerCase() == 'style') {
        kukit.E = '[style] attribute is not allowed with [setAttribute]';
        throw kukit.E;
    }
    kukit.dom.setAttribute(oper.node, oper.parms.name, 
        oper.parms.value);
});
kukit.commandsGlobalRegistry.registerFromAction('setAttribute',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('setKssAttribute', function(oper) {
    oper.componentName = '[setKssAttribute] action';
    oper.evaluateParameters(['name', 'value'], {});
    kukit.dom.setKssAttribute(oper.node, oper.parms.name, 
        oper.parms.value);
});
kukit.commandsGlobalRegistry.registerFromAction('setKssAttribute',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('setStyle', function(oper) {
    oper.componentName = '[setStyle] action';
    oper.evaluateParameters(['name', 'value'], {});
    oper.node.style[oper.parms.name] = oper.parms.value;
});
kukit.commandsGlobalRegistry.registerFromAction('setStyle', 
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('addClass', function(oper) {
    oper.componentName = '[addClass] action';
    oper.evaluateParameters(['value'], {});
    addClassName(oper.node, oper.parms.value);
});
kukit.commandsGlobalRegistry.registerFromAction('addClass',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('removeClass', function(oper) {
    oper.componentName = '[removeClass] action';
    oper.evaluateParameters(['value'], {});
    removeClassName(oper.node, oper.parms.value);
});
kukit.commandsGlobalRegistry.registerFromAction('removeClass',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('insertHTMLAfter', function(oper) {
    oper.componentName = '[insertHTMLAfter] action';
    oper.evaluateParameters(['html'], {'withKssSetup':true});
    oper.evalBool('withKssSetup');
    oper.parms.html = kukit.dom.forceToDom(oper.parms.html);
    var content = oper.parms.html;
    var parentNode = oper.node.parentNode;
    var toNode = kukit.dom.getNextSiblingTag(oper.node);
    var insertedNodes;
    if (toNode == null) {
        insertedNodes = kukit.dom.appendChildren(content.childNodes,
            parentNode);
    } else {
        insertedNodes = kukit.dom.insertBefore(content, parentNode, toNode);
    }
    kukit.logDebug(insertedNodes.length + ' nodes inserted.');
    // update the events for the new nodes
    if (oper.parms.withKssSetup) {
        kukit.engine.setupEvents(insertedNodes);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('insertHTMLAfter',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('insertHTMLBefore', function(oper) {
    oper.componentName = '[insertHTMLBefore] action';
    oper.evaluateParameters(['html'], {'withKssSetup':true});
    oper.evalBool('withKssSetup');
    oper.parms.html = kukit.dom.forceToDom(oper.parms.html);
    var content = oper.parms.html;
    var toNode = oper.node;
    var parentNode = toNode.parentNode;
    var insertedNodes = kukit.dom.insertBefore(content, parentNode, toNode);
    kukit.logDebug(insertedNodes.length + ' nodes inserted.');
    // update the events for the new nodes
    if (oper.parms.withKssSetup) {
        kukit.engine.setupEvents(insertedNodes);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('insertHTMLBefore',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('insertHTMLAsLastChild', function(oper) {
    oper.componentName = '[insertHTMLAsLastChild] action';
    oper.evaluateParameters(['html'], {'withKssSetup':true});
    oper.evalBool('withKssSetup');
    oper.parms.html = kukit.dom.forceToDom(oper.parms.html);
    var insertedNodes = kukit.dom.appendChildren(oper.parms.html,
        oper.node);
    insertedNodes = kukit.dom.appendChildren(oper.parms.html.childNodes,
        oper.node);
    kukit.logDebug(insertedNodes.length + ' nodes inserted.');
    // update the events for the new nodes
    if (oper.parms.withKssSetup) {
        kukit.engine.setupEvents(insertedNodes);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('insertHTMLAsLastChild',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('insertHTMLAsFirstChild', function(oper) {
    oper.componentName = '[insertHTMLAsFirstChild] action';
    oper.evaluateParameters(['html'], {'withKssSetup':true});
    oper.evalBool('withKssSetup');
    oper.parms.html = kukit.dom.forceToDom(oper.parms.html);
    var content = oper.parms.html;
    var parentNode = oper.node;
    var toNode = parentNode.firstChild;
    var insertedNodes;
    if (toNode == null) {
        insertedNodes = kukit.dom.appendChildren(content.childNodes, 
            parentNode);
    } else {
        insertedNodes = kukit.dom.insertBefore(content, parentNode, toNode);
    }
    kukit.logDebug(insertedNodes.length + ' nodes inserted.');
    // update the events for the new nodes
    if (oper.parms.withKssSetup) {
        kukit.engine.setupEvents(insertedNodes);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('insertHTMLAsFirstChild',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('deleteNodeAfter', function(oper) {
    oper.componentName = '[deleteNodeAfter] action';
    oper.evaluateParameters([], {});
    var parentNode = oper.node.parentNode;
    var toNode = kukit.dom.getNextSiblingTag(oper.node);
    if (toNode != null) {
        parentNode.removeChild(toNode);
    }  
});
kukit.commandsGlobalRegistry.registerFromAction('deleteNodeAfter', 
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('deleteNodeBefore', function(oper) {
    oper.componentName = '[deleteNodeBefore] action';
    oper.evaluateParameters([], {});
    var parentNode = oper.node.parentNode;
    var toNode = kukit.dom.getPreviousSiblingTag(oper.node);
    parentNode.removeChild(toNode);
});
kukit.commandsGlobalRegistry.registerFromAction('deleteNodeBefore', 
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('deleteNode', function(oper) {
    oper.componentName = '[deleteNode] action';
    oper.evaluateParameters([], {});
    var parentNode = oper.node.parentNode;
    parentNode.removeChild(oper.node);
});
kukit.commandsGlobalRegistry.registerFromAction('deleteNode', 
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('clearChildNodes', function(oper) {
    oper.componentName = '[clearChildNodes] action';
    // TODO get rid of none
    oper.evaluateParameters([], {'none': false});
    kukit.dom.clearChildNodes(oper.node);
});
kukit.commandsGlobalRegistry.registerFromAction('clearChildNodes',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('focus', function(oper) {
    oper.componentName = '[focus] action';
    // TODO get rid of none
    oper.evaluateParameters([], {'none': false});
    kukit.dom.focus(oper.node);
});
kukit.commandsGlobalRegistry.registerFromAction('focus',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('moveNodeAfter', function(oper) {
    oper.componentName = '[moveNodeAfter] action';
    oper.evaluateParameters(['html_id'], {});
    var node = oper.node;
    var parentNode = node.parentNode;
    parentNode.removeChild(node);
    var toNode = document.getElementById(oper.parms.html_id);
    var nextNode = kukit.dom.getNextSiblingTag(toNode);
    if (nextNode == null) {
        toNode.parentNode.appendChild(node);
    } else {
        parentNode.insertBefore(node, nextNode);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('moveNodeAfter',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('moveNodeBefore', function(oper) {
    oper.componentName = '[moveNodeBefore] action';
    oper.evaluateParameters(['html_id'], {});
    var node = oper.node;
    // no need to remove it, as insertNode does it anyway
    // var parentNode = node.parentNode;
    // parentNode.removeChild(node);
    var toNode = document.getElementById(oper.parms.html_id);
    var parentNode = toNode.parentNode;
    parentNode.insertBefore(node, toNode);
});
kukit.commandsGlobalRegistry.registerFromAction('moveNodeBefore',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('moveNodeAsLastChild', function(oper) {
    oper.componentName = '[moveNodeAsLastChild] action';
    oper.evaluateParameters(['html_id'], {});
    var node = oper.node;
    // no need to remove it, as insertNode does it anyway
    // var parentNode = node.parentNode;
    // parentNode.removeChild(node);
    var parentNode = document.getElementById(oper.parms.html_id);
    parentNode.appendChild(node);
});
kukit.commandsGlobalRegistry.registerFromAction('moveNodeAsLastChild', 
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('copyChildNodesFrom', function(oper) {
    oper.componentName = '[copyChildNodesFrom] action';
    oper.evaluateParameters(['html_id'], {});
    var fromNode = document.getElementById(oper.parms.html_id);
    Sarissa.copyChildNodes(fromNode, oper.node);
});
kukit.commandsGlobalRegistry.registerFromAction('copyChildNodesFrom',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('copyChildNodesTo', function(oper) {
    oper.componentName = '[copyChildNodesTo] action';
    oper.evaluateParameters(['html_id'], {});
    toNode = document.getElementById(oper.parms.html_id);
    Sarissa.copyChildNodes(oper.node, toNode);
});
kukit.commandsGlobalRegistry.registerFromAction('copyChildNodesTo',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register('setStateVar', function(oper) {
    oper.componentName = '[setStateVar] action';
    oper.evaluateParameters(['varname', 'value'], {});
    kukit.engine.stateVariables[oper.parms.varname] =
        oper.parms.value;
});
kukit.commandsGlobalRegistry.registerFromAction('setStateVar',
    kukit.cr.makeGlobalCommand);

kukit.actionsGlobalRegistry.register('continueEvent', function(oper) {
    // Trigger continuation event. Event will be triggered on the same node
    // or on all the nodes bound for the current event state.
    // allows excess parms in the following check.
    oper.componentName = '[continueEvent] action';
    oper.evaluateParameters(['name'], {'allnodes': 'false'}, '', true);
    oper.evalBool('allnodes', 'continueEvent');
    var parms = oper.parms;
    var binderInstance = oper.binderInstance;
    var allNodes = parms.allnodes;
    // marshall it, the rest of the parms will be passed
    var actionParameters = {};
    for (var key in parms) {
        if (key != 'name' && key != 'allnodes') {
            actionParameters[key] = parms[key];
        }
    }
    if (parms.allnodes) {
        binderInstance.__continueEvent_allNodes__(parms.name,
            actionParameters);
    } else {
        // execution happens on the orignode
        binderInstance.__continueEvent__(parms.name, oper.orignode,
            actionParameters);
    }
});
kukit.commandsGlobalRegistry.registerFromAction('continueEvent',
    kukit.cr.makeGlobalCommand);

kukit.actionsGlobalRegistry.register('executeCommand', function(oper) {
    // Allows executing a local action on a different selection.
    //
    // allows excess parms in the following check
    oper.componentName = '[executeCommand] action';
    oper.evaluateParameters(['name', 'selector'],
                       {'selectorType': null},
                       '', true);
    var parms = oper.parms;
    // marshall it, the rest of the parms will be passed
    var actionParameters = {};
    for (var key in parms) {
        if (key != 'name' && key != 'selector' && key != 'selectorType') {
            actionParameters[key] = parms[key];
        }
    }
    var command = new kukit.cr.makeCommand(parms.selector,
            parms.name, parms.selectorType, actionParameters);
    command.execute(oper);
});


// Add/remove a class to/from a node
kukit.actionsGlobalRegistry.register('toggleClass', function (oper) {
    oper.componentName = '[toggleClass] action';
    // BBB 4 month, until 2007-10-18
    // oper.evaluateParameters(['value'], {});
    kukit.actionsGlobalRegistry.BBB_classParms(oper);

    var node = oper.node;
    var className = oper.parms.value;

    var nodeclass = kukit.dom.getAttribute(node, 'class');
    var classFoundAtIndex = -1;
    var parts = nodeclass.split(' ');
    for(var i=0; i<parts.length; i++){
        if(parts[i]==className){
            classFoundAtIndex = i;
        }
    }
    if(classFoundAtIndex==-1){
        parts.push(className);
    } else {
        parts.splice(classFoundAtIndex, 1);
    }
    kukit.dom.setAttribute(node, 'class', parts.join(' '));
});
kukit.commandsGlobalRegistry.registerFromAction('toggleClass',
    kukit.cr.makeSelectorCommand);

/*
*  XXX Compatibility settings for old command names.
*  These will be removed as soon as all current use cases are changed.
*  Do not use these as your code will break!
* 
*/
// BBB remove at 2007-10-18
kukit.commandsGlobalRegistry.registerFromAction('replaceInnerHTML', 
    kukit.cr.makeSelectorCommand, 'setHtmlAsChild');
kukit.commandsGlobalRegistry.registerFromAction('replaceHTML',
    kukit.cr.makeSelectorCommand, 'replaceNode');
kukit.commandsGlobalRegistry.registerFromAction('insertHTMLAfter',
    kukit.cr.makeSelectorCommand, 'addAfter');
kukit.commandsGlobalRegistry.registerFromAction('deleteNodeAfter',
    kukit.cr.makeSelectorCommand, 'removeNextSibling');
kukit.commandsGlobalRegistry.registerFromAction('deleteNodeBefore',
    kukit.cr.makeSelectorCommand, 'removePreviousSibling');
kukit.commandsGlobalRegistry.registerFromAction('deleteNode',
    kukit.cr.makeSelectorCommand, 'removeNode');
kukit.commandsGlobalRegistry.registerFromAction('clearChildNodes',
    kukit.cr.makeSelectorCommand, 'clearChildren');
kukit.commandsGlobalRegistry.registerFromAction('copyChildNodesFrom',
    kukit.cr.makeSelectorCommand, 'copyChildrenFrom');
kukit.commandsGlobalRegistry.registerFromAction('copyChildNodesTo',
    kukit.cr.makeSelectorCommand, 'copyChildrenTo');
kukit.commandsGlobalRegistry.registerFromAction('setStateVar',
    kukit.cr.makeGlobalCommand, 'setStatevar');
// BBB 4 month, until 2007-10-18
kukit.actionsGlobalRegistry.register('addClassName', function(oper) {
    var msg = 'Deprecated the [addClassName]  action, use [addClass] instead!';
    kukit.logWarning(msg);
    oper.componentName = '[addClassName] action';
    kukit.actionsGlobalRegistry.BBB_classParms(oper);
    kukit.actionsGlobalRegistry.get('addClass')(oper);
});
kukit.commandsGlobalRegistry.registerFromAction('addClassName',
    kukit.cr.makeSelectorCommand);
// BBB 4 month, until 2007-10-18
kukit.actionsGlobalRegistry.register('removeClassName', function(oper) {
    var msg = 'Deprecated the [removeClassName]  action, use [removeClass]';
    msg += 'instead !';
    kukit.logWarning(msg);
    oper.componentName = 'removeClassName action';
    kukit.actionsGlobalRegistry.BBB_classParms(oper);
    kukit.actionsGlobalRegistry.get('removeClass')(oper);
});
kukit.commandsGlobalRegistry.registerFromAction('removeClassName',
    kukit.cr.makeSelectorCommand);

// BBB 4 month, until 2007-10-18
kukit.actionsGlobalRegistry.BBB_classParms = function(oper) {
    var old;
    var has_old;
    if (typeof(oper.parms.className) != 'undefined') {
        old = oper.parms.className;
        has_old = true;
    var msg = 'Deprecated the [className] parameter in ' + oper.componentName;
    msg += ', use [value] instead !';
    kukit.logWarning(msg); 
    }
    if (typeof(oper.parms.name) != 'undefined') {
        old = oper.parms.name;
        has_old = true;
        var msg = 'Deprecated the [name] parameter in ' + oper.componentName;
        msg += ', use [value] instead !';
        kukit.logWarning(msg);
    }
    if (has_old) {
        if (typeof(oper.parms.value) == 'undefined') {
            oper.parms = {value: old};
        } else {
            oper.parms = {};
        }
    }
};
// end BBB



/* Use onDOMLoad event to initialize kukit
   earlier then the document is fully loaded,
   but after the DOM is at its place already.

   This functionality is missing from Plone 2.1,
   the script is present in >=2.5, but it is not
   always added to RR - it needs to be added manually.

   If it's present we use it.
*/

kukit.plone = {};

if (typeof(addDOMLoadEvent) != 'undefined') {

    var f = function() {
        kukit.log('KSS started by Plone DOMLoad event.');
        kukit.bootstrapFromDOMLoad();
    };
    addDOMLoadEvent(f);
    kukit.log('KSS bootstrap set up in Plone DOMLoad event.');
} else {
    var msg = 'Plone addDOMLoadEvent not found by KSS, DOMLoad activation';
    msg += ' skipped (you might want to add event-registration.js to ';
    msg += ' ResourceRegistries).';
    kukit.logWarning(msg);
}

/* Base kukit plugins for Plone*/

kukit.actionsGlobalRegistry.register("plone-initKupu", function(oper) {
    kukit.logDebug('Enter plone-initKupu');
    oper.evaluateParameters([], {}, 'plone-initKupu action');
    // we start from the iframe node...
    if (oper.node.tagName.toLowerCase() != 'iframe') {
        kukit.E = 'The plone-initKupu action can only be setup on an iframe';
        kukit.E += ' node.';
        throw kukit.E;
    }
    var divnode = oper.node.parentNode.parentNode.parentNode.parentNode;
    var id = divnode.id;
    if (! id) {
        kukit.E = 'The plone-initKupu action did not find the editor id from';
        kukit.E += ' the iframe node.';
        throw kukit.E;
    }
 
    //
    // Register the kupu editor in KSS
    // This enables KSS to update the textarea explicitely. 
    //
    var prefix = '#'+id+' ';
    var textarea = getFromSelector(prefix+'textarea.kupu-editor-textarea');
    kukit.fo.fieldUpdateRegistry.register(textarea,
            {editor: null,
             node: textarea,
             doInit: function() {
                kukit.log('Setup Kupu initialization on load event.');
                var self = this;
                initKupuOnLoad = function() {
                    kukit.log('Initialize Kupu from onload event.');
                    self.editor = initPloneKupu(id);
                };
                this.editor = initPloneKupu(id);
                registerEventListener(window, "load", initKupuOnLoad);
                },
             doUpdate: function() {
                this.editor.saveDataToField(this.node.form, this.node);
                // set back _initialized
                // XXX check if this is actually ok?
                this.editor._initialized = true;
                }
             });
    kukit.logDebug('plone-initKupu action done.');
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initKupu', 
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register("plone-followLink", function(oper) {
    oper.evaluateParameters([], {}, 'plone-followLink action');
    var url = oper.node.href;
    if (url.substr(0, 7) == "http://") {
        // redirect to it
        window.location.replace(url);
    } else if (url.substr(0, 13) == "javascript://") {
        // execute it
        eval(url.substr(13));
    }
});
kukit.commandsGlobalRegistry.registerFromAction('plone-followLink',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register("plone-submitCurrentForm", function (oper) {
    oper.evaluateParameters([], {}, 'plone-submitCurrentForm action');
    // disable the onbeforeunload event since we want to submit now.
    window.onbeforeunload = null;
    var form = new kukit.fo.CurrentFormLocator(oper.node).getForm();
    form.submit();
});
kukit.commandsGlobalRegistry.registerFromAction('plone-submitCurrentForm',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register("plone-initFormTabs", function(oper) {
    oper.evaluateParameters([], {}, 'plone-initFormTabs action');
    if (oper.node.tagName.toLowerCase() != 'form') {
        kukit.E = 'The plone-initFormTabs action can only execute on a form';
        kukit.E += ' node as a target.';
        throw kukit.E;
    }
    var form = oper.node;  
    ploneFormTabbing.initializeForm(form);
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initFormTabs', kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register("plone-initFormProtection", function(oper) {
    oper.evaluateParameters([], {}, 'plone-initFormProtection action');
    if (oper.node.tagName.toLowerCase() != 'form') {
        kukit.E = 'The plone-initFormProtection action can only execute on';
        kukit.E += ' a form node as a target.';
        throw kukit.E;
    }
    var form = oper.node;  
    if (! window.onbeforeunload) {
        window.onbeforeunload = new BeforeUnloadHandler().execute;
    }
    var tool = window.onbeforeunload.tool;
    // We add the new tool to the 
    tool.addForm(form);
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initFormProtection',
    kukit.cr.makeSelectorCommand);

kukit.actionsGlobalRegistry.register("plone-formProtectionCheck", 
    function(oper) {
    oper.evaluateParameters([], {}, 'plone-formProtectionCheck action');
    // Find the binderInstance of the switcher.
    // (since we are in an action and not in the event,
    // we don't have it at hand.
    // Note that we would not necessarily need the singleton)
    var binderInfo =
        kukit.engine.binderInfoRegistry.getSingletonBinderInfoByName('plone',
        'formProtectionChecked');
    var binderInstance = binderInfo.getBinderInstance();
    // check if the form has change
    var message;
    if ( window.onbeforeunload) {
        var tool = window.onbeforeunload.tool;
        message = tool.execute();
    }
    // Do we need the popup?
    var result = true;
    if (message) {
        var confirmMsg = 'Are you sure you want to navigate away from this';
        confirmMsg += ' page?\n\n' + message + '\n\nPress OK to continue,';
        confirmMsg += ' or Cancel to stay on the current page.';
        result = confirm(confirmMsg);
    }
    // arrange the continuation events
    if (result) {
        // Continue with the real action.
        var action = 'formProtectionChecked';
    } else {
        // Continue with the cancel action.
        var action = 'formProtectionFailed';
    }
    binderInstance.__continueEvent__(action, oper.node, {});
});

kukit.commandsGlobalRegistry.registerFromAction('plone-formProtectionCheck',
    kukit.cr.makeSelectorCommand);

kukit.plone.FormProtectionCheckedEvents = function() {
};

kukit.plone.FormProtectionCheckedEvents.prototype.__default_failed__ = 
    function(name, oper) {
};

kukit.eventsGlobalRegistry.register('plone', 'formProtectionChecked',
    kukit.plone.FormProtectionCheckedEvents, null, null);

kukit.eventsGlobalRegistry.register('plone', 'formProtectionFailed',
    kukit.plone.FormProtectionCheckedEvents, null, '__default_failed__');

// Form Locking

kukit.actionsGlobalRegistry.register("plone-initLockingProtection",
    function(oper) {
    oper.evaluateParameters([], {}, 'plone-initLockingProtection action');
    if (oper.node.tagName.toLowerCase() != 'form') {
        kukit.E = 'The plone-initLockingProtection action can only execute';
        kukit.E += ' on a form node as a target.';
        throw kukit.E;
    }
    if (! window.onunload) {
        var handler = new plone.UnlockHandler().execute;
        window.onunload = handler;
    }
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initLockingProtection',
    kukit.cr.makeSelectorCommand);


kukit.actionsGlobalRegistry.register("plone-removeLockProtection",
    function(oper) {
    oper.evaluateParameters([], {}, 'plone-removeLockProtection action');
    if ( window.onunload) {
        window.onunload = null;
    }
});
kukit.commandsGlobalRegistry.registerFromAction('plone-removeLockProtection',
    kukit.cr.makeGlobalCommand);

// Folder contents shift click selection
kukit.actionsGlobalRegistry.register("plone-initShiftDetection",
    function(oper) {
    oper.evaluateParameters([], {}, 'plone-initShiftDetection action');

    kukit.engine.stateVariables['plone-shiftdown'] = false;
    document.onkeydown = function(e) {
        var evt = e || window.event;
        if(evt.keyCode == 16){
            kukit.engine.stateVariables['plone-shiftdown'] = true;
        }
    };

    document.onkeyup = function(e) {
        var evt = e || window.event;
        if(evt.keyCode == 16){
            kukit.engine.stateVariables['plone-shiftdown'] = false;
        }
    };
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initShiftDetection',
    kukit.cr.makeSelectorCommand);


kukit.actionsGlobalRegistry.register("plone-initCheckBoxSelection",
    function(oper) {
    oper.evaluateParameters([], {}, 'plone-initCheckBoxSelection action');
    kukit.engine.stateVariables['plone-foldercontents-firstcheckeditem'] = null;
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initCheckBoxSelection',
    kukit.cr.makeSelectorCommand);


kukit.actionsGlobalRegistry.register("plone-createCheckBoxSelection",
    function(oper) {
    var actionMsg = 'plone-createCheckBoxSelection action';
    oper.evaluateParameters(['group'], {}, actionMsg);

    var node = oper.node;
    var firstItemVarName = 'plone-foldercontents-firstcheckeditem';
    var firstItem = kukit.engine.stateVariables[firstItemVarName];
    if(firstItem && kukit.engine.stateVariables['plone-shiftdown']) {
        var group = oper.parms.group;
        var allNodes = kukit.dom.cssQuery(group);
        var start = null;
        var end = null;
        for(var i=0; i < allNodes.length; i++){
            if(allNodes[i] == firstItem){
                start = i;
            }
            else if(allNodes[i] == node){
                end = i;
            }
        }
        if(start>end){
            var temp = start;
            start = end;
            end = temp;
        }

        for(var i=start; i <= end; i++){
            allNodes[i].checked = firstItem.checked;
        }
    }
    else {
        kukit.engine.stateVariables[firstItemVarName] = node;
    }
});
kukit.commandsGlobalRegistry.registerFromAction('plone-createCheckBoxSelection',
    kukit.cr.makeSelectorCommand);


kukit.actionsGlobalRegistry.register("plone-initDragAndDrop",
    function(oper) {
    oper.evaluateParameters(['table'], {}, 'plone-initDragAndDrop action');
    var table = oper.parms.table;
    ploneDnDReorder.table = cssQuery(table)[0];
    if (!ploneDnDReorder.table)
        return;
    ploneDnDReorder.rows = cssQuery(table + " > tr," +
                                    table + " > tbody > tr");
    var targets = cssQuery(table + " > tr > td.draggable," +
                           table + " > tbody > tr > td.draggable");
    for (var i=0; i<targets.length; i++) {
        if (hasClassName(targets[i], 'notDraggable'))
            continue;
	var target = targets[i];
        target.onmousedown=ploneDnDReorder.doDown;
        target.onmouseup=ploneDnDReorder.doUp;
        addClassName(target, "draggingHook");
	target.innerHTML = '::'
    }
});
kukit.commandsGlobalRegistry.registerFromAction('plone-initDragAndDrop',
    kukit.cr.makeSelectorCommand);


// Scriptaculous Effects

if (typeof(Effect) != "undefined") {
    kukit.HASEFFECTS = 1;
} else {
    kukit.HASEFFECTS = 0;
}

if (kukit.HASEFFECTS && typeof(Effect.Transitions) != "undefined") {
    kukit.actionsGlobalRegistry.register("effect", function (oper) {
        oper.evaluateParameters([], {'type': 'fade'}, 'scriptaculous effect');
        var node = oper.node;
        if (oper.parms.type == 'fade') {
        new Effect.Fade(node);
        } else if (oper.parms.type == 'appear') {
        new Effect.Appear(node);
        } else if (oper.parms.type == 'puff') {
        new Effect.Puff(node);
        } else if (oper.parms.type == 'blinddown') {
        new Effect.BlindDown(node);
        } else if (oper.parms.type == 'blindup') {
        new Effect.BlindUp(node);
        }
    });

    kukit.commandsGlobalRegistry.registerFromAction('effect', kukit.cr.makeSelectorCommand);

    // This is terrible. We needed to copy this part
    // from prototype. Notice that I put this.$ =
    // in the beginning. Without that the function
    // declarations in IE won't overwrite each others,
    // and one of them (first or last occurence) comes
    // in. Now, we have a contradicting $ declaration
    // in Mochikit, causing the problem.

    this.$ = function $() {
      var results = [], element;
      for (var i = 0; i < arguments.length; i++) {
        element = arguments[i];
        if (typeof element == 'string')
          element = document.getElementById(element);
        results.push(Element.extend(element));
      }
      return results.length < 2 ? results[0] : results;
    };
}


// bind action menus on load


kukit.actionsGlobalRegistry.register("bindActionMenus", function (oper) {
        initializeMenus();
        kukit.logDebug('Plone menus initialized');
    });

kukit.log('Plone legacy [initializeMenus] action registered.');



// bind external links marking on load

kukit.plonelegacy = {};

if (typeof(scanforlinks) == 'undefined') {
    kukit.plonelegacy.bindExternalLinks = function() {}
    }
else {
    kukit.plonelegacy.bindExternalLinks = function() {
        scanforlinks();
        }
    }

kukit.actionsGlobalRegistry.register("bindExternalLinks", function (oper) {
        kukit.plonelegacy.bindExternalLinks();
        kukit.logDebug('Plone external links bound.');
    });

kukit.log('Plone legacy [bindExternalLinks] action registered.');





// bind collapsible sections on load


kukit.actionsGlobalRegistry.register("initializeCollapsible", function (oper) {
        activateCollapsibles();
    });

kukit.log('Plone legacy [initializeCollapsible] action registered.');




// bind toc code


kukit.actionsGlobalRegistry.register("createTableOfContents", function (oper) {
        createTableOfContents();
    });
kukit.commandsGlobalRegistry.registerFromAction('createTableOfContents', kukit.cr.makeGlobalCommand);

kukit.log('Plone [createTableOfContents] action registered.');

