# Source: helma/Html.js

    /*
     * Helma License Notice
     *
     * The contents of this file are subject to the Helma License
     * Version 2.0 (the "License"). You may not use this file except in
     * compliance with the License. A copy of the License is available at
     * http://adele.helma.org/download/helma/license.txt
     *
     * Copyright 1998-2006 Helma Software. All Rights Reserved.
     *
     * $RCSfile: Html.js,v $
     * $Author$
     * $Revision$
     * $Date$
     */

    /**
     * @fileoverview Fields and methods of the helma.Html
     * and helma.Html.Tablewriter classes.
     * <br /><br />
     * To use this optional module, its repository needs to be added to the
     * application, for example by calling app.addRepository('modules/helma/Html.js')
     */

    // take care of any dependencies
    app.addRepository('modules/core/String.js');
    app.addRepository('modules/core/Object.js');
    app.addRepository('modules/core/Array.js');

    /**
     * Define the global namespace if not existing
     */
    if (!global.helma) {
        global.helma = {};
    }

    /**
     * Creates a new instance of helma.Html
     * @class This class provides various methods for rendering
     * X/Html tags.
     * @returns A newly created instance of helma.Html
     * @constructor
     */
    helma.Html = function() {
        return this;
    };

    /**
     * Static helper method that renders an arbitrary markup part.
     * @param {String} name The element's name
     * @param {String} start Prefix of each rendered element
     * @param {String} end Suffix of each rendered element
     * @param {Object} attr Optional element attributes
     */
    helma.Html.renderMarkupPart = function(name, start, end, attr) {
        res.write(start);
        res.write(name);
        if (attr) {
            for (var i in attr) {
                if (i == "prefix" || i == "suffix" ||
                    i == "default" || attr[i] == null) {
                    continue;
                }
                res.write(" ");
                res.write(i);
                res.write("=\"");
                res.write(attr[i]);
                res.write("\"");
            }
        }
        res.write(end);
        return;
    };

    /**
     * Static helper method used in helma.Html.checkBox
     * and helma.Html.dropDown to check if a current value
     * matches against one or more selected values passed
     * as argument
     * @param {String} value The current value to check
     * @param {String|Array} selectedValue Either a single
     * value to check against the current value, or an array
     * containing values.
     * @returns True in case the value is among the selected
     * values, false otherwise
     * @type Boolean
     */
    helma.Html.isSelected = function(value, selectedValue) {
        if (selectedValue == null || value == null)
            return false;
        if (selectedValue instanceof Array)
            return selectedValue.contains(value);
        return value == selectedValue;
    };

    /** @ignore */
    helma.Html.prototype.toString = function() {
        return "[helma.Html]";
    };

    /**
     * Renders the opening tag of an arbitrary x/html tag
     * @param {String} name The tag name
     * @param {Object} attr An optional object containing element attributes
     */
    helma.Html.prototype.openTag = function(name, attr) {
        helma.Html.renderMarkupPart(name, "<", ">", attr);
        return;
    };

    /**
     * Returns the opening tag of an arbitrary x/html tag
     * @param {String} name The tag name
     * @param {Object} attr An optional object containing element attributes
     * @returns The rendered x/html opening tag
     * @type String
     * @see #openTag
     */
    helma.Html.prototype.openTagAsString = function(name, attr) {
        res.push();
        helma.Html.renderMarkupPart(name, "<", ">", attr);
        return res.pop();
    };

    /**
     * Renders the closing tag of an arbitrary x/html tag
     * @param {String} name The tag name
     */
    helma.Html.prototype.closeTag = function(name) {
        helma.Html.renderMarkupPart(name, "</", ">", null);
        return;
    };

    /**
     * Returns the closing tag of an arbitray x/html element
     * @param {String} name The tag name
     * @returns The rendered closing tag
     * @type String
     * @see #closeTag
     */
    helma.Html.prototype.closeTagAsString = function(name) {
        res.push();
        helma.Html.renderMarkupPart(name, "</", ">", null);
        return res.pop();
    };

    /**
     * Renders an empty arbitrary x/html tag ("contentless tag")
     * @param {String} name The tag name
     * @param {Object} attr An optional object containing tag attributes
     */
    helma.Html.prototype.tag = function(name, attr) {
        helma.Html.renderMarkupPart(name, "<", " />", attr);
        return;
    };

    /**
     * Returns an empty arbitrary x/html tag ("contentless tag")
     * @param {String} name The tag name
     * @param {Object} attr An optional object containing tag attributes
     * @returns The rendered element
     * @type String
     * @see #tag
     */
    helma.Html.prototype.tagAsString = function(name, attr) {
        res.push();
        helma.Html.renderMarkupPart(name, "<", " />", attr);
        return res.pop();
    };

    /**
     * Renders an arbitrary x/html element
     * @param {String} name The element name
     * @param {String} str The content of the element
     * @param {Object} attr An optional object containing element attributes
     */
    helma.Html.prototype.element = function(name, str, attr) {
        helma.Html.renderMarkupPart(name, "<", ">", attr);
        res.write(str);
        helma.Html.renderMarkupPart(name, "</", ">");
        return;
    };

    /**
     * Return an arbitrary x/html element
     * @param {String} name The element name
     * @param {String} str The content of the element
     * @param {Object} attr An optional object containing element attributes
     * @returns The rendered element
     * @type String
     * @see #element
     */
    helma.Html.prototype.elementAsString = function(name, str, attr) {
        res.push();
        this.element(name, str, attr);
        return res.pop();
    };

    /**
     * Renders an x/html link tag
     * @param {Object} attr An object containing the link attributes
     * @param {String} text The text to appear as link
     */
    helma.Html.prototype.link = function(attr, text) {
        if (!attr) {
            res.write("[Html.link: insufficient arguments]");
            return;
        }
        this.openTag("a", attr);
        res.write(text);
        this.closeTag("a");
        return;
    };

    /**
     * Returns a rendered x/html link tag
     * @param {Object} attr An object containing the link attributes
     * @param {String} text The text to appear as link
     * @returns The rendered link tag
     * @type String
     * @see #link
     */
    helma.Html.prototype.linkAsString = function(attr, text) {
        res.push();
        this.link(attr, text);
        return res.pop();
    };

    /**
     * Renders an x/html input tag of type "hidden"
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.hidden = function(param) {
        if (!param) {
            res.write("[Html.hidden: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        attr.type = "hidden";
        attr.value = (attr.value != null) ? encodeForm(attr.value) : "";
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html input tag of type "hidden"
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered input element
     * @type String
     * @see #hidden
     */
    helma.Html.prototype.hiddenAsString = function(attr) {
        res.push();
        this.hidden(attr);
        return res.pop();
    };

    /**
     * Renders an x/html text input tag
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.input = function(param) {
        if (!param) {
            res.write("[Html.input: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        attr.type = param.type || "text";
        attr.value = (attr.value != null) ? encodeForm(attr.value) : "";
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html text input tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered text input tag
     * @type String
     * @see #input
     */
    helma.Html.prototype.inputAsString = function(attr) {
        res.push();
        this.input(attr);
        return res.pop();
    };

    /**
     * Renders an x/html textarea tag
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.textArea = function(param) {
        if (!param) {
            res.write("[Html.textArea: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        var value = (attr.value != null) ? encodeForm(attr.value) : "";
        delete attr.value;
        this.openTag("textarea", attr);
        res.write(value);
        this.closeTag("textarea");
        return;
    };

    /**
     * Returns a rendered x/html textarea tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered textarea tag
     * @type String
     * @see #textArea
     */
    helma.Html.prototype.textAreaAsString = function(attr) {
        res.push();
        this.textArea(attr);
        return res.pop();
    };

    /**
     * Renders an x/html checkbox input tag
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.checkBox = function(param) {
        if (!param) {
            res.write("[Html.checkBox: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        attr.type = "checkbox";
        if (attr.selectedValue != null) {
            if (helma.Html.isSelected(param.value, param.selectedValue))
                attr.checked = "checked";
            else
                delete attr.checked;
            delete attr.selectedValue;
        }
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html checkbox input tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered checkbox tag
     * @type String
     * @see #checkBox
     */
    helma.Html.prototype.checkBoxAsString = function(attr) {
        res.push();
        this.checkBox(attr);
        return res.pop();
    };

    /**
     * Renders an x/html radiobutton input tag
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.radioButton = function(param) {
        if (!param) {
            res.write("[Html.radioButton: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        attr.type = "radio";
        if (attr.selectedValue != null) {
            if (attr.value == attr.selectedValue)
                attr.checked = "checked";
            else
                delete attr.checked;
            delete attr.selectedValue;
        }
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html radio input tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered element
     * @type String
     * @see #radioButton
     */
    helma.Html.prototype.radioButtonAsString = function(attr) {
        res.push();
        this.radioButton(attr);
        return res.pop();
    };

    /**
     * Renders an x/html submit input tag
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.submit = function(param) {
        if (!param) {
            res.write("[Html.submit: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        attr.type = "submit";
        if (!attr.name)
            attr.name = attr.type;
        attr.value = (attr.value != null) ? encodeForm(attr.value) : attr.type;
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html submit input tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered submit input tag
     * @type String
     * @see #submit
     */
    helma.Html.prototype.submitAsString = function(attr) {
        res.push();
        this.submit(attr);
        return res.pop();
    };

    /**
     * Renders an x/html button input tag
     * @param {Object} param An object containing the tag attributes
     */
    helma.Html.prototype.button = function(param) {
        if (!param) {
            res.write("[Html.button: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        attr.type = "button";
        if (!attr.name)
            attr.name = attr.type;
        attr.value = (attr.value != null) ? encodeForm(attr.value) : attr.type;
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html button input tag
     * @param {Object} param An object containing the tag attributes
     * @returns The rendered button input tag
     * @type String
     * @see #button
     */
    helma.Html.prototype.buttonAsString = function(attr) {
        res.push();
        this.button(attr);
        return res.pop();
    };

    /**
     * Renders a x/html drop down select box
     * @param {Object} param An object containing the tag attributes
     * @param {Array} options Either an array of strings, an array with
     * several <code>{value: v, display: d}</code> objects, or a collection
     * of <code>["value", "display"]</code> arrays in an array
     * @param {String} selectedValue The value to pre-select
     * @param {String} firstOption An optional first option to display in the
     * select box (this option will always have no value)
     */
    helma.Html.prototype.dropDown = function(param, options, selectedValue, firstOption) {
        if (!param) {
            res.write("[Html.dropDown: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        this.openTag("select", attr);
        res.write("\n ");
        if (firstOption) {
            this.openTag("option", {value: ""});
            res.write(firstOption);
            this.closeTag("option");
            res.write("\n ");
        }
        var hasOpenGroup = false;
        for (var i in options) {
            if (options[i].group) {
                hasOpenGroup && html.closeTag("optgroup");
                html.openTag("optgroup", {label: options[i].group});
                hasOpenGroup = true;
            }
            var attr = new Object();
            var display = "";
            if ((options[i] instanceof Array) && options[i].length > 0) {
                // option is an array
                attr.value = options[i][0];
                display = options[i][1];
            } else if (options[i].value != null && options[i].display != null) {
                // option is an object
                attr.value = options[i].value;
                if (options[i]["class"] != null) {
                   attr["class"] = options[i]["class"];
                }
                display = options[i].display;
            } else {
                // assume option is a string
                attr.value = i;
                display = options[i];
            }
            if (helma.Html.isSelected(attr.value, selectedValue))
                attr.selected = "selected";
            this.openTag("option", attr);
            res.write(display);
            this.closeTag("option");
            res.write("\n ");
        }
        hasOpenGroup && html.closeTag("optgroup");
        this.closeTag("select");
        res.write("\n ");
        return;
    };

    /**
     * Returns a rendered x/html drop down select box
     * @param {Object} param An object containing the tag attributes
     * @param {Array} options Either an array of strings, an array with
     * several <code>{value: v, display: d}</code> objects, or a collection
     * of <code>["value", "display"]</code> arrays in an array
     * @param {String} selectedValue The value to pre-select
     * @param {String} firstOption An optional first option to display in the
     * select box (this option will always have no value)
     * @returns The rendered drop down select box
     * @type String
     * @see #dropDown
     */
    helma.Html.prototype.dropDownAsString = function(attr, options, selectedValue, firstOption) {
        res.push();
        this.dropDown(attr, options, selectedValue, firstOption);
        return res.pop();
    };

    /**
     *  Renders an image map based on an array containing the map parameters.
     *  @param {String} name The name of the image map
     *  @param {Array} param An array containing objects, where each of them
     *  contains the attributes for a single image map entry
     */
    helma.Html.prototype.map = function(name, param) {
        if (!name || !param) {
            res.write("[Html.map: insufficient arguments]");
            return;
        }
        this.openTag("map", {name: name});
        var attr = Object.prototype.reduce.call(param);
        for (var i in areas) {
            if (!areas[i].alt)
                areas[i].alt = "";
            if (!areas[i].shape)
                areas[i].shape = "rect";
            this.openTag("area", areas[i]);
        }
        this.closeTag("map");
        return;
    };

    /**
     *  Returns a rendered image map based on an array containing the map parameters.
     *  @param {String} name The name of the image map
     *  @param {Array} areas An array containing objects, where each of them
     *  contains the attributes for a single image map entry
     * @returns The rendered image map
     * @type String
     * @see #map
     */
    helma.Html.prototype.mapAsString = function(name, areas) {
        res.push();
        this.map(name, areas);
        return res.pop();
    };

    /**
     * Renders a complete x/html table.
     * @param {Array} headers An array containing table headers
     * @param {Array} data A two-dimensional array containing the table data
     * @param {Object} param An object containing the following properties:
     * <ul>
     * <li><code>table</code>: Attributes to render within the opening <code>&lt;table&gt;</code> tag</li>
     * <li><code>tr</code>: Attributes to render within each <code>&lt;tr&gt;</code> tag</li>
     * <li><code>td</code>: Attributes to render within each <code>&lt;td&gt;</code> tag</li>
     * <li><code>th</code>: Attributes to render within each <code>&lt;th&gt;</code> tag</li>
     * <li><code>trHead</code>: Attributes to render within each <code>&lt;tr&gt;</code> tag
     in the header area of the table</li>
     * <li><code>trEven</code>: Attributes to render within each even <code>&lt;tr&gt;</code> tag</li>
     * <li><code>trOdd</code>: Attributes to render within each odd <code>&lt;tr&gt;</code> tag</li>
     * <li><code>tdEven</code>: Attributes to render within each even <code>&lt;td&gt;</code> tag</li>
     * <li><code>tdOdd</code>: Attributes to render within each odd <code>&lt;td&gt;</code> tag</li>
     * <li><code>thEven</code>: Attributes to render within each even <code>&lt;th&gt;</code> tag</li>
     * <li><code>thOdd</code>: Attributes to render within each odd <code>&lt;th&gt;</code> tag</li>
     * </ul>
     */
    helma.Html.prototype.table = function(headers, data, param) {
        if (!param) {
            res.write("[Html.table: insufficient arguments]");
            return;
        }
        var attr = Object.prototype.reduce.call(param);
        if (!attr.trHead) attr.trHead = attr.tr;
        if (!attr.trEven) attr.trEven = attr.tr;
        if (!attr.trOdd)  attr.trOdd = attr.tr;
        if (!attr.tdEven) attr.tdEven = attr.td;
        if (!attr.tdOdd)  attr.tdOdd = attr.td;
        if (!attr.thEven) attr.thEven = attr.th;
        if (!attr.thOdd)  attr.thOdd = attr.th;
        this.openTag("table", attr.table);
        if (headers) {
            this.openTag("tr", attr.trHead);
            for (var i in headers) {
                var evenOdd = i % 2 == 0 ? "Even" : "Odd";
                this.openTag("th", attr["th"+evenOdd]);
                res.write(headers[i]);
                this.closeTag("th");
            }
            this.closeTag("tr");
        }
        for (var i in data) {
            var evenOdd = i % 2 == 0 ? "Even" : "Odd";
            this.openTag("tr", attr["tr"+evenOdd]);
            for (var j in data[i]) {
                var evenOddCell = j % 2 == 0 ? "Even" : "Odd";
                this.openTag("td", attr["td"+evenOddCell]);
                res.write(data[i][j]);
                this.closeTag("td");
            }
            this.closeTag("tr");
        }
        this.closeTag("table");
        return;
    };

    /**
     * Returns a rendered x/html table
     * @param {Array} headers An array containing table headers
     * @param {Array} data A two-dimensional array containing the table data
     * @param {Object} attr For a description see {@link #table}
     * @returns The rendered table
     * @type String
     * @see #table
     */
    helma.Html.prototype.tableAsString = function(headers, data, attr) {
        res.push();
        this.table(headers, data, attr);
        return res.pop();
    };

    /*********************************************************************/
    /*                                                                                         */
    /* the following functions should be deliberately altered or removed */
    /* (most of these can easily be replaced by the methods they call)    */
    /*                                                                                         */
    /*********************************************************************/

    /**
     * Renders an x/html opening link tag
     * @param {Object} attr An object containing the tag attributes
     */
    helma.Html.prototype.openLink = function(attr) {
        this.openTag("a", attr);
        return;
    };

    /**
     * Returns an x/html opening link tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered open link tag
     * @type String
     * @see #openTag
     */
    helma.Html.prototype.openLinkAsString = function(attr) {
        return this.openTagAsString("a", attr);
    };

    /**
     * Renders an x/html closing link tag
     */
    helma.Html.prototype.closeLink = function() {
        this.closeTag("a");
        return;
    };

    /**
     * Returns a rendered x/html closing link tag
     * @returns Rhe rendered closing link tag
     * @type String
     * @see #closeLink
     */
    helma.Html.prototype.closeLinkAsString = function() {
        return this.closeTagAsString("a");
    };

    /**
     * Renders a color definition string. If the string passed as
     * argument contains only hex characters it will be prefixed with a
     * hash sign if necessary, otherwise this method assumes that the
     * value is a named color (eg. "yellow").
     * @param {String} c The color definintion
     * @deprecated
     */
    helma.Html.prototype.color = function(c) {
        if (c) {
            var nonhex = /[^0-9,a-f]/gi;
            if (!c.match(nonhex)) {
                c = c.pad("0", 6);
                res.write("#");
            }
        }
        res.write(c);
        return;
    };

    /**
     * Returns a color definition.
     * @param {String} c The color definintion
     * @returns The rendered color definition
     * @type String
     * @see #color
     * @deprecated
     */
    helma.Html.prototype.colorAsString = function(c) {
        res.push();
        this.color(c);
        return res.pop();
    };

    /**
     * Renders an x/html opening form tag
     * @param {Object} attr An object containing the tag attributes
     */
    helma.Html.prototype.form = function(attr) {
        this.openTag("form", attr);
        return;
    };

    /**
     * Returns an x/html opening form tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered opening form tag
     * @type String
     * @see #form
     */
    helma.Html.prototype.formAsString = function(attr) {
        res.push();
        this.form(attr);
        return res.pop();
    };

    /**
     * Renders an x/html password input tag
     * @param {Object} attr An object containing the tag attributes
     */
    helma.Html.prototype.password = function(attr) {
        if (!attr) {
            res.write("[Html.password: insufficient arguments]");
            return;
        }
        attr.type = "password";
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html password input tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered password input tag
     * @type String
     * @see #password
     */
    helma.Html.prototype.passwordAsString = function(attr) {
        res.push();
        this.password(attr);
        return res.pop();
    };

    /**
     * Renders an x/html file input tag
     * @param {Object} attr An object containing the tag attributes
     */
    helma.Html.prototype.file = function(attr) {
        if (!attr) {
            res.write("[Html.file: insufficient arguments]");
            return;
        }
        attr.type = "file";
        this.tag("input", attr);
        return;
    };

    /**
     * Returns a rendered x/html file input tag
     * @param {Object} attr An object containing the tag attributes
     * @returns The rendered file input tag
     * @type String
     * @see #file
     */
    helma.Html.prototype.fileAsString = function(attr) {
        res.push();
        this.file(attr);
        return res.pop();
    };

    /**
     * Parses the string passed as argument and converts any
     * URL in it into a link tag
     * @param {String} str The string wherein URLs should be
     * converted into link tags
     * @returns The string containing URLs converted into link tags
     * @type String
     */
    helma.Html.prototype.activateUrls = function(str) {
        var re = /(^|\/>|\s+)([fhtpsr]+:\/\/[^\s]+?)([\.,;:\)\]\"]?)(?=[\s<]|$)/gim;
        var func = function(str, p1, p2, p3) {
            res.push();
            res.write(p1);
            res.write('<a href="');
            res.write(p2);
            res.write('">');
            res.write(p2.clip(50, "...", true));
            res.write('</a>');
            res.write(p3);
            return res.pop();
        };
        return str.replace(re, func);
    };

    /**
     * Creates a new TableWriter instance
     * @class This class provides various methods for
     * programmatically creating an x/html table.
     * @param {Number} numberOfColumns The number of columns in the table
     * @param {Object} attr An object containing attributes to use when
     * rendering the single table elements. For a description see {@link #table}.
     * @returns An instance of TableWriter
     * @constructor
     */
    helma.Html.TableWriter = function(numberOfColumns, attr) {
        if (isNaN(numberOfColumns))
            throw "Illegal argument in TableWriter(): first argument must be a number";
        if (numberOfColumns < 1)
            throw "Illegal argument in TableWriter(): first argument must be > 1";
        /** @private */
        this.ncols = numberOfColumns;
        /** @private */
        this.written = 0;
        // if no attributes object given, create an empty one
        if (!attr)
            attr = {};
        if (!attr.trEven) attr.trEven = attr.tr;
        if (!attr.trOdd)  attr.trOdd = attr.tr;
        if (!attr.trHead) attr.trHead = attr.trEven;
        if (!attr.tdEven) attr.tdEven = attr.td;
        if (!attr.tdOdd)  attr.tdOdd = attr.td;
        if (!attr.thEven) attr.thEven = attr.th;
        if (!attr.thOdd)  attr.thOdd = attr.th;
        /** @private */
        this.attr = attr;

        /**
         * If set to true the first row of the table data is rendered
         * using <code>&lt;th&gt;</code> tags (defaults to false).
         * @type Boolean
         */
        this.writeHeader = false;

        /**
         * If set to true the TableWriter returns the rendered table
         * as string, otherwise the table is written directly to response,
         * which is the default.
         * @type Boolean
         */
        this.writeString = false;

        this.dontEnum("ncols", "written", "attr", "writeHeader", "writeString");

        return this;
    };

    /** @ignore */
    helma.Html.TableWriter.prototype.toString = function() {
        return "[helma.Html.TableWriter]";
    }

    /**
     * Writes a single table cell to response.
     * @param {String} text The content of the table cess
     * @param {Object} attr An optional object containig attributes
     * to render for this table cell
     */
    helma.Html.TableWriter.prototype.write = function(text, attr) {
        // set up some variables
        var isHeaderRow = (this.writeHeader && this.written < this.ncols);
        var isNewRow = (this.written % this.ncols == 0);
        var isEvenRow = ((this.written / this.ncols) % 2 == 0);
        var isEvenCol = ((this.written % this.ncols) % 2 == 0);
        // write out table and table row tags
        if (this.written == 0) {
            if (this.writeString)
                res.push();
            helma.Html.prototype.openTag.call(this, "table", this.attr.table);
            helma.Html.prototype.openTag.call(this, "tr", this.attr.trHead);
        } else if (isNewRow) {
            helma.Html.prototype.closeTag.call(this, "tr");
            if (isEvenRow)
                 helma.Html.prototype.openTag.call(this, "tr", this.attr.trEven);
            else
                 helma.Html.prototype.openTag.call(this, "tr", this.attr.trOdd);
        }
        // get the attribute object for the table cell
        if (!attr) {
            // no explicit attribute given
            if (isEvenCol) {
                attr = isHeaderRow ? this.attr.thEven : this.attr.tdEven;
            } else {
                attr = isHeaderRow ? this.attr.thOdd : this.attr.tdOdd;
            }
        }
        // write out table cell tag
        helma.Html.prototype.openTag.call(this, isHeaderRow ? "th" : "td", attr);
        // write out table cell contents
        if (text) {
            res.write(text);
        }
        // close table cell
        helma.Html.prototype.closeTag.call(this, isHeaderRow ? "th" : "td");
        if (attr && !isNaN(attr.colspan)) {
            this.written += attr.colspan;
        } else {
            this.written += 1;
        }
        return;
    };

    /**
     * Closes all open table tags. If {@link #writeString} is set to
     * true, this method returns the rendered table.
     * @returns The rendered table, if {@link #writeString} is set to
     * true, otherwise void.
     * @type String
     */
    helma.Html.TableWriter.prototype.close = function() {
        if (this.written > 0) {
            while (this.written++ % this.ncols != 0)
                res.write("<td></td>");
            res.write("</tr></table>");
            this.written = 0;
        }
        if (this.writeString)
            return res.pop();
        return;
    };

    helma.lib = "Html";
    helma.dontEnum(helma.lib);
    for (var i in helma[helma.lib])
        helma[helma.lib].dontEnum(i);
    for (var i in helma[helma.lib].prototype)
        helma[helma.lib].prototype.dontEnum(i);
    for (var i in helma[helma.lib].TableWriter.prototype)
        helma[helma.lib].TableWriter.prototype.dontEnum(i);
    delete helma.lib;