//**********************************************************************************************************************
// DMTF - Distributed Management Task Force, Inc. - http://www.dmtf.org
// See dsp2023_readme.txt for copyright information.
//
// tocgen.js - part of the DMTF DSP2023 zip archive.
//
// Javascript file to generate the tables of contents, figures, and tables into the MRP HTML file generated by DSP8029.
//
// Javascript level: ECMAScript Edition 3 / JavaScript 1.5
// HTML DOM level: W3C HTML DOM 1
//**********************************************************************************************************************

/*
 * This function needs to be invoked after the HTML file has been loaded by the browser, for example
 * using the "onload" event of the HTML "body" element.
 * The logic in this function assumes that the HTML is represented as a DOM tree using proper HTML rules.
 * This means for example, that a nested "p" begin tag implicitly ends the outer "p" element and starts a
 * sibling "p" element.
 */
function body_load() {

    /*
     * Generate Table of Contents (TOC)
     * Assumed HTML structure (using [] as delimiters):
     *   [h1 class="Heading1" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h1 class="Heading1-NoNum" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h1 class="Heading1-NoTOC" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h2 class="Heading2" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h2 class="Heading2-NoNum" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h2 class="Heading2-NoTOC" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h3 class="Heading3" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h3 class="Heading3-NoNum" id="heading-id"]heading title text[/h1]
     *   ...
     *   [h3 class="Heading3-NoTOC" id="heading-id"]heading title text[/h1]
     *   ...
     * Treatment of "class" attribute values:
     *   class "HeadingN" increments and displays a heading number, and is generated into the TOC
     *   class "HeadingN-NoNum" does not increment or display a heading number, but is generated into the TOC
     *   class "HeadingN-NoTOC" does not increment or display a heading number, and is not generated into the TOC
     */

    var divToc = document.getElementById("div-toc"); // Element being updated to contain the TOC
    var tocListNode = document.createElement("dl");
        tocListNode.setAttribute("class","TOC");
    var elemList = document.body.getElementsByTagName("*"); // returns flat list of references to direct and indirect children in original DOM tree
    var elemNode;
    var h1Num = 0;
    var h2Num = 0;
    var h3Num = 0;
    var a1Num = 0;
    var a2Num = 0;
    var a3Num = 0;
    var elemNode_textContent = null;


    // Since heading elements of different levels are all siblings and direct children of the "body" element,
    // we walk through all elements in document order and test for their element name.

    var ACode = 0x41; // Unicode code point of "A", for annex numbering

    for (var i = 0; i < elemList.length; ++i) {
        elemNode = elemList[i];

        switch (elemNode.nodeName.toLowerCase()) {

        case "h1":

            elemNode_textContent = elemNode.firstChild.nodeValue;
			// Prince-XML does not support textContent, so we use nodeValue.

            if (elemNode_textContent != null && elemNode_textContent != "" && elemNode.className != "Heading1-NoTOC") {
                // The h1 element has a text specified, and the class does not preclude a TOC entry.
                // We have to test both null and empty string, since the JavaScript engines in different
                // tools return this differently.

                var tocEntryNode = document.createElement("dd");
                tocEntryNode.setAttribute("class","TOC-Entry");
                var tocEntryText = "";

                if (elemNode.className == "Heading1" || elemNode.className == "") {
                    ++h1Num;
                    h2Num = 0;
                    h3Num = 0;
                    tocEntryText += h1Num+"\u00a0\u00a0\u00a0";
                }
                else if (elemNode.className == "Annex1") {
                    ++a1Num;
                    a2Num = 0;
                    a3Num = 0;
                    tocEntryText += "ANNEX\u00a0"+String.fromCharCode(ACode+a1Num-1)+"\u00a0\u00a0\u00a0";
                }

                var linkNode = document.createElement("a");
                linkNode.setAttribute("class","TocRef");
                linkNode.setAttribute("href","#"+elemNode.id);

                // Todo: The textContent attribute used in the following statement removes any
                // contained HTML without replacing it with whitespace. This is not ideal, since it
                // causes informational Annex titles to appear as e.g. "(informational)Change log",
                // i.e. without any intervening space between "(informational)" and "Change log".
                // This should be changed to some better approach that retains a space.
                tocEntryText += elemNode_textContent;
                linkNode.appendChild(document.createTextNode(tocEntryText));

                tocEntryNode.appendChild(linkNode);
                tocListNode.appendChild(tocEntryNode);
            }
            break;

        case "h2":

            elemNode_textContent = elemNode.firstChild.nodeValue;
			// Prince-XML does not support textContent, so we use nodeValue.

            if (elemNode_textContent != null && elemNode_textContent != "" && elemNode.className != "Heading2-NoTOC") {
                // The h2 element has a text specified, and the class does not preclude a TOC entry.
                // We have to test both null and empty string, since the JavaScript engines in different
                // tools return this differently.

                var tocEntryNode = document.createElement("dd");
                tocEntryNode.setAttribute("class","TOC-Entry");
                var tocEntryText = "";

                if (elemNode.className == "Heading2" || elemNode.className == "") {
                    ++h2Num;
                    h3Num = 0;
                    tocEntryText += h1Num+"."+h2Num+"\u00a0\u00a0\u00a0";
                }
                else if (elemNode.className == "Annex2") {
                    ++a2Num;
                    a3Num = 0;
                    tocEntryText += "ANNEX\u00a0"+String.fromCharCode(ACode+a1Num-1)+"."+a2Num+"\u00a0\u00a0\u00a0";
                }

                var linkNode = document.createElement("a");
                linkNode.setAttribute("class","TocRef");
                linkNode.setAttribute("href","#"+elemNode.id);

                tocEntryText += elemNode_textContent;
                linkNode.appendChild(document.createTextNode(tocEntryText));

                var h2ddNode = document.createElement("dd");
                h2ddNode.setAttribute("class","TOC-Entry");
                h2ddNode.appendChild(linkNode);

                var h2dlNode = document.createElement("dl");
                h2dlNode.setAttribute("class","TOC-Entry");
                h2dlNode.appendChild(h2ddNode);

                tocEntryNode.appendChild(h2dlNode);
                tocListNode.appendChild(tocEntryNode);
            }
            break;

        case "h3":

            elemNode_textContent = elemNode.firstChild.nodeValue;
			// Prince-XML does not support textContent, so we use nodeValue.

            if (elemNode_textContent != null && elemNode_textContent != "" && elemNode.className != "Heading3-NoTOC") {
                // The h3 element has a text specified, and the class does not preclude a TOC entry.
                // We have to test both null and empty string, since the JavaScript engines in different
                // tools return this differently.

                var tocEntryNode = document.createElement("dd");
                tocEntryNode.setAttribute("class","TOC-Entry");
                var tocEntryText = "";

                if (elemNode.className == "Heading3" || elemNode.className == "") {
                    ++h3Num;
                    tocEntryText += h1Num+"."+h2Num+"."+h3Num+"\u00a0\u00a0\u00a0";
                }
                else if (elemNode.className == "Annex3") {
                    ++a3Num;
                    tocEntryText += "ANNEX\u00a0"+String.fromCharCode(ACode+a1Num-1)+"."+a2Num+"."+a3Num+"\u00a0\u00a0\u00a0";
                }

                var linkNode = document.createElement("a");
                linkNode.setAttribute("class","TocRef");
                linkNode.setAttribute("href","#"+elemNode.id);

                tocEntryText += elemNode_textContent;
                linkNode.appendChild(document.createTextNode(tocEntryText));

                var h3ddNode = document.createElement("dd");
                h3ddNode.setAttribute("class","TOC-Entry");
                h3ddNode.appendChild(linkNode);

                var h3dlNode = document.createElement("dl");
                h3dlNode.setAttribute("class","TOC-Entry");
                h3dlNode.appendChild(h3ddNode);

                var h2ddNode = document.createElement("dd");
                h2ddNode.setAttribute("class","TOC-Entry");
                h2ddNode.appendChild(h3dlNode);

                var h2dlNode = document.createElement("dl");
                h2dlNode.setAttribute("class","TOC-Entry");
                h2dlNode.appendChild(h2ddNode);

                tocEntryNode.appendChild(h2dlNode);
                tocListNode.appendChild(tocEntryNode);
            }
            break;
        }
    }

    var tocTextNode = divToc.firstChild; // the original text node with dummy text
    divToc.replaceChild(tocListNode,tocTextNode);


    /*
     * Generate Table of Figures (TOF)
     * Assumed HTML structure (using [] as delimiters):
     *   [div class="Figure" id="figure-id"]
     *     [img ...]
     *     [p class="Figure-Caption"]figure caption text[/p]
     *   [/div]
     * The two class attributes are mandatory in MRP profiles.
     * The "img" element typically will have no end tag, since the format of the MRP HTML is true HTML.
     *
     * Note on previous design where the outer element was a "p" element instead of the "div":
     * The inner "p" element is typically represented in the DOM tree as a sibling of the outer "p" element,
     * since its begin tag implicitly ends the outer "p" element (in true HTML). This is the case for
     * example when creating the HTML using Xalan. However, some browsers, when creating the HTML dynamically,
     * represent the inner "p" element as a child node of the outer "p" element.
     */
    var divTof = document.getElementById("div-tof");   // element being updated to contain the TOF
    var tofListNode = document.createElement("dl");    // top-level element of the tree with generated TOF
    var divAllNodeList = document.body.getElementsByTagName("div"); // returns flat list of references to direct and indirect children in original DOM tree
    var fNum = 0;                                      // current figure number

    for (var i = 0; i < divAllNodeList.length; ++i) {
        if (divAllNodeList[i].className == "Figure") {
            var divFigureNode = divAllNodeList[i];
            var captionText = "(undefined)";
            var id = divFigureNode.id;

            if (id != null && id != "") {
                // We have to test both null and empty string, since the JavaScript engines in different
				// tools return this differently.
				 
                var pCaptionNode = null;
                var node;
                
				// Locate the next child that is a "p" element with class "Figure-Caption"
                var childNodeList = divFigureNode.childNodes;
                for (var j = 0; j < childNodeList.length; ++j) {
                    node = childNodeList[j];
                    if (node.nodeName.toLowerCase() == "p" && node.className == "Figure-Caption") {
                        pCaptionNode = node;
                        break;
                    }
                }
                if (pCaptionNode != null) {
                    captionText = pCaptionNode.firstChild.nodeValue;
                    if (captionText == null) {
                        captionText = "(undefined)";
                        error_msg( divFigureNode, "Profile Error: No caption text specified on 'p' element for figure caption");
                    }
                }
                else {
                    error_msg( divFigureNode, "Profile Error: No 'p' element with class 'Figure-Caption' specified for 'div' element with class 'Figure'");
                }
            } else {
                error_msg( divFigureNode, "Profile Error: No 'id' attribute specified on 'div' element with class 'Figure'");
            }

            var tofEntryNode = document.createElement("dd");
            tofEntryNode.setAttribute("class","TOC-Entry");
            var tofEntryText = "";

            ++fNum;
            tofEntryText += "Figure\u00a0"+fNum+"\u00a0\u2013\u00a0";

            var linkNode = document.createElement("a");
            linkNode.setAttribute("class","TocRef");
            linkNode.setAttribute("href","#"+id);

            tofEntryText += captionText;
            linkNode.appendChild(document.createTextNode(tofEntryText));

            tofEntryNode.appendChild(linkNode);
            tofListNode.appendChild(tofEntryNode);
        }
    }

    var tofTextNode = divTof.firstChild;       // the original text node with dummy text
    divTof.replaceChild(tofListNode,tofTextNode);


    /* Generate Table of Tables (TOT)
     * Tag structure (using [] as delimiters):
     *   [table class="Table" id="table-id"]
     *     [caption class="Table-Caption"]table caption text[/caption]
     *     ...
     *   [/table]
     * The "class" attribute on the "table" and "caption" elements is optional in MRP profiles.
     */

    var divTot = document.getElementById("div-tot");   // element being updated to contain the TOT
    var totListNode = document.createElement("dl");    // top-level element of the tree with generated TOT
    var tableAllNodeList = document.body.getElementsByTagName("table"); // returns flat list of references to direct and indirect children in original DOM tree
    var tNum = 0;                                      // current table number

    for (var i = 0; i < tableAllNodeList.length; ++i) {
        var tableNode = tableAllNodeList[i];
        var id = tableNode.id;

        // Since the "class" attribute on the "table" element is optional,
        // we go by the presence of the "id" attribute and ignore all other
        // "table" elements (without notice). Note that things like the Work
        // in Progress notice are also HTML tables.
        if (id != null && id != "") {
            // We have to test both null and empty string, since the JavaScript engines in different
            // tools return this differently.

            var captionText = null;

            for (var j = 0; j < tableNode.childNodes.length; ++j) {
                var captionNode = tableNode.childNodes[j];
                if (captionNode.nodeName.toLowerCase() == "caption") {
                    captionText = captionNode.firstChild.nodeValue;
                    break;
                }
            }
            if (captionText == null) {
                captionText = "(undefined)";
                error_msg( tableNode, "Profile Error: No 'caption' child element for table caption specified on 'table' element");
            }

            var totEntryNode = document.createElement("dd");
            totEntryNode.setAttribute("class","TOC-Entry");
            var totEntryText = "";

            ++tNum;
            totEntryText += "Table\u00a0"+tNum+"\u00a0\u2013\u00a0";

            var linkNode = document.createElement("a");
            linkNode.setAttribute("class","TocRef");
            linkNode.setAttribute("href","#"+id);

            totEntryText += captionText;
            linkNode.appendChild(document.createTextNode(totEntryText));

            totEntryNode.appendChild(linkNode);
            totListNode.appendChild(totEntryNode);
        }
    }

    var totTextNode = divTot.firstChild;       // the original text node with dummy text
    divTot.replaceChild(totListNode,totTextNode);


    /* Generate summary of errors
     * Tag structure (using [] as delimiters):
     *   [span class="ErrorMsg"]error message text[/span]
     */
    var pAllNodeList = document.body.getElementsByTagName("span"); // returns flat list of references to direct and indirect children in original DOM tree
    var numErrors = 0;
    for (var i = 0; i < pAllNodeList.length; ++i) {
        if (pAllNodeList[i].className == "ErrorMsg") {
            ++numErrors;
        }
    }
    if (numErrors > 0) {
        var divErr = document.getElementById("div-err");       // element being updated to contain the error summary
        error_msg( divErr, "Profile Error: This profile contains " + numErrors + " errors (search for 'Error:')");
    }


    /* Todo: Modify links to subclauses to display heading number (instead of heading text)
     * Tag structure (using [] as delimiters):
     * Headings that are the targets of the links:
     *   [h3 class="Heading3" id="heading-id"]heading title text[/h3]
     * The Where
     */

}

/*
 * This function emits an error message using the same CSS style DSP8029 uses for error messages.
 * It attaches the error message as a "span" child element of a target element node.
 */
function error_msg (targetNode, msgText) {

    var pNode = document.createElement("span");
    pNode.setAttribute("class","ErrorMsg");

    pNode.appendChild(document.createTextNode(msgText));

    targetNode.appendChild(pNode);
}
