// ------------------------------------------------------------------------------------------ //
//  ReSpec.js -- a specification-writing tool
//  Robin Berjon, http://berjon.com/
//  ----------------------------------------------------------------------------------------- //
//  Documentation: http://dev.w3.org/2009/dap/ReSpec.js/documentation.html
//  License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
// ------------------------------------------------------------------------------------------ //

// SUPPORT
//  The official support channel for ReSpec is spec-prod@w3.org.
//  The archives are available at http://lists.w3.org/Archives/Public/spec-prod/
//  You can subscribe by sending email to spec-prod-request@w3.org with "subscribe" as the
//  subject line.
//  Please use that instead of emailing me (Robin) directly as the chances are that questions
//  or enhancement ideas will be shared by others. Thanks!

// XXX TODO
//  - move to the top of dev. hierarchy
//  - add autolinking to headers in the output (like WebIDL)
//  - better inline dependent CSS
//  - add typographical conventions section
//  - WebIDL
//      . make it so that extended attributes on members and attributes are only wrapped if needed
//      . make processor aware of some extended attributes (e.g. Constructor)
//      . support variadic params
//      . support arrays
//      . support special operations: getter, setter, creator, deleter, caller, omittable
//      . support stringifiers
//  - add support for a test variant of the specification, based on the ideas in P+C
//  - some refactoring is in order
//  - make a widget that can save using the FS API, and inject the API without it being in the template,
//    inline CSS without hassle, etc.
//  - make a list of links to issues appear on a key combination
//  - warn on empty links to no dfn (perhaps in a special debug mode?)
//  - make everything that uses "::before" actually generate the real content instead

(function () {
if (typeof(berjon) == "undefined") berjon = {};
var sn;
function _errEl () {
    var err = document.getElementById("respec-err");
    if (err) return err.firstElementChild;
    err = sn.element("div", 
                        { id: "respec-err", 
                          style: "position: fixed; width: 350px; top: 10px; right: 10px; border: 3px double #f00; background: #fff",
                          "class": "removeOnSave" },
                        document.body);
    return sn.element("ul", {}, err);
}
function error (str) {
    sn.element("li", { style: "color: #c00" }, _errEl(), str);
}
function warning (str) {
    sn.element("li", { style: "color: #666" }, _errEl(), str);
}
berjon.respec = function () {
    for (var k in this.status2text) {
        if (this.status2long[k]) continue;
        this.status2long[k] = this.status2text[k];
    }
};
berjon.respec.prototype = {
    title:          null,
    additionalCopyrightHolders: null,
    overrideCopyright: null,
    editors:        [],
    authors:        [],

    recTrackStatus: ["FPWD", "WD", "LC", "CR", "PR", "PER", "REC"],
    noTrackStatus:  ["MO", "unofficial", "base"], 
    status2text:    {
        NOTE:           "Note",
        "WG-NOTE":      "Working Group Note",
        "CG-NOTE":      "Co-ordination Group Note",
        "IG-NOTE":      "Interest Group Note",
        "Member-SUBM":  "Member Submission",
        "Team-SUBM":    "Team Submission",
        XGR:            "Incubator Group Report",
        MO:             "Member-Only Document",
        ED:             "Editor's Draft",
        FPWD:           "Working Draft",
        WD:             "Working Draft",
		"FPWD-NOTE":    "Working Draft",
        "WD-NOTE": 		"Working Draft", 
		"LC-NOTE":      "Working Draft", 
        LC:             "Working Draft",
        CR:             "Candidate Recommendation",
        PR:             "Proposed Recommendation",
        PER:            "Proposed Edited Recommendation",
        REC:            "Recommendation",
        RSCND:          "Rescinded Recommendation",
        unofficial:     "Unofficial Draft",
        base:           "Document",
        "draft-finding":    "Draft TAG Finding",
        "finding":      "TAG Finding"
    },
    status2long:    {
        FPWD:           "First Public Working Draft",
		"FPWD-NOTE": 	"First Public Working Draft", 
        LC:             "Last Call Working Draft",
        "LC-NOTE": 		"Last Call Working Draft"
    },
    status2maturity:    {
        FPWD:       "WD",
        LC:         "WD",
		"FPWD-NOTE": "WD", 
       	"WD-NOTE":  "WD", 
		"LC-NOTE":  "LC",
        "WG-NOTE":  "NOTE"
    },
    
    isLocal:    false,

    loadAndRun:    function () {
        var scripts = document.querySelectorAll("script[src]");
        var rs, base;
        for (var i = 0; i < scripts.length; i++) {
            var src = scripts[i].src;
            if (/\/js\/respec\.js$/.test(src)) {
                rs = scripts[i];
                base = src.replace(/js\/respec\.js$/, "");
            }
        }
        this.base = base;
        if (base.indexOf("file://") == 0) this.isLocal = true;
        
        var loaded = [];
        var deps = ["js/simple-node.js", "js/shortcut.js", "bibref/biblio.js", "js/sh_main.min.js"];
        var head = document.getElementsByTagName('head')[0];
        var obj = this;
        for (var i = 0; i < deps.length; i++) {
            var dep = deps[i];
            var sel = document.createElement('script');
            sel.type = 'text/javascript';
            sel.src = base + dep;
            sel.setAttribute("class", "remove");
            sel.onload = function (ev) {
                loaded.push(ev.target.src);
                if (obj.isLocal && ev.target.src.indexOf("sh_main") > 0) {
                    // dirty hack to fix local loading of SHJS
                    this.oldSHLoad = window.sh_load;
                    window.sh_load = function (language, element, prefix, suffix) {
                        if (language in sh_requests) {
                            sh_requests[language].push(element);
                            return;
                        }
                        sh_requests[language] = [element];
                        var url = prefix + 'sh_' + language + suffix;
                        var shLang = document.createElement('script');
                        shLang.type = 'text/javascript';
                        shLang.src = url;
                        shLang.setAttribute("class", "remove");
                        shLang.onload = function (ev) {
                            var elements = sh_requests[language];
                            for (var i = 0; i < elements.length; i++) {
                                sh_highlightElement(elements[i], sh_languages[language]);
                            }
                        };
                        head.appendChild(shLang);
                    };
                }
                if (loaded.length == deps.length) {
                    sn = new berjon.simpleNode({
                        "":     "http://www.w3.org/1999/xhtml",
                        "x":    "http://www.w3.org/1999/xhtml"
                    }, document);
                    obj.run();
                }
            };
            head.appendChild(sel);
        }
    },
    
    run:    function () {
        document.body.style.display = "none";
        try {
            this.extractConfig();
            if (respecConfig.preProcess) {
                for (var i = 0; i < respecConfig.preProcess.length; i++) respecConfig.preProcess[i].apply(this);
            }
            this.makeTemplate();

            // This is done REALLY early in case the transform ends up
            // needing to include something
            this.doTransforms() ;

            // This is done early so that if other data gets embedded it will be 
            // processed
            this.includeFiles();

            this.dfn();
            this.inlines();

            this.webIDL();
            this.examples();

            // only process best practices if element with class
            // practicelab found, do not slow down non best-practice
            // docs.
            // doBestPractices must be called before makeTOC, fjh
            // this might not work with old browsers like IE 8

            var bpnode = document.getElementsByClassName("practicelab");
            if(bpnode.length > 0) this.doBestPractices(); 

            this.informative();
            this.fixHeaders();

            this.makeTOC();
            this.idHeaders();

            if (respecConfig.postProcess) {
                for (var i = 0; i < respecConfig.postProcess.length; i++) respecConfig.postProcess[i].apply(this);
            }

            // if (this.doMicroData) this.makeMicroData();
            if (this.doRDFa) this.makeRDFa();
            this.makeSectionRefs(); // allow references to sections using name for text, fjh
           this.unHTML5();
            this.removeRespec();

            // shortcuts
            var obj = this;
            // shortcut.add("Alt+H", function () { obj.toHTML(); });
            // shortcut.add("Shift+Alt+H", function () { obj.toHTMLSource(); });
            shortcut.add("Ctrl+Shift+Alt+S", function () { obj.showSaveOptions(); });
            shortcut.add("Esc", function () { obj.hideSaveOptions(); });
        }
        catch (e) {
            document.body.style.display = "inherit";
            error("Processing error: " + e);
            if (typeof(console) != "undefined" && console.log) console.log(e);
        }
        document.body.style.display = "inherit";
    },

    makeRDFa:  function () {
        var abs = document.getElementById("abstract");
        if (abs) {
            var rel = 'dcterms:abstract' ;
            var ref = abs.getAttribute('property') ;
            if (ref) {
                rel = ref + ' ' + rel ;
            }
            abs.setAttribute('property', rel) ;
            abs.setAttribute('datatype', '') ;
        }
        // annotate sections with Section data
        var secs = document.querySelectorAll("section");
        for (var i = 0; i < secs.length; i++) {
            // if the section has an id, use that.  if not, look at the first child for an id
            var about = '' ;
            // the first child should be a header - that's what we will annotate
            var fc = secs[i].firstElementChild;
            var ref = secs[i].getAttribute('id') ;
            if ( ref ) {
                about = '#' + ref ;
            } else {
                if (fc) {
                    ref = fc.getAttribute('id') ;
                    if (ref) {
                        about = '#' + ref;
                    }
                }
            }
            if (about != '') {
                secs[i].setAttribute('typeof', 'bibo:Chapter') ;
                secs[i].setAttribute('about', about) ;
            }
        }
    },
    
    saveMenu: null,
    showSaveOptions:    function () {
        var obj = this;
        this.saveMenu = sn.element("div",
                        { style: "position: fixed; width: 400px; top: 10px; padding: 1em; border: 5px solid #90b8de; background: #fff" },
                        document.body);
        sn.element("h4", {}, this.saveMenu, "Save Options");
        var butH = sn.element("button", {}, this.saveMenu, "Save as HTML");
        butH.onclick = function () { obj.hideSaveOptions(); obj.toHTML(); };
        var butS = sn.element("button", {}, this.saveMenu, "Save as HTML (Source)");
        butS.onclick = function () { obj.hideSaveOptions(); obj.toHTMLSource(); };
        var butS = sn.element("button", {}, this.saveMenu, "Save as XHTML");
        butS.onclick = function () { obj.hideSaveOptions(); obj.toXHTML(); };
        var butS = sn.element("button", {}, this.saveMenu, "Save as XHTML (Source)");
        butS.onclick = function () { obj.hideSaveOptions(); obj.toXHTMLSource(); };
        if (this.diffTool && (this.previousDiffURI || this.previousURI) ) {
            var butD = sn.element("button", {}, this.saveMenu, "Diffmark");
            butD.onclick = function () { obj.hideSaveOptions(); obj.toDiffHTML(); };
        }

    },
    
    hideSaveOptions:    function () {
        if (!this.saveMenu) return;
        this.saveMenu.parentNode.removeChild(this.saveMenu);
    },

    toString:    function () {
        var str = "<!DOCTYPE html";
        var dt = document.doctype;
        if (dt && dt.publicId) {
            str += " PUBLIC '" + dt.publicId + "' '" + dt.systemId + "'";
        }
        else { // when HTML5 is allowed we can remove this
            str += " PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'";
        }
        str += ">\n";
        str += "<html";
        var ats = document.documentElement.attributes;
        var prefixAtr = '' ;

        for (var i = 0; i < ats.length; i++) {
            var an = ats[i].name;
            if (an == "xmlns" || an == "xml:lang") continue;
            if (an == "prefix") {
                prefixAtr = ats[i].value;
                continue;
            }
            str += " " + an + "=\"" + this._esc(ats[i].value) + "\"";
        }
        if (this.doRDFa) {
            if (prefixAtr != '') prefixAtr += ' ';
            prefixAtr += "dcterms: http://purl.org/dc/terms/ bibo: http://purl.org/ontology/bibo/ foaf: http://xmlns.com/foaf/0.1/ xsd: http://www.w3.org/2001/XMLSchema#";
            str += " prefix=\"" + this._esc(prefixAtr) + "\"";
        }

        str += ">\n";
        str += document.documentElement.innerHTML;
        str += "</html>";
        return str;
    },

    toXML:        function () {
        var str = "<?xml version='1.0' encoding='UTF-8'?>\n<!DOCTYPE html";
        var dt = document.doctype;
        if (dt && dt.publicId) {
            str += " PUBLIC '" + dt.publicId + "' '" + dt.systemId + "'";
        }
        else { 
            if (this.doRDFa) {
                // use the standard RDFa doctype
                str += " PUBLIC '-//W3C//DTD XHTML+RDFa 1.0//EN' 'http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd'";
            } else {
                str += " PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'";
            }
        }
        str += ">\n";
        str += "<html";
        var ats = document.documentElement.attributes;
        var prefixAtr = '' ;

        var hasxmlns = false;
        for (var i = 0; i < ats.length; i++) {
            var an = ats[i].name;
            if (an == "lang") continue;
            if (an == "xmlns") hasxmlns = true;
            if (an == "prefix") {
                prefixAtr = ats[i].value;
                continue;
            }
            str += " " + an + "=\"" + this._esc(ats[i].value) + "\"";
        }
        if (!hasxmlns) str += ' xmlns="http://www.w3.org/1999/xhtml"';
        if (this.doRDFa) {
            str += " xmlns:dcterms='http://purl.org/dc/terms/' xmlns:bibo='http://purl.org/ontology/bibo/' xmlns:foaf='http://xmlns.com/foaf/0.1/' xmlns:xsd='http://www.w3.org/2001/XMLSchema#'";
            if (prefixAtr != '') {
                var list = prefixAtr.split(/\s+/) ;
                for (var i = 0; i < list.length; i += 2) {
                    var n = list[i] ;
                    n = n.replace(/:$/,'');
                    str += ' xmlns:'+n+'="' + list[i+1] + '"';
                }
            }
        }
        str += ">\n";
        // walk the entire DOM tree grabbing nodes and emitting them - possibly modifying them
        // if they need the funny closing tag
        var pRef = this ;
        var selfClosing = {};
        "br img input area base basefont col isindex link meta param hr".split(" ").forEach(function (n) {
            selfClosing[n] = true;
        });
        var dumpNode = function (node) {
            var out = '' ;
            // if the node is the document node.. process the children
            if ( node.nodeType == 9 || ( node.nodeType == 1 && node.nodeName.toLowerCase() == 'html' ) ) {
                for (var i = 0; i < node.childNodes.length; i++) out += dumpNode(node.childNodes[i]) ;
            } 
            // element
            else if (1 === node.nodeType) {
                var ename = node.nodeName.toLowerCase() ;
                out += '<' + ename ;
                for (var i = 0; i < node.attributes.length; i++) {
                    var atn = node.attributes[i]
                    out += " " + atn.name + "=\"" + pRef._esc(atn.value) + "\"";
                }
                if (selfClosing[ename]) {
                    out += ' />';
                }
                else {
                    out += '>';
                    // XXX removing this, as it does not seem correct at all
                    // if ( ename == 'pre' ) {
                    //     out += "\n" + node.innerHTML;
                    // } 
                    // else {
                        for (var i = 0; i < node.childNodes.length; i++) out += dumpNode(node.childNodes[i]);
                    // }
                    out += '</' + ename + '>';
                }
            }
            // comments
            else if (8 === node.nodeType) {
                out += "\n<!-- " + node.nodeValue + " -->\n";
            }
            // text or cdata
            else if (3 === node.nodeType || 4 === node.nodeType) {
                out += pRef._esc(node.nodeValue);
            }
            // we don't handle other types for the time being
            else {
                warning("Cannot handle serialising nodes of type: " + node.nodeType);
            }
            return out;
        };
        var node = document.documentElement;
        str += dumpNode(document.documentElement) ;
        str += "</html>";
        return str;
    },
    
    toDiffHTMLSource:  function () {

    },

    toDiffHTML:  function () {
        // create a diff marked version against the previousURI
        // strategy - open a window in which there is a form with the
        // data needed for diff marking - submit the form so that the response populates 
        // page with the diff marked version
        var base = window.location.href;
        base = base.replace(/\/[^\/]*$/, "/");
        var str = "<!DOCTYPE html>\n";
        str += "<html";
        var ats = document.documentElement.attributes;
        for (var i = 0; i < ats.length; i++) {
            str += " " + ats[i].name + "=\"" + this._esc(ats[i].value) + "\"";
        }
        str += ">\n";
        str += "<head><title>diff form</title></head>\n";
        str += "<body><form name='form' method='POST' action='" + this.diffTool + "'>\n";
        str += "<input type='hidden' name='base' value='" + base + "'>\n";
        if (this.previousDiffURI) {
            str += "<input type='hidden' name='oldfile' value='" + this.previousDiffURI + "'>\n"; 
        } else {
            str += "<input type='hidden' name='oldfile' value='" + this.previousURI + "'>\n";
        }
        str += '<input type="hidden" name="newcontent" value="' + this._esc(this.toString()) + '">\n';
        str += '<p>Please wait...</p>';
        str += "</form></body></html>\n";


        var x = window.open() ;
        x.document.write(str) ;
        x.document.close() ;
        x.document.form.submit() ;
    },

    toHTML:    function () {
        var x = window.open();
        x.document.write(this.toString());
        x.document.close();
    },
    
    toHTMLSource:    function () {
        var x = window.open();
        x.document.write("<pre>" + this._esc(this.toString()) + "</pre>");
        x.document.close();
    },
    
    toXHTML:    function () {
        var x = window.open();
        x.document.write(this.toXML()) ;
        x.document.close();
    },
    
    toXHTMLSource:    function () {
        var x = window.open();
        x.document.write("<pre>" + this._esc(this.toXML()) + "</pre>");
        x.document.close();
    },
    
    // --- METADATA -------------------------------------------------------
    extractConfig:    function () {
        this.title = document.title;
        var cfg;
        if (respecConfig) cfg = respecConfig;
        else              cfg = {};
        // defaulting
        if (!cfg.specStatus) cfg.specStatus = "ED";
        // the below is experimental, use this if it fails:
        // cfg.publishDate = new Date();
        if (!cfg.publishDate) {
            cfg.publishDate = this._parseLastModified(document.lastModified);
        }
        else {
            cfg.publishDate = this._parseDate(cfg.publishDate);
        }
        if (cfg.previousPublishDate) cfg.previousPublishDate = this._parseDate(cfg.previousPublishDate);
        if (cfg.previousPublishDate && ! cfg.previousMaturity && cfg.specStatus.indexOf("finding") === -1) 
            error("Previous date is set, but not previousMaturity");
        if (cfg.lcEnd) cfg.lcEnd = this._parseDate(cfg.lcEnd);
        if (cfg.crEnd) cfg.crEnd = this._parseDate(cfg.crEnd);
        if (cfg.specStatus == "LC" && !cfg.lcEnd) error("If specStatus is set to LC, then lcEnd must be defined");
        if (cfg.specStatus == "CR" && !cfg.crEnd) error("If specStatus is set to CR, then crEnd must be defined");
        if (!cfg.editors) cfg.editors = [];
        if (!cfg.authors) cfg.authors = [];
        if (!cfg.alternateFormats) cfg.alternateFormats = [];
        if (cfg.inlineCSS === undefined) cfg.inlineCSS = true;
        if (!cfg.noIDLSorting) cfg.noIDLSorting = false;
        if (cfg.noIDLIn === undefined) cfg.noIDLIn = true;
        if (cfg.tocIntroductory === undefined) cfg.tocIntroductory = false;
        if (!cfg.maxTocLevel) cfg.maxTocLevel = 0;
        if (!cfg.diffTool) cfg.diffTool = 'http://www5.aptest.com/standards/htmldiff/htmldiff.pl';
        if (!cfg.noRecTrack) cfg.noRecTrack = false;
        if (!cfg.doRDFa) cfg.doRDFa = false;
        for (var k in cfg) this[k] = cfg[k];
        this.isRecTrack = cfg.noRecTrack ? false : this.recTrackStatus.indexOf(this.specStatus) >= 0;
        this.isNoTrack = this.noTrackStatus.indexOf(this.specStatus) >= 0;
        // this.specStatus = this._getMetaFor("http://berjon.com/prop/spec-status", "ED");
        // this.shortName = this._getMetaFor("http://berjon.com/prop/short-name", "xxx-xxx");
        // this.publishDate = this._getDateFor("head > time[itemprop='http://berjon.com/prop/publish-date']");
        // this.prevPublishDate = this._getDateFor("head > time[itemprop='http://berjon.com/prop/previous-publish-date']");
    },
    
    _getMetaFor:    function (iProp, def) {
        var meta = document.querySelector("head > meta[itemprop='" + iProp + "']");
        if (meta) return meta.getAttribute("content");
        else      return def;
    },

    _getDateFor:    function (sel) {
        var el = document.querySelector(sel);
        if (el) {
            var val = el.getAttribute('datetime');
            return new Date(val.substr(0, 4), val.substr(5, 2), val.substr(8, 2));
        }
        else {
            return new Date();
        }
    },
    
    // --- W3C BASICS -----------------------------------------------------------------------------------------
    makeTemplate:   function () {
        this.rootAttr();
        this.addCSS();
        this.makeHeaders();
        this.makeAbstract();
        this.makeSotD();
        this.makeConformance();
    },
    
    rootAttr:   function () {
        document.documentElement.setAttribute("lang", "en");
        document.documentElement.setAttribute("dir", "ltr");
        if (this.doRDFa) {
            document.documentElement.setAttribute("about", "");
            document.documentElement.setAttribute("property", "dcterms:language");
            document.documentElement.setAttribute("content", "en");
        }
    },
    
    addCSS: function () {
        if (this.extraCSS) {
            for (var i = 0; i < this.extraCSS.length; i++) this._insertCSS(this.extraCSS[i], this.inlineCSS);
        }
        var statStyle = this.specStatus;
        if (statStyle == "FPWD" || statStyle == "LC" || statStyle == "WD-NOTE" || statStyle == "LC-NOTE" || statStyle == "FPWD-NOTE")  {
            statStyle = "WD";
        } 
        else if (statStyle === "finding" || statStyle === "draft-finding") statStyle = "base";
// else if ( statStyle == "WD-NOTE" || statStyle == "LC-NOTE"
//        || statStyle == "FPWD-NOTE") {
//            statStyle = "WG-NOTE";
//        }
        var css;
        if (statStyle == "unofficial") {
            css = "https://www.w3.org/StyleSheets/TR/w3c-unofficial";
        }
        else if (statStyle == "base") {
            css = "https://www.w3.org/StyleSheets/TR/base";
        }
        else {
            css = "https://www.w3.org/StyleSheets/TR/W3C-" + statStyle;// + ".css";
        }
        this._insertCSS(css, false);
    },

    doTransforms: function() {
        var divs = document.querySelectorAll("[data-transform]");
        for (var i = 0; i < divs.length; i++) {
            var div = divs[i];
            var content = div.innerHTML ;
            var flist = div.getAttribute('data-transform');
            if (flist) {
                var methods = flist.split(/\s+/) ;
                for (var j = 0; j < methods.length; j++) {
                    var call = 'content = ' + methods[j] + '(this,content)' ;
                    try {
                        eval(call) ;
                    } catch (e) {
                        warning('call to ' + call + ' failed with ' + e) ;
                    }
                }
                div.removeAttribute('data-transform') ;
            }
            if (content) {
                div.innerHTML = content ;
            }
        }
    },

    includeFiles: function() {
        var divs = document.querySelectorAll("[data-include]");
        for (var i = 0; i < divs.length; i++) {
            var div = divs[i];
            var URI = div.getAttribute('data-include');
            var content = this._readFile(URI) ;
            if (content) {
                var flist = div.getAttribute('data-oninclude');
                if (flist) {
                    var methods = flist.split(/\s+/) ;
                    for (var j = 0; j < methods.length; j++) {
                        var call = 'content = ' + methods[j] + '(this,content,URI)' ;
                        try {
                            eval(call) ;
                        } catch (e) {
                            warning('call to ' + call + ' failed with ' + e) ;
                        }
                    }
                    div.removeAttribute('data-oninclude') ;
                }
                div.removeAttribute('data-include') ;
                div.innerHTML = content ;
            }
        }
    },

    // single function used to display people information for editors,
    // authors, etc (fjh 2009-12-04)

    showPeople: function(name, people) {
        var header = "";

        if (people.length == 0) return header;
        var re = '' ;
        var rp = '' ;
        var rl = '' ;
        var rt = '' ;
        var rm = '' ;
        var rn = '' ;
        var rwu = '' ;
        var rpu = '' ;
        if ( this.doRDFa ) {
            if ( name == 'Editor' ) {
                re = " rel='bibo:editor'";
                rn = " property='foaf:name'";
                rm = " rel='foaf:mbox'";
                rp = " typeof='foaf:Person'";
                rwu = " rel='foaf:workplaceHomepage'";
                rpu = " rel='foaf:homepage'";
            } else if (name == 'Author' ) {
                re = " rel='dcterms:contributor'";
                rn = " property='foaf:name'";
                rm = " rel='foaf:mbox'";
                rp = " typeof='foaf:Person'";
                rwu = " rel='foaf:workplaceHomepage'";
                rpu = " rel='foaf:homepage'";
            }
        }

        if (people.length > 1) {
            header += "<dt" + rl  + ">" + name + "s:</dt>";
        } else {
            header += "<dt>" + name + ":</dt>";
        }


        for (var i = 0; i < people.length; i++) {
            var pers = people[i];
            if (this.doRDFa) {
                header += "<dd" + re +"><span" + rp + ">";
            } else {
                header += "<dd>";
            }
            if (pers.url) {
                if (this.doRDFa) {
                    header += "<a" + rpu + rn + " content='" + pers.name +  "' href='" + pers.url + "'>" + pers.name + "</a>";
                } else {
                    header += "<a href='" + pers.url + "'>"+ pers.name + "</a>";
                }
            } else {
                header += "<span" + rn + ">" + pers.name + "</span>";
            }
            if (pers.company) {
                header += ", ";
                if (pers.companyURL) {
                    header += "<a" + rwu + " href='" + pers.companyURL + "'>" + pers.company + "</a>";
                } else {
                    header += pers.company;
                }
            }
            if (pers.mailto) {
                header += " <span class='ed_mailto'><a" + rm + " href='mailto:" + pers.mailto + "'>" + pers.mailto + "</a></span> ";
            }
            if (pers.note) {
                header += " ( " + pers.note + " )";
            }
            if (this.doRDFa) {
                header += "</span>\n";
            }
            header += "</dd>\n";
        }
        return header;
    },
    
    makeTAGHeaders:    function () {
        var base = "http://www.w3.org/2001/tag/doc/",
            latestVersion = base + this.shortName,
            thisVersion = latestVersion + "-" + this._concatDate(this.publishDate, "-"),
            header = "<div class='head'><p>" +
                     "<a href='http://www.w3.org/'><img width='72' height='48' src='https://www.w3.org/Icons/w3c_home' alt='W3C'/></a>";
        header += "<h1 class='title' id='title'>" + this.title + "</h1>";
        if (this.subtitle) header += "<h2 id='subtitle'>" + this.subtitle + "</h2>";
        header += "<h2>" + this.status2text[this.specStatus] + " " + this._humanDate(this.publishDate) + "</h2><dl>";
        header += "<dt>This version:</dt><dd><a href='" + thisVersion + "'>" + thisVersion + "</a></dd>\n" + 
                  "<dt>Latest published version:</dt><dd><a href='" + latestVersion + "'>" + latestVersion + "</a></dd>"; 
        if (this.edDraftURI) {
            header += "<dt>Latest editor's draft:</dt><dd><a href='" + this.edDraftURI + "'>" + this.edDraftURI + "</a></dd>";
        }
        if (this.previousPublishDate) {
            var prevVersion = latestVersion + "-" + this._concatDate(this.previousPublishDate, "-");
            header += "<dt>Previous version:</dt><dd><a href='" + prevVersion + "'>" + prevVersion + "</a></dd>"; 
        }
        if(this.editors.length == 0) {
            header += "<dt>" + "Editor" + ":</dt>";
            error("There must be at least one editor.");
        }
        header += this.showPeople("Editor", this.editors);
        header += this.showPeople("Author", this.authors);
        header += "</dl><p class='copyright'>";
        header += 
            "<a href='http://www.w3.org/Consortium/Legal/ipr-notice#Copyright'>Copyright</a> &copy; " ;
        if (this.copyrightStart && this.copyrightStart != this.publishDate.getFullYear()) header += this.copyrightStart + '-';
        header += this.publishDate.getFullYear();
        header += " <a href='http://www.w3.org/'><acronym title='World Wide Web Consortium'>W3C</acronym></a><sup>&reg;</sup> " +
            "(<a href='http://www.csail.mit.edu/'><acronym title='Massachusetts Institute of Technology'>MIT</acronym></a>, " +
            "<a href='http://www.ercim.eu/'><acronym title='European Research Consortium for Informatics and Mathematics'>ERCIM</acronym></a>, " +
            "<a href='http://www.keio.ac.jp/'>Keio</a>), All Rights Reserved. " +
            "W3C <a href='http://www.w3.org/Consortium/Legal/ipr-notice#Legal_Disclaimer'>liability</a>, " + 
            "<a href='http://www.w3.org/Consortium/Legal/ipr-notice#W3C_Trademarks'>trademark</a> and " +
            "<a href='http://www.w3.org/Consortium/Legal/copyright-documents'>document use</a> rules apply." +
            "</p><hr/></div>";
        return header;
    },

    makeNormalHeaders:    function () {
        var mat = (this.status2maturity[this.specStatus]) ? this.status2maturity[this.specStatus] : this.specStatus;
        var thisVersion = "http://www.w3.org/TR/" + this.publishDate.getFullYear() + "/" + mat + "-" +
                          this.shortName + "-" + this._concatDate(this.publishDate) + "/";
        if (this.specStatus == "ED") thisVersion = this.edDraftURI;
        var latestVersion, prevVersion;
        if (this.previousPublishDate) {
            var pmat = (this.status2maturity[this.previousMaturity]) ? this.status2maturity[this.previousMaturity] : this.previousMaturity;
            if (!this.previousURI) {
                this.previousURI = "http://www.w3.org/TR/" + this.previousPublishDate.getFullYear() + "/" + pmat + "-" + this.shortName + "-" + this._concatDate(this.previousPublishDate) + "/";
            }
            if (this.doRDFa) {
                prevVersion = "<a rel='dcterms:replaces' href='" + this.previousURI + "'>" + this.previousURI + "</a>";
            } else {
                prevVersion = "<a href='" + this.previousURI + "'>" + this.previousURI + "</a>";
            }
            // var latestURI = "http://www.w3.org/TR/" + this.shortName + "/";
            // latestVersion = "<a href='" + latestURI + "'>" + latestURI + "</a>";
        }
        else {
            prevVersion = "none";
            // latestVersion = "none";
        }
        var latestURI = "http://www.w3.org/TR/" + this.shortName + "/";
        latestVersion = "<a href='" + latestURI + "'>" + latestURI + "</a>";
        var header = "<div class='head'><p>";
        if (this.specStatus != "unofficial")
            header += "<a href='http://www.w3.org/'><img width='72' height='48' src='https://www.w3.org/Icons/w3c_home' alt='W3C'/></a>";
        if (this.specStatus == 'XGR') 
            header += "<a href='http://www.w3.org/2005/Incubator/XGR/'><img alt='W3C Incubator Report' src='https://www.w3.org/2005/Incubator/images/XGR' height='48' width='160'/></a>";
        if ( this.doRDFa ) {
            header +=
                "<h1 property='dcterms:title' class='title' id='title'>" + this.title + "</h1>" ;
            if (this.subtitle) {
                header += "<h2 property='bibo:subtitle' id='subtitle'>" + this.subtitle + "</h2>" ;
            }
            header +=
                "<h2 property='dcterms:issued' datatype='xsd:dateTime' content='" + this._ISODate(this.publishDate) + "'>" + (this.specStatus == "unofficial" ? "" : "W3C ") + 
                this.status2text[this.specStatus] + " " + this._humanDate(this.publishDate) + "</h2><dl>";
        } else {
            header +=
                "<h1 class='title' id='title'>" + this.title + "</h1>" ;
            if (this.subtitle) {
                header += "<h2 id='subtitle'>" + this.subtitle + "</h2>" ;
            }
            header +=
                "<h2>" + (this.specStatus == "unofficial" ? "" : "W3C ") + 
                this.status2text[this.specStatus] + " " + this._humanDate(this.publishDate) + "</h2><dl>";
        }
        if (!this.isNoTrack) {
            header += "<dt>This version:</dt><dd><a href='" + thisVersion + "'>" + thisVersion + "</a></dd>" + 
                      "<dt>Latest published version:</dt><dd>" + latestVersion + "</dd>"; 
            if (this.edDraftURI) {
                header += "<dt>Latest editor's draft:</dt><dd><a href='" + this.edDraftURI + "'>" + this.edDraftURI + "</a></dd>";
            }
        }
        if (this.testSuiteURI) {
        	header += "<dt>Test suite:</dt><dd><a href='" + this.testSuiteURI + "'>" + this.testSuiteURI + "</a></dd>";
        }
        if (this.implementationReportURI) {
        	header += "<dt>Implementation report:</dt><dd><a href='" + this.implementationReportURI + "'>" + this.implementationReportURI + "</a></dd>";
        }
        if (this.specStatus != "FPWD" && this.specStatus != "FPWD-NOTE" &&
            !this.isNoTrack) {
            if (!this.prevED) {
                header += "<dt>Previous version:</dt><dd>" + prevVersion + "</dd>";
            } else {
                header += "<dt>Previous editor's draft:</dt><dd>" + prevED + "</dd>";
            }
        }

        if (this.prevRecShortname) {
            var prevRecURI = "http://www.w3.org/TR/" + this.prevRecShortname + "/";
            header += "<dt>Latest recommendation:</dt><dd>" + 
                '<a href="' + prevRecURI + '">' + prevRecURI + "</a></dd>";
        }

        if(this.editors.length == 0) {
            header += "<dt>" + "Editor" + ":</dt>";
            error("There must be at least one editor.");
        }
        header += this.showPeople("Editor", this.editors);
        header += this.showPeople("Author", this.authors);
        header += "</dl>";

        if (this.errata) {
            header += '<p>Please refer to the <a href="' + this.errata + '">errata</a> for this document, which may include some normative corrections.</p>';
        }

        if (this.alternateFormats.length > 0) {
            var len = this.alternateFormats.length ;
            if (len == 1) {
                header += '<p>This document is also available in this non-normative format: ';
            } else {
                header += '<p>This document is also available in these non-normative formats: ';
            }
            for (var f = 0; f < len; f++) {
                if (f > 0) {
                    if ( len == 2) {
                        header += ' ';
                    } else {
                        header += ', ' ;
                    }
                    if (f == len - 1) {
                        header += 'and ';
                    }
                }
                var ref = this.alternateFormats[f] ;
                header += "<a href='" + ref.uri + "'>" + ref.label + "</a>" ;
            }
            header += '.</p>';
        }

        if (this.specStatus == "REC")
            header += '<p>The English version of this specification is the only normative version. Non-normative <a href="http://www.w3.org/Consortium/Translation/">translations</a> may also be available.</p>';

        if (this.specStatus == "unofficial") {
            var copyright;
            if (this.additionalCopyrightHolders) copyright = "<p class='copyright'>" + this.additionalCopyrightHolders + "</p>";
            else if (this.overrideCopyright) copyright = this.overrideCopyright;
            else copyright = "<p class='copyright'>This document is licensed under a <a class='subfoot' href='http://creativecommons.org/licenses/by/3.0/' rel='license'>Creative Commons Attribution 3.0 License</a>.</p>";
            header += copyright;
        }
        else {
            if (this.overrideCopyright) {
                header += this.overrideCopyright;
            }
            else {
                header += "<p class='copyright'>";
                if (this.doRDFa) {
                    header += "<a rel='license' href='http://www.w3.org/Consortium/Legal/ipr-notice#Copyright'>Copyright</a> &copy; ";
                }
                else {
                    header += "<a href='http://www.w3.org/Consortium/Legal/ipr-notice#Copyright'>Copyright</a> &copy; ";
                }
                if (this.copyrightStart) {
                    header += this.copyrightStart + '-';
                }
                header += this.publishDate.getFullYear();
                if (this.additionalCopyrightHolders) header += " " + this.additionalCopyrightHolders + " &amp;";
                if (this.doRDFa) {
                    header += " <span rel='dcterms:publisher'><span typeof='foaf:Organization'><a rel='foaf:homepage' property='foaf:name' content='World Wide Web Consotrium' href='http://www.w3.org/'><acronym title='World Wide Web Consortium'>W3C</acronym></a><sup>&reg;</sup></span></span> ";
                } else {
                    header += " <a href='http://www.w3.org/'><acronym title='World Wide Web Consortium'>W3C</acronym></a><sup>&reg;</sup> ";
                }
                header +=
                    "(<a href='http://www.csail.mit.edu/'><acronym title='Massachusetts Institute of Technology'>MIT</acronym></a>, " +
                    "<a href='http://www.ercim.eu/'><acronym title='European Research Consortium for Informatics and Mathematics'>ERCIM</acronym></a>, " +
                    "<a href='http://www.keio.ac.jp/'>Keio</a>), All Rights Reserved. " +
                    "W3C <a href='http://www.w3.org/Consortium/Legal/ipr-notice#Legal_Disclaimer'>liability</a>, " + 
                    "<a href='http://www.w3.org/Consortium/Legal/ipr-notice#W3C_Trademarks'>trademark</a> and " +
                    "<a href='http://www.w3.org/Consortium/Legal/copyright-documents'>document use</a> rules apply.</p>";

            }
        }
        header += "<hr/></div>";
        return header;
    },
    
    makeHeaders:    function () {
        var header;
        if (this.specStatus === "finding" || this.specStatus === "draft-finding") header = this.makeTAGHeaders();
        else header = this.makeNormalHeaders();
        var tmp = sn.element("div");
        tmp.innerHTML = header;
        document.body.insertBefore(tmp.firstChild, document.body.firstChild);
    },
    
    makeAbstract:    function () {
        var abs = document.getElementById("abstract");
        if (!abs) error("Document must have one element with ID 'abstract'");
        var h2 = sn.element("h2", {}, null, "Abstract");
        abs.insertBefore(h2, abs.firstChild);
        sn.addClass(abs, "introductory");
    },
    
    makeSotD:     function () {
        var sotd;
        var mat = (this.status2maturity[this.specStatus]) ? this.status2maturity[this.specStatus] : this.specStatus;
        var custom = document.getElementById("sotd");

        if (this.specStatus == "unofficial") {
            sotd = "<section id='sotd' class='introductory'><h2>Status of This Document</h2>" +
            "<p>This document is merely a public working draft of a potential specification. It has " +
            "no official standing of any kind and does not represent the support or consensus of any " +
            "standards organisation.</p>";
            if (custom) sotd += custom.innerHTML;
            sotd += "</section>";
        }
        else if (this.specStatus === "finding" || this.specStatus === "draft-finding") {
            sotd = "<section id='sotd' class='introductory'><h2>Status of This Document</h2>";
            if (custom) sotd += custom.innerHTML;
            else sotd += "<p style='color: red'>ReSpec does not support automated SotD generation for TAG findings, please specify one using a &lt;section> element with ID=sotd.</p>";
            sotd += "</section>";
        }
        else if (this.isNoTrack) {
            var mc = (this.specStatus == "MO") ? " member-confidential" : "";
            sotd = "<section id='sotd' class='introductory'><h2>Status of This Document</h2>" +
                "<p>This document is merely a W3C-internal" + mc + " document. It has no "+
                "official standing of any kind and does not represent consensus of the W3C Membership.</p>";
            if (custom) sotd += custom.innerHTML;
            sotd += "</section>";
        }
        else {
            var art = "a ";
            if (this.specStatus == "ED" || this.specStatus == "XGR" || this.specStatus == "IG-NOTE") art = "an ";
            sotd = "<section id='sotd' class='introductory'><h2>Status of This Document</h2>" +
                "<p><em>This section describes the status of this document at the time of its publication. Other " +
                "documents may supersede this document. A list of current W3C publications and the latest revision " +
                "of this technical report can be found in the <a href='http://www.w3.org/TR/'>W3C technical reports " +
                "index</a> at http://www.w3.org/TR/.</em></p>";
            if (custom) sotd += custom.innerHTML;
            sotd +=
                "<p>This document was published by the <a href='" + this.wgURI + "'>" + this.wg + "</a> as " + art + this.status2long[this.specStatus] + ".";
            if (this.isRecTrack && this.specStatus != "REC") sotd += " This document is intended to become a W3C Recommendation.";
            sotd +=
                " If you wish to make comments regarding this document, please send them to <a href='mailto:" + this.wgPublicList + "@w3.org'>" + 
                this.wgPublicList + "@w3.org</a> (<a href='mailto:" + this.wgPublicList + "-request@w3.org?subject=subscribe'>subscribe</a>, " +
                "<a href='http://lists.w3.org/Archives/Public/" + this.wgPublicList + "/'>archives</a>).";
            if (this.specStatus == "LC") sotd += " The Last Call period ends " + this._humanDate(this.lcEnd) + ".";
            if (this.specStatus == "CR") sotd += " W3C publishes a Candidate Recommendation to indicate that the document is believed" +
                                                 " to be stable and to encourage implementation by the developer community. This" +
                                                 " Candidate Recommendation is expected to advance to Proposed Recommendation no earlier than " +
                                                 this._humanDate(this.crEnd) + ".";
            sotd += " All feedback is welcome.</p>";
            if (this.specStatus != "REC") {
                sotd += "<p>Publication as a " + this.status2text[this.specStatus] + " does not imply endorsement by the W3C Membership. " +
                    "This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate " +
                    "to cite this document as other than work in progress.</p>";
            }
            if (this.specStatus == "LC") 
                sotd += "<p>This is a Last Call Working Draft and thus the Working Group has determined that this document has satisfied the " +
                        "relevant technical requirements and is sufficiently stable to advance through the Technical Recommendation process.</p>";
            sotd +=
                "<p>This document was produced by a group operating under the <a href='http://www.w3.org/Consortium/Patent-Policy-20040205/'>5 February " +
                "2004 W3C Patent Policy</a>.";

			if (!this.isRecTrack && mat == "WD")
				sotd += " The group does not expect this document to become a W3C Recommendation.";
			
			sotd +=
				" W3C maintains a <a href='" + this.wgPatentURI + "' rel='disclosure'>public list of any patent disclosures</a> " +
                "made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An " +
                "individual who has actual knowledge of a patent which the individual believes contains " +
                "<a href='http://www.w3.org/Consortium/Patent-Policy-20040205/#def-essential'>Essential Claim(s)</a> must disclose the " +
                "information in accordance with <a href='http://www.w3.org/Consortium/Patent-Policy-20040205/#sec-Disclosure'>section " +
                "6 of the W3C Patent Policy</a>.</p>";
            if (this.addPatentNote) sotd += "<p>" + this.addPatentNote + "</p>";
            sotd += "</section>";
        }
        if (custom) custom.parentNode.removeChild(custom);

        var tmp = sn.element("div");
        tmp.innerHTML = sotd;
        var abs = document.getElementById("abstract");
        abs.parentNode.insertBefore(tmp.firstChild, abs.nextSibling);
    },

    makeConformance:    function () {
        var confo = document.getElementById("conformance");
        if (!confo) return;
        var dummy = sn.element("div");
        if (confo.childNodes.length > 0) sn.copyChildren(confo, dummy);
        sn.element("h2", {}, confo, "Conformance");
        confo.innerHTML += "<p>As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, " +
                           "and notes in this specification are non-normative. Everything else in this specification is " +
                           "normative.</p>\n<p>The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, RECOMMENDED, MAY, " +
                           "and OPTIONAL in this specification are to be interpreted as described in [[!RFC2119]].</p>\n";
        sn.copyChildren(dummy, confo);
    },

    informative:    function () {
        var secs = document.querySelectorAll("section.informative");
        for (var i = 0; i < secs.length; i++) {
            var sec = secs[i];
            var p = sn.element("p");
            sn.element("em", {}, p, "This section is non-normative.");
            sec.insertBefore(p, sec.firstElementChild.nextSibling);
        }
    },

    examples:    function () {
        // reindent
        var exes = document.querySelectorAll("pre.example");
        for (var i = 0; i < exes.length; i++) {
            var ex = exes[i];
            var lines = ex.innerHTML.split("\n");
            while (lines.length && /^\s*$/.test(lines[0])) lines.shift();
            while (/^\s*$/.test(lines[lines.length - 1])) lines.pop();
            var matches = /^(\s+)/.exec(lines[0]);
            if (matches) {
                var rep = new RegExp("^" + matches[1]);
                for (var j = 0; j < lines.length; j++) {
                    lines[j] = lines[j].replace(rep, "");
                }
            }
            ex.innerHTML = lines.join("\n");
        }
        // highlight
        sh_highlightDocument(this.base + "js/lang/", ".min.js");
    },

    fixHeaders:    function () {
        var secs = document.querySelectorAll("section > h1:first-child, section > h2:first-child, section > h3:first-child, section > h4:first-child, section > h5:first-child, section > h6:first-child");
        for (var i = 0; i < secs.length; i++) {
            var sec = secs[i];
            var depth = sn.findNodes("ancestor::x:section|ancestor::section", sec).length + 1;
            if (depth > 6) depth = 6;
            var h = "h" + depth;
            if (sec.localName.toLowerCase() != h) sn.renameEl(sec, h);
        }
    },
    
    makeTOC:    function () {
        var ul = this.makeTOCAtLevel(document.body, [0], 1);
        if (!ul) return;
        var sec = sn.element("section", { id: "toc" });
        sn.element("h2", { "class": "introductory" }, sec, "Table of Contents");
        sec.appendChild(ul);
        document.body.insertBefore(sec, document.getElementById("sotd").nextSibling);
    },
    
    appendixMode:   false,
    lastNonAppendix:    0,
    alphabet:   "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    makeTOCAtLevel:    function (parent, current, level) {
        var xpath = this.tocIntroductory ? "./x:section|./section" :
                                           "./x:section[not(@class='introductory')]|./section[not(@class='introductory')]"
        var secs = sn.findNodes(xpath, parent);
        if (secs.length == 0) return null;
        var ul = sn.element("ul", { "class": "toc" });
        for (var i = 0; i < secs.length; i++) {
            var sec = secs[i],
                isIntro = sn.hasClass(sec, "introductory");
            if (!sec.childNodes.length) continue;
            var h = sec.firstElementChild;
            var ln = h.localName.toLowerCase();
            if (ln != "h2" && ln != "h3" && ln != "h4" && ln != "h5" && ln != "h6") continue;
            var title = h.textContent;
            var hKids = sn.documentFragment();
            for (var j = 0; j < h.childNodes.length; j++) {
                var node = h.childNodes[j].cloneNode(true);
                hKids.appendChild(node);
                if (node.nodeType == Node.ELEMENT_NODE) {
                    var ln = node.localName.toLowerCase();
                    if (ln == "a") {
                        node = sn.renameEl(node, "span");
                        var cl = node.getAttribute("class");
                        if (!cl) cl = "";
                        else cl = " " + cl;
                        // node.setAttribute("class", "formerLink" + cl);
                        sn.addClass(node, "formerLink" + cl);
                        node.removeAttribute("href");
                    }
                    else if (ln == "dfn") {
                        node = sn.renameEl(node, "span");
                        node.removeAttribute("id");
                    }
                }
            }
            var id = sn.makeID(sec, null, title);
            if (!isIntro) current[current.length-1]++;
            var secnos = current.slice();
            if (sn.hasClass(sec, "appendix") && current.length == 1 && !this.appendixMode) {
                this.lastNonAppendix = current[0];
                this.appendixMode = true;
            }
            if (this.appendixMode) secnos[0] = this.alphabet.charAt(current[0] - this.lastNonAppendix);
            var secno = secnos.join(".");
            if (!/\./.test(secno)) secno = secno + ".";
            var df = sn.documentFragment();
            if (!isIntro) sn.element("span", { "class": "secno" }, df, secno + " ");
            // sn.text(" ", df);
            var df2 = df.cloneNode(true);
            h.insertBefore(df, h.firstChild);
            // if this is a top level item, insert
            // an OddPage comment so html2ps will correctly
            // paginate the output
            if (/\.$/.test(secno)) {
                var com = document.createComment('OddPage') ;
                h.parentNode.insertBefore(com, h) ;
            }
            // sn.text(title, df2);
            df2.appendChild(hKids);
            var a = sn.element("a", { href: "#" + id, 'class' : 'tocxref' }, null, [df2]);
            var item = sn.element("li", { "class":"tocline" }, ul, [a]);
            
            if (this.maxTocLevel && level >= this.maxTocLevel) continue;
            current.push(0);
            var sub = this.makeTOCAtLevel(sec, current, level + 1);
            if (sub) item.appendChild(sub) ;
            current.pop();
        }
        
        return ul;
    },

    idHeaders:    function () {
        var heads = document.querySelectorAll("h2, h3, h4, h5, h6");
        for (var i = 0; i < heads.length; i++) {
            var h = heads[i];
            if (h.hasAttribute("id")) continue;
            var par = h.parentNode;
            if (par.localName.toLowerCase() == "section" && par.hasAttribute("id") && !h.previousElementSibling) continue;
            sn.makeID(h, null);
        }
    },
    
    // --- INLINE PROCESSING ----------------------------------------------------------------------------------
    inlines:    function () {
        document.normalize();
        
        // PRE-PROCESSING
        var norms = {}, informs = {}, abbrMap = {}, acroMap = {}, badrefs = {};
        var badrefcount = 0;
        var abbrs = document.querySelectorAll("abbr[title]");
        for (var i = 0; i < abbrs.length; i++) abbrMap[abbrs[i].textContent] = abbrs[i].getAttribute("title");
        var acros = document.querySelectorAll("acronym[title]");
        for (var i = 0; i < acros.length; i++) acroMap[acros[i].textContent] = acros[i].getAttribute("title");
        var aKeys = [];
        for (var k in abbrMap) aKeys.push(k);
        for (var k in acroMap) aKeys.push(k);
        aKeys.sort(function (a, b) {
            if (b.length < a.length) return -1;
            if (a.length < b.length) return 1;
            return 0;
        });
        var abbrRx = aKeys.length ? "|(?:\\b" + aKeys.join("\\b)|(?:\\b") + "\\b)" : "";
        var rx = new RegExp("(\\bMUST(?:\\s+NOT)?\\b|\\bSHOULD(?:\\s+NOT)?\\b|\\bSHALL(?:\\s+NOT)?\\b|" + 
                            "\\bMAY\\b|\\b(?:NOT\\s+)?REQUIRED\\b|\\b(?:NOT\\s+)?RECOMMENDED\\b|\\bOPTIONAL\\b|" +
                            "(?:\\[\\[(?:!)?[A-Za-z0-9-]+\\]\\])" +
                            abbrRx + ")");
        // PROCESSING
        var txts = sn.findNodes(".//text()", document.body);
        for (var i = 0; i < txts.length; i++) {
            var txt = txts[i];
            var subtxt = txt.data.split(rx);
            var df = sn.documentFragment();
            while (subtxt.length) {
                var t = subtxt.shift();
                var matched = null;
                if (subtxt.length) matched = subtxt.shift();
                sn.text(t, df);
                if (matched) {
                    // RFC 2129
                    if (/MUST(?:\s+NOT)?|SHOULD(?:\s+NOT)?|SHALL(?:\s+NOT)?|MAY|(?:NOT\s+)?REQUIRED|(?:NOT\s+)?RECOMMENDED|OPTIONAL/.test(matched)) {
                        matched = matched.toLowerCase();
                        sn.element("em", { "class": "rfc2119", title: matched }, df, matched);
                    }
                    // BIBREF
                    else if (/^\[\[/.test(matched)) {
                        var ref = matched;
                        ref = ref.replace(/^\[\[/, "");
                        ref = ref.replace(/]]$/, "");
                        var norm = false;
                        if (ref.indexOf("!") == 0) {
                            norm = true;
                            ref = ref.replace(/^!/, "");
                        }
                        if (berjon.biblio[ref]) {
                            if (norm) norms[ref] = true;
                            else      informs[ref] = true;
                            sn.text("[", df);
                            // embed a cite with an a inside of it
                            var el = sn.element("cite", {} , df);
                            sn.element("a", { "class": "bibref", rel: "biblioentry", href: "#bib-" + ref }, el, ref);
                            sn.text("]", df);
                        }
                        else {
                            badrefcount++;
                            if ( badrefs[ref] ) {
                                badrefs[ref] = badrefs[ref] + 1 ;
                            } else {
                                badrefs[ref] =  1 ;
                            }
                        }
                    }
                    // ABBR
                    else if (abbrMap[matched]) {
                        if (sn.findNodes("ancestor::abbr", txt)) sn.text(matched, df);
                        else sn.element("abbr", { title: abbrMap[matched] }, df, matched);
                    }
                    // ACRO
                    else if (acroMap[matched]) {
                        if (sn.findNodes("ancestor::acronym", txt)) sn.text(matched, df);
                        else sn.element("acronym", { title: acroMap[matched] }, df, matched);
                        // sn.element("acronym", { title: acroMap[matched] }, df, matched);
                    }
                    // FAIL
                    else {
                        error("Found token '" + matched + "' but it does not correspond to anything");
                    }
                }
            }
            txt.parentNode.replaceChild(df, txt);
        }
        
        // POST-PROCESSING
        // bibref
        if(badrefcount > 0) {
            error("Got " + badrefcount + " tokens looking like a reference, not in biblio DB: ");
            for (var item in badrefs) {
                error("Bad ref: " + item + ", count = " + badrefs[item]);
            }
        }

        var del = [];
        for (var k in informs) {
            if (norms[k]) del.push(k);
        }
        for (var i = 0; i < del.length; i++) delete informs[del[i]];

        var refsec = sn.element("section", { id: "references", "class": "appendix" }, document.body);
        sn.element("h2", {}, refsec, "References");
        if (this.refNote) { 
            var refnote = sn.element("p", {}, refsec);
            refnote.innerHTML= this.refNote;
        }

        var types = ["Normative", "Informative"];
        for (var i = 0; i < types.length; i++) {
            var type = types[i];
            var refMap = (type == "Normative") ? norms : informs;
            var sec = sn.element("section", {}, refsec);
            sn.makeID(sec, null, type + " references");
            sn.element("h3", {}, sec, type + " references");
            var refs = [];
            for (var k in refMap) refs.push(k);
            refs.sort();
            if (refs.length) {
                var dl = sn.element("dl", { "class": "bibliography" }, sec);
                if (this.doRDFa) {
                    dl.setAttribute('about', '') ;
                }
                for (var j = 0; j < refs.length; j++) {
                    var ref = refs[j];
                    sn.element("dt", { id: "bib-" + ref }, dl, "[" + ref + "]");
                    var dd = sn.element("dd", {}, dl);
                    if (this.doRDFa) {
                        if (type == 'Normative') {
                            dd.setAttribute('rel','dcterms:requires');
                        } else {
                            dd.setAttribute('rel','dcterms:references');
                        }
                    }
                    if (berjon.biblio[ref]) dd.innerHTML = berjon.biblio[ref] + "\n";
                }
            }
            else {
                sn.element("p", {}, sec, "No " + type.toLowerCase() + " references.");
            }
        }
        
    },
        
    dfn:    function () {
        document.normalize();
        var dfnMap = {};
        var dfns = document.querySelectorAll("dfn");
        for (var i = 0; i < dfns.length; i++) {
            var dfn = dfns[i];
            var title = this._getDfnTitle(dfn);
            dfnMap[title.toLowerCase()] = sn.makeID(dfn, "dfn", title);
        }
        
        var ants = document.querySelectorAll("a:not([href])");
        for (var i = 0; i < ants.length; i++) {
            var ant = ants[i];
            // if (ant.getAttribute("class") == "externalDFN") continue;
            if (sn.hasClass(ant, "externalDFN")) continue;
            var title = this._getDfnTitle(ant).toLowerCase();
            if (dfnMap[title] && !(dfnMap[title] instanceof Function)) {
                ant.setAttribute("href", "#" + dfnMap[title]);
                // ant.setAttribute("class", "internalDFN");
                sn.addClass(ant, "internalDFN");
            }
            else {
                // we want to use these for other links too
                // error("No definition for title: " + title);
            }
        }
    },

    doBestPractices: function () {
        this.practiceNum = 1;
        var spans = document.querySelectorAll("span.practicelab");
        var contents = "<h2>Best Practices Summary</h2><ul>"
        // scan all the best practices to number them and add handle
        // at same time generate summary section contents if section
        // is provided in source, using links if possible
        //
        // probably not the most efficient here but only used for best
        // practices document
        for (var i = 0; i < spans.length; i++) {
            var span = spans[i];
            var label = span.innerHTML;
            var ref = span.getAttribute("id");
            var handle = "Best Practice " + this.practiceNum;
            var content =  ": " + label;
            var item = handle + content;
            if(!ref) {
                contents += "<li>" + handle + content + "</li>";
            } else {
                contents += "<li><a href='#" + ref + "'>" + handle +
                    "</a>" + content + "</li>";
            }
            span.innerHTML = item;
            this.practiceNum++;
        }
        contents += "</ul>";

        var sec = document.getElementById("bp-summary");
        if(!sec) {
//             alert("no bp-summary section");
            return;
        }
        sec.innerHTML = contents;
    },
    
    //  <link href="section id" class="sectionRef" />

//     returnObjById: function( id ) 
//     { 
//     if (document.getElementById) 
//         var returnVar = document.getElementById(id); 
//     else if (document.all) 
//         var returnVar = document.all[id]; 
//     else if (document.layers) 
//         var returnVar = document.layers[id]; 
//     return returnVar; 
//     }, 

    makeSectionRefs: function () {
        var secrefs = document.querySelectorAll("a.sectionRef");
        for (var i = 0; i < secrefs.length; i++) {
            var secref = secrefs[i];

            // get the link href and section title
            var h = secref.getAttribute('href');
            var id = h.substring(1);

            var sec = document.getElementById(id);
            var secno = "Not found"+ id;
            
            if(sec) {
                var span = sec.firstElementChild;

                if(span) {
                    secno = span.textContent;
                }
            }

            title = "section " + secno;

            // create new a reference to section using section title
            secref.innerHTML = title;
        }
    },
    
    // --- WEB IDL --------------------------------------------------------------------------------------------
    webIDL:    function () {
        var idls = document.querySelectorAll(".idl"); // should probably check that it's not inside one
        var infNames = [];
        for (var i = 0; i < idls.length; i++) {
            var idl = idls[i];
            var w = new berjon.WebIDLProcessor({ noIDLSorting: this.noIDLSorting, noIDLIn: this.noIDLIn });
            var inf = w.definition(idl);
            var df = w.makeMarkup();
            idl.parentNode.replaceChild(df, idl);
            if (inf.type == "interface" || inf.type == "exception" || inf.type == "dictionary") infNames.push(inf.id);
        }
        document.normalize();
        var ants = document.querySelectorAll("a:not([href])");
        for (var i = 0; i < ants.length; i++) {
            var ant = ants[i];
            if (sn.hasClass(ant, "externalDFN")) continue;
            var name = ant.textContent;
            if (infNames.indexOf(name) >= 0) {
                ant.setAttribute("href", "#idl-def-" + name);
                // ant.setAttribute("class", "idlType");
                sn.addClass(ant, "idlType");
                ant.innerHTML = "<code>" + name + "</code>";
            }
        }
    },

    // --- CLEANUP --------------------------------------------------------------------------------------------
    removeRespec:    function () {
        var rs = document.querySelectorAll(".remove");
        for (var i = 0; i < rs.length; i++) rs[i].parentNode.removeChild(rs[i]);
    },
    
    unHTML5:    function () {
        var secs = document.querySelectorAll("section");
        for (var i = 0; i < secs.length; i++) {
            var sec = secs[i];
            var div = sn.renameEl(sec, "div");
            // div.setAttribute("class", "section"); // XXX that kills previous class, may not be a problem
            sn.addClass(div, "section");
        }
    },
    
    // --- HELPERS --------------------------------------------------------------------------------------------
    _insertCSS:    function (css, inlined) {
        // if (document.createStyleSheet) return document.createStyleSheet(css);
        if (inlined) {
            try {
                // this is synchronous because order of the CSS matters (if though PubRules doesn't detect this
                // correctly :-) If it's slow, turn off inlineCSS during development
                var xhr = new XMLHttpRequest();
                xhr.open("GET", css, false);
                // xhr.onreadystatechange = function () {
                //     if (this.readyState == 4) {
                //         if (this.status == 200) {
                //             sn.element("style", { type: "text/css" }, document.documentElement.firstElementChild, this.responseText);
                //         }
                //         else {
                //             error("There appear to have been a problem fetching the style sheet; status=" + this.status);
                //         }
                //     }
                // };
                xhr.send(null);
                if (xhr.status == 200) {
                    sn.element("style", { type: "text/css" }, document.documentElement.firstElementChild, xhr.responseText);
                }
                else {
                    error("There appears to have been a problem fetching the style sheet; status=" + xhr.status);
                }
            }
            catch (e) {
                // warning("There was an error with the request, probably that you're working from disk.");
                this._insertCSS(css, false);
            }
        }
        else {
            sn.element("link", {
                href:       css,
                rel:        "stylesheet",
                type:       "text/css",
                charset:    "utf-8"
            }, document.documentElement.firstElementChild);
        }
    },

    _readFile:    function (URI) {
            try {
                var xhr = new XMLHttpRequest();
                xhr.open("GET", URI, false);
                xhr.send(null);
                if (xhr.status == 200) {
                    return xhr.responseText ;
                } else {
                    error("There appears to have been a problem fetching the file " + URI + "; status=" + xhr.status);
                }
            }
            catch (e) {
                warning("There was an error with the request to load " + URI + ", probably that you're working from disk.");
            }
    },

    _humanMonths:   ["January", "February", "March", "April", "May", "June", "July",
                     "August", "September", "October", "November", "December"],
    _humanDate:    function (date) {
        return this._lead0(date.getDate()) + " " + this._humanMonths[date.getMonth()] + " " + date.getFullYear();
    },
    
    _concatDate:    function (date, sep) {
        if (!sep) sep = "";
        return "" + date.getFullYear() + sep + this._lead0(date.getMonth() + 1) + sep + this._lead0(date.getDate());
    },

    _ISODate:       function (date) {
        return "" + date.getUTCFullYear() +'-'+ this._lead0(date.getUTCMonth() + 1)+'-' + this._lead0(date.getUTCDate()) +'T'+this._lead0(date.getUTCHours())+':'+this._lead0(date.getUTCMinutes()) +":"+this._lead0(date.getUTCSeconds())+'+0000';
    },
    
    _parseDate:    function (str) {
        return new Date(str.substr(0, 4), (str.substr(5, 2) - 1), str.substr(8, 2));
    },

    _parseLastModified:    function (str) {
        if (!str) return new Date();
        return new Date(Date.parse(str));
        // return new Date(str.substr(6, 4), (str.substr(0, 2) - 1), str.substr(3, 2));
    },
    
    
    _lead0:    function (str) {
        str = "" + str;
        return (str.length == 1) ? "0" + str : str;
    },
    
    _getDfnTitle:    function (dfn) {
        var title;
        if (dfn.hasAttribute("title")) title = dfn.getAttribute("title");
        else if (dfn.childNodes.length == 1 && dfn.firstChild.nodeType == Node.ELEMENT_NODE && 
                 (dfn.firstChild.localName.toLowerCase() == "abbr" || dfn.firstChild.localName.toLowerCase() == "acronym") &&
                 dfn.firstChild.hasAttribute("title")) title = dfn.firstChild.getAttribute("title");
        else title = dfn.textContent;
        title = this._norm(title);
        return title;
    },
    
    _norm:    function (str) {
        str = str.replace(/^\s+/, "").replace(/\s+$/, "");
        return str.split(/\s+/).join(" ");
    },
    
    _esc:    function (s) {
        s = s.replace(/&/g,'&amp;');
        s = s.replace(/>/g,'&gt;');
        s = s.replace(/"/g,'&quot;');
        s = s.replace(/</g,'&lt;');
        return s;
    }
};

berjon.WebIDLProcessor = function (cfg) {
    this.parent = { type: "module", id: "outermost", children: [] };
    if (!cfg) cfg = {};
    for (var k in cfg) this[k] = cfg[k];
};
berjon.WebIDLProcessor.prototype = {
    definition:    function (idl) {
        var def = { children: [] };
        var str = idl.getAttribute("title");
        str = this.parseExtendedAttributes(str, def);
        if      (str.indexOf("interface") == 0 || str.indexOf("partial") === 0) this.interface(def, str, idl);
        else if (str.indexOf("exception") == 0) this.exception(def, str, idl);
        else if (str.indexOf("dictionary") == 0) this.dictionary(def, str, idl);
        else if (str.indexOf("typedef") == 0)   this.typedef(def, str, idl);
        else if (/\bimplements\b/.test(str))     this.implements(def, str, idl);
        else    error("Expected definition, got: " + str);
        this.parent.children.push(def); // this should be done at the caller level
        this.processMembers(def, idl);
        return def;
    },
    
    interface:  function (inf, str, idl) {
        inf.type = "interface";
        var match = /^\s*(partial\s+)?interface\s+([A-Za-z][A-Za-z0-9]*)(?:\s+:\s*([^{]+)\s*)?/.exec(str);
        if (match) {
            inf.partial = !!match[1];
            inf.id = match[2];
            inf.refId = this._id(inf.id);
            if (idl.getAttribute('data-merge')) {
                inf.merge = [];
                var merge = idl.getAttribute('data-merge').split(' ');
                for (var i = 0; i < merge.length; i++) inf.merge.push(merge[i]);
            }
            if (match[3]) inf.superclasses = match[3].split(/\s*,\s*/);
        }
        else {
            error("Expected interface, got: " + str);
        }
        return inf;
    },
    
    dictionary:  function (inf, str, idl) {
        inf.type = "dictionary";
        var match = /^\s*dictionary\s+([A-Za-z][A-Za-z0-9]*)(?:\s+:\s*([^{]+)\s*)?/.exec(str);
        if (match) {
            inf.id = match[1];
            inf.refId = this._id(inf.id);
            if (match[2]) inf.superclasses = match[2].split(/\s*,\s*/);
        }
        else {
            error("Expected dictionary, got: " + str);
        }
        return inf;
    },
    
    exception:  function (exc, str, idl) {
        exc.type = "exception";
        var match = /^\s*exception\s+([A-Za-z][A-Za-z0-9]*)\s*/.exec(str);
        if (match) {
            exc.id = match[1];
            exc.refId = this._id(exc.id);
        }
        else error("Expected exception, got: " + str);
        return exc;
    },
    
    typedef:    function (tdf, str, idl) {
        tdf.type = "typedef";
        tdf.extendedAttributes = null; // remove them in case some were there by mistake
        var match = /^\s*typedef\s+(.+)\s+(\S+)\s*$/.exec(str);
        if (match) {
            var type = match[1];
            tdf.nullable = false;
            if (/\?$/.test(type)) {
                type = type.replace(/\?$/, "");
                tdf.nullable = true;
            }
            tdf.array = false;
            if (/\[\]$/.test(type)) {
                type = type.replace(/\[\]$/, "");
                tdf.array = true;
            }
            tdf.datatype = type;
            tdf.id = match[2];
            tdf.refId = this._id(tdf.id);
            tdf.description = sn.documentFragment();
            sn.copyChildren(idl, tdf.description);
        }
        else {
            error("Expected typedef, got: " + str);
        }
        return tdf;
    },
    
    implements: function (imp, str, idl) {
        imp.type = "implements";
        imp.extendedAttributes = null; // remove them in case some were there by mistake
        var match = /^\s*(.+?)\s+implements\s+(.+)\s*$/.exec(str);
        if (match) {
            imp.id = match[1];
            imp.refId = this._id(imp.id);
            imp.datatype = match[2];
            imp.description = sn.documentFragment();
            sn.copyChildren(idl, imp.description);
        }
        else {
            error("Expected implements, got: " + str);
        }
        return imp;
    },
    
    processMembers:    function (obj, el) {
        var exParent = this.parent;
        this.parent = obj;
        var dts = sn.findNodes("./dt", el);
        for (var i = 0; i < dts.length; i++) {
            var dt = dts[i];
            var dd = dt.nextElementSibling; // we take a simple road
            var mem;
            if (obj.type == "exception") {
                mem = this.exceptionMember(dt, dd);
            }
            else if (obj.type == "dictionary") {
                mem = this.dictionaryMember(dt, dd);
            }
            else {
                mem = this.interfaceMember(dt, dd);
            }
            obj.children.push(mem);
        }
        this.parent = exParent;
    },
    
    parseConst:    function (mem, str) {
        // CONST
        var match = /^\s*const\s+\b([^=]+\??)\s+([^=\s]+)\s*=\s*(.*)$/.exec(str);
        if (match) {
            mem.type = "constant";
            var type = match[1];
            mem.nullable = false;
            if (/\?$/.test(type)) {
                type = type.replace(/\?$/, "");
                mem.nullable = true;
            }
            mem.datatype = type;
            mem.id = match[2];
            mem.refId = this._id(mem.id);
            mem.value = match[3];
            return true;
        }
        return false;
    },
    
    exceptionMember:    function (dt, dd) {
        var mem = { children: [] };
        var str = this._norm(dt.textContent);
        mem.description = sn.documentFragment();
        sn.copyChildren(dd, mem.description);
        str = this.parseExtendedAttributes(str, mem);
    
        if (this.parseConst(mem, str)) return mem;

        // FIELD
        var match = /^\s*(.*?)\s+(\S+)\s*$/.exec(str);
        if (match) {
            mem.type = "field";
            var type = match[1];
            mem.nullable = false;
            if (/\?$/.test(type)) {
                type = type.replace(/\?$/, "");
                mem.nullable = true;
            }
            mem.array = false;
            if (/\[\]$/.test(type)) {
                type = type.replace(/\[\]$/, "");
                mem.array = true;
            }
            mem.datatype = type;
            mem.id = match[2];
            mem.refId = this._id(mem.id);
            return mem;
        }

        // NOTHING MATCHED
        error("Expected exception member, got: " + str);
    },
    
    dictionaryMember:    function (dt, dd) {
        var mem = { children: [] };
        var str = this._norm(dt.textContent);
        mem.description = sn.documentFragment();
        sn.copyChildren(dd, mem.description);
        str = this.parseExtendedAttributes(str, mem);
    
        // MEMBER
        var match = /^\s*([^=]+\??)\s+([^=\s]+)(?:\s*=\s*(.*))?$/.exec(str);
        // var match = /^\s*(.*?)\s+(\S+)\s*$/.exec(str);
        if (match) {
            mem.type = "member";
            var type = match[1];
            mem.id = match[2];
            mem.refId = this._id(mem.id);
            mem.defaultValue = match[3];
            mem.nullable = false;
            if (/\?$/.test(type)) {
                type = type.replace(/\?$/, "");
                mem.nullable = true;
            }
            mem.array = false;
            if (/\[\]$/.test(type)) {
                type = type.replace(/\[\]$/, "");
                mem.array = true;
            }
            mem.datatype = type;
            return mem;
        }

        // NOTHING MATCHED
        error("Expected dictionary member, got: " + str);
    },
    
    interfaceMember:    function (dt, dd) {
        var mem = { children: [] };
        var str = this._norm(dt.textContent);
        var extPrm = (sn.findNodes("dl[@class='parameters']", dd))[0];
        var excepts = sn.findNodes("*[@class='exception']", dd);
        var hadId = false;
        if (dd.id) hadId = true;
        else dd.id = "temporaryIDJustForCSS";
        dd.refId = this._id(dd.id);
        // var sgrs = sn.findNodes("*[@class='setraises' or @class='getraises' or]", dd);
        var sgrs = document.querySelectorAll("#" + dd.id + " .getraises, #" + dd.id + " .setraises");
        if (!hadId) dd.removeAttribute("id");
        mem.description = sn.documentFragment();
        sn.copyChildren(dd, mem.description);
        str = this.parseExtendedAttributes(str, mem);
        var match;
        
        // ATTRIBUTE
        match = /^\s*(?:(readonly)\s+)?attribute\s+\b(.*?)\s+(\S+)\s*$/.exec(str);
        if (match) {
            mem.type = "attribute";
            mem.readonly = (match[1] == "readonly");
            var type = match[2];
            mem.nullable = false;
            if (/\?$/.test(type)) {
                type = type.replace(/\?$/, "");
                mem.nullable = true;
            }
            mem.array = false;
            if (/\[\]$/.test(type)) {
                type = type.replace(/\[\]$/, "");
                mem.array = true;
            }
            mem.datatype = type;
            mem.id = match[3];
            mem.refId = this._id(mem.id);
            mem.raises = [];
            if (sgrs.length) {
                for (var i = 0; i < sgrs.length; i++) {
                    var el = sgrs[i];
                    var exc = {
                        id:     el.getAttribute("title"),
                        onSet:  sn.hasClass(el, "setraises"),
                        onGet:  sn.hasClass(el, "getraises"),
                    };
                    if (el.localName.toLowerCase() == "dl") {
                        exc.type = "codelist";
                        exc.description = [];
                        var dts = sn.findNodes("./dt", el);
                        for (var j = 0; j < dts.length; j++) {
                            var dt = dts[j];
                            var dd = dt.nextElementSibling;
                            var c = { id: dt.textContent, description: sn.documentFragment() };
                            sn.copyChildren(dd, c.description);
                            exc.description.push(c);
                        }
                    }
                    else if (el.localName.toLowerCase() == "div") {
                        exc.type = "simple";
                        exc.description = sn.documentFragment();
                        sn.copyChildren(el, exc.description);
                    }
                    else {
                        error("Do not know what to do with exceptions being raised defined outside of a div or dl.");
                    }
                    el.parentNode.removeChild(el);
                    mem.raises.push(exc);
                }
            }
            
            return mem;
        }
            
        if (this.parseConst(mem, str)) return mem;
            
        // METHOD
        match = /^\s*\b(.*?)\s+\b(\S+)\s*\(\s*(.*)\s*\)\s*$/.exec(str);
        if (match) {
            mem.type = "method";
            var type = match[1];
            mem.nullable = false;
            if (/\?$/.test(type)) {
                type = type.replace(/\?$/, "");
                mem.nullable = true;
            }
            mem.array = false;
            if (/\[\]$/.test(type)) {
                type = type.replace(/\[\]$/, "");
                mem.array = true;
            }
            mem.datatype = type;
            mem.id = match[2];
            mem.refId = this._id(mem.id);
            mem.params = [];
            var prm = match[3];
            mem.raises = [];
            
            if (excepts.length) {
                for (var i = 0; i < excepts.length; i++) {
                    var el = excepts[i];
                    var exc = { id: el.getAttribute("title") };
                    if (el.localName.toLowerCase() == "dl") {
                        exc.type = "codelist";
                        exc.description = [];
                        var dts = sn.findNodes("./dt", el);
                        for (var j = 0; j < dts.length; j++) {
                            var dt = dts[j];
                            var dd = dt.nextElementSibling;
                            var c = { id: dt.textContent, description: sn.documentFragment() };
                            sn.copyChildren(dd, c.description);
                            exc.description.push(c);
                        }
                    }
                    else if (el.localName.toLowerCase() == "div") {
                        exc.type = "simple";
                        exc.description = sn.documentFragment();
                        sn.copyChildren(el, exc.description);
                    }
                    else {
                        error("Do not know what to do with exceptions being raised defined outside of a div or dl.");
                    }
                    el.parentNode.removeChild(el);
                    mem.raises.push(exc);
                }
            }

            if (extPrm) {
                extPrm.parentNode.removeChild(extPrm);
                var dts = sn.findNodes("./dt", extPrm);
                for (var i = 0; i < dts.length; i++) {
                    var dt = dts[i];
                    var dd = dt.nextElementSibling; // we take a simple road
                    var prm = dt.textContent;
                    var p = {};
                    prm = this.parseExtendedAttributes(prm, p);
                    var match = /^\s*\b(.+?)\s+([^\s]+)\s*$/.exec(prm);
                    if (match) {
                        var type = match[1];
                        p.nullable = false;
                        if (/\?$/.test(type)) {
                            type = type.replace(/\?$/, "");
                            p.nullable = true;
                        }
                        p.array = false;
                        if (/\[\]$/.test(type)) {
                            type = type.replace(/\[\]$/, "");
                            p.array = true;
                        }
                        p.datatype = type;
                        p.id = match[2];
                        p.refId = this._id(p.id);
                        p.description = sn.documentFragment();
                        sn.copyChildren(dd, p.description);
                        mem.params.push(p);
                    }
                    else {
                        error("Expected parameter definition, got: " + prm);
                        break;
                    }
                }
            }
            else {
                while (prm.length) {
                    var p = {};
                    prm = this.parseExtendedAttributes(prm, p);
                    // either up to end of string, or up to ,
                    var re = /^\s*(?:in\s+)?\b([^,]+)\s+\b([^,\s]+)\s*(?:,)?\s*/;
                    var match = re.exec(prm);
                    if (match) {
                        prm = prm.replace(re, "");
                        var type = match[1];
                        p.nullable = false;
                        if (/\?$/.test(type)) {
                            type = type.replace(/\?$/, "");
                            p.nullable = true;
                        }
                        p.array = false;
                        if (/\[\]$/.test(type)) {
                            type = type.replace(/\[\]$/, "");
                            p.array = true;
                        }
                        p.datatype = type;
                        p.id = match[2];
                        p.refId = this._id(p.id);
                        mem.params.push(p);
                    }
                    else {
                        error("Expected parameter list, got: " + prm);
                        break;
                    }
                }
            }
            
            // apply optional
            var isOptional = false;
            for (var i = 0; i < mem.params.length; i++) {
                var p = mem.params[i];
                var pkw = p.datatype.split(/\s+/);
                var idx = pkw.indexOf("optional");
                if (idx > -1) {
                    isOptional = true;
                    pkw.splice(idx, 1);
                    p.datatype = pkw.join(" ");
                }
                p.optional = isOptional;
            }
            
            return mem;
        }

        // NOTHING MATCHED
        error("Expected interface member, got: " + str);
    },
    
    parseExtendedAttributes:    function (str, obj) {
        str = str.replace(/^\s*\[([^\]]+)\]\s+/, function (x, m1) { obj.extendedAttributes = m1; return ""; });
        return str;
    },
    
    makeMarkup:    function () {
        var df = sn.documentFragment();
        var pre = sn.element("pre", { "class": "idl" }, df);
        pre.innerHTML = this.writeAsWebIDL(this.parent, 0);
        df.appendChild(this.writeAsHTML(this.parent));
        return df;
    },

    writeAsHTML:    function (obj) {
        if (obj.type == "module") {
            if (obj.id == "outermost") {
                if (obj.children.length > 1) error("We currently only support one structural level per IDL fragment");
                return this.writeAsHTML(obj.children[0]);
            }
            else {
                warning("No HTML can be generated for module definitions.");
                return sn.element("span");
            }
        }
        else if (obj.type == "typedef") {
            var cnt;
            if (obj.description && obj.description.childNodes.length) {
                cnt = [obj.description];
            }
            else {
                // yuck -- should use a single model...
                var tdt = sn.element("span", { "class": "idlTypedefType" }, null);
                tdt.innerHTML = this.writeDatatype(obj.datatype);
                cnt = [ sn.text("Throughout this specification, the identifier "),
                        sn.element("span", { "class": "idlTypedefID" }, null, obj.id),
                        sn.text(" is used to refer to the "),
                        sn.text(obj.array ? "array of " : ""),
                        tdt,
                        sn.text(obj.nullable ? " (nullable)" : ""),
                        sn.text(" type.")];
            }
            return sn.element("div", { "class": "idlTypedefDesc" }, null, cnt);
        }
        else if (obj.type == "implements") {
            var cnt;
            if (obj.description && obj.description.childNodes.length) {
                cnt = [obj.description];
            }
            else {
                cnt = [ sn.text("All instances of the "),
                        sn.element("code", {}, null, [sn.element("a", {}, null, obj.id)]),
                        sn.text(" type are defined to also implement the "),
                        sn.element("a", {}, null, obj.datatype),
                        sn.text(" interface.")];
                cnt = [sn.element("p", {}, null, cnt)];
            }
            return sn.element("div", { "class": "idlImplementsDesc" }, null, cnt);
        }

        else if (obj.type == "exception") {
            var df = sn.documentFragment();
            var curLnk = "widl-" + obj.refId + "-";
            var types = ["field", "constant"];
            for (var i = 0; i < types.length; i++) {
                var type = types[i];
                var things = obj.children.filter(function (it) { return it.type == type });
                if (things.length == 0) continue;
                if (!this.noIDLSorting) {
                    things.sort(function (a, b) {
                        if (a.id < b.id) return -1;
                        if (a.id > b.id) return 1;
                          return 0;
                    });
                }
                
                var sec = sn.element("section", {}, df);
                var secTitle = type;
                secTitle = secTitle.substr(0, 1).toUpperCase() + secTitle.substr(1) + "s";
                sn.element("h2", {}, sec, secTitle);
                var dl = sn.element("dl", { "class": type + "s" }, sec);
                for (var j = 0; j < things.length; j++) {
                    var it = things[j];
                    var dt = sn.element("dt", { id: curLnk + it.refId }, dl);
                    sn.element("code", {}, dt, it.id);
                    var desc = sn.element("dd", {}, dl, [it.description]);
                    if (type == "field") {
                        sn.text(" of type ", dt);
                        if (it.array) sn.text("array of ", dt);
                        var span = sn.element("span", { "class": "idlFieldType" }, dt);
                        var matched = /^sequence<(.+)>$/.exec(it.datatype);
                        if (matched) {
                            sn.text("sequence<", span);
                            sn.element("a", {}, span, matched[1]);
                            sn.text(">", span);
                        }
                        else {
                            sn.element("a", {}, span, it.datatype);
                        }
                        if (it.nullable) sn.text(", nullable", dt);
                    }
                    else if (type == "constant") {
                        sn.text(" of type ", dt);
                        sn.element("span", { "class": "idlConstType" }, dt, [sn.element("a", {}, null, it.datatype)]);
                        if (it.nullable) sn.text(", nullable", dt);
                    }
                }
            }
            return df;
        }

        else if (obj.type == "dictionary") {
            var df = sn.documentFragment();
            var curLnk = "widl-" + obj.refId + "-";
            var things = obj.children;
            if (things.length == 0) return df;
            if (!this.noIDLSorting) {
                things.sort(function (a, b) {
                    if (a.id < b.id) return -1;
                    if (a.id > b.id) return 1;
                      return 0;
                });
            }
            
            var sec = sn.element("section", {}, df);
            cnt = [sn.text("Dictionary "),
                   sn.element("a", { "class": "idlType" }, null, obj.id),
                   sn.text(" Members")];
            sn.element("h2", {}, sec, cnt);
            var dl = sn.element("dl", { "class": "dictionary-members" }, sec);
            for (var j = 0; j < things.length; j++) {
                var it = things[j];
                var dt = sn.element("dt", { id: curLnk + it.refId }, dl);
                sn.element("code", {}, dt, it.id);
                var desc = sn.element("dd", {}, dl, [it.description]);
                sn.text(" of type ", dt);
                if (it.array) sn.text("array of ", dt);
                var span = sn.element("span", { "class": "idlMemberType" }, dt);
                var matched = /^sequence<(.+)>$/.exec(it.datatype);
                if (matched) {
                    sn.text("sequence<", span);
                    sn.element("a", {}, span, matched[1]);
                    sn.text(">", span);
                }
                else {
                    sn.element("a", {}, span, it.datatype);
                }
                if (it.nullable) sn.text(", nullable", dt);
                if (it.defaultValue) {
                    sn.text(", defaulting to ", dt);
                    sn.element("code", {}, dt, [sn.text(it.defaultValue)]);
                }
            }
            return df;
        }

        else if (obj.type == "interface") {
            var df = sn.documentFragment();
            var curLnk = "widl-" + obj.refId + "-";
            var types = ["attribute", "method", "constant"];
            for (var i = 0; i < types.length; i++) {
                var type = types[i];
                var things = obj.children.filter(function (it) { return it.type == type });
                if (things.length == 0) continue;
                if (!this.noIDLSorting) {
                    things.sort(function (a, b) {
                        if (a.id < b.id) return -1;
                        if (a.id > b.id) return 1;
                          return 0;
                    });
                }
                
                var sec = sn.element("section", {}, df);
                var secTitle = type;
                secTitle = secTitle.substr(0, 1).toUpperCase() + secTitle.substr(1) + "s";
                sn.element("h2", {}, sec, secTitle);
                var dl = sn.element("dl", { "class": type + "s" }, sec);
                for (var j = 0; j < things.length; j++) {
                    var it = things[j];
                    var id = (type == "method") ? this.makeMethodID(curLnk, it) : sn.idThatDoesNotExist(curLnk + it.refId);
                    var dt = sn.element("dt", { id: id }, dl);
                    sn.element("code", {}, dt, it.id);
                    var desc = sn.element("dd", {}, dl, [it.description]);
                    if (type == "method") {
                        if (it.params.length) {
                            var table = sn.element("table", { "class": "parameters" }, desc);
                            var tr = sn.element("tr", {}, table);
                            ["Parameter", "Type", "Nullable", "Optional", "Description"].forEach(function (tit) { sn.element("th", {}, tr, tit); });
                            for (var k = 0; k < it.params.length; k++) {
                                var prm = it.params[k];
                                var tr = sn.element("tr", {}, table);
                                sn.element("td", { "class": "prmName" }, tr, prm.id);
                                var tyTD = sn.element("td", { "class": "prmType" }, tr);
                                var matched = /^sequence<(.+)>$/.exec(prm.datatype);
                                if (matched) {
                                    sn.element("code", {}, tyTD, [  sn.text("sequence<"), 
                                                                    sn.element("a", {}, null, matched[1]), 
                                                                    sn.text(">")]);
                                }
                                else {
                                    var cnt = [sn.element("a", {}, null, prm.datatype)];
                                    if (prm.array) cnt.push(sn.text("[]"));
                                    sn.element("code", {}, tyTD, cnt);
                                }
                                if (prm.nullable) sn.element("td", { "class": "prmNullTrue" }, tr, "\u2714");
                                else              sn.element("td", { "class": "prmNullFalse" }, tr, "\u2718");
                                if (prm.optional) sn.element("td", { "class": "prmOptTrue" }, tr, "\u2714");
                                else              sn.element("td", { "class": "prmOptFalse" }, tr, "\u2718");
                                var cnt = prm.description ? [prm.description] : "";
                                sn.element("td", { "class": "prmDesc" }, tr, cnt);
                            }
                        }
                        else {
                            sn.element("div", {}, desc, [sn.element("em", {}, null, "No parameters.")]);
                        }
                        if (it.raises.length) {
                            var table = sn.element("table", { "class": "exceptions" }, desc);
                            var tr = sn.element("tr", {}, table);
                            ["Exception", "Description"].forEach(function (tit) { sn.element("th", {}, tr, tit); });
                            for (var k = 0; k < it.raises.length; k++) {
                                var exc = it.raises[k];
                                var tr = sn.element("tr", {}, table);
                                sn.element("td", { "class": "excName" }, tr, [sn.element("a", {}, null, exc.id)]);
                                var dtd = sn.element("td", { "class": "excDesc" }, tr);
                                if (exc.type == "simple") {
                                    dtd.appendChild(exc.description);
                                }
                                else {
                                    var ctab = sn.element("table", { "class": "exceptionCodes" }, dtd );
                                    for (var m = 0; m < exc.description.length; m++) {
                                        var cd = exc.description[m];
                                        var tr = sn.element("tr", {}, ctab);
                                        sn.element("td", { "class": "excCodeName" }, tr, [sn.element("code", {}, null, cd.id)]);
                                        sn.element("td", { "class": "excCodeDesc" }, tr, [cd.description]);
                                    }
                                }
                            }
                        }
                        else {
                            sn.element("div", {}, desc, [sn.element("em", {}, null, "No exceptions.")]);
                        }
                        var reDiv = sn.element("div", {}, desc);
                        sn.element("em", {}, reDiv, "Return type: ");
                        var matched = /^sequence<(.+)>$/.exec(it.datatype);
                        if (matched) {
                            sn.element("code", {}, reDiv, [ sn.text("sequence<"), 
                                                            sn.element("a", {}, null, matched[1]), 
                                                            sn.text(">")]);
                        }
                        else {
                            var cnt = [sn.element("a", {}, null, it.datatype)];
                            if (it.array) cnt.push(sn.text("[]"));
                            sn.element("code", {}, reDiv, cnt);
                        }
                        if (it.nullable) sn.text(", nullable", reDiv);
                    }
                    else if (type == "attribute") {
                        sn.text(" of type ", dt);
                        if (it.array) sn.text("array of ", dt);
                        var span = sn.element("span", { "class": "idlAttrType" }, dt);
                        var matched = /^sequence<(.+)>$/.exec(it.datatype);
                        if (matched) {
                            sn.text("sequence<", span);
                            sn.element("a", {}, span, matched[1]);
                            sn.text(">", span);
                        }
                        else {
                            sn.element("a", {}, span, it.datatype);
                        }
                        if (it.readonly) sn.text(", readonly", dt);
                        if (it.nullable) sn.text(", nullable", dt);
                        
                        if (it.raises.length) {
                            var table = sn.element("table", { "class": "exceptions" }, desc);
                            var tr = sn.element("tr", {}, table);
                            ["Exception", "On Get", "On Set", "Description"].forEach(function (tit) { sn.element("th", {}, tr, tit); });
                            for (var k = 0; k < it.raises.length; k++) {
                                var exc = it.raises[k];
                                var tr = sn.element("tr", {}, table);
                                sn.element("td", { "class": "excName" }, tr, [sn.element("a", {}, null, exc.id)]);
                                ["onGet", "onSet"].forEach(function (gs) {
                                    if (exc[gs]) sn.element("td", { "class": "excGetSetTrue" }, tr, "\u2714");
                                    else         sn.element("td", { "class": "excGetSetFalse" }, tr, "\u2718");
                                });
                                var dtd = sn.element("td", { "class": "excDesc" }, tr);
                                if (exc.type == "simple") {
                                    dtd.appendChild(exc.description);
                                }
                                else {
                                    var ctab = sn.element("table", { "class": "exceptionCodes" }, dtd );
                                    for (var m = 0; m < exc.description.length; m++) {
                                        var cd = exc.description[m];
                                        var tr = sn.element("tr", {}, ctab);
                                        sn.element("td", { "class": "excCodeName" }, tr, [sn.element("code", {}, null, cd.id)]);
                                        sn.element("td", { "class": "excCodeDesc" }, tr, [cd.description]);
                                    }
                                }
                            }
                        }
                        else {
                            sn.element("div", {}, desc, [sn.element("em", {}, null, "No exceptions.")]);
                        }
                    }
                    else if (type == "constant") {
                        sn.text(" of type ", dt);
                        sn.element("span", { "class": "idlConstType" }, dt, [sn.element("a", {}, null, it.datatype)]);
                        if (it.nullable) sn.text(", nullable", dt);
                    }
                }
            }
            if (typeof obj.merge !== "undefined" && obj.merge.length > 0) {
                // hackish: delay the execution until the DOM has been initialized, then merge
                setTimeout(function () {
                    for (var i = 0; i < obj.merge.length; i++) {
                        var idlInterface = document.querySelector("#idl-def-" + obj.refId),
                            idlDictionary = document.querySelector("#idl-def-" + obj.merge[i]);
                        idlDictionary.parentNode.parentNode.removeChild(idlDictionary.parentNode);
                        idlInterface.appendChild(document.createElement("br"));
                        idlInterface.appendChild(idlDictionary);
                    }
                }, 0);
            }
            return df;
        }
    },
    
    makeMethodID:    function (cur, obj) {
        var id = cur + obj.refId + "-" + obj.datatype + "-";
        var params = [];
        for (var i = 0, n = obj.params.length; i < n; i++) {
            var prm = obj.params[i];
            params.push(prm.datatype + (prm.array ? "Array" : "") + "-" + prm.id)
        }
        id += params.join("-");
        return sn.sanitiseID(id);
        // return sn.idThatDoesNotExist(id);
    },
    
    writeAsWebIDL:    function (obj, indent) {
        if (obj.type == "module") {
            if (obj.id == "outermost") {
                var str = "";
                for (var i = 0; i < obj.children.length; i++) str += this.writeAsWebIDL(obj.children[i], indent);
                return str;
            }
            else {
                var str = "<span class='idlModule'>";
                if (obj.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + obj.extendedAttributes + "</span>]\n";
                str += this._idn(indent) + "module <span class='idlModuleID'>" + obj.id + "</span> {\n";
                for (var i = 0; i < obj.children.length; i++) str += this.writeAsWebIDL(obj.children[i], indent + 1);
                str += this._idn(indent) + "};</span>\n";
                return str;
            }
        }
        else if (obj.type == "typedef") {
            var nullable = obj.nullable ? "?" : "";
            var arr = obj.array ? "[]" : "";
            return  "<span class='idlTypedef' id='idl-def-" + obj.refId + "'>typedef <span class='idlTypedefType'>" + 
                    this.writeDatatype(obj.datatype) +
                    "</span>" + arr + nullable + " <span class='idlTypedefID'>" + obj.id + "</span>;</span>";
        }
        else if (obj.type == "implements") {
            return  "<span class='idlImplements'><a>" + obj.id + "</a> implements <a>" + obj.datatype + "</a>;";
        }
        else if (obj.type == "interface") {
            var str = "<span class='idlInterface' id='idl-def-" + obj.refId + "'>";
            if (obj.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + obj.extendedAttributes + "</span>]\n";
            str += this._idn(indent);
            if (obj.partial) str += "partial ";
            str += "interface <span class='idlInterfaceID'>" + obj.id + "</span>";
            if (obj.superclasses && obj.superclasses.length) str += " : " +
                                                obj.superclasses.map(function (it) {
                                                                        return "<span class='idlSuperclass'><a>" + it + "</a></span>"
                                                                    })
                                                                .join(", ");
            str += " {\n";
            // we process attributes and methods in place
            var maxAttr = 0, maxMeth = 0, maxConst = 0, hasRO = false;
            obj.children.forEach(function (it, idx) {
                var len = it.datatype.length;
                if (it.nullable) len = len + 1;
                if (it.array) len = len + 2;
                if (it.type == "attribute") maxAttr = (len > maxAttr) ? len : maxAttr;
                else if (it.type == "method") maxMeth = (len > maxMeth) ? len : maxMeth;
                else if (it.type == "constant") maxConst = (len > maxConst) ? len : maxConst;
                if (it.type == "attribute" && it.readonly) hasRO = true;
            });
            var curLnk = "widl-" + obj.refId + "-";
            for (var i = 0; i < obj.children.length; i++) {
                var ch = obj.children[i];
                if (ch.type == "attribute") str += this.writeAttribute(ch, maxAttr, indent + 1, curLnk, hasRO);
                else if (ch.type == "method") str += this.writeMethod(ch, maxMeth, indent + 1, curLnk);
                else if (ch.type == "constant") str += this.writeConst(ch, maxConst, indent + 1, curLnk);
            }
            str += this._idn(indent) + "};</span>\n";
            return str;
        }
        else if (obj.type == "exception") {
            var str = "<span class='idlException' id='idl-def-" + obj.refId + "'>";
            if (obj.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + obj.extendedAttributes + "</span>]\n";
            str += this._idn(indent) + "exception <span class='idlExceptionID'>" + obj.id + "</span> {\n";
            var maxAttr = 0, maxConst = 0, hasRO = false;
            obj.children.forEach(function (it, idx) {
                var len = it.datatype.length;
                if (it.nullable) len = len + 1;
                if (it.array) len = len + 2;
                if (it.type == "field")   maxAttr = (len > maxAttr) ? len : maxAttr;
                else if (it.type == "constant") maxConst = (len > maxConst) ? len : maxConst;
            });
            var curLnk = "widl-" + obj.refId + "-";
            for (var i = 0; i < obj.children.length; i++) {
                var ch = obj.children[i];
                if (ch.type == "field") str += this.writeField(ch, maxAttr, indent + 1, curLnk);
                else if (ch.type == "constant") str += this.writeConst(ch, maxConst, indent + 1, curLnk);
            }
            str += this._idn(indent) + "};</span>\n";
            return str;
        }
        else if (obj.type == "dictionary") {
            var str = "<span class='idlDictionary' id='idl-def-" + obj.refId + "'>";
            if (obj.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + obj.extendedAttributes + "</span>]\n";
            str += this._idn(indent) + "dictionary <span class='idlDictionaryID'>" + obj.id + "</span>";
            if (obj.superclasses && obj.superclasses.length) str += " : " +
                                                obj.superclasses.map(function (it) {
                                                                        return "<span class='idlSuperclass'><a>" + it + "</a></span>"
                                                                    })
                                                                .join(", ");
            str += " {\n";
            var max = 0;
            obj.children.forEach(function (it, idx) {
                var len = it.datatype.length;
                if (it.nullable) len = len + 1;
                if (it.array) len = len + 2;
                max = (len > max) ? len : max;
            });
            var curLnk = "widl-" + obj.refId + "-";
            for (var i = 0; i < obj.children.length; i++) {
                var ch = obj.children[i];
                str += this.writeMember(ch, max, indent + 1, curLnk);
            }
            str += this._idn(indent) + "};</span>\n";
            return str;
        }
    },
    
    writeField:    function (attr, max, indent, curLnk) {
        var str = "<span class='idlField'>";
        if (attr.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + attr.extendedAttributes + "</span>]\n";
        str += this._idn(indent);
        var pad = max - attr.datatype.length;
        if (attr.nullable) pad = pad - 1;
        if (attr.array) pad = pad - 2;
        var nullable = attr.nullable ? "?" : "";
        var arr = attr.array ? "[]" : "";
        str += "<span class='idlFieldType'>" + this.writeDatatype(attr.datatype) + arr + nullable + "</span> ";
        for (var i = 0; i < pad; i++) str += " ";
        str += "<span class='idlFieldName'><a href='#" + curLnk + attr.refId + "'>" + attr.id + "</a></span>";
        str += ";</span>\n";
        return str;
    },

    writeAttribute:    function (attr, max, indent, curLnk, hasRO) {
        var sets = [], gets = [];
        if (attr.raises.length) {
            for (var i = 0; i < attr.raises.length; i++) {
                var exc = attr.raises[i];
                if (exc.onGet) gets.push(exc);
                if (exc.onSet) sets.push(exc);
            }
        }
        
        var str = "<span class='idlAttribute'>";
        if (attr.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + attr.extendedAttributes + "</span>]\n";
        str += this._idn(indent);
        if (hasRO) {
            if (attr.readonly) str += "readonly ";
            else               str += "         ";
        }
        str += "attribute ";
        var pad = max - attr.datatype.length;
        if (attr.nullable) pad = pad - 1;
        if (attr.array) pad = pad - 2;
        var nullable = attr.nullable ? "?" : "";
        var arr = attr.array ? "[]" : "";
        str += "<span class='idlAttrType'>" + this.writeDatatype(attr.datatype) + arr + nullable + "</span> ";
        for (var i = 0; i < pad; i++) str += " ";
        str += "<span class='idlAttrName'><a href='#" + curLnk + attr.refId + "'>" + attr.id + "</a></span>";
        if (gets.length) {
            str += " getraises (" +
                   gets.map(function (it) { return "<span class='idlRaises'><a>" + it.id + "</a></span>"; }).join(", ") +
                   ")";
        }
        if (sets.length) {
            str += " setraises (" +
                   sets.map(function (it) { return "<span class='idlRaises'><a>" + it.id + "</a></span>"; }).join(", ") +
                   ")";
        }
        str += ";</span>\n";
        return str;
    },
    
    writeMethod:    function (meth, max, indent, curLnk) {
        var str = "<span class='idlMethod'>";
        if (meth.extendedAttributes) str += this._idn(indent) + "[<span class='extAttr'>" + meth.extendedAttributes + "</span>]\n";
        str += this._idn(indent);
        var pad = max - meth.datatype.length;
        if (meth.nullable) pad = pad - 1;
        if (meth.array) pad = pad - 2;
        var nullable = meth.nullable ? "?" : "";
        var arr = meth.array ? "[]" : "";
        str += "<span class='idlMethType'>" + this.writeDatatype(meth.datatype) + arr + nullable + "</span> ";
        for (var i = 0; i < pad; i++) str += " ";
        var id = this.makeMethodID(curLnk, meth);
        // str += "<span class='idlMethName'><a href='#" + curLnk + meth.refId + "'>" + meth.id + "</a></span> (";
        str += "<span class='idlMethName'><a href='#" + id + "'>" + meth.id + "</a></span> (";
        var obj = this;
        str += meth.params.map(function (it) {
                                    var nullable = it.nullable ? "?" : "";
                                    var optional = it.optional ? "optional " : "";
                                    var arr = it.array ? "[]" : "";
                                    var inp = obj.noIDLIn ? "" : "in ";
                                    var prm = "<span class='idlParam'>";
                                    if (it.extendedAttributes) prm += "[<span class='extAttr'>" + it.extendedAttributes + "</span>] ";
                                    prm += inp + optional + "<span class='idlParamType'>" + obj.writeDatatype(it.datatype) + arr + nullable + "</span> " +
                                    "<span class='idlParamName'>" + it.id + "</span>" +
                                    "</span>";
                                    return prm;
                                })
                          .join(", ");
        str += ")";
        if (meth.raises.length) {
            str += " raises ("
            str += meth.raises.map(function (it) { return "<span class='idlRaises'><a>" + it.id + "</a></span>"; })
                              .join(", ");
            str += ")";
        }
        str += ";</span>\n";
        return str;
    },
    
    writeConst:    function (cons, max, indent, curLnk) {
        var str = "<span class='idlConst'>";
        str += this._idn(indent);
        str += "const ";
        var pad = max - cons.datatype.length;
        if (cons.nullable) pad = pad - 1;
        var nullable = cons.nullable ? "?" : "";
        str += "<span class='idlConstType'><a>" + cons.datatype + "</a>" + nullable + "</span> ";
        for (var i = 0; i < pad; i++) str += " ";
        str += "<span class='idlConstName'><a href='#" + curLnk + cons.refId + "'>" + cons.id + "</a></span> = " +
               "<span class='idlConstValue'>" + cons.value + "</span>;</span>\n";
        return str;
    },

    writeMember:    function (memb, max, indent, curLnk) {
        var str = "<span class='idlMember'>";
        str += this._idn(indent);
        var pad = max - memb.datatype.length;
        if (memb.nullable) pad = pad - 1;
        var nullable = memb.nullable ? "?" : "";
        str += "<span class='idlMemberType'><a>" + memb.datatype + "</a>" + nullable + "</span> ";
        for (var i = 0; i < pad; i++) str += " ";
        str += "<span class='idlMemberName'><a href='#" + curLnk + memb.refId + "'>" + memb.id + "</a></span>";
        if (memb.defaultValue) str += " = <span class='idlMemberValue'>" + memb.defaultValue + "</span>"
        str += ";</span>\n";
        return str;
    },

    writeDatatype:    function (dt) {
        var matched = /^sequence<(.+)>$/.exec(dt);
        if (matched) {
            return "sequence&lt;<a>" + matched[1] + "</a>&gt;";
        }
        else {
            return "<a>" + dt + "</a>";
        }
    },

    _idn:    function (lvl) {
        var str = "";
        for (var i = 0; i < lvl; i++) str += "    ";
        return str;
    },

    // XXX make this generally available (refactoring)
    _norm:    function (str) {
        str = str.replace(/^\s+/, "").replace(/\s+$/, "");
        return str.split(/\s+/).join(" ");
    },
    
    _id:    function (id) {
        return id.replace(/[^a-zA-Z_-]/g, "");
    }
};

// hackish, but who cares?
window.onload = function () {
    // (new berjon.respec()).run();
    (new berjon.respec()).loadAndRun();
};

function dbg (obj) {
    var str = "";
    for (var k in obj) str += k + "=" + obj[k] + "\n";
    alert("DUMP\n" + str);
}
})();