//**********************************************************************************************************************
// DMTF - Distributed Management Task Force, Inc. - http://www.dmtf.org
// See the readme file in the DSP2023 ZIP archive for copyright information.
// This file is part of the DSP2023 ZIP archive and has been last changed in DSP2023 Version 1.1.0.
//
// tocgen.js - Javascript file to generate certain elements in an MRP HTML file.
//
// Javascript level: ECMAScript Edition 3 / JavaScript 1.5
// HTML DOM level: W3C HTML DOM 1
//
// This file allows switching the use of short hashed link targets on or off. Search for "this.enabled" to see the
// current setting.
//
// Change history: See readme file of DSP2023.
//
// Last Updated: 2014-01-23
//
//**********************************************************************************************************************


/*
 * 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() {


    var divMap = document.getElementById("div-map");// Element being updated to contain the reference map
    var mapNode = document.createElement("div");    // Its only child element, and the root for any reference map
                                                    // entries

    var idAutoNum = 0;                              // Initial value for auto-generated 'id' attribute values. 
    var idAutoPrefix = "HG-Auto-";                  // Prefix string for auto-generated heading id values.

    var idHashDict = new HashDict();                // Hash dictionary for id values ('id', 'name', 'href' attributes).

    var idMapPrefix = "MAP_";                       // Prefix string for id values in the reference map.


    /*
     * Generate Table of Contents (TOC) and TOC mapping table.
     *
     * HTML structure that is searched for (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
     *
     * Generated HTML structure for the TOC (using [] as delimiters):
     *   [div id="div-toc"]     This element already exists and serves as target for the generated child elements.
     *     [dl class="TOC"]
     *       [dd class="TOC-Entry"][a href="#HG-Foreword" class="TocRef"]Foreword[/a][/dd]
     *       [dd class="TOC-Entry"][a href="#HG-Introduction" class="TocRef"]Introduction[/a][/dd]
     *       [dd class="TOC-Entry"][a href="#HG-Scope" class="TocRef"]1   Scope[/a][/dd]
     *       . . . Higher level headings are nested using [dl] elements.
     *     [/dl]
     *
     * Generated HTML structure for the reference mapping table (using [] as delimiters, and assuming that
     * idMapPrefix = "MAP_"):
     *   [div id="div-map"]   This element already exists and serves as target for the generated child elements.
     *     [div id="MAP_HG-Scope" linktext="Clause 1" title="Scope"/]
     *     [div id="MAP_HG-References" linktext="Clause 2" title="Normative references"/]
     *     [div id="MAP_HG-Terms" linktext="Clause 3" title="Terms and definitions"/]
     *     [div id="MAP_HG-Terms-General" linktext="3.1" title="General"/]
     *     . . .  This is a flat sequence without nesting for higher level headings.
     *   [/div]
     *   Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
     */

    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 h4Num = 0;
    var h5Num = 0;
    var h6Num = 0;
    var a1Num = 0;
    var a2Num = 0;
    var a3Num = 0;
    
    // 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":

            var hname = "h1";
            var elemClass = elemNode.className;

            var headingText = childrenText(elemNode);
            if (headingText == "") {
                error_msg( elemNode, "Profile Error: No heading text specified on '"+hname+"' element");
            }

            // Auto-generate missing 'id' attribute
            if (elemNode.id == null || elemNode.id == "") {
                // We have to test both null and empty string, because different JavaScript engines return this
                // differently.
                elemNode.id = idAutoPrefix + idAutoNum.toString();
                idAutoNum += 1;
            }

            // Change 'id' attribute value of Hx elements to a hash value.
            elemNode.id = idHashDict.hash(elemNode.id);

            var tocEntryText = "";
            var mapLinkText = "";
            if (elemClass == "Heading1-NoTOC" || elemClass == "Heading1-NoNum" || elemClass == "Heading1-Title") {
                // tocEntryText: For NoNum, no change. For NoTOC, not used.
                mapLinkText += headingText;
            }
            else if (elemClass == "Heading1" || elemClass == null || elemClass == "") {
                ++h1Num;
                h2Num = 0;
                h3Num = 0;
                h4Num = 0;
                h5Num = 0;
                h6Num = 0;
                tocEntryText += h1Num+"\u00a0\u00a0\u00a0";
                mapLinkText += "Clause "+h1Num;
            }
            else if (elemClass == "Annex1") {
                ++a1Num;
                a2Num = 0;
                a3Num = 0;
                tocEntryText += "ANNEX\u00a0"+String.fromCharCode(ACode+a1Num-1)+"\u00a0\u00a0\u00a0";
                mapLinkText += "Annex "+String.fromCharCode(ACode+a1Num-1);
            }
            else {
                error_msg( elemNode, "Profile Error: Unsupported value for 'class' attribute of '"+hname+
                                     "' element: "+elemClass);
            }

            if (elemNode.className != "Heading1-NoTOC" && elemClass != "Heading1-Title") {

                // Add the TOC entry

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

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

                var tocEntryNode = document.createElement("dd");
                tocEntryNode.setAttribute("class","TOC-Entry");
                tocEntryNode.appendChild(document.createTextNode("\u000a"));
                tocEntryNode.appendChild(linkNode);

                tocListNode.appendChild(document.createTextNode("\u000a"));
                tocListNode.appendChild(tocEntryNode);
            }

            // Add the reference map entry

            var mapEntryNode = document.createElement("div");
            mapEntryNode.setAttribute("id",idMapPrefix+elemNode.id);
            mapEntryNode.setAttribute("linktext",mapLinkText);
            // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
            mapEntryNode.setAttribute("title",headingText);

            mapNode.appendChild(document.createTextNode("\u000a"));
            mapNode.appendChild(mapEntryNode);

            break;

        case "h2":

            var hname = "h2";
            var elemClass = elemNode.className;

            var headingText = childrenText(elemNode);
            if (headingText == "" && (elemClass != "Term-RefNum" && elemClass != "Sym-RefNum")) {
                error_msg( elemNode, "Profile Error: No heading text specified on '"+hname+"' element");
            }

            // Auto-generate missing 'id' attribute
            if (elemNode.id == null || elemNode.id == "") {
                // We have to test both null and empty string, because different JavaScript engines return this
                // differently.
                elemNode.id = idAutoPrefix + idAutoNum.toString();
                idAutoNum += 1;
            }

            // Change 'id' attribute value of Hx elements to a hash value.
            elemNode.id = idHashDict.hash(elemNode.id);

            var tocEntryText = "";
            var mapLinkText = "";
            if (elemClass == "Heading2-NoTOC" || elemClass == "Heading2-NoNum") {
                // tocEntryText: For NoNum, no change. For NoTOC, not used.
                mapLinkText += headingText;
            }
            else if (elemClass == "Heading2" || elemClass == null || elemClass == "" ||
                     elemClass == "Term-RefNum" || elemClass == "Sym-RefNum") {
                ++h2Num;
                h3Num = 0;
                h4Num = 0;
                h5Num = 0;
                h6Num = 0;
                tocEntryText += h1Num+"."+h2Num+"\u00a0\u00a0\u00a0";
                mapLinkText += h1Num+"."+h2Num;
            }
            else if (elemClass == "Annex2") {
                ++a2Num;
                a3Num = 0;
                tocEntryText += "ANNEX\u00a0"+String.fromCharCode(ACode+a1Num-1)+"."+a2Num+"\u00a0\u00a0\u00a0";
                mapLinkText += "Annex "+String.fromCharCode(ACode+a1Num-1)+"."+a2Num;
            }
            else {
                error_msg( elemNode, "Profile Error: Unsupported value for 'class' attribute of '"+hname+
                                     "' element: "+elemClass);
            }

            if (elemClass != "Heading2-NoTOC" &&
                elemClass != "Term-RefNum" && elemClass != "Sym-RefNum") {

                // Add the TOC entry

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

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

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

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

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

                tocListNode.appendChild(document.createTextNode("\u000a"));
                tocListNode.appendChild(tocEntryNode);
            }

            if (elemClass != "Term-RefNum" && elemClass != "Sym-RefNum") {

                // Add the reference map entry

                var mapEntryNode = document.createElement("div");
                mapEntryNode.setAttribute("id",idMapPrefix+elemNode.id);
                mapEntryNode.setAttribute("linktext",mapLinkText);
                // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
                mapEntryNode.setAttribute("title",headingText);

                mapNode.appendChild(document.createTextNode("\u000a"));
                mapNode.appendChild(mapEntryNode);
            }

            break;

        case "h3":

            var hname = "h3";
            var elemClass = elemNode.className;

            var headingText = childrenText(elemNode);
            if (headingText == "") {
                error_msg( elemNode, "Profile Error: No heading text specified on '"+hname+"' element");
            }

            // Auto-generate missing 'id' attribute
            if (elemNode.id == null || elemNode.id == "") {
                // We have to test both null and empty string, because different JavaScript engines return this
                // differently.
                elemNode.id = idAutoPrefix + idAutoNum.toString();
                idAutoNum += 1;
            }

            // Change 'id' attribute value of Hx elements to a hash value.
            elemNode.id = idHashDict.hash(elemNode.id);

            var tocEntryText = "";
            var mapLinkText = "";
            if (elemClass == "Heading3-NoTOC" || elemClass == "Heading3-NoNum") {
                // tocEntryText: For NoNum, no change. For NoTOC, not used.
                mapLinkText += headingText;
            }
            else if (elemClass == "Heading3" || elemClass == null || elemClass == "") {
                ++h3Num;
                h4Num = 0;
                h5Num = 0;
                h6Num = 0;
                tocEntryText += h1Num+"."+h2Num+"."+h3Num+"\u00a0\u00a0\u00a0";
                mapLinkText += h1Num+"."+h2Num+"."+h3Num;
            }
            else if (elemClass == "Annex3") {
                ++a3Num;
                tocEntryText += "ANNEX\u00a0"+String.fromCharCode(ACode+a1Num-1)+"."+a2Num+"."+a3Num+
                                "\u00a0\u00a0\u00a0";
                mapLinkText += "Annex "+String.fromCharCode(ACode+a1Num-1)+"."+a2Num+"."+a3Num;
            }
            else {
                error_msg( elemNode, "Profile Error: Unsupported value for 'class' attribute of '"+hname+
                                     "' element: "+elemClass);
            }

            if (elemClass != "Heading3-NoTOC") {

                // Add the TOC entry

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

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

                var h3ddNode = document.createElement("dd");
                h3ddNode.setAttribute("class","TOC-Entry");
                h3ddNode.appendChild(document.createTextNode("\u000a"));
                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);

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

                tocListNode.appendChild(document.createTextNode("\u000a"));
                tocListNode.appendChild(tocEntryNode);
            }

            // Add the reference map entry

            var mapEntryNode = document.createElement("div");
            mapEntryNode.setAttribute("id",idMapPrefix+elemNode.id);
            mapEntryNode.setAttribute("linktext",mapLinkText);
            // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
            mapEntryNode.setAttribute("title",headingText);

            mapNode.appendChild(document.createTextNode("\u000a"));
            mapNode.appendChild(mapEntryNode);
            
            break;

        case "h4":

            var hname = "h4";
            var elemClass = elemNode.className;

            var headingText = childrenText(elemNode);
            if (headingText == "") {
                error_msg( elemNode, "Profile Error: No heading text specified on '"+hname+"' element");
            }

            // Auto-generate missing 'id' attribute
            if (elemNode.id == null || elemNode.id == "") {
                // We have to test both null and empty string, because different JavaScript engines return this
                // differently.
                elemNode.id = idAutoPrefix + idAutoNum.toString();
                idAutoNum += 1;
            }

            // Change 'id' attribute value of Hx elements to a hash value.
            elemNode.id = idHashDict.hash(elemNode.id);

            var mapLinkText = "";
            if (elemClass == "Heading4" || elemClass == null || elemClass == "") {
                ++h4Num;
                h5Num = 0;
                h6Num = 0;
                mapLinkText += h1Num+"."+h2Num+"."+h3Num+"."+h4Num;
            }
            else {
                error_msg( elemNode, "Profile Error: Unsupported value for 'class' attribute of '"+hname+
                                     "' element: "+elemClass);
            }

            // Add the reference map entry

            var mapEntryNode = document.createElement("div");
            mapEntryNode.setAttribute("id",idMapPrefix+elemNode.id);
            mapEntryNode.setAttribute("linktext",mapLinkText);
            // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
            mapEntryNode.setAttribute("title",headingText);

            mapNode.appendChild(document.createTextNode("\u000a"));
            mapNode.appendChild(mapEntryNode);
            
            break;

        case "h5":

            var hname = "h5";
            var elemClass = elemNode.className;

            var headingText = childrenText(elemNode);
            if (headingText == "") {
                error_msg( elemNode, "Profile Error: No heading text specified on '"+hname+"' element");
            }

            // Auto-generate missing 'id' attribute
            if (elemNode.id == null || elemNode.id == "") {
                // We have to test both null and empty string, because different JavaScript engines return this
                // differently.
                elemNode.id = idAutoPrefix + idAutoNum.toString();
                idAutoNum += 1;
            }

            // Change 'id' attribute value of Hx elements to a hash value.
            elemNode.id = idHashDict.hash(elemNode.id);

            var mapLinkText = "";
            if (elemClass == "Heading5" || elemClass == null || elemClass == "") {
                ++h5Num;
                h6Num = 0;
                mapLinkText += h1Num+"."+h2Num+"."+h3Num+"."+h4Num+"."+h5Num;
            }
            else {
                error_msg( elemNode, "Profile Error: Unsupported value for 'class' attribute of '"+hname+
                                     "' element: "+elemClass);
            }

            // Add the reference map entry

            var mapEntryNode = document.createElement("div");
            mapEntryNode.setAttribute("id",idMapPrefix+elemNode.id);
            mapEntryNode.setAttribute("linktext",mapLinkText);
            // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
            mapEntryNode.setAttribute("title",headingText);

            mapNode.appendChild(document.createTextNode("\u000a"));
            mapNode.appendChild(mapEntryNode);
            
            break;

        case "h6":

            var hname = "h6";
            var elemClass = elemNode.className;

            var headingText = childrenText(elemNode);
            if (headingText == "") {
                error_msg( elemNode, "Profile Error: No heading text specified on '"+hname+"' element");
            }

            // Auto-generate missing 'id' attribute
            if (elemNode.id == null || elemNode.id == "") {
                // We have to test both null and empty string, because different JavaScript engines return this
                // differently.
                elemNode.id = idAutoPrefix + idAutoNum.toString();
                idAutoNum += 1;
            }

            // Change 'id' attribute value of Hx elements to a hash value.
            elemNode.id = idHashDict.hash(elemNode.id);

            var mapLinkText = "";
            if (elemClass == "Heading6" || elemClass == null || elemClass == "") {
                ++h6Num;
                mapLinkText += h1Num+"."+h2Num+"."+h3Num+"."+h4Num+"."+h5Num+"."+h6Num;
            }
            else {
                error_msg( elemNode, "Profile Error: Unsupported value for 'class' attribute of '"+hname+
                                     "' element: "+elemClass);
            }

            // Add the reference map entry

            var mapEntryNode = document.createElement("div");
            mapEntryNode.setAttribute("id",idMapPrefix+elemNode.id);
            mapEntryNode.setAttribute("linktext",mapLinkText);
            // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
            mapEntryNode.setAttribute("title",headingText);

            mapNode.appendChild(document.createTextNode("\u000a"));
            mapNode.appendChild(mapEntryNode);

            break;

        }
    }

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


    /*
     * Generate Table of Figures (TOF)
     *
     * HTML structure that is searched for (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.
     *
     * Generated HTML structure (using [] as delimiters):
     *   [div id="div-tof"]     This element already exists and serves as target for the generated child elements.
     *     [dl class="TOC"]     This is the TOF with its entries
     *       [dd class="TOC-Entry"][a href="#F-AD" class="TocRef"]Figure 1 - DMTF adaptation diagram[/a][/dd]
     *       [dd class="TOC-Entry"][a href="#F-ODCentralMethod" class="TocRef"]Figure 2 - Central methodology[/a][/dd]
     *       . . .
     *     [/dl]
     *   [/div]
     * In addition, entries are added to the reference map (see processing of headings).
     */

    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
        tofListNode.setAttribute("class","TOC");
    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 id = divFigureNode.id;
            var captionText = null;

            if (id != null && id != "") {
                // We have to test both null and empty string, since the JavaScript engines in different tools return
                // this differently.

                // Change 'id' attribute value of figure caption div elements to a hash value.
                var id_hashed = idHashDict.hash(id);
                divFigureNode.id = id_hashed;

                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) {
                    if (pCaptionNode.childNodes.length >= 1) {
                        captionText = pCaptionNode.firstChild.nodeValue.replace(/(^\s+|\s+$)/g,'');
                    } else {
                        captionText = "";
                    }
                }
            } else {
                error_msg( divFigureNode, "Profile Error: No 'id' attribute specified on 'div' element with "+
                                          "class 'Figure'");
            }

            // An omitted caption element ('p' element with class 'Figure-Caption') is the indicator that we don't want
            // this figure to be referenceable (e.g. via mrp:FigureLink) or to be included in the Table of Figures.
            if (captionText != null) {

                if (captionText == "") {
                    error_msg( divFigureNode, "Profile Error: Caption element ('p' element with class 'Figure-Caption') "+
                                              "for the figure has an empty caption text");
                }
                
                var tofEntryText = "";
                var mapLinkText = "";

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

                // Add the TOF entry

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

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

                var tofEntryNode = document.createElement("dd");
                tofEntryNode.setAttribute("class","TOC-Entry");
                tofEntryNode.appendChild(document.createTextNode("\u000a"));
                tofEntryNode.appendChild(linkNode);

                tofListNode.appendChild(document.createTextNode("\u000a"));
                tofListNode.appendChild(tofEntryNode);

                // Add the reference map entry

                var mapEntryNode = document.createElement("div");
                mapEntryNode.setAttribute("id",idMapPrefix+id_hashed);
                mapEntryNode.setAttribute("linktext",mapLinkText);
                // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
                mapEntryNode.setAttribute("title",captionText);

                mapNode.appendChild(document.createTextNode("\u000a"));
                mapNode.appendChild(mapEntryNode);
            }
        }
    }

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


    /*
     * Generate Table of Tables (TOT).
     *
     * HTML structure that is searched for (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.
     *
     * Generated HTML structure (using [] as delimiters):
     *   [div id="div-tot"]     This element already exists and serves as target for the generated child elements.
     *     [dl class="TOC"]
     *       [dd class="TOC-Entry"][a href="#T-ProfileReferences" class="TocRef"]Table 1 - Profile references[/a][/dd]
     *       [dd class="TOC-Entry"][a href="#T-Features" class="TocRef"]Table 2 - Features[/a][/dd]
     *       . . .
     *     [/dl]
     *   [/div]
     * In addition, entries are added to the reference map (see processing of headings).
     */

    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
        totListNode.setAttribute("class","TOC");
    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.

            // Change 'id' attribute value of table elements to a hash value.
            var id_hashed = idHashDict.hash(id);
            tableNode.id = id_hashed;
            
            var captionText = null;

            for (var j = 0; j < tableNode.childNodes.length; ++j) {
                var captionNode = tableNode.childNodes[j];
                if (captionNode.nodeName.toLowerCase() == "caption") {
                    if (captionNode.childNodes.length >= 1) {
                        captionText = captionNode.firstChild.nodeValue.replace(/(^\s+|\s+$)/g,'');
                    } else {
                        captionText = "";
                    }
                    break;
                }
            }

            // An omitted 'caption' element is the indicator that we don't want this table to be referenceable
            // (e.g. via mrp:TableLink) or to be included in the Table of Tables.
            if (captionText != null) {

                if (captionText == "") {
                    error_msg( tableNode, "Profile Error: 'caption' element for the table has an empty caption text");
                }
                
                var totEntryText = "";
                var mapLinkText = "";

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

                // Add the TOT entry

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

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

                var totEntryNode = document.createElement("dd");
                totEntryNode.setAttribute("class","TOC-Entry");
                totEntryNode.appendChild(document.createTextNode("\u000a"));
                totEntryNode.appendChild(linkNode);

                totListNode.appendChild(document.createTextNode("\u000a"));
                totListNode.appendChild(totEntryNode);

                // Add the reference map entry

                var mapEntryNode = document.createElement("div");
                mapEntryNode.setAttribute("id",idMapPrefix+id_hashed);
                mapEntryNode.setAttribute("linktext",mapLinkText);
                // Note: HTML does not define a "linktext" attribute, but it is supposed to tolerate it.
                mapEntryNode.setAttribute("title",captionText);

                mapNode.appendChild(document.createTextNode("\u000a"));
                mapNode.appendChild(mapEntryNode);
            }
        }
    }

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


    // We need to append the reference map now to the document, in order to be able to search for the elements.
    divMap.appendChild(mapNode);


    /*
     * Process named anchors identifying headings and add them to the reference map.
     *
     * HTML structure that is searched for (using [] as delimiters):
     *   [div style="..."]
     *     [a name="H-anchor-name"/]
     *   [/div]
     *   ... (possibly multiple div elements as described above)
     *   [h4 id="H-heading-id" class="Heading4" title="heading-title"]heading-text[/h4]
     *
     * The map entry that is created uses H-anchor-name as an id and heading-text and
     * heading-title from the heading element.
     */

    var aAllNodeList = document.body.getElementsByTagName("a");     // returns flat list of references to direct and
                                                                    // indirect children in original DOM tree
    for (var i = 0; i < aAllNodeList.length; ++i) {
        var aNode = aAllNodeList[i];

        aName = aNode.getAttribute("name")

        if (aName != null && aName != "") {
            // We have to test both null and empty string, since the JavaScript engines in different tools return this
            // differently.

            // This is a named anchor.

            // Change 'id' attribute value of table elements to a hash value.
            var aName_hashed = aName;
            if (!idHashDict.is_hash_value(aName)) {
                aName_hashed = idHashDict.hash(aName);
                aNode.setAttribute("name",aName_hashed);
            }
            
            if (aName.slice(0,1) == "H") {
                // The 'a' element is a named anchor in front of a heading (we follow the convention of starting them
                // with 'H')

                // The element of interest is the Hx element following the named anchor element.
                // Note the 'a' element is wrappered by a 'div' element, so we go one up and one forward.
                var hNode = aNode.parentNode.nextSibling;

                // Skip any non-element nodes (e.g. text nodes), and "div" elements, until the next element node is
                // reached:
                while (hNode != null && (hNode.nodeType != 1 || hNode.tagName.toUpperCase() == "DIV")) {
                    hNode = hNode.nextSibling;
                }

                if (hNode != null && hNode.tagName.length == 2 && hNode.tagName.slice(0,1).toUpperCase() == "H") {
                    // Note: Prince XML does not by default represent tagName in upper case, as defined in Javascript.

                    // We look up the heading in the reference map and use that data. This is necessary because here
                    // we don't know the heading numbers anymore. Also, during heading element processing, we could
                    // not possibly resolve forward references. Bottom line, we need this second pass through the a
                    // elements.

                    var refMapNode = document.getElementById(idMapPrefix+hNode.id); // Note: hNode.id is already hashed.
                    if (refMapNode != null) {

                        // Add the reference map entry

                        var mapEntryNode = document.createElement("div");
                        mapEntryNode.setAttribute("id",idMapPrefix+aName_hashed);
                        mapEntryNode.setAttribute("linktext",refMapNode.getAttribute("linktext"));
                        mapEntryNode.setAttribute("title",refMapNode.title);

                        mapNode.appendChild(document.createTextNode("\u000a"));
                        mapNode.appendChild(mapEntryNode);
                    }
                    else {
                        error_msg( hNode, "Internal Error: Heading element with id '"+hNode.id+
                                          "' is not mapped in reference mapping table.");
                    }
                }
                else {
                    error_msg( divToc, "Profile Error: Anchor element with name '"+aName+
                                       "' is not followed by a heading element (next element: "+hNode.tagName+").");
                }
            }
        }
    }

    mapNode.appendChild(document.createTextNode("\u000a"));


    /*
     * Modify links to subclauses, figures and tables to replace the displayed link text with subclause, table and
     * figure numbers.
     * Verify that these links are not dangling.
     * Here we use the entries from the reference map built earlier.
     *
     * HTML structure that is searched for (using [] as delimiters):
     *   [a class="HeadingRef" href="#heading-id" title="title text"]subclause "subclause title"[/a]
     *
     * Generated HTML structure (using [] as delimiters):
     *   [a class="HeadingRef" href="#heading-id" title="title text"]mapped-title[/a]
     */

    var aAllNodeList = document.body.getElementsByTagName("a");     // returns flat list of references to direct and
                                                                    // indirect children in original DOM tree
    for (var i = 0; i < aAllNodeList.length; ++i) {
        var aNode = aAllNodeList[i];
        var aHref = aNode.getAttribute("href");     // For the href attribute, Prince XML does not support direct
                                                    // access (i.e. aNode.href).
        var aClass = aNode.getAttribute("class");   // For the class attribute, Prince XML does not support direct
                                                    // access (i.e. aNode.class).
        if (aHref != null && aClass != "UrlRef") {
            // the 'a' element is a reference, and does not have class="UrlRef" (i.e. is not a real URL)

            var refId = aHref.substr(aHref.search("#")+1);

            // Change 'href' attribute value of all 'a' elements to a hash value.
            var refId_hashed = refId;
            if (!idHashDict.is_hash_value(refId)) {
                refId_hashed = idHashDict.hash(refId);
                aHref = "#" + refId_hashed;
                aNode.setAttribute("href",aHref);
            }
            
            // Fix up heading, table and figure references to be by number.
            if (aNode.className == "HeadingRef" || aNode.className == "TableRef" || aNode.className == "FigureRef") {
                // Look up the reference map.
                var refMapNode = document.getElementById(idMapPrefix+refId_hashed);
                if (refMapNode != null) {
                    var linkText = refMapNode.getAttribute("linktext");
                    if (linkText == null)  {
                        error_msg( aNode, "Internal Error: Undefined linktext for mapping id '"+refId+"'");
                    }
                    else {
                        var linkTextNode = document.createTextNode(linkText);
                        var childNode = aNode.firstChild;
                        if (childNode != null)  {
                            aNode.replaceChild(linkTextNode,aNode.firstChild);
                        }
                        else {
                            aNode.appendChild(linkTextNode);
                        }
                        aNode.setAttribute("title",linkText+" - "+refMapNode.title);
                    }
                }
                else {
                    error_msg( aNode, "Profile Error: Heading, table or figure link to undefined id '"+refId+"'");
                }
            }
        }
    }


    /*
     * Generate line numbers.
     *
     * HTML structure that is searched for (using [] as delimiters):
     *   [body ...]
     *     [div id="div-ln"]                <- Searched by ID
     *       [h1]Section Heading[/h1]       <- HTML elements, at any nesting level
     *       [p]Paragraph Text[/p]
     *       ... more HTML elements ...
     *     [/div]
     *   [/body]
     *
     * Generated HTML structure (using [] as delimiters):
     *   [body ...]
     *     [div id="div-ln"]
     *       [div class="lineNumber"]     <- Inserted before HTML element to be numbered
     *       [h1]Section Heading[/h1]
     *       [div class="lineNumber"]     <- Inserted before HTML element to be numbered
     *       [p]Paragraph Text[/p]
     *       ... more HTML elements ...   (HTML elements to be numbered get a div inserted before them)
     *     [/div]
     *   [/body]
     */
    var divLN = document.getElementById("div-ln");
    if (divLN != null)
    {
        var toBeNumberedElemNameList = [
            "h1", "h2", "h3", "h4", "h5", "h6", "h7",
            "p", "li", "textarea",
            "table",
            "img" ];
            // Note: The TR element is no longer in this list, because inserting DIV elements in between TR elements
            // causes the rowspan attribute no longer to work correctly.
        for (var n = 0; n < toBeNumberedElemNameList.length; ++n)
        {
            var toBeNumberedElemName = toBeNumberedElemNameList[n];

            var toBeNumberedElemNodeList = document.body.getElementsByTagName(toBeNumberedElemName);
            for (var i = 0; i < toBeNumberedElemNodeList.length; ++i)
            {
                var toBeNumberedElemNode = toBeNumberedElemNodeList[i];
                var lnDivNode = document.createElement("div");
                lnDivNode.className = "lineNumber";
                toBeNumberedElemNode.parentNode.insertBefore(lnDivNode,toBeNumberedElemNode);
            }
        }
    }


    /*
     * Remove the error message that this script did not run.
     *
     * This error is generated by DSP8029 in order so it remains in the HTML in case this script does not run at all
     * or does not run to completion (well, at least down to this point).
     *
     * HTML structure that is assumed (using [] as delimiters):
     *   [body ...]
     *     [div id="div-notrun"]                                <- Searched by ID
     *       [span class="ErrorMsg"]Setup Error: bla...[/span]
     *     [/div]
     *   [/body]
     * 
     * The [span] child element with the error msg is removed.
     */
    var divNotRun = document.getElementById("div-notrun");
    if (divNotRun != null) {
        // we can be tolerant in the error checking because the error msg simply remains if something does not work.
        var spanNotRun = divNotRun.childNodes[0];
        if (spanNotRun != null && spanNotRun.nodeName.toLowerCase() == "span" &&
            spanNotRun.className == "ErrorMsg")
        {
            divNotRun.removeChild(spanNotRun);
            var notRunCommentNode = document.createComment(" tocgen.js: Removed the error message that the tocgen.js "+
                                                           "script was not run. ");
            divNotRun.appendChild(notRunCommentNode);
        }
    }


    /*
     * Remove whitespace before [/i] and [/b] end elements.
     *
     * XML editors when reformatting the MRP XML file commonly format <i> and <b> elements that contain other elements,
     * into their own lines. This adds whitespace which is sometimes unintended, for example when the </i> and </b>
     * elements are immediately followed by punctuation such as comma or dot.
     *
     * Example before reformatting:
     *   [p]The first entity is [i][mrp:TermLink refName="Term_x"]x[/mrp:TermLink][/i], and ...[/p]
     * 
     * Example after reformatting, with unintended whitespace added between [/mrp:TermLink] and [/i]:
     *   [p]The first entity is [i]
     *       [mrp:TermLink refName="Term_x"]x[/mrp:TermLink]
     *   [/i], and ...[/p]
     */
    var RemoveWSElemTagList = [ "i", "b" ];
    for (var t = 0; t < RemoveWSElemTagList.length; ++t) {
        var tagName = RemoveWSElemTagList[t];
        var elemNodeList = document.getElementsByTagName(tagName);
        for (var i = 0; i < elemNodeList.length; ++i)
        {
            var elemNode = elemNodeList[i];
            if (elemNode.childNodes.length > 0) {
                var lastChildNode = elemNode.lastChild;
                if (lastChildNode.nodeType == 3 && lastChildNode.nodeValue.replace(/(^\s+|\s+$)/g,'') == "") {
                    // Remove the unintended text node with the whitespace.
                    elemNode.removeChild(lastChildNode);
                }
            }
        }
    }

    
    /*
     * Generate summary of errors.
     *
     * HTML structure that is searched for (using [] as delimiters):
     *   [span class="ErrorMsg"]error message text[/span]
     *
     * Generated HTML structure (using [] as delimiters):
     *   [div id="div-err"]     This element already exists and serves as target for the generated child elements.
     *     [span class="ErrorMsg"]Summary error message text[/span]
     *   [/div]
     * The child element is generated only if error messages exist in the HTML file.
     */

    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:')");
    }


    /*
     * Remove running this script.
     *
     * This is useful for debugging scenarios where the HTML resulting from this script is saved (e.g. with Firefox's
     * "Save Page As" function) and investigated using a Web browser. It is useful if this script does not run again on
     * its own output, in this case.
     * 
     * HTML structure that is assumed (using [] as delimiters):
     *   [body id="onload" onload="body_load()"]              <- Searched by ID
     *   ...
     *   [/body]
     */
    var onloadElem = document.getElementById("onload");         // element that runs this script via its 'onload' attr.
    if (onloadElem != null && onloadElem != "") {
        onloadElem.removeAttribute("onload");
        var onloadCommentNode = document.createComment(" tocgen.js: Removed the attribute: onload=\"body_load()\" to "+
                                                       "avoid running the tocgen.js script on its output. ");
        onloadElem.insertBefore(onloadCommentNode, onloadElem.firstChild);
    }


}


/*
 * 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);
}


/*
 * This function walks through all direct and indirect child nodes of the specified element node, and returns a
 * concatenation of the text of any text nodes (at any child level), using the space as a concatenation character.
 */
function childrenText(elemNode)
{
    var txt = "";
    var childNode = null;
    for (var i = 0; i < elemNode.childNodes.length; i++) {
        childNode = elemNode.childNodes[i];
        if (childNode.nodeType == 3 &&  // We use a numeric node type value because IE does not support Node.TEXT_NODE.
            childNode.nodeValue != null && childNode.nodeValue != "") {
            txt += " ";
            txt += childNode.nodeValue;
        }
        else {
            txt += childrenText(childNode);
        }
    }
    return txt;
}


/*
 * Return a decimal representation of the input number with leading zeros padded to the given (total) size.
 */ 
function padded(num,size) {
    var s = String(num);
    while (s.length < size)
        s = "0" + s;
    return s;
}


/*
 * Hash dictionary "class".
 *
 * An object of this class can be used to create and maintain short hashed values for link targets, from original
 * values of such link targets.
 * 
 * The constructor function below basically defines the class by initializing its attributes.
 */
function HashDict()
{
   this.enabled = true;              // On/off switch for using short hashed link targets.
                                     //   true = use short hashed link targets (e.g. "HASH-0123456789-0").
                                     //   false = use (long) original targets (e.g. "HG-A-MyAdaptation-P-MyProperty").

   this.prefix = "HASH-";            // Prefix for hash value.
   this.sep = "-";                   // Separator between plain hash value and collision suffix number.

   this.occupiedValues = {};         // The already occupied hash values and their last occupied collision suffix:
                                     //   key: plain hash value (see _plain_hash() description)
                                     //   value: last occupied collision suffix number

   this.org2hashDict = {};           // The org2hash mapping dictionary:
                                     //   key: original value
                                     //   value: hashed value (plain hash value + collision suffix)
}


/*
 * Hash dictionary method:
 * 
 * Determine whether a value is a hashed value, and return true or false to indicate that.
 * 
 * This works regardless of whether this hash dictionary is enabled or disabled.
 */
HashDict.prototype.is_hash_value = function(hash_str)
{
    _hashed = (hash_str.search(this.prefix) == 0);
    return _hashed;
};


/*
 * Hash dictionary method:
 * 
 * If hashing is enabled in this dictionary, return the hash value of an original value, and add it to the hash
 * dictionary if it does not yet exist. The existing hash value in the dictionary is used, if it already exists.
 * 
 * If hashing is disabled, return the original value. This allows the method to be used both for hashing enabled and
 * disabled.
 */
HashDict.prototype.hash = function(org_str)
{
    var hash_str;
    if (!this.enabled) {
        hash_str = org_str;
    }
    else if (this.exists(org_str)) {
        hash_str = this.get_hash(org_str);
    }
    else {
        hash_str = this.add(org_str);
    }
    return hash_str;
};


/*
 * Hash dictionary method:
 * 
 * Determine whether an original value exists in the hash dictionary and return true or false.
 * 
 * Preconditions:
 * - This hash dictionary is enabled.
 */
HashDict.prototype.exists = function(org_str)
{
    var _exists = org_str in this.org2hashDict;
    return _exists;
};


/*
 * Hash dictionary method:
 *
 * Add an original value to the hash dictionary and return its (full) hash value.
 *
 * Preconditions:
 * - The original value does not exist in the hash dictionary yet (see exists() method).
 * - This hash dictionary is enabled.
 */
HashDict.prototype.add = function(org_str)
{
    var plain_hash_str = this._plain_hash_value(org_str);
    
    // handle hash collisions
    var collision_suffix;
    if (plain_hash_str in this.occupiedValues) {
        collision_suffix = this.occupiedValues[plain_hash_str] + 1;
    }
    else {
        collision_suffix = 0;
    }
    this.occupiedValues[plain_hash_str] = collision_suffix;

    var hash_str = this.prefix + plain_hash_str + this.sep + collision_suffix.toString();

    // add to dictionary
    this.org2hashDict[org_str] = hash_str;

    return hash_str;
};


/*
 * Hash dictionary method:
 * 
 * Get the (full) hash value of an original value.
 * 
 * Preconditions:
 * - The original value exists in the hash dictionary (see exists() method).
 * - This hash dictionary is enabled.
 */
HashDict.prototype.get_hash = function(org_str)
{
    var hash_str = this.org2hashDict[org_str];
    return hash_str;
};


/*
 * Internal hash dictionary method:
 * 
 * Return the plain hash value of an original string value.
 * 
 * The returned hash value is a 0-padded 10-char decimal string representation of a 32-bit unsigned integer.
 * This is an internal method that is used by the other hash dictionary methods but not by users of the hash dictionary.
 * Kudos: Hash algorithm is based on http://stackoverflow.com/a/7616484
 */
HashDict.prototype._plain_hash_value = function(org_str)
{
    var hash_int = 0;
    var len = org_str.length; 
    for (var i = 0; i < len; i++) {
        hash_int = ((hash_int << 5) - hash_int) + org_str.charCodeAt(i);
        hash_int |= 0; // Convert back to (signed) integer
    }
    hash_int = Math.abs(hash_int);
    return padded(hash_int,10);
};

