var classAir = function(config)
{
    // Library references //

    var _this = this;
    this.version = '1.0';
    this.toString = function(){ return 'Alert IR'; };
    arguments.callee.toString = function(){ return 'Alert IR - Constructor'; };

    // Private properties //

    /**
    * Library memory
    * @type Object
    */
    var _memory = {};

    // Public properties //

    /**
    * Configuration.
    * @type Object
    */
    this.config = config || {
        servername : window.location.hostname
    };

    /**
    * i18n settings.
    * @type Object
    */
    this.i18n =
    {
        lang : 'en',
        number :
        {
            decimals : 2,
            decimalPoint : '.',
            forceDecimals : false,
            negativeParens : false,
            thousandsSeparator : ','
        }
    };

    /**
    * Determines if page is displayed in standard- or quirksmode.
    * @type Boolean
    */
    this.isQuirksmode = function(){ return (document.compatMode !== 'CSS1Compat') ? true : false; };
    this.isQuirksmode.toString = function(){ return (document.compatMode !== 'CSS1Compat') ? true : false; };

    /**
    * Location information.
    * @type Object
    */
    this.location =
    {
        hash : function(){ return _this.ltrim(window.location.hash, '#'); }, // foo&bar
        host : window.location.host, // www.alertir.com (or www.alertir.com:81 if port is present)
        hostname : window.location.hostname, // www.alertir.com
        href : window.location.href, // complete url
        pathname : window.location.pathname, // /index.php
        port : (window.location.port) ? window.location.port : '', // 81 (if present)
        protocol : window.location.protocol, // http:
        search : window.location.search // ?foo=bar&alpha=beta
    };
    this.location.toString = function()
    {
        return _this.echor(this);
    };

    /**
    * Current location parameters.
    * @type Object
    */
    this.parameters = {};

    /**
    * Settings.
    * @type Object
    */
    this.settings =
    {
        cssPrefix : 'air_',
        windowOnloadCallstack : true,
        modulePath : '/javascript/shared/air/module/',
        modulePathCSS : '/styles/shared/air/module/'
    };

    /**
    * z-index value.
    * @type Integer
    */
    this.zIndex = 100;

    // Private functions //

    /**
    * Initialize script.
    * @ignore
    */
    function _init()
    {
        // Enable cross domain scripting
//        document.domain = document.domain.replace(/(.*\.)?([^.]+\.[^.]+)$/, '$2');

        // Onload actions
        _this.addEvent(_this.o({element : window, event : 'load', action : _onload, libraryLoader : true}));

        // Library internal memory
        _memory.lib = {};

        // Library modules information storage
        _memory.lib.modules = {};

        // Library stylesheets information storage
        _memory.lib.stylesheets = {};

        // Indicates if window is loaded or not
        _memory.lib.windowIsLoaded = false;
    }

    /**
    * Onload actions.
    * @ignore
    */
    function _onload()
    {
        var i, m;

        // Indicates that the window loaded and ready
        _memory.lib.windowIsLoaded = true;

        // Check AFW settings
//        if(_this.config === null)
//        {
//            _this.error('Config parameter not set for JS library.');
//            return;
//        }

        // Set i18n settings, if present
        if(_this.config.i18n)
        {
            _this.i18n = _this.mergeObjects(_this.i18n, _this.config.i18n);
        }

        // If to use callstack for window.onload events
        if(_this.settings.windowOnloadCallstack && _memory.addEvent && _memory.addEvent.window)
        {
            for(i = 0, m = _memory.addEvent.window.length; i < m; ++i)
            {
                _memory.addEvent.window[i]();
            }
        }
    }

    // Public functions //

    /**
    * Add one or more unique class name to one or more elements.
    *
    * @param {Mixed} elements   DOM element, ID CSV string or array of IDs or DOM elements.
    *                           Required.
    * @param {String} names     CSV string or array.
    *                           Required.
    * @version 1.0 2010-06-24
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.addClass = function(elements, classes)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var i, m, i2, m2, temp, result = true;
            options.elements = _this.getCollection(options.elements, [' ', ','], 'dom');
            options.classes = _this.getCollection(options.classes, [' ', ',']);
            for(i = 0, m = options.elements.length; i < m; ++i)
            {
                for(i2 = 0, m2 = options.classes.length; i2 < m2; ++i2)
                {
                    if(!_this.classExists(options.elements[i], options.classes[i2]))
                    {
                        options.elements[i].className += ' ' + options.classes[i2];
                    }
                    else
                    {
                        result = false;
                    }
                }
                options.elements[i].className = _this.trim(options.elements[i].className);
            }
            return result;
        }
        return false;
    };
    this.addClass.rules =
    {
        required : ['elements', 'classes'],
        types :
        {
            elements : ['dom', 'string', 'array'],
            classes : ['string', 'array']
        }
    };

    /**
    * Add an event listener to an element or object.
    *
    * @param {Mixed} element    Element or object.
    *                           Required.
    * @param {String} event     Event type.
    *                           Required.
    * @param {Function} action  The function to be called upon event triggering.
    *                           Required.
    * @param {Boolean} bubble   If to bubble the event or not.
    *                           Optional. Default: False
    * @version 1.0 2010-07-01
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.addEvent = function(element, event, action, bubble)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(!_memory.addEvent)
            {
                _memory.addEvent = {};
                _memory.addEvent.window = [];
            }
            if(_this.settings.windowOnloadCallstack && options.event == 'load' && !options.libraryLoader && window == options.element)
            {
                _memory.addEvent.window.push(options.action);
            }
            else
            {
                options.bubble = (options.bubble === undefined) ? false : options.bubble;
                if(options.element.addEventListener)
                {
                    options.element.addEventListener(options.event, options.action, options.bubble);
                    return true;
                }
                else if(options.element.attachEvent)
                {
                    options.element.attachEvent('on' + options.event, options.action);
                    return true;
                }
            }
        }
        return false;
    };
    this.addEvent.rules =
    {
        required : ['element', 'event', 'action'],
        types :
        {
            element : ['dom', 'object'],
            event : ['string'],
            action : ['function'],
            bubble : ['boolean']
        }
    };

    /**
    * Get content height and re-set outer frame to display content without scrollbar.
    *
    * @version 1.0 2010-06-23
    * @author Mathias Petersson
    */
    this.autoFrameHeight = function()
    {
        _this.addEvent(window, 'load', function()
        {
            var documentDimensions = _this.getDimensions();
            _this.echor(documentDimensions, true);
        });
    };

    /**
    * Brings DOM element to front.
    *
    * @param {Object} element   DOM element.
    *                           Required.
    * @version 1.0 2010-09-22
    * @author Mathias Petersson
    */
    this.bringToFront = function(element)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            _this.zIndex++;
            options.element.style.zIndex = _this.zIndex;
        }
    };
    this.bringToFront.rules =
    {
        required : ['element'],
        types :
        {
            element : ['dom']
        }
    };

    /**
    * Check if an element has requested attribut(es).
    *
    * @param {Object} element       The element to be checked.
    *                               Required.
    * @param {Mixed} attributes     Attributes to check. Either a string,
    *                               a CSV or an array. Each attribute can contain
    *                               a value, defined with an equal sign.
    *                               Only checks if an element has an attribute
    *                               defined if no value is given.
    *                               Required.
    * @version 1.0 2010-08-19
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.checkAttribute = function(element, attributes)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var result = true;
            if(options._types.attributes == 'string')
            {
                options.attributes = options.attributes.split(',');
            }
            if(_this.getType(options.attributes) != 'array')
            {
                return false;
            }
            for(var i = 0, m = options.attributes.length; i < m; ++i)
            {
                if(options.attributes[i].indexOf('=') > -1)
                {
                    options.attributes[i] = _this.trim(options.attributes[i]).split('=');
                    if(!options.element.getAttribute(options.attributes[i][0]) || options.element.getAttribute(options.attributes[i][0]) != options.attributes[i][1])
                    {
                        result = false;
                        break;
                    }
                }
                else
                {
                    if(!options.element.getAttribute(options.attributes[i]))
                    {
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }
        return false;
    };
    this.checkAttribute.rules =
    {
        required : ['element', 'attributes'],
        types :
        {
            element : ['dom'],
            attributes : ['string', 'array']
        }
    };

    /**
    * Check if an element has a specific class name or names.
    *
    * @param {Object} element   DOM element or ID value.
    *                           Required.
    * @param {Mixed} classes    CSV string or array.
    *                           Required.
    * @version 1.0 2010-06-24
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.classExists = function(element, classes)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var i, m, elementClasses;
            if(options._types.element == 'string')
            {
                if(!(options.element = _this.getDOM(options.element)))
                {
                    return false;
                }
                if(_this.getType(options.element) == 'array')
                {
                    options.element = options.element[0];
                }
            }
            options.classes = _this.getCollection(options.classes, [' ', ',']);
            elementClasses = options.element.className.split(' ');
            for(i = 0, m = options.classes.length; i < m; ++i)
            {
                if(!_this.inArray(options.classes[i], elementClasses))
                {
                    return false;
                }
            }
            return true;
        }
        return false;
    };
    this.classExists.rules =
    {
        required : ['element', 'classes'],
        types :
        {
            element : ['dom', 'string'],
            classes : ['string', 'array']
        }
    };

    /**
    * Clone an object.
    *
    * @param {Mixed} object         The variable to be cloned.
    *                               Required.
    * @param {Boolean} cloneNodes   If to clone nodes.
    *                               Optional. Default: false
    * @version 1.0 2010-09.29
    * @author Mathias Petersson
    * @return Result.
    * @type Mixed
    */
    this.clone = function(object, cloneNodes)
    {
        var x, i, m, result;
        cloneNodes = (cloneNodes ? true : false);
        switch(_this.getType(object))
        {
            case 'object':
                result = {}
                for(x in object)
                {
                    switch(_this.getType(object[x]))
                    {
                        case 'object':
                        case 'array':
                            result[x] = _this.clone(object[x], cloneNodes);
                        break;
                        case 'dom':
                            if(cloneNodes)
                            {
                                result[x] = object[x].cloneNode(true);
                            }
                            else
                            {
                                result[x] = object[x];
                            }
                        break;
                        default:
                            result[x] = object[x];
                        break;
                    }
                }
            break;
            case 'array':
                result = [];
                for(i = 0, m = object.length; i < m; ++i)
                {
                    switch(_this.getType(object[i]))
                    {
                        case 'object':
                        case 'array':
                            result[i] = _this.clone(object[i], cloneNodes);
                        break;
                        case 'dom':
                            if(cloneNodes)
                            {
                                result[i] = object[i].cloneNode(true);
                            }
                            else
                            {
                                result[i] = object[i];
                            }
                        break;
                        default:
                            result[i] = object[i];
                        break;
                    }
                }
            break;
            case 'dom':
                if(cloneNodes)
                {
                    result = object.cloneNode(true);
                }
                else
                {
                    result = object;
                }
            break;
            default:
                result = object;
            break;
        }
        return result;
    };

    /**
    * Dump data to Firebug console (Firefox plugin).
    * Pass multiple arguments and output will be grouped. First argument will become group title.
    *
    * @param {Mixed} message    The message to be printed.
    *                           Required.
    * @param {Boolean} trace    If to trace the call.
    *                           Optional. Default: false
    * @version 1.0 2010-09-14
    * @author Mathias Petersson
    */
    this.debug = function(message, trace)
    {
        if(!_memory.debug)
        {
            _memory.debug = {};
            _memory.debug.count = 0;
        }
        _memory.debug.count++;
        if(window['console'] && console.group)
        {
            if(arguments.length > 1)
            {
                console.group(_memory.debug.count + ':', arguments[0]);
                for(var i = 1, m = arguments.length; i < m; ++i)
                {
                    console.debug(arguments[i]);
                }
                if(trace)
                {
                    console.group('Trace:');
                    console.trace();
                    console.groupEnd();
                }
                console.groupEnd();
            }
            else
            {
                console.group(_memory.debug.count + ':', message);
                if(trace)
                {
                    console.group('Trace:');
                    console.trace();
                    console.groupEnd();
                }
                console.groupEnd();
            }
        }
    };

    /**
    * Delete a cookie.
    *
    * @param {Mixed} name       Cookie name.
    *                           Required.
    * @param {String} path      Cookie path.
    *                           Optional. Default: undefined
    * @param {String} domain    Cookie domain.
    *                           Optional. Default: undefined
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Result.
    * @type Mixed
    */
    this.deleteCookie = function(name, path, domain)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(_this.getCookie(options.name))
            {
                document.cookie = options.name + '=' +
                ((options.path) ? ';path=' + options.path : '') +
                ((options.domain) ? ';domain=' + options.domain : '') +
                ';expires=Thu, 01-Jan-1970 00:00:01 GMT';
            }
            return true;
        }
        return false;
    };
    this.deleteCookie.rules =
    {
        required : ['name'],
        types :
        {
            name : ['string'],
            path : ['string'],
            domain : ['string']
        }
    };

    /**
    * Examine an variable and it's contents. Can either return the result as an string,
    * print it in a specific container or create a DOM element and place it at the bottom
    * of the page.
    *
    * @param {Mixed} variable   The variable to be examined.
    *                           Required.
    * @param {Mixed} container  Either null or undefined (return as string),
    *                           DOM element (append to element) or True (append to bottom of the page).
    *                           Optional. Default:
    * @param {String} info      Information string to label the output.
    *                           Optional. Default:
    * @version 1.0 2010-07-01
    * @author Mathias Petersson
    * @return Result if parameter container is undefined. Otherwise, nothing.
    * @type String
    */
    this.echor = function(variable, container, info)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var result = '<pre class="' + _this.settings.cssPrefix + 'js-echor">' + ((options.info) ? '<span class="info">' + options.info + ':</span>\n' : '') + _this.printr(options.variable) + '</pre>';
            if(options.container !== undefined)
            {
                if(options._types.container == 'dom')
                {
                    var div = document.createElement('DIV');
                    div.innerHTML = result;
                    options.container.appendChild(div);
                }
                else if(options._types.container == 'boolean' && options.container)
                {
                    options.container = document.createElement('DIV');
                    options.container.innerHTML = result;
                    document.body.appendChild(options.container);
                }
                else if(options._types.container == 'string')
                {
                    if(options.container = _this.getDOM(options.container))
                    {
                        if(_this.getType(options.container) == 'array')
                        {
                            options.container = options.container[0];
                        }
                        options.container.innerHTML += result;
                    }
                }
                return;
            }
            return result;
        }
        return false;
    };
    this.echor.rules =
    {
        required : ['variable'],
        types :
        {
            container : ['dom', 'boolean', 'string'],
            info : ['string']
        }
    };

    /**
    * Display error message.
    *
    * @param {String} message   Message to display. "\n" is replaced with <br />.
    *                           Required.
    * @version 1.0 2010-09-14
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.error = function(message)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var div = document.createElement('DIV');
            div.innerHTML = message.toString().replace(/\n/mg, '<br />');
            div.className = _this.settings.cssPrefix + 'error';
            if(_memory.lib.windowIsLoaded)
            {
                document.body.insertBefore(div, document.body.childNodes[0]);
            }
            else
            {
                _this.addEvent(window, 'load', function()
                {
                    document.body.insertBefore(div, document.body.childNodes[0]);
                });
            }
            return true;
        }
        return false;
    };
    this.error.rules =
    {
        required : ['message'],
        types :
        {
            message : ['string', 'number']
        }
    };

    /**
    * Generate a (DOM-)safe ID
    *
    * @param string id
    * @param string prefix [optional:'id']
    * @access public
    * @return string
    */
    this.generateSafeID = function(id, prefix) {
        var options = _this.isValidInputs();
        if(options) {
            var s, i, m;
            options.id = options.id.split('');
            options.prefix = (options._types.prefix == 'string' && options.prefix ? options.prefix : 'id') + '_';
            for(s = '', i = 0, m = options.id.length; i < m; ++i) {
                s += options.id[i].charCodeAt(0);
            }
            return options.prefix + s;
        }
        return '';
    };
    this.generateSafeID.rules = {
        required : ['id'],
        types :
        {
            id : ['string'],
            prefix : ['string']
        }
    };

    /**
    * Transform a CSV string to an array.
    * May also be used to collect DOM elements.
    *
    * @param {Mixed} list       CSV string or array containing various data types.
    *                           Required.
    * @param {Mixed} separator  Value separator character. May be an array with characters.
    *                           Optional. Default: ,
    * @param {String} listType  Set to 'dom' to return a element collection.
    *                           Optional. Default:
    * @version 1.0 2010-06-29
    * @author Mathias Petersson
    * @return Collection of values.
    * @type Array
    */
    this.getCollection = function(list, separator, listType)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var i, m, result = [];
            options.separator = (options.separator) ? options.separator : ',';
            options.listType = (options.listType) ? options.listType.toLowerCase() : '';
            if(options._types.separator == 'array')
            {
                options.separator = new RegExp(options.separator.join('|'), 'gi');
            }
            switch(options._types.list)
            {
                case 'string':
                    options.list = options.list.split(options.separator);
                case 'array':
                    for(i = 0, m = options.list.length; i < m; ++i)
                    {
                        switch(_this.getType(options.list[i]))
                        {
                            case 'array':
                                result = result.concat(_this.getCollection(options.list[i], options.separator));
                            break;
                            case 'string':
                                result.push(_this.trim(options.list[i]));
                            break;
                            case 'dom':
                                result.push(options.list[i]);
                            break;
                        }
                    }
                break;
                case 'dom':
                    if(options.listType == 'dom')
                    {
                        result.push(options.list);
                    }
                break;
            }
            if(options.listType == 'dom')
            {
                list = result;
                result = [];
                var dom;
                for(i = 0, m = list.length; i < m; ++i)
                {
                    if(_this.getType(list[i]) == 'dom')
                    {
                        result.push(list[i]);
                    }
                    else if(dom = _this.getDOM(list[i]))
                    {
                        result.push(dom);
                    }
                }
            }
            return result;
        }

        return false;
    };
    this.getCollection.rules =
    {
        required : ['list'],
        types :
        {
            list : ['dom', 'string', 'array'],
            separator : ['string', 'array'],
            listType : ['string']
        }
    };

    /**
    * Get cookie value.
    *
    * @param {String} name  Cookie name.
    *                       Required.
    * @param {Mixed} value  Return value if cookie isn't found.
    *                       Optional. Default: undefined
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Result.
    * @type Mixed
    */
    this.getCookie = function(name, value)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var i, m, cookies, cookie, result;
            cookies = document.cookie.split(';');
            for(i = 0, m = cookies.length; i < m; ++i)
            {
                cookie = cookies[i].split('=');
                if(_this.trim(cookie[0]) == options.name)
                {
                    if(cookie.length)
                    {
                        result = unescape(_this.trim(cookie[1]));
                    }
                    break;
                }
            }
            return result;
        }
        return value;
    };
    this.getCookie.rules =
    {
        required : ['name'],
        types :
        {
            list : ['string']
        }
    };

    /**
    * Fetch dimensions of an element or document. Both viewport and complete dimensions are returned.
    *
    * @param {Mixed} element    DOM element or element ID. Complete document if undefined.
    *                           Optional. Default:
    * @version 1.0 2010-06-23
    * @author Mathias Petersson
    * @return Object containing object and viewport dimensions.
    * @type Object
    */
    this.getDimensions = function(element)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            function getDimensions(element)
            {
                var dimensions = {};
                if(!element)
                {
                    if(_this.isQuirksmode())
                    {
                        dimensions.width = document.body.scrollWidth;
                        dimensions.height = _this.getHighest(document.body.scrollHeight, document.documentElement.scrollHeight);
                        dimensions.viewport =
                        {
                            width : document.body.clientWidth || document.documentElement.clientWidth || window.innerWidth,
                            height : document.body.clientHeight || document.documentElement.clientHeight || window.innerHeight
                        };
                    }
                    else
                    {
                        dimensions.width = document.documentElement.scrollWidth;
                        dimensions.height = _this.getHighest(document.documentElement.scrollHeight, document.documentElement.clientHeight);
                        dimensions.viewport =
                        {
                            width : document.documentElement.clientWidth || document.body.clientWidth || window.innerWidth,
                            height : document.documentElement.clientHeight || document.body.clientHeight || window.innerHeight
                        };
                    }
                }
                else
                {
                    dimensions.width = element.offsetWidth;
                    dimensions.height = element.offsetHeight;
                    dimensions.viewport =
                    {
                        width : element.clientWidth,
                        height : element.clientHeight
                    };
                    dimensions.body =
                    {
                        width : element.scrollWidth,
                        height : element.scrollHeight
                    };
                }
                return dimensions;
            }
            result = {viewport: {width: 0, height: 0}, width: 0, height: 0};
            if(options.element === undefined)
            {
                result = getDimensions(false);
                return result;
            }
            else if(options._types.element == 'dom')
            {
                result = getDimensions(options.element);
                return result;
            }
            else if(options._types.element == 'string')
            {
                if((element = _this.getDOM(options.element)))
                {
                    result = getDimensions(element);
                }
                return result;
            }
        }
        return false;
    };
    this.getDimensions.rules =
    {
        required : [],
        types :
        {
            element : ['string', 'dom']
        }
    };

    /**
    * Fetch and return DOM elements. Either by element IDs or by element types or by class names.
    * Or a combination of them all. Search through DOM tree or from a specific node. Through root element children
    * or complete tree underneath root element.
    * The fetched DOM element will be passed clean if a single element ID is passed.
    *
    * @param {Mixed} elements       ID CSV string or array of strings.
    *                               Optional. Defualt:
    * @param {String} nodeName      Fetch elements from specific element types.
    *                               Optional. Default: *
    * @param {String} className     Fetch elements with specifig class names.
    *                               Optional. Default:
    * @param {String} attribute     Fetch elements with these attributes and values.
    *                               Multiple attributes shall be separated with commas.
    *                               If no value for each attribute is passed, script only checks if attribute is defined.
    *                               Optional. Default:
    * @param {Mixed} root           From where to begin the search. May be element ID or a DOM element.
    *                               Optional. Default: document.body
    * @param {Boolean} children     If to only search through roots child elements (one level).
    *                               Optional. Default: false
    * @param {Boolean} associative  If to return the collection with associative keys.
    *                               Optional. Default: true
    * @param {Boolean} numerical    If to return the collection with numerical keys.
    *                               Optional. Default: true
    * @version 1.0 2010-08-24
    * @author Mathias Petersson
    * @return List of collected elements.
    * @type Array
    */
    this.getDOM = function(elements, nodeName, className, attribute, root, children, associative, numerical)
    {
        function __getDOM(id)
        {
            var dom = false;
            if(document.getElementById)
            {
                if(!(dom = document.getElementById(id)))
                {
                    dom = false;
                }
            }
            return dom;
        }
        var options = _this.isValidInputs();
        if(options)
        {
            var result = {}, list = [], x, i, m, dom, onlyNumericalIndexes;
            if(_this.inArray(options._types.elements, ['string', 'array']))
            {
                list = options.elements;
                x = 0;
                if(options._types.elements == 'string')
                {
                    list = _this.getCollection(list, [' ', ',']);
                    if(list.length == 1)
                    {
                        return __getDOM(list[0]);
                    }
                }
                for(i = 0, m = list.length; i < m; ++i)
                {
                    if(dom = __getDOM(list[i]))
                    {
                        if((options.associative === undefined || !options.associative)
                        || !dom || dom.id === undefined || dom.id == ''
                        || (options.numerical === undefined || options.numerical))
                        {
                            result[x] = dom;
                        }
                        if((options.associative === undefined || options.associative)
                        && dom && dom.id !== undefined && dom.id != '')
                        {
                            result[dom.id] = dom;
                        }
                        x++;
                    }
                }
                return (x == 0) ? false : result;
            }
            else
            {
                if(options._types.root == 'string')
                {
                    if(!(options.root = __getDOM(options.root)))
                    {
                        return false;
                    }
                }
                options.nodeName = (!options.nodeName || options.nodeName == '') ? '*' : options.nodeName.toUpperCase();
                options.attribute = (options.attribute === undefined) ? false : options.attribute;
                options.root = (options.root === undefined || options.root == '') ? document.body : options.root;
                options.children = (options.children === undefined) ? false : options.children;
                options.associative = (options.associative === undefined) ? true : options.associative;
                options.elements = options.root.getElementsByTagName(options.nodeName);
                if(options.children)
                {
                    options.elements = options.root.childNodes;
                    for(i in options.elements)
                    {
                        if(_this.getType(options.elements[i]) == 'dom')
                        {
                            if(options.nodeName == '*' || options.elements[i].nodeName.toUpperCase() == options.nodeName)
                            {
                                if(!options.className || _this.classExists(options.elements[i], options.className))
                                {
                                    if(!options.attribute || _this.checkAttribute(options.elements[i], options.attribute))
                                    {
                                        list.push(options.elements[i]);
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    for(i in options.elements)
                    {
                        if(_this.getType(options.elements[i]) == 'dom')
                        {
                            if(!options.className || _this.classExists(options.elements[i], options.className))
                            {
                                if(!options.attribute || _this.checkAttribute(options.elements[i], options.attribute))
                                {
                                    list.push(options.elements[i]);
                                }
                            }
                        }
                    }
                }
                onlyNumericalIndexes = true;
                for(i = 0, m = list.length; i < m; ++i)
                {
                    if((options.associative === undefined || options.associative)
                    && list[i] && list[i].id !== undefined && list[i].id != '')
                    {
                        onlyNumericalIndexes = false;
                        result[list[i].id] = list[i];
                        if(options.numerical === undefined || options.numerical)
                        {
                            result[i] = list[i];
                        }
                    }
                    else
                    {
                        result[i] = list[i];
                    }
                }
                if(onlyNumericalIndexes)
                {
                    result = [];
                    for(i = 0, m = list.length; i < m; ++i)
                    {
                        result.push(list[i]);
                    }
                }
            }
            return (list.length > 0) ? result : false;
        }

        return false;
    };
    this.getDOM.rules =
    {
        required : [],
        types :
        {
            elements : ['string', 'array'],
            nodeName : ['string'],
            classNames : ['string', 'array'],
            attribute : ['string', 'array'],
            root : ['string', 'dom'],
            children : ['boolean'],
            associative : ['boolean'],
            numerical : ['boolean']
        }
    };

    /**
    * Fetch and return first DOM element from parent.
    *
    * @param {Mixed} parent     DOM element or element ID.
    *                           Required.
    * @param {String} nodeName  Look for element with these node names.
    *                           Single string or separated with commas or spaces.
    *                           Optional. Default: *
    * @version 1.0 2010-09-27
    * @author Mathias Petersson
    * @return Result.
    * @type Object
    */
    this.getFirstChild = function(parent, nodeName)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var children, x, i, m, starNode = false;
            if(options._types.parent == 'string')
            {
                options.parent = _this.getDOM(options.parent);
            }
            if(options.parent)
            {
                if(options._types.nodeName == 'string')
                {
                    if(_this.inArray(options.nodeName, ['', '*']))
                    {
                        starNode = true;
                    }
                    options.nodeName = _this.getCollection(options.nodeName, [' ', ',']);
                }
                for(i = 0, m = options.nodeName.length; i < m; ++i)
                {
                    options.nodeName[i] = options.nodeName[i].toUpperCase();
                }
                children = options.parent.getElementsByTagName('*');
                for(x in children)
                {
                    if(_this.getType(children[x]) == 'dom')
                    {
                        if(starNode || _this.inArray(children[x].nodeName.toUpperCase(), options.nodeName))
                        {
                            return children[x];
                            break;
                        }
                    }
                }
            }
        }
        return false;
    };
    this.getFirstChild.rules =
    {
        required : ['parent'],
        types :
        {
            parent : ['dom', 'string'],
            nodeName : ['string', 'array']
        }
    };

    /**
    * Return highest number from passed parameters. Pass as many as you like.
    *
    * @version 1.0 2010-05-12
    * @author Mathias Petersson
    * @return Highest number.
    * @type Number
    */
    this.getHighest = function()
    {
        var result = false;
        for(var i = 0, m = arguments.length; i < m; ++i)
        {
            if(_this.getType(arguments[i]) == 'number')
            {
                if(result === false || arguments[i] > result)
                {
                    result = arguments[i];
                }
            }
        }
        return result;
    };

    /**
    * Return lowest number from passed parameters. Pass as many as you like.
    *
    * @version 1.0 2010-05-12
    * @author Mathias Petersson
    * @return Lowest number.
    * @type Number
    */
    this.getLowest = function()
    {
        var result = false;
        for(var i = 0, m = arguments.length; i < m; ++i)
        {
            if(_this.getType(arguments[i]) == 'number')
            {
                if(result === false || arguments[i] < result)
                {
                    result = arguments[i];
                }
            }
        }
        return result;
    };

    /**
    * Get parameter value from current location or from passed collection.
    *
    * @param {String} name      Requested parameter name.
    *                           Required.
    * @param {Mixed} value      Default return value.
    *                           Optional. Default: undefined
    * @param {Mixed} collection From where to extract parameters.
    *                           Optional. Default: location
    * @version 1.0 2010-05-17
    * @author Mathias Petersson
    * @return Result of the request.
    * @type Mixed
    */
    this.getParam = function(name, value, collection)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var result = options.value;
            if(!options.collection)
            {
                options.collection = _this.parameters;
            }
            else if(options._types.collection == 'string' || _this.getType(options.collection.search) == 'string')
            {
                options.collection = (_this.getType(options.collection.search) == 'string') ? options.collection.search : options.collection;
                options.collection = _this.parseSearchstring(options.collection);
            }
            if(_this.getType(options.collection) == 'object' && options.collection[options.name] !== undefined)
            {
                result = options.collection[options.name];
            }
            return result;
        }
        return value;
    };
    this.getParam.rules =
    {
        required : ['name'],
        types :
        {
            name : ['string'],
            collection : ['dom', 'string']
        }
    };

    /**
    * Get parent node of element.
    *
    * @param {Mixed} element    DOM element or element ID.
    *                           Required.
    * @param {String} nodeName  Look only for parent elements of this type. String, array or CSV.
    *                           Optional. Default: *
    * @param {String} className Look only for parent elements wich has this, or these class names.
    *                           Single class or CSV.
    *                           Optional. Default:
    * @param {String} attribute Look only for parent elements wich has this, or these attributes.
    *                           CSV string, equal-sign separated key/values.
    *                           Optional. Default:
    * @param {Number} maxLevels Maximum number of levels to go through.
    *                           Optional. Default: 200
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return Result.
    * @type DOM
    */
    this.getParent = function(element, nodeName, className, attribute, maxLevels)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(options._types.element == 'string')
            {
                option.element = _this.getDOM(options.element);
            }
            if(options._types.nodeName == 'string')
            {
                options.nodeName = (options.nodeName == '' ? '*' : options.nodeName);
                options.nodeName = _this.getCollection(options.nodeName, [' ', ',']);
            }
            if(!options.nodeName)
            {
                options.nodeName = ['*'];
            }
            if(options.element)
            {
                var i, m, found = false;
                for(i = 0, m = options.nodeName.length; i < m; ++i)
                {
                    options.nodeName[i] = options.nodeName[i].toUpperCase();
                }
                options.maxLevels = (options.maxLevels) ? options.maxLevels : 200;
                i = 0;
                while(!found)
                {
                    ++i;
                    if(element.parentNode)
                    {
                        element = element.parentNode;
                        if(_this.inArray(element.nodeName.toUpperCase(), options.nodeName) || _this.inArray('*', options.nodeName))
                        {
                            if(!options.className || _this.classExists(element, options.className))
                            {
                                if(!options.attribute || _this.checkAttribute(element, options.attribute))
                                {
                                    found = true;
                                    break;
                                }
                            }
                        }
                    }
                    else
                    {
                        break;
                    }
                    if(i > options.maxLevels)
                    {
                        break;
                    }
                }
                if(found)
                {
                    return element;
                }
            }
        }
        return false;
    };
    this.getParent.rules =
    {
        required : ['element'],
        types :
        {
            element : ['string', 'dom'],
            nodeName : ['string', 'array'],
            className : ['string'],
            attribute : ['string'],
            maxLevels : ['number']
        }
    };

    /**
    * Get element position.
    *
    * @param {Object} element   DOM element.
    *                           Required.
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return Object containing top and left position.
    * @type Object
    */
    this.getPosition = function(element)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(options._types.element == 'string')
            {
                options.element = _this.getDOM(options.element);
            }
            if(options.element)
            {
                var result =
                {
                    left : 0,
                    top : 0
                };
                if(options.element && options.element.offsetParent)
                {
                    do
                    {
                        result.left += options.element.offsetLeft;
                        result.top += options.element.offsetTop;
                    } while(options.element = options.element.offsetParent)
                }
            }
            return result;
        }
        return false;
    };
    this.getPosition.rules =
    {
        required : [],
        types :
        {
            element : ['string', 'dom']
        }
    };

    /**
    * Collects all cells from passed row.
    *
    * @param {Object} row   Row to parse.
    *                       Required.
    * @param {Boolean} headers  If to include th-tags.
    *                           Optional. Default: true
    * @version 1.0 2010-10-13
    * @author Mathias Petersson
    * @return Result.
    * @type Array
    */
    this.getRowCells = function(row, headers)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            options.headers = (options.headers !== undefined ? options.headers : true);
            var i, m, children, cells = [];
            children = _this.getDOM(null, null, null, null, options.row, true, false, true);
            for(i = 0, m = children.length; i < m; ++i)
            {
                switch(children[i].nodeName.toUpperCase())
                {
                    case 'TH':
                        if(!options.headers)
                        {
                            continue;
                        }
                    case 'TD':
                        cells.push(children[i])
                    break;
                }
            }
            return cells;
        }
    };
    this.getRowCells.rules =
    {
        required : ['row'],
        types :
        {
            row : ['dom'],
            headers : ['boolean']
        }
    };

    /**
    * Collects all rows from passed table.
    * Collects rows from thead, tbody and tfoot groups.
    *
    * @param {Object} table Table to parse.
    *                       Required.
    * @version 1.0 2010-09-27
    * @author Mathias Petersson
    * @return Result.
    * @type Array
    */
    this.getTableRows = function(table)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var groups, i, m, rows = [];
            groups = _this.getDOM(null, null, null, null, options.table, true, false, true);
            for(i = 0, m = groups.length; i < m; ++i)
            {
                switch(groups[i].nodeName.toUpperCase())
                {
                    case 'THEAD':
                    case 'TBODY':
                    case 'TFOOT':
                        rows = rows.concat(_this.getTableRows(groups[i]));
                    break;
                    case 'TR':
                        rows.push(groups[i])
                    break;
                }
            }
            return rows;
        }
    };
    this.getTableRows.rules =
    {
        required : ['table'],
        types :
        {
            table : ['dom']
        }
    };

    /**
    * Get type of variable.
    *
    * @param {Object} variable  Variable to be checked.
    *                           Required.
    * @author Mathias Petersson
    * @version 1.0 2010-05-19
    * @return Result.
    * @type Mixed
    * @note Can't use _isValidOptions()!
    */
    this.getType = function(input)
    {
        var type = typeof(input);
        if(!_memory.getType) _memory.getType = {};
        if(type == 'object')
        {
            if(!input)
            {
                return 'null';
            }
            if(input.constructor)
            {
                var match, key, constructor, types;
                constructor = input.constructor.toString();
                match = constructor.match(/(\w+)\(/);
                if(match)
                {
                    constructor = match[1].toLowerCase();
                }
                types = ['boolean', 'number', 'string', 'array', 'date'];
                for(key in types)
                {
                    if(constructor == types[key])
                    {
                        type = types[key];
                        break;
                    }
                }
            }
            if(type == 'object')
            {
                if(input.childNodes && input.nodeType && input.nodeType == 1)
                {
                    type = 'dom';
                }
                else if(input.eventPhase || input.cancelBubble)
                {
                    type = 'event';
                }
            }
        }
        if(!_memory.getType[type]) _memory.getType[type] = 0;
        _memory.getType[type]++;
        return type;
    };

    /**
    * Check if object has the requested properties
    *
    * @param object object
    * @param mixed properties
    */
    this.hasProperties = function(object, properties) {
        var options = _this.isValidInputs();
        if(options)
        {
            var i, m, x;
            if(options._types.properties == 'array') {
                for(i = 0, m = properties.length; i < m; ++i) {
                    if(!options.object.hasOwnProperty(properties[i])) {
                        return false;
                    }
                }
            } else if(options._types.properties == 'object') {
                for(x in options.properties) {
                    options.properties[x] = (_this.getType(options.properties[x]) == 'array' ? options.properties[x] : [options.properties[x]]);
                    if(!options.object.hasOwnProperty(x) || (options.properties[x] != ['*'] && !_this.inArray(_this.getType(options.object[x]), options.properties[x]))) {
                        return false;
                    }
                }
            } else if(options._types.properties == 'string') {
                if(!options.object.hasOwnProperty(options.properties)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }
    this.hasProperties.rules =
    {
        required : ['object', 'properties'],
        types :
        {
            object : ['object'],
            properties : ['object', 'array', 'string']
        }
    };

    /**
    * Checks if a value exists in an array.
    *
    * @param {Mixed} value          The value to check for.
    *                               Required.
    * @param {Array} collection     The array to be searched.
    *                               Required.
    * @version 1.0 2010-08-19
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    * @note Can't use _isValidOptions()!
    */
    this.inArray = function(value, collection, strict)
    {
        if(value === undefined || _this.getType(collection) != 'array')
        {
            return false;
        }
        var x;
        strict = strict || false;
        for(x in collection)
        {
            if((!strict && collection[x] == value)
            || (strict && collection[x] === value))
            {
                return true;
            }
        }
        return false;
    };

    /**
    * Initiate a container.
    * Returns already initiated container if found.
    *
    * @param {String} name      Container name.
    *                           Required.
    * @param {Object} variable  The variable container wherein the reference shall be stored.
    *                           Optional. Default: undefined
    * @version 1.0 2010-09-15
    * @author Mathias Petersson
    * @return Result.
    * @type Object
    */
    this.initiateContainer = function(name, variable)
    {
        var options = _this.isValidInputs();
        var result = {};
        if(options)
        {
            result.toString = function(){ return 'Reference container: ' + options.name; };
            if(options.variable && options.variable.toString() == 'Reference container: ' + options.name)
            {
                result = variable;
            }
        }
        return result;
    };
    this.initiateContainer.rules =
    {
        required : ['name'],
        types :
        {
            name : ['string'],
            variable : ['object']
        }
    };

    /**
    * Intify values. Makes numbers that has been adressed as strings numbers.
    *
    * @param {Mixed} data   Data collection.
    *                       Required.
    * @version 2.0 2011-02-21
    * @author Mathias Petersson
    * @return Result.
    * @type Array
    */
    this.intify = function(data)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var i, m, value, re = /^[-(]?[0-9.]+\)?$/;
            if(options._types.data == 'string')
            {
                if(options.data)
                {
                    value = options.data.replace(/[ ,]/g, '');
                    if(re.test(value))
                    {
                        if((value.substring(0, 1) == '(' && value.substring(value.length - 1) != ')')
                        || (value.substring(0, 1) != '(' && value.substring(value.length - 1) == ')'))
                        {
                            options.data = value;
                        }
                        else if(value.substring(0, 1) == '(')
                        {
                            value = '-' + value.substring(1, value.length - 1);
                        }
                        options.data = parseFloat(value);
                    }
                }
            }
            else
            {
                for(i = 0, m = options.data.length; i < m; ++i)
                {
                    if(options.data[i])
                    {
                        value = options.data[i].replace(/[ ,]/g, '');
                        if(re.test(value))
                        {
                            if((value.substring(0, 1) == '(' && value.substring(value.length - 1) != ')')
                            || (value.substring(0, 1) != '(' && value.substring(value.length - 1) == ')'))
                            {
                                continue;
                            }
                            if(value.substring(0, 1) == '(')
                            {
                                value = '-' + value.substring(1, value.length - 1);
                            }
                            options.data[i] = parseFloat(value);
                        }
                    }
                }
            }
        }
        return options.data;
    };
    this.intify.rules =
    {
        required : ['data'],
        types :
        {
            data : ['array', 'string']
        }
    };

    /**
    * Check if requested variable is empty.
    *
    * @param {Object} variable  The variable to be checked.
    *                           Required.
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.isEmpty = function(variable)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            switch(options._types.variable)
            {
                case 'dom':
                case 'number':
                case 'date':
                case 'function':
                case 'event':
                case 'boolean':
                    return false;
                break;
                case 'object':
                    for(var x in options.variable)
                    {
                        if(options.variable.hasOwnProperty !== undefined && options.variable.hasOwnProperty(x))
                        {
                            return false;
                        }
                    }
                    return true;
                break;
                case 'array':
                    return ((options.variable.length) ? false : true);
                break;
                case 'string':
                    return ((options.variable == '') ? true : false);
                break;
            }
        }
        _this.error('isEmpty(): getType(typeof argument) isn\'t implemented\nType of argument was: ' + typeof(variable));
        return true;
    };
    this.isEmpty.rules =
    {
        required : ['variable'],
        types :
        {

        }
    };

    /**
    * Validates passed function inputs.
    * Returns input parameter types as an object if validation passed. False if not.
    *
    * @version 1.0 2010-06-14
    * @author Mathias Petersson
    * @return Object with parameter types, or false.
    * @type Mixed
    * @note Never call this function from inArray() or getType()!
    */
    this.isValidInputs = function()
    {
        var inputs = arguments.callee.caller.arguments;
        var rules = arguments.callee.caller.rules;
        var parameters = arguments.callee.caller.toString().match(/^[\w\s]*\((.*?)\)/i);
        var result = {_types : {}}, i, m, x;
        if(parameters)
        {
            parameters = parameters[1].replace(/ /g, '').split(',');
            for(i = 0, m = parameters.length; i < m; ++i)
            {
                result[parameters[i]] = undefined;
            }
        }
        if(inputs.length == 1 && _this.getType(inputs[0]) == 'object' && inputs[0].isOptions)
        {
            for(x in inputs[0])
            {
                result[x] = inputs[0][x];
            }
        }
        else
        {
            for(i = 0, m = inputs.length; i < m; ++i)
            {
                result[parameters[i]] = inputs[i];
            }
        }
        for(x in result)
        {
            if(x !== '_types')
            {
                result._types[x] = _this.getType(result[x]);
            }
        }
        if(rules)
        {
            if(rules.types)
            {
                for(x in rules.types)
                {
                    if(result[x] !== undefined)
                    {
                        if(!_this.inArray(_this.getType(result[x]), rules.types[x]))
                        {
                            result[x] = undefined;
                        }
                    }
                }
            }
            if(rules.required)
            {
                for(i = 0, m = rules.required.length; i < m; ++i)
                {
                    if(result[rules.required[i]] === undefined)
                    {
                        result = false;
                        break;
                    }
                }
            }
        }
        return result;
    };

    /**
    * Module loader.
    * Module name is it's filename.
    *
    * @param {String} name          Module name.
    *                               Required.
    * @param {String} version       Moudle version.
    *                               Required.
    * @param {String} constructor   Module constructor.
    *                               Required.
    * @param {String} path          Module path.
    *                               Optional. Default: settings.modulePath
    * @version 1.0 2010-09-24
    * @author Mathias Petersson
    */
    this.load = function(name, version, constructor, path)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var errorMessageDiffVersion = 'An error occurred while trying to load module "' + options.name + '" version ' + options.version + '.\n- Version {VERNUM} is already loaded.'
            var errorMessageWrongLibrary = 'An error occurred while trying to load module "' + options.name + '" version ' + options.version + '.\n- Another variable/constructor is already defined as "' + options.constructor + '".'
            var versionNumber = null;
            if(!_memory.lib.modules[name.replace(/\W/mig, '_')])
            {
                options.path = (options.path ? options.path : _this.settings.modulePath);
                if(!window[options.constructor] || options.path != _this.settings.modulePath)
                {
                    var script = document.createElement('SCRIPT');
                    script.src = 'http://' + _this.config.servername + options.path + options.name + (options.version != '' ? '-' + options.version : '') + '.js';
                    document.getElementsByTagName('HEAD')[0].appendChild(script);
                    _memory.lib.modules[options.name.replace(/\W/mig, '_')] =
                    {
                        version : options.version
                    };
                }
                else
                {
                    var dummy = new window[options.constructor](_this);
                    if(dummy.toString().substring(0, _this.toString().length) == _this.toString())
                    {
                        if(dummy.version != options.version)
                        {
                            versionNumber = dummy.version;
                        }
                        else
                        {
                            _memory.lib.modules[options.name.replace(/\W/mig, '_')] =
                            {
                                version : dummy.version
                            };
                        }
                    }
                    else
                    {
                        _this.error(errorMessageWrongLibrary);
                    }
                    dummy = null;
                }
            }
            else if(_memory.lib.modules[options.name.replace(/\W/mig, '_')].version != options.version)
            {
                versionNumber = _memory.lib.modules[options.name.replace(/\W/mig, '_')].version;
            }
            if(versionNumber !== null)
            {
                _this.error(errorMessageDiffVersion.replace('{VERNUM}', versionNumber));
            }
        }
    };
    this.load.rules =
    {
        required : ['name', 'version', 'constructor'],
        types :
        {
            'name' : ['string'],
            'version' : ['string'],
            'constructor' : ['string'],
            'path' : ['string']
        }
    };

    /**
    * Stylesheet loader.
    * Stylesheet name is it's filename.
    *
    * @param {String} name      Stylesheet name.
    *                           Required.
    * @param {String} version   Stylesheet version.
    *                           Required.
    * @param {String} path      Module path.
    *                           Optional. Default: settings.modulePath
    * @version 1.0 2010-09-17
    * @author Mathias Petersson
    */
    this.loadStylesheet = function(name, version, path)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(!_memory.lib.stylesheets[options.name.replace(/\W/mig, '_')])
            {
                options.path = (options.path ? options.path : _this.settings.modulePathCSS);
                var link = document.createElement('LINK');
                link.type = 'text/css';
                link.rel = 'stylesheet';
                link.media = 'all';
                link.href = 'http://' + _this.config.servername + options.path + options.name + (options.version != '' ? '-' + options.version : '') + '.css';
                document.getElementsByTagName('HEAD')[0].appendChild(link);
                _memory.lib.stylesheets[options.name.replace(/\W/mig, '_')] =
                {
                    version : options.version
                };
            }
            else if(_memory.lib.stylesheets[options.name.replace(/\W/mig, '_')].version != options.version)
            {
                _this.error('An error occurred while trying to load stylesheet "' + options.name + '" version ' + options.version + '.\n- Version ' + _memory.lib.stylesheets[options.name.replace(/\W/mig, '_')].version + ' is already loaded.');
            }
        }
    };
    this.loadStylesheet.rules =
    {
        required : ['name', 'version'],
        types :
        {
            'name' : ['string'],
            'version' : ['string'],
            'path' : ['string']
        }
    };

    /**
    * Removes leading characters.
    *
    * @version 1.0 2010-05-06
    * @author Mathias Petersson
    * @see #trim
    */
    this.ltrim = function(string, characters)
    {
        characters = (characters === undefined) ? '\\s' : characters;
        return string.replace(new RegExp('^[' + characters + ']+', 'g'), '');
    };

    /**
    * Merge two or more objects. Pass two or more arguments.
    *
    * @version 1.0 2010-09-21
    * @author Mathias Petersson
    * @return Result.
    * @type Object
    */
    this.mergeObjects = function()
    {
        var result = null, i, m;
        for(i = 0, m = arguments.length; i < m; ++i)
        {
            if(_this.getType(arguments[i]) == 'object')
            {
                if(!result)
                {
                    result = arguments[i];
                }
                else
                {
                    result = _this.mergeObjects.merge(result, arguments[i]);
                }
            }
        }
        return (result ? result : {});
    };
    this.mergeObjects.merge = function(o1, o2)
    {
        var result = {}, x, t1, t2, change;
        for(x in o1)
        {
            result[x] = o1[x];
        }
        for(x in o2)
        {
            change = false;
            if(result[x] === undefined)
            {
                change = true;
            }
            else
            {
                t1 = _this.getType(result[x]);
                t2 = _this.getType(o2[x]);
                if(t1 != t2 || t1 != 'object')
                {
                    change = true;
                }
                else if(t1 == 'object')
                {
                    result[x] = _this.mergeObjects.merge(result[x], o2[x]);
                }
            }
            if(change)
            {
                result[x] = o2[x];
            }
        }
        return result;
    };

    /**
    * Share modules between modules.
    *
    * @param {Object} modules   Object containing modules.
    *                           Required.
    * @version 1.0 2010-09-21
    * @author Mathias Petersson
    */
    this.moduleSharing = function(modules)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var x;
            for(x in modules)
            {
                modules[x].linkModules(modules);
            }
        }
    };
    this.moduleSharing.rules =
    {
        required : ['modules'],
        types :
        {
            modules : ['object']
        }
    };

    /**
    * Number formatting.
    *
    * @param {Number} number            The number.
    *                                   Required.
    * @param {Number} decimals          Amount of decimals to show.
    *                                   Optional. Default: _this.i18n.number.decimals
    * @param {String} decPoint          Decimal point.
    *                                   Optional. Default: _this.i18n.number.decimalPoint
    * @param {String} thousandsSep      Thousand separator.
    *                                   Optional. Default: _this.i18n.number.thousandsSeparator
    * @param {Boolean} negativeParens   If to display negative numbers with parens.
    *                                   Optional. Default: _this.i18n.number.negativeParens
    * @param {Boolean} forceDecimals    If to display decimals even if they're none.
    *                                   Optional. Default: _this.i18n.number.forceDecimals
    * @version 1.0 2010-10-11
    * @author Mathias Petersson
    * @return Result.
    * @type String
    */
    this.numberFormat = function(number, decimals, decPoint, thousandsSep, negativeParens, forceDecimals)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var prec, s = '', toFixedFix = function(n, prec)
            {
                var k = Math.pow(10, prec);
                return '' + Math.round(n * k) / k;
            };
            options.number = (options.number + '').replace(',', '').replace(' ', '');
            options.number = (!isFinite(+options.number) ? 0 : +options.number);
            options.decimals = (options.decimals !== undefined ? options.decimals : _this.i18n.number.decimals);
            options.decPoint = (options.decPoint ? options.decPoint : _this.i18n.number.decimalPoint);
            options.thousandsSep = (options.thousandsSep ? options.thousandsSep : _this.i18n.number.thousandsSeparator);
            options.negativeParens = (options.negativeParens !== undefined ? options.negativeParens : _this.i18n.number.negativeParens);
            options.forceDecimals = (options.forceDecimals !== undefined ? options.forceDecimals : _this.i18n.number.forceDecimals);
            prec = (!isFinite(+options.decimals) ? 0 : Math.abs(options.decimals));
            s = (prec ? toFixedFix(options.number, prec) : '' + Math.round(options.number)).split('.');
            re = new RegExp(options.decPoint + '0+$', 'g');
            if(s[0].length > 3)
            {
                s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, options.thousandsSep);
            }
            if((s[1] || '').length < prec)
            {
                s[1] = s[1] || '';
                s[1] += new Array(prec - s[1].length + 1).join('0');
            }
            s = s.join(options.decPoint);
            if(!options.forceDecimals && (new RegExp('[' + options.decPoint + ']0+$', 'g')).test(s))
            {
                s = s.substring(0, s.indexOf(options.decPoint));
            }
            else if(!options.forceDecimals && s.indexOf(options.decPoint) > 0)
            {
                s = _this.rtrim(s, '0');
            }
            if(options.negativeParens && s.substring(0, 1) == '-')
            {
                s = '(' + s.substring(1) + ')';
            }
            return s;
        }
        return number;
    };
    this.numberFormat.rules =
    {
        required : ['number'],
        types :
        {
            number : ['number'],
            decimals : ['number'],
            decPoint : ['string'],
            thousandsSep : ['string'],
            negativeParens : ['boolean'],
            forceDecimals : ['boolean']
        }
    };

    /**
    * Options object constructor.
    *
    * @param {Object} object    Options object.
    *                           Required.
    * @version 1.0 2010-09-20
    * @author Mathias Petersson
    * @return Result.
    * @type Object
    */
    this.o = function(object)
    {
        var options = _this.isValidInputs();
        var result = {isOptions : true};
        if(options)
        {
            result = _this.mergeObjects(options.object, result);
        }
        return result;
    };
    this.o.rules =
    {
        required : ['object'],
        types :
        {
            object : ['object']
        }
    };

    /**
    * Parse passed search string and return an object with collection of values.
    *
    * @param {Mixed} search Search string to parse. Can be a string, url or a link element.
    *                       If undefined, current location is parsed and result is stored in
    *                       .parameters and passed back.
    *                       Optional. Default: location
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return Collection of search parameters and values.
    * @type Object
    */
    this.parseSearchstring = function(search)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var result = {}, collection, i, m, a, searchstring = (options.search) ? options.search : _this.location.search;
            if(_this.getType(searchstring) != 'string')
            {
                if(_this.getType(searchstring.search) != 'string')
                {
                    return false;
                }
                searchstring = searchstring.search;
            }
            collection = _this.trim(searchstring, '?').split('&');
            for(i = 0, m = collection.length; i < m; ++i)
            {
                a = collection[i].split('=');
                if(_this.trim(a[0] != ''))
                {
                    result[_this.urlDecode(a[0])] = (a.length) ? _this.urlDecode(a[1]) : undefined;
                }
            }
            if(options._types.search == 'undefined')
            {
                _this.parameters = result;
            }
            return result;
        }
        return false;
    };
    this.parseSearchstring.rules =
    {
        required : [],
        types :
        {
            search : ['dom', 'string']
        }
    };

    /**
    * Change a DOM element ID by adding a numberical value at it's end.
    *
    * @param {Mixed} element    The element.
    *                           Required.
    * @version 1.0 2010-08-24
    * @author Mathias Petersson
    */
    this.populateID = function(element)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var i = 1, id = element.id + '-';
            while(_this.getDOM(id + i))
            {
                ++i;
            }
            element.id = id + i;
        }
    };
    this.populateID.rules =
    {
        required : ['element'],
        types :
        {
            element : ['dom']
        }
    };

    /**
    * Print contents of an variable.
    *
    * @version 1.0 2010-05-06
    * @author Mathias Petersson
    * @see #echor
    */
    this.printr = function(variable, indent)
    {
        var x, i, m, result = '', type = _this.getType(variable);
        indent = indent || '';
        switch(type)
        {
            case 'string':
                result += '<span title="' + type + '" class="' + type + '">' + variable.replace('<', '&lt;').replace('>', '&gt;') + '</span>';
            break;
            case 'date':
            case 'number':
            break;
            case 'dom':
                result += '<span title="DOM element" class="dom-element" onclick="alert(\"type: ' + variable.nodeName + '\\nid: ' + variable.id + '\\nclass: ' + variable.className + '\")">DOM element</span>';
            break;
            case 'event':
                result += '<span title="Event" class="event">Event</span>';
            break;
            case 'function':
                result += '<span title="Function" class="function">Function</span>';
            break;
            case 'array':
                result += '<span title="array" class="array">Array(</span>\n';
                i = 0;
                m = variable.length;
                for(x in variable)
                {
                    i++;
                    result += indent + '    <span title="array" class="array">' + x + ' =></span> ' + _this.printr(variable[x], indent + '    ');
                    if(i < m)
                    {
                        result += ',';
                    }
                    result += '\n';
                }
                result += indent + '<span title="array" class="array">)</span>';
            break;
            case 'object':
                result += '<span title="object" class="object">Object{</span>\n';
                i = 0;
                for(x in variable)
                {
                    i++;
                    if(i > 1)
                    {
                        result += ',\n';
                    }
                    result += indent + '    <span title="object" class="object">' + x + ' =></span> ' + _this.printr(variable[x], indent + '    ');
                }
                result += '\n' + indent + '<span title="object" class="object">}</span>';
            break;
            case 'boolean':
                result += '<span title="Boolean" class="boolean">' + (variable ? 'true' : 'false') + '</span>';
            break;
        }
        return result;
    };

    /**
    * Initiate and terminate Firebug profiler (Firefox plugin).
    *
    * @param {String} id    Profiler identifier.
    *                       Required.
    * @version 1.0 2010-09-14
    * @author Mathias Petersson
    */
    this.profile = function(id)
    {
        if(window.console !== undefined && console.profile)
        {
            if(!_memory.profile)
            {
                _memory.profile = {};
                _memory.profile.identifier = {};
                _memory.profile.count = 0;
            }
            id = id.replace(/\W/mig, '_');
            if(_memory.profile.identifier[id])
            {
                console.profileEnd(_memory.profile.identifier[id] + ': ' + id);
                _memory.profile.identifier[id] = false;
            }
            else
            {
                _memory.profile.count++;
                _memory.profile.identifier[id] = _memory.profile.count;
                console.profile(_memory.profile.identifier[id] + ': ' + id);
            }
        }
    };

    /**
    * Return a randomized ID name.
    *
    * @param {Number} length    Total length of returned ID excl. prefix.
    *                           Optional. Default: 12
    * @param {String} prefix    ID prefix.
    *                           Optional. Default:
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Randomized ID name.
    * @type String
    */
    this.randomID = function(length, prefix)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var result = '', i, m, characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
            options.length = (options.length) ? options.length : 12;
            options.prefix = (options.prefix) ? options.prefix : '';
            for(i = 0, m = characters.length; i < options.length; ++i)
            {
                if(result == '')
                {
                    result += characters[_this.randomNumber(10, m - 1)];
                }
                else
                {
                    result += characters[_this.randomNumber(m - 1)];
                }
            }
            return options.prefix + result;
        }
        return false;
    };
    this.randomID.rules =
    {
        required : [],
        types :
        {
            length : ['number'],
            prefix : ['string']
        }
    };

    /**
    * Return a randomized number between min and max.
    *
    * Min defaults to 0 and max to 100. If only one argument
    * is passed, it's treated as a maximum value and min defaults to 0.
    *
    * @param {Number} min   Minimum value.
    *                       Optional. Default: 0
    * @param {Number} max   Maximum value.
    *                       Optional. Default: 100
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Result.
    * @type Number
    */
    this.randomNumber = function(min, max)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            options.min = (options.min === undefined) ? 0 : options.min;
            options.max = (options.max === undefined) ? 100 : options.max;
            if(options._types.max == 'undefined')
            {
                options.max = options.min;
                options.min = 0;
            }
            return Math.floor(Math.random() * ((options.max - options.min) + 1)) + options.min;
        }
    };
    this.randomNumber.rules =
    {
        required : [],
        types :
        {
            min : ['number'],
            max : ['number']
        }
    };

    /**
    * Removes class name(s) from an element.
    *
    * @param {Object} element   The element from where class names shall be removed.
    *                           DOM element or element ID.
    *                           Required.
    * @param {Mixed} classes    Class names to be removed. CSV string or an array.
    *                           Required.
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Class names of element.
    * @type String
    */
    this.removeClass = function(element, classes)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(options._types.element == 'string')
            {
                options.element = _this.getDOM(options.element);
            }
            if(options.element)
            {
                var i, m, currentClassNames, classNames = '';
                options.classes = _this.getCollection(options.classes, [' ', ',']);
                currentClassNames = _this.getCollection(options.element.className, [' ', ',']);
                for(i = 0, m = currentClassNames.length; i < m; ++i)
                {
                    if(!_this.inArray(currentClassNames[i], options.classes))
                    {
                        classNames += currentClassNames[i] + ' ';
                    }
                }
                options.element.className = (classNames) ? _this.trim(classNames) : '';
                return classNames;
            }
        }
        return false;
    };
    this.removeClass.rules =
    {
        required : ['element', 'classes'],
        types :
        {
            element : ['dom', 'string'],
            classes : ['string', 'array']
        }
    };

    /**
    * Remove an event listener from an element or object.
    *
    * @param {Mixed} element    Element or object.
    *                           Required.
    * @param {String} event     Event type.
    *                           Required.
    * @param {Function} action  The function to be called upon event triggering.
    *                           Required.
    * @param {Boolean} bubble   If to bubble the event or not.
    *                           Optional. Default: False
    * @version 1.0 2010-09-28
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.removeEvent = function(element, event, action, bubble)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(element.removeEventListener)
            {
                element.removeEventListener(event, action, bubble);
            }
            else if(element.detachEvent)
            {
                element.detachEvent('on' + event, action);
            }
        }
    };
    this.removeEvent.rules =
    {
        required : ['element', 'event', 'action'],
        types :
        {
            element : ['dom', 'object'],
            event : ['string'],
            action : ['function'],
            bubble : ['boolean']
        }
    };

    /**
    * Removes an element from document.
    *
    * @param {Mixed} element    DOM element or element ID.
    *                           Required.
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.removeElement = function(element)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            if(options._types.element == 'string')
            {
                options.element = _this.getDOM(options.element);
            }
            if(options.element)
            {
                var parent = _this.getParent(options.element);
                if(parent)
                {
                    return (parent.removeChild(options.element)) ? true : false;
                }
            }
        }
        return false;
    };
    this.removeElement.rules =
    {
        required : ['element'],
        types :
        {
            element : ['dom', 'string']
        }
    };

    /**
    * Removes trailing characters.
    *
    * @version 1.0 2010-05-06
    * @author Mathias Petersson
    * @see #trim
    */
    this.rtrim = function(string, characters)
    {
        characters = (characters === undefined) ? '\\s' : characters;
        return string.replace(new RegExp('[' + characters + ']+$', 'g'), '');
    };

    /**
    * Removes all leading and trailing occurrences of a set of characters.
    * If no characters are specified it will trim whitespace characters.
    *
    * @param {String} string        The string to be trimmed.
    *                               Required.
    * @param {String} characters    Characters to be removed, leave undefined
    *                               for whitespace characters.
    *                               Optional. Default:
    * @version 1.0 2010-05-06
    * @author Mathias Petersson
    * @return Trimmed string.
    * @type String
    */
    this.trim = function(string, characters)
    {
        return _this.ltrim(_this.rtrim(string, characters), characters);
    };

    /**
    * Sets a cookie value.
    *
    * Expiration options can be mixed together. Defaults to 30 days.
    *
    * @param {String} name      Cookie name.
    *                           Required.
    * @param {Mixed} value      Cookie value.
    *                           Required.
    * @param {Number} days      Expires in X days.
    *                           Optional. Default:
    * @param {Number} hours     Expires in X hours.
    *                           Optional. Default:
    * @param {Number} minutes   Expires in X minutes.
    *                           Optional. Default:
    * @param {Number} seconds   Expires in X seconds.
    *                           Optional. Default:
    * @param {String} path      Cookie path.
    *                           Optional. Default:
    * @param {String} domain    Cookie domain.
    *                           Optional. Default:
    * @param {Boolean} secure   Tells browser to send cookie over ssl to the server.
    *                           Optional. Default: false
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.setCookie = function(name, value, days, hours, minutes, seconds, path, domain, secure)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var today, expire, tomorrow;
            options.days = parseInt(options.days) || 0;
            options.hours = parseInt(options.hours) || 0;
            options.minutes = parseInt(options.minutes) || 0;
            options.seconds = parseInt(options.seconds) || 0;
            expire = ((((((options.days * 24) + options.hours) * 60) + options.minutes) * 60) + options.seconds) * 1000;
            expire = (expire) ? expire : 2592000000; // Remove this, or alter, to modify default life span
            today = new Date()
            today.setTime(today.getTime());
            tomorrow = new Date(today.getTime() + expire);
            document.cookie = options.name + '=' + escape(options.value) +
            ((expire) ? ';expires=' + tomorrow.toGMTString() : '') +
            ((options.path) ? ';path=' + options.path : '') +
            ((options.domain) ? ';domain=' + options.domain : '') +
            ((options.secure) ? ';secure' : '');
            return true;
        }
        return false;
    };
    this.setCookie.rules =
    {
        required : ['name', 'value'],
        types :
        {
            name : ['string'],
            days : ['number'],
            hours : ['number'],
            minutes : ['number'],
            seconds : ['number'],
            path : ['string'],
            domain : ['string'],
            secure : ['boolean']
        }
    };

    /**
    * Adds opacity to an element.
    *
    * @param {Object} element   DOM element.
    *                           Required.
    * @param {Number} opacity   Opacity value range 0 to 100.
    *                           Required.
    * @version 1.0 2010-08-25
    * @author Mathias Petersson
    * @return Result.
    * @type Boolean
    */
    this.setOpacity = function(element, opacity)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            options.element.style.opacity = options.opacity / 100;
            options.element.style.filter = 'alpha(opacity=' + options.opacity + ')';
            options.element.style.zoom = 1;
            return true;
        }
        return false;
    };
    this.setOpacity.rules =
    {
        required : ['element', 'opacity'],
        types :
        {
            element : ['dom'],
            opacity : ['number']
        }
    };

    /**
    * Build an object inside another.
    *
    * @param {Object} element  Element to carry the object.
    * @param {String} name  Object name. Alphanumeric characters, separated with punctuations.
    * @param {Mixed} value  Object value.
    * @version 1.0 2010-05-12
    * @author Mathias Petersson
    * @return Result of the request.
    * @type Boolean
    */
    this.setPrototype = function(element, name, value)
    {
        var options = _this.isValidInputs();
        if(options)
        {

        }
        /*
        if(element === undefined || name === undefined
        || !air.isType(element, 'dom')
        || !air.isType(name, 'string')
        || !/^[a-z.]+$/i.test(name))
        {
            return false;
        }

        var namePath = (air.container + '.' + name).split('.');
        var reference = element;
        for(var i = 0, m = namePath.length; i < m; ++i)
        {
            if(i + 1 == m)
            {
                reference[namePath[i]] = (value === undefined) ? {} : value;
            }
            else
            {
                if(reference[namePath[i]] === undefined)
                {
                    reference[namePath[i]] = {};
                }
                reference = reference[namePath[i]];
            }
        }

        return true;
        */
    };
    this.setPrototype.rules =
    {
        required : ['element', 'name'],
        types :
        {
            element : ['dom'],
            name : ['string']
        }
    };

    /**
    * Initiate and terminate a timer for Firebug console (Firefox plugin).
    *
    * @param {String} id    Timer identifier.
    *                       Required.
    * @version 1.0 2010-09-14
    * @author Mathias Petersson
    */
    this.time = function(id)
    {
        if(window.console !== undefined && conosle.time)
        {
            if(!_memory.time)
            {
                _memory.time = {};
                _memory.time.identifier = {};
                _memory.time.count = 0;
            }
            id = id.replace(/\W/mig, '_');
            if(_memory.time.identifier[id])
            {
                console.timeEnd(_memory.time.identifier[id] + ': ' + id);
                _memory.time.identifier[id] = false;
            }
            else
            {
                _memory.time.count++;
                _memory.time.identifier[id] = _memory.time.count;
                console.time(_memory.time.identifier[id] + ': ' + id);
            }
        }
    };

    /**
    * URL decoding.
    *
    * @param {String} string    String to URL decode.
    *                           Required.
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return URL decoded value.
    * @type String
    */
    this.urlDecode = function(string)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            return unescape(options.string);
        }
        return false;
    };
    this.urlDecode.rules =
    {
        required : ['string'],
        types :
        {
            string : ['string']
        }
    };

    /**
    * URL encoding.
    *
    * @param {String} string    String to URL encode.
    *                           Required.
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return URL encoded value.
    * @type String
    */
    this.urlEncode = function(string)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            options.string = escape(options.string.toString());
            return options.string.replace(/[*+\/@]|%20/g, function(s)
            {
                switch(s)
                {
                    case '*':
                        s = '%2A';
                    break;
                    case '+':
                        s = '%2B';
                    break;
                    case '/':
                        s = '%2F';
                    break;
                    case '@':
                        s = '%40';
                    break;
                    case '%20':
    //                        s = '+';
                    break;
                }
                return s;
            });
        }
        return false;
    };
    this.urlEncode.rules =
    {
        required : ['string'],
        types :
        {
            string : ['string']
        }
    };

    /**
    * UTF-8 decoding.
    *
    * @param {String} string    String to UTF-8 decode.
    *                           Required.
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return UTF-8 decoded value.
    * @type String
    */
    this.utf8Decode = function(string)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            var result = '', m = options.string.length, i = c = c1 = c2 = 0;
            while(i < m)
            {
                c = options.string.charCodeAt(i);
                if (c < 128)
                {
                    result += String.fromCharCode(c);
                    i++;
                }
                else if((c > 191) && (c < 224))
                {
                    c2 = options.string.charCodeAt(i + 1);
                    result += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                }
                else
                {
                    c2 = options.string.charCodeAt(i + 1);
                    c3 = options.string.charCodeAt(i + 2);
                    result += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }
            }
            return result;
        }
        return false;
    };
    this.utf8Decode.rules =
    {
        required : ['string'],
        types :
        {
            string : ['string']
        }
    };

    /**
    * UTF-8 encoding.
    *
    * @param {String} string    String to UTF-8 encode.
    *                           Required.
    * @version 1.0 2010-08-26
    * @author Mathias Petersson
    * @return UTF-8 encoded value.
    * @type String
    */
    this.utf8Encode = function(string)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            options.string = options.string.replace(/\r\n/g, '\n');
            var result = '', i, m, c;
            for(i = 0, m = options.string.length; i < m; ++i)
            {
                c = options.string.charCodeAt(i);
                if(c < 128)
                {
                    result += String.fromCharCode(c);
                }
                else if((c > 127) && (c < 2048))
                {
                    result += String.fromCharCode((c >> 6) | 192);
                    result += String.fromCharCode((c & 63) | 128);
                }
                else
                {
                    result += String.fromCharCode((c >> 12) | 224);
                    result += String.fromCharCode(((c >> 6) & 63) | 128);
                    result += String.fromCharCode((c & 63) | 128);
                }
            }
            return result;
        }
        return false;
    };
    this.utf8Encode.rules =
    {
        required : ['string'],
        types :
        {
            string : ['string']
        }
    };

    /**
    * Make a log post.
    *
    * @param {String} message   The message.
    *                           Required.
    * @param {String} type      Log type. inArray('warning', 'error', 'info')
    *                           Optional. Default: warning
    * @version 1.0 2010-10-22
    * @author Mathias Petersson
    */
    this.log = function(message, type)
    {
        var options = _this.isValidInputs();
        if(options)
        {
            options.type = (_this.inArray(options.type, ['warning', 'error', 'info']) ? options.type : 'warning');
            _this.debug(options.type, options.message);
        }
    };
    this.log.rules =
    {
        required : ['message'],
        types :
        {
            message : ['string'],
            type : ['string']
        }
    };


    // Initialize script //

    _init();

    return arguments.callee.toString();
};
var air = new classAir();
