//
//  nodes.js
//  Written by Stefan Habel
//  Version 2.7
//  Last modified: 13.01.2007
//


//!
//! Removes all child nodes from the given parent node.
//!
//! \param node     the node from which to remove all child nodes
//!
window.RemoveAllChildren = function ( node )
{
    if (!node) {
        alert("Could not remove child nodes from undefined root node.");
        return;
    }
    if (node.nodeType == undefined)
        node = document.getElementById(node);
    if (node == null || node.nodeType == undefined) {
        alert("Could not remove child nodes from root node " + node + ".");
        return;
    }

    if (node.innerHTML != undefined)
        node.innerHTML = "";
    else
        while (node && node.firstChild)
            node.removeChild(node.firstChild);
}


//!
//! Replaces HTML entities in the given text by their corresponding ASCII character.
//!
//! \param text     the text to replace HTML entities in
//! \return The modified text.
//!
window.ReplaceHtmlEntities = function ( text )
{
    var entityTranslationCharcode = [
        ["amp",     38],
        ["nbsp",   160],
        ["laquo",  171],
        ["micro",  181],
        ["raquo",  187],
        ["eacute", 233]
    ];
    var entityTranslationString = [
        ["quot",   '"'],
        ["#64",    "@"],
        ["ndash",  "–"],
        ["hellip", "…"],
        ["lt",     "<"],
        ["gt",     ">"],
        ["Auml",   "Ä"],
        ["Ouml",   "Ö"],
        ["Uuml",   "Ü"],
        ["auml",   "ä"],
        ["ouml",   "ö"],
        ["uuml",   "ü"],
        ["szlig",  "ß"]
    ];
    for (var i = 0; i < entityTranslationCharcode.length; ++i) {
        var translation = entityTranslationCharcode[i];
        var entity   = translation[0];
        var charcode = translation[1];
        text = text.replace(eval('/&' + entity + ';/g'), String.fromCharCode(charcode));
    }
    for (var i = 0; i < entityTranslationString.length; ++i) {
        var translation = entityTranslationString[i];
        var entity = translation[0];
        var string = translation[1];
        text = text.replace(eval('/&' + entity + ';/g'), string);
    }
    text = text.replace(/&#([0-9]+);/g, String.fromCharCode(RegExp.$1));
    return text;
}


//!
//! Creates attributes for the given node corresponding to the given attribute string (in HTML format).
//!
//! \param node         the node that is to be decorated with the given HTML attributes
//! \param attributes   the HTML attributes to decorate the node with
//!
window.CreateAttributes = function ( node, attributes )
{
    var a = attributes;

    // parse attributes
    //
    var iterations = 0;
    while (a && ++iterations < 100) {
        // remove spaces at beginning
        while (a && a.charAt(0) == ' ')
            a = a.substr(1, a.length - 1);

        if (a) {
            // find first "=" character
            var i = 0;
            while (i < a.length && a.charAt(i) != '=' && a.charAt(i) != '/' && a.charAt(i) != ' ')
                ++i;

            var name = a.substring(0, i);
            if (a.charAt(i) == ' ' || i == a.length) {
                alert('ERROR: No value found after attribute "' + name + '".');
                return;
            }

            if (a.charAt(i) == '=') {
                // find first quote character
                var n = i + 1;
                while (n < a.length && a.charAt(n) != '"' && a.charAt(n) != ' ')
                    ++n;

                if (a.charAt(n) == '"') {
                    var t = n + 1;
                    while (t < a.length && (a.charAt(t) != '"' || a.charAt(t - 1) == '\\') )
                        ++t;

//                    if (name != 'style') {
                        var value = a.substring(n + 1, t);
                        // create attribute for given node
                        var attrib = document.createAttribute(name);
//                        attrib.nodeValue = ReplaceHtmlEntities(value);
                        attrib.nodeValue = value;
                        node.setAttributeNode(attrib);
//                    }

                    a = a.substring(t + 1, a.length);
                }
            }

        } // if (a)

    } // while [iterations]

    if (iterations == 100)
        console.error("[CreateAttributes] Too many iterations. Check attributes in HTML code: " + attributes);
}


//!
//! Returns whether content is expected to follow the given start tag.
//!
//! \param tag  the tag to be checked
//! \return True, if the given tag is expected to have content following, otherwise False.
//!
window.IsContentExpectedForTag = function ( tag )
{
    // a list of tags that do not have any content
    var tagsWithoutContent = [
        'br', 'hr', 'img'
    ];
    var result = true;
    var i = 0;
    while (result && i < tagsWithoutContent.length)
        result = tagsWithoutContent[i++] != tag;
    return result;
}


//!
//! Recursively appends child nodes to the given node corresponding to the given HTML source code.
//!
//! \param node     the node to append child nodes to
//! \param html     the HTML code from which to create child nodes
//! \param depth    the depth in the node hierarchy, starting at a root node with a value of 0
//!
window.CreateHtmlNodes = function ( node, html, depth )
{
    if (!node) {
        console.error("[CreateHtmlNodes] Could not create HTML nodes for undefined root node.");
        return;
    }
    if (node.nodeType == undefined)
        node = document.getElementById(node);
    if (node == null || node.nodeType == undefined) {
        console.error("[CreateHtmlNodes] Could not create HTML nodes for root node " + node + ".");
        return;
    }

    if (node.innerHTML != undefined) {
        node.innerHTML = html;
        return;
    }

    var debug = '';
    var iterations = 0;

    if (depth == undefined)
        depth = 0;

    if (depth == 0)
        // replace HTML entities with special characters
        html = ReplaceHtmlEntities(html);

    while (html && ++iterations < 100) {

        var n = html.indexOf('<');

        if (n != 0) {
            var text = n == -1
                     ? html
                     : html.substr(0, n);

//            // replace HTML entities with special characters
//            text = ReplaceHtmlEntities(text);
      
            // add plain text child node to given node
            var textNode = document.createTextNode(text);
            node.appendChild(textNode);
      
            var beforeTag = n == -1
                          ? ''
                          : ' before tag';
            html = n == -1
                 ? ''
                 : html.substr(n, html.length - n);
        }

        if (n > -1) {

            // retrieve tag name
            var t = 1;
            while (t < html.length && html.charAt(t) != " " && html.charAt(t) != "/" && html.charAt(t) != ">")
                ++t;
            var tag = html.substr(1, t - 1);
//            console.info("[CreateHtmlNodes] Tag of type \"" + tag + "\" found.");

            // create new element for tag
            var element = document.createElement(tag);

            // retrieve attributes
            while (t < html.length && html.charAt(t) != ">")
                ++t;
            var start = 1 + tag.length + 1;
            var tagLength = t - start + 1;
            var attributesLength = tagLength - 1;
            while (html.charAt(start + attributesLength - 1) == " " || html.charAt(start + attributesLength - 1) == "/")
                --attributesLength;
            var attributes = html.substr(start, attributesLength);
//            console.info("[CreateHtmlNodes] Attributes: \"" + attributes + "\"");
            if (attributes)
                CreateAttributes(element, attributes);

            // check if tag has contents
            if (IsContentExpectedForTag(tag)) {

                // retrieve contents of tag element
                var tstart = t + 1;

                var index = tstart;
                var tagDepth = 0;
                var endTagFound = false;
                var testString = '';

                while (index < html.length && !endTagFound) {
                    testString = '<' + tag.toLowerCase() + ' ';
                    if (html.substr(index, testString.length) == testString)
                        // another opening tag of the same type found
                        ++tagDepth;

                    testString = '<' + tag.toLowerCase() + '>';
                    if (html.substr(index, testString.length) == testString)
                        // another opening tag of the same type found
                        ++tagDepth;

                    testString = '</' + tag.toLowerCase() + '>';
                    if (html.substr(index, testString.length) == testString) {
                        // closing tag of the type found
                        --tagDepth;
                        if (tagDepth < 0)
                            endTagFound = true;
                    }
                    ++index;
                }

                if (!endTagFound) {
                    console.error("[CreateHtmlNodes] End tag not found. Check HTML code: " + html);
                    return;
                }

                var tend = index - 1;

                var contents = html.substr(tstart, tend - tstart);

                if (contents)
                    // recursively create HTML nodes for tag contents
                    CreateHtmlNodes(element, contents, depth + 1);

                html = html.substring(tend + tag.length + 3, html.length);

            } else {

                //> tag w/o contents
                html = html.substring(tag.length + 2 + tagLength, html.length);

            }

            // append new tag element to target node
            node.appendChild(element);

        } // if (n > -1)

    } // while

    if (iterations == 100)
        console.error("[CreateHtmlNodes] Too many iterations. Check tags in HTML code: " + html);
}
