/* 
 * File: domutils.js
 *   Miscellany functions to operate on DOM trees.
 * 
 * *Path:* xmlutils/domutils.js
 */
 
ML.importMod("xmlutils/xmlutils.js");

/*
 * Class: Node
 *
 * Group: Methods
 *
 * Method: toString
 *   Convert node to textual fortm.
 * 
 * Parameters:
 *   type - if equal "html" this method will return content in html
 *     format, if equal "text" only text is gathered, in *null* normal
 *     content is returned.
 *   normalize - if set to *true* this will return normalized text (with merged
 *     all adjacent whitespaces, and removed whitespaces from begining end ending).
 * 
 * Returns:
 *   Node converted to string.
 */
Document.prototype.toString =
Element.prototype.toString =
Text.prototype.toString =
Node.prototype.toString = function(type, normalize)
{
    if (type == "text") {
        if (normalize)
            return normalizeWS(extractText(this));
       return extractText(this);
    } 
    if (type == "html")
        return extractHtml(this);

    return extract(this);
}

function extractText(dom)
{
    var i, ret;

    if (dom.nodeType == dom.ELEMENT_NODE) {
        ret = "";
        for (i = 0; i < dom.childNodes.length; i++)
            ret += extractText(dom.childNodes[i]);
        return ret;
    } else
        return dom.nodeValue;
}

function extract(dom)
{
    if (dom.nodeType == dom.ELEMENT_NODE) {
        var ret, i;

        if (dom.stanzaType == 2)
            return "</" + dom.nodeName + ">";

        ret = "<" + dom.nodeName;

        for (i = 0; i < dom.attributes.length; i++) {
            ret += " " + dom.attributes[i].nodeName +
                "='" + encodeEntity(dom.attributes[i].nodeValue) + "'";
        }
        
        if (dom.hasChildNodes()) {
            ret += ">";

            for (i = 0; i < dom.childNodes.length; i++)
                ret += extract(dom.childNodes[i]);

            if (dom.stanzaType != 1)
                ret += "</" + dom.nodeName + ">";
        } else if (dom.stanzaType == 1)
            ret += ">";
        else
            ret += "/>";
        return ret;
    } else if (dom.nodeType == dom.DOCUMENT_NODE) {
        ret = "";
        for (i = 0; i < dom.childNodes.length; i++)
            ret += extract(dom.childNodes[i]);
        return ret;
    } else
        return encodeEntity(dom.nodeValue);
}

function extractHtml(dom)
{
    if (dom.nodeType == dom.ELEMENT_NODE) {
        var ret, i;

        if (dom.stanzaType == 2)
            return "<span class='tag'><span class='tag-end'>&lt;/<span class='tag-name'>"+
		dom.nodeName+"</span>&gt;</span></span>";

        ret = "<span class='tag'><span class='tag-start'>&lt;<span class='tag-name'>"+
    	    dom.nodeName+"</span>";

        for (i = 0; i < dom.attributes.length; i++) {
            ret += " <span class='attrib-name'>"+ dom.attributes[i].nodeName +
                "</span>=<span class='attrib-value'>'" +
                encodeEntity(dom.attributes[i].nodeValue) +
                "'</span>";
        }
        
        if (dom.hasChildNodes()) {
            ret += "&gt;</span>";

            for (i = 0; i < dom.childNodes.length; i++)
                ret += extractHtml(dom.childNodes[i]);

            if (dom.stanzaType != 1)
                ret += "<span class='tag-end'>&lt;/<span class='tag-name'>"+
        		    dom.nodeName+"</span>&gt;</span></span>";
        } else if (dom.stanzaType == 1)
            ret += "&gt;</span></span>";
        else
            ret += "/&gt;</span></span>";
        return ret;
    } else if (dom.nodeType == dom.DOCUMENT_NODE) {
        ret = "";
        for (i = 0; i < dom.childNodes.length; i++)
            ret += extractHtml(dom.childNodes[i]);
        return ret;
    } else
        return "<span class='text'>"+encodeEntity(dom.nodeValue)+"</span>";
}

/*
 * Group: Properties
 *
 * Variable: firstChildElement
 *   First element from node child nodes list. This property can be *null*
 *   if this isn't Element or when this Element doesn't have child nodes, or
 *   it doesn't have child Element nodes.
 */
Node.prototype.firstChildElement getter = function()
{
    var i;

    for (i = 0; i < this.childNodes.length; i++)
        if (this.childNodes[i].nodeType == this.ELEMENT_NODE)
            return this.childNodes[i];
    return null;
}

/*
 * Variable: lastChildElement
 *   Last element from node child nodes list. This property can be *null*
 *   if this isn't Element or when this Element doesn't have child nodes, or
 *   it doesn't have child Element nodes.
 */
Node.prototype.lastChildElement getter = function()
{
    var i;

    for (i = this.childNodes.length - 1; i >= 0; i--)
        if (this.childNodes[i].nodeType == this.ELEMENT_NODE)
            return this.childNodes[i];
    return null;
}

/*
 * Variable: nextSiblingElement
 *   Next element from sibling list. This property can be *null*
 *   if this node isn't Element or when this Element doesn't have siblings, or
 *   it doesn't have any Element nodes sibling.
 */
Node.prototype.nextSiblingElement getter = function()
{
    if (this.parentNode && this.parentNode.__locate) {
        var i = this.parentNode.__locate(this)+1;

        for (; i < this.parentNode.childNodes.length; i++)
            if (this.parentNode.childNodes[i].nodeType == this.ELEMENT_NODE)
                return this.parentNode.childNodes[i];
    }
    return null;
}

/*
 * Variable: previousSiblingElement
 *   Previous element from sibling list. This property can be *null*
 *   if this node isn't Element or when this Element doesn't have siblings, or
 *   it doesn't have any Element nodes sibling.
 */
Node.prototype.previousSiblingElement getter = function()
{
    if (this.parentNode && this.parentNode.__locate) {
        var i = this.parentNode.__locate(this)-1;

        for (; i >= 0; i--)
            if (this.parentNode.childNodes[i].nodeType == this.ELEMENT_NODE)
                return this.parentNode.childNodes[i];
    }
    return null;
}


