HTML utility code.


§1. Definitions.

§2. Keeping track of which images are referred to in the HTML we generate:

    dictionary *image_usages = NULL;     how many times, if at all, this image has been used

    typedef struct image_usage {
        struct text_stream *leafname;
        int usage_count;
        struct filename *resolved_to;
        MEMORY_MANAGEMENT
    } image_usage;

    typedef struct image_source {
        struct pathname *src;     a location from which images can be taken
        MEMORY_MANAGEMENT
    } image_source;

The structure image_usage is accessed in 3/fln and here.

The structure image_source is private to this section.

§3. We can either generate entirely fresh HTML pages, or we can inject material into a division inside a copy of an existing page: this is called "top-and-tailing".

    int first_request_for_tt = TRUE;     will the next request be the first?
    text_stream *DSET_main_tail_matter = NULL;
    text_stream *DSET_main_top_matter = NULL;
    text_stream *DSET_section_tail_matter = NULL;
    text_stream *DSET_section_top_matter = NULL;

§4. Images. In the Inform application, a special http transport called inform: is used for references to the in-app documentation, so an image called orange.png will be found at the URL inform:/orange.png.

Otherwise, of course, we need to make an images folder alongside the HTML we produce. It will be called images, so relative to the HTML, the URL would then be images/orange.png.

However, it"s also possible that the book"s instructions tell us to use images from an already-existing location instead.

Note that each time the URL of an image is looked up, we make a note of it, so that we can keep track of which images we actually need.

    void HTMLUtilities::image_URL(OUTPUT_STREAM, text_stream *leafname) {
        if (image_usages == NULL) image_usages = Dictionaries::new(100, FALSE);
        image_usage *iu = NULL;
        if (Dictionaries::find(image_usages, leafname)) {
            iu = Dictionaries::read_value(image_usages, leafname);
        } else {
            Dictionaries::create(image_usages, leafname);
            iu = CREATE(image_usage);
            iu->usage_count = 0;
            iu->leafname = Str::duplicate(leafname);
            Dictionaries::write_value(image_usages, leafname, iu);
        }
        iu->usage_count++;
        filename *F = Filenames::in_folder(indoc_settings->images_path, leafname);
        if (indoc_settings->html_for_Inform_application == 1) WRITE("inform:/%/f", F);
        else {
            if (indoc_settings->images_copy)
                F = Filenames::in_folder(Pathnames::from_text(I"images"), leafname);
            WRITE("%/f", F);
        }
        iu->resolved_to = F;
    }

The function HTMLUtilities::image_URL is used in §13, §17, 1/mn (§1.1), 2/rr (§4.5), 2/css (§7).

§5. For ebook use only:

    void HTMLUtilities::note_images(void) {
        image_usage *iu;
        LOOP_OVER(iu, image_usage)
            Epub::note_image(indoc_settings->ebook, iu->resolved_to);
    }

The function HTMLUtilities::note_images is used in 1/mn (§1).

§6. Suppose we are indeed copying images into place: we have to get them from somewhere. The somewheres are folders called "image sources", and this routine adds one:

    void HTMLUtilities::add_image_source(pathname *path) {
        image_source *is = CREATE(image_source);
        is->src = path;
    }

The function HTMLUtilities::add_image_source is used in 1/ins (§3, §5.1).

§7. During the copying process, we look for each image, say orange.png, in each of these image sources, starting from the most recently added and working backwards. As soon as we find a file of that name, we copy it over.

    void HTMLUtilities::copy_images(void) {
        if (indoc_settings->images_copy) {
            pathname *I = Pathnames::subfolder(indoc_settings->destination, I"images");
            Pathnames::create_in_file_system(I);
            image_usage *iu;
            LOOP_OVER(iu, image_usage) {
                int found = FALSE;
                image_source *is;
                LOOP_BACKWARDS_OVER(is, image_source) {
                    filename *F = Filenames::in_folder(is->src, iu->leafname);
                    if (TextFiles::exists(F)) {
                        found = TRUE;
                        Shell::copy(F, I, "-f");
                        break;
                    }
                }
                if (found == FALSE) { Errors::with_text("unable to find the image %S", iu->leafname); }
            }
        }
    }

The function HTMLUtilities::copy_images is used in 1/mn (§1).

§8. HTML file structure.

    void HTMLUtilities::begin_file(OUTPUT_STREAM, volume *V) {
        HTML::declare_as_HTML(OUT, indoc_settings->XHTML);
        filename *CSS = NULL;
        if ((V) && (Str::len(V->vol_CSS_leafname) > 0))
            CSS = Filenames::from_text(V->vol_CSS_leafname);
        HTML::begin_head(OUT, CSS);
        TEMPORARY_TEXT(comment);
        char *monthname[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
        WRITE_TO(comment, "Generated by indoc on %d %s %d",
            the_present->tm_mday, monthname[the_present->tm_mon], the_present->tm_year+1900);
        HTML::comment(OUT, comment);
        DISCARD_TEXT(comment);
    }

    void HTMLUtilities::write_title(OUTPUT_STREAM, text_stream *title) {
        WRITE("<title>%S</title>\n", title);
    }

The function HTMLUtilities::begin_file is used in 2/rnd (§16.2), 3/iu (§1), 4/cm (§9.1).

The function HTMLUtilities::write_title is used in 2/rnd (§16.2), 3/iu (§1), 4/cm (§9.1).

§9. Here's text within a link:

    void HTMLUtilities::general_link(OUTPUT_STREAM, text_stream *cl, text_stream *to, text_stream *text) {
        HTML::begin_link_with_class(OUT, cl, to);
        WRITE("%S", text);
        HTML::end_link(OUT);
    }

The function HTMLUtilities::general_link is used in §11, 3/iu (§4), 3/cai (§9.2.3.4, §9.2.3.5), 3/ei (§3.5), 4/cm (§5, §6, §9.3.1, §9.3.2.1, §10), 4/ca (§5, §6), 4/ct (§3, §6).

§10. And a link with both text and a title (tooltip) tag:

    void HTMLUtilities::titled_link(OUTPUT_STREAM, text_stream *cl, text_stream *to, text_stream *ti, text_stream *text) {
        HTML::begin_link_with_class_title(OUT, cl, to, ti);
        WRITE("%S", text);
        HTML::end_link(OUT);
    }

The function HTMLUtilities::titled_link appears nowhere else.

§11. And a standardised paragraph which is itself a link:

    void HTMLUtilities::textual_link(OUTPUT_STREAM, text_stream *to, text_stream *text) {
        HTML_OPEN("p");
        HTMLUtilities::general_link(OUT, I"standardlink", to, text);
        HTML_CLOSE("p");
    }

The function HTMLUtilities::textual_link is used in 4/cm (§9.3.1).

§12. Miscellaneous HTML. Horizontal ruled lines.

    void HTMLUtilities::ruled_line(OUTPUT_STREAM) {
        if (indoc_settings->format == HTML_FORMAT) HTML_TAG("hr")
        else WRITE("----------------------------------------------------------------------\n");
    }

The function HTMLUtilities::ruled_line is used in 2/rnd (§9.1), 4/nd (§6), 4/cm (§4), 4/ca (§4), 4/ct (§5), 4/cr (§5, §6), 4/cu (§5, §6).

§13. Images.

    void HTMLUtilities::image_element(OUTPUT_STREAM, text_stream *name) {
        TEMPORARY_TEXT(details);
        WRITE_TO(details, "alt=\"%S\" src=\"", name);
        HTMLUtilities::image_URL(details, name);
        WRITE_TO(details, "\"");
        HTML::tag(OUT, "img", details);
        DISCARD_TEXT(details);
    }

    void HTMLUtilities::image_element_scaled(OUTPUT_STREAM, text_stream *name, int xsize, int ysize) {
        TEMPORARY_TEXT(details);
        WRITE_TO(details, "alt=\"%S\" src=\"", name);
        HTMLUtilities::image_URL(details, name);
        WRITE_TO(details, "\" width=\"%d\" height=\"%d\"", xsize, ysize);
        HTML::tag(OUT, "img", details);
        DISCARD_TEXT(details);
    }

    void HTMLUtilities::image_with_id(OUTPUT_STREAM, text_stream *name, text_stream *id) {
        TEMPORARY_TEXT(details);
        WRITE_TO(details, "alt=\"%S\" src=\"", name);
        HTMLUtilities::image_URL(details, name);
        WRITE_TO(details, "\" id=\"%S\"", id);
        HTML::tag(OUT, "img", details);
        DISCARD_TEXT(details);
    }

    void HTMLUtilities::asterisk_image(OUTPUT_STREAM, text_stream *name) {
        TEMPORARY_TEXT(details);
        WRITE_TO(details, "class=\"asterisk\" alt=\"*\" src=\"");
        HTMLUtilities::image_URL(details, name);
        WRITE_TO(details, "\"");
        HTML::tag_sc(OUT, "img", details);
        DISCARD_TEXT(details);
    }

The function HTMLUtilities::image_element is used in 2/rnd (§11.2.2.3), 4/ct (§3), 4/cr (§10).

The function HTMLUtilities::image_element_scaled is used in 2/rnd (§11.2.2.3).

The function HTMLUtilities::image_with_id is used in 4/cm (§6), 4/ca (§6).

The function HTMLUtilities::asterisk_image is used in 2/exm (§8.2.2), 2/rnd (§14), 4/cr (§6.2).

§14. The "extra" functions here are for revealing or concealing page content when the user clicks a button. Each such piece of content is in its own uniquely-ID'd <div>, as follows:

    void HTMLUtilities::extra_div_open(OUTPUT_STREAM, int id) {
        HTML_OPEN_WITH("div", "id=\"extra%d\" style=\"display: none;\"", id);
    }

The function HTMLUtilities::extra_div_open is used in 4/cm (§9.3.2.1).

§15. And the following links provide the wiring for the buttons:

    void HTMLUtilities::extra_link(OUTPUT_STREAM, int id) {
        TEMPORARY_TEXT(onclick);
        WRITE_TO(onclick, "showExtra('extra%d', 'plus%d'); return false;", id, id);
        HTML::begin_link_with_class_onclick(OUT, NULL, I"#", onclick);
        DISCARD_TEXT(onclick);
        TEMPORARY_TEXT(details);
        WRITE_TO(details, "alt=\"show\" id=\"plus%d\" src=\"", id);
        HTMLUtilities::extra_icon(details, I"extra");
        WRITE_TO(details, "\"");
        HTML::tag(OUT, "img", details);
        DISCARD_TEXT(details);
        HTML_CLOSE("a");
        WRITE("&#160;");
    }

    void HTMLUtilities::all_extras_link(OUTPUT_STREAM, text_stream *from) {
        WRITE("&#160;");
        TEMPORARY_TEXT(onclick);
        WRITE_TO(onclick, "showExtra%S(); return false;", from);
        HTML::begin_link_with_class_onclick(OUT, NULL, I"#", onclick);
        DISCARD_TEXT(onclick);
        TEMPORARY_TEXT(details);
        WRITE_TO(details, "alt=\"show\" id=\"plus%S\" src=\"", from);
        HTMLUtilities::extra_icon(details, I"extrab");
        WRITE_TO(details, "\"");
        HTML::tag(OUT, "img", details);
        DISCARD_TEXT(details);
        HTML_CLOSE("a");
    }

The function HTMLUtilities::extra_link is used in 4/cm (§9.3.2.1).

The function HTMLUtilities::all_extras_link is used in 4/cm (§9.3.2).

§16. These links call some standard Javascript functions, thus:

    void HTMLUtilities::write_javascript_for_buttons(OUTPUT_STREAM) {
        WRITE("    function showExtra(id, imid) {\n");
        WRITE("        if (document.getElementById(id).style.display == 'block') {\n");
        WRITE("            document.getElementById(id).style.display = 'none';\n");
        WRITE("            document.getElementById(imid).src = '");
            HTMLUtilities::extra_icon(OUT, I"extra");
            WRITE("';\n");
        WRITE("        } else {\n");
        WRITE("            document.getElementById(id).style.display = 'block';\n");
        WRITE("            document.getElementById(imid).src = '");
            HTMLUtilities::extra_icon(OUT, I"extraclose");
            WRITE("';\n");
        WRITE("        }\n");
        WRITE("    }\n");
        WRITE("    function onLoaded() {\n");
        WRITE("        if (window.location.hash) {\n");
        WRITE("            var hash = window.location.hash.substring(2);\n");
        WRITE("            if (hash.search(\"_\") >= 0) {\n");
        WRITE("                var res = hash.split(\"_\");\n");
        WRITE("                showExample(\"example\"+res[1]);\n");
        WRITE("            } else {\n");
        WRITE("                showExample(\"example\"+hash);\n");
        WRITE("            }\n");
        WRITE("        }\n");
        WRITE("    }\n");
        WRITE("    window.onload=onLoaded;\n");
        WRITE("    function showExample(id) {\n");
        WRITE("        if (document.getElementById(id).style.display == 'block') {\n");
        WRITE("            document.getElementById(id).style.display = 'none';\n");
        WRITE("        } else {\n");
        WRITE("            document.getElementById(id).style.display = 'block';\n");
        WRITE("        }\n");
        WRITE("    }\n");
        WRITE("    function openExtra(id, imid) {\n");
        WRITE("        document.getElementById(id).style.display = 'block';\n");
        WRITE("        document.getElementById(imid).src = '");
            HTMLUtilities::extra_icon(OUT, I"extraclose");
            WRITE("';\n");
        WRITE("    }\n");
        WRITE("    function closeExtra(id, imid) {\n");
        WRITE("        document.getElementById(id).style.display = 'none';\n");
        WRITE("        document.getElementById(imid).src = '");
            HTMLUtilities::extra_icon(OUT, I"extra");
            WRITE("';\n");
        WRITE("    }\n");
    }

The function HTMLUtilities::write_javascript_for_buttons is used in 2/rnd (§16.2), 4/cm (§9.1).

§17. Where:

    void HTMLUtilities::extra_icon(OUTPUT_STREAM, text_stream *name) {
        TEMPORARY_TEXT(img);
        WRITE_TO(img, "%S.png", name);
        HTMLUtilities::image_URL(OUT, img);
        DISCARD_TEXT(img);
    }

The function HTMLUtilities::extra_icon is used in §15, §16, §18.

§18. And the following compiles a set of specific functions for numbered buttons, used on the Contents page:

    void HTMLUtilities::write_javascript_for_contents_buttons(OUTPUT_STREAM) {
        volume *V;
        LOOP_OVER(V, volume) {
            WRITE("    function showExtra%S() {\n", V->vol_abbrev);
            WRITE("        if (document.getElementById('plus%S').src.indexOf('", V->vol_abbrev);
                HTMLUtilities::extra_icon(OUT, I"extrab");
                WRITE("') >= 0) {\n");
            for (int i=0; i<V->vol_chapter_count; i++) {
                int bn = (V->allocation_id)*1000+i;
                WRITE("            openExtra('extra%d', 'plus%d');\n", bn, bn);
            }
            WRITE("            document.getElementById('plus%S').src = '", V->vol_abbrev);
                HTMLUtilities::extra_icon(OUT, I"extracloseb");
                WRITE("';\n");
            WRITE("        } else {\n");
            for (int i=0; i<V->vol_chapter_count; i++) {
                int bn = (V->allocation_id)*1000+i;
                WRITE("            closeExtra('extra%d', 'plus%d');\n", bn, bn);
            }
            WRITE("            document.getElementById('plus%S').src = '", V->vol_abbrev);
                HTMLUtilities::extra_icon(OUT, I"extrab");
                WRITE("';\n");
            WRITE("        }\n");
            WRITE("	}\n");
        }
    }

The function HTMLUtilities::write_javascript_for_contents_buttons is used in 4/cm (§9.1).

§19. JavaScript to handle the paste icon works differently on different platforms.

    void HTMLUtilities::paste_script(OUTPUT_STREAM, text_stream *content, int code_num) {
        HTML::open_javascript(OUT, FALSE);
        WRITE("function pasteCode");
        if (Str::len(content) == 0) WRITE("(code");
        else WRITE("%d(code", code_num);
        WRITE(") {\n");

        if (indoc_settings->javascript_paste_method == PASTEMODE_Andrew)
            WRITE("    var myProject = window.Project;\n");
        if (indoc_settings->javascript_paste_method == PASTEMODE_David)
            WRITE("    var myProject = external.Project;\n");
        WRITE("\n");
        WRITE("    myProject.selectView('source');\n");
        WRITE("    myProject.pasteCode(");
        if (Str::len(content) == 0) { WRITE("code"); }
        else WRITE("'%S'", content);
        WRITE(");\n}\n");
        HTML::close_javascript(OUT);
    }

    void HTMLUtilities::create_script(OUTPUT_STREAM, text_stream *content, int code_num, text_stream *titling) {
        HTML::open_javascript(OUT, FALSE);
        WRITE("function createNewProject");
        if (Str::len(content) == 0) WRITE("(code");
        else WRITE("%d(code", code_num);
        WRITE(", title) {\n");

        if (indoc_settings->javascript_paste_method == PASTEMODE_Andrew)
            WRITE("    var myProject = window.Project;\n");
        if (indoc_settings->javascript_paste_method == PASTEMODE_David)
            WRITE("    var myProject = external.Project;\n");

        WRITE("\n");
        WRITE("    myProject.createNewProject(");
        if (Str::len(content) == 0) WRITE("title");
        else WRITE("'%S'", titling);
        WRITE(", ");
        if (Str::len(content) == 0) WRITE("code");
        else WRITE("'%S'", content);
        WRITE(");\n}\n");
        HTML::close_javascript(OUT);
    }

The function HTMLUtilities::paste_script is used in 2/rnd (§11.2.2.3, §16.2).

The function HTMLUtilities::create_script is used in 2/rnd (§11.2.2.3, §16.2).

§20. Definitions. Some nice little boxes for syntax definitions in computery manuals:

    int no_definition_anchors = 0;

    void HTMLUtilities::definition_box(OUTPUT_STREAM, text_stream *defn, text_stream *sigil, volume *V, section *S) {
        if (indoc_settings->format == HTML_FORMAT) {
            TEMPORARY_TEXT(anchor);
            WRITE_TO(anchor, "defn%d", no_definition_anchors++);

            TEMPORARY_TEXT(comment);
            WRITE_TO(comment, "START PHRASE \"%S\"", anchor);
            HTML::comment(OUT, comment);
            DISCARD_TEXT(comment);

            HTML_OPEN_WITH("div", "class=\"definition\"");
            HTML::anchor(OUT, anchor);
            HTMLUtilities::defn_unpack(OUT, defn, V, S, anchor);
            WRITE("\n");
            DISCARD_TEXT(anchor);

            HTML::comment(OUT, I"END PHRASE");

            TEMPORARY_TEXT(defnhead);
            WRITE_TO(defnhead, "definition of %S", sigil);
            HTML::comment(OUT, defnhead);
            DISCARD_TEXT(defnhead);
        } else {
            WRITE("PHRASE: %S\n", defn);
        }
    }

    void HTMLUtilities::end_definition_box(OUTPUT_STREAM) {
        if (indoc_settings->format == HTML_FORMAT) {
            WRITE("\n");
            HTML::comment(OUT, I"end definition");
            HTML_CLOSE("div");
        } else WRITE("\n");
    }

The function HTMLUtilities::definition_box is used in 2/rr (§4.6.2).

The function HTMLUtilities::end_definition_box is used in 2/rr (§4.6.2).

§21. Though these are in fact quite a lot of trouble:

    void HTMLUtilities::defn_unpack(OUTPUT_STREAM, text_stream *given_defn, volume *V, section *S, text_stream *anchor) {
        match_results mr = Regexp::create_mr();
        <Given alternates divided by an ampersand, recurse to unpack each in turn 21.1>;

        TEMPORARY_TEXT(defn);
        Str::copy(defn, given_defn);

        TEMPORARY_TEXT(index_as);
        <Work out an index mark for this definition 21.2>;

        if (indoc_settings->inform_definitions_mode) {
            <Rewrite definition as an HTML paragraph of class defnprototype 21.3>;
            <Set the kind of the result of the phrase in italic, not bold 21.4>;
            <Specially set and specially index a phrase to decide a condition 21.5>;
            <Specially set and specially index a text substitution 21.6>;
            <Tidy up the definition paragraph 21.7>;
            WRITE("%S", defn);
        } else {
            HTML_OPEN_WITH("span", "class=\"definitionterm\"");
            WRITE("%S", defn);
            HTML_CLOSE("span");
        }
        Regexp::dispose_of(&mr);
    }

The function HTMLUtilities::defn_unpack is used in §20, §21.1.

§21.1. <Given alternates divided by an ampersand, recurse to unpack each in turn 21.1> =

        if (Regexp::match(&mr, given_defn, L"(%c*?) & (%c*)")) {
            HTMLUtilities::defn_unpack(OUT, mr.exp[0], V, S, anchor);
            HTML_TAG("br");
            WRITE("<i>or:</i>&#160;&#160;&#160;");
            HTMLUtilities::defn_unpack(OUT, mr.exp[1], V, S, anchor);
            Regexp::dispose_of(&mr);
            return;
        }

This code is used in §21.

§21.2. <Work out an index mark for this definition 21.2> =

        Str::copy(index_as, given_defn);
        if (indoc_settings->inform_definitions_mode) Regexp::replace(index_as, L" ...%c*", NULL, 0);
        Regexp::replace(index_as, L": *", NULL, 0);
        WRITE_TO(index_as, "=___=!definition");
        Indexes::mark_index_term(index_as, V, S, anchor, NULL, NULL, NULL);

This code is used in §21.

§21.3. <Rewrite definition as an HTML paragraph of class defnprototype 21.3> =

        TEMPORARY_TEXT(proto);
        HTML::open(proto, "p", I"class='defnprototype'");
        given alternative wordings, put only the first in boldface
        Regexp::replace(defn, L"(%i+?)/(%C+)", L"%0</b>/%1<b>", REP_REPEATING);
        WRITE_TO(proto, "<b>%S</b>", defn);
        HTML::close(proto, "p");
        Str::copy(defn, proto);

This code is used in §21.

§21.4. <Set the kind of the result of the phrase in italic, not bold 21.4> =

        if (Regexp::match(&mr, defn, L"(%c*) ... (%c*?)</b>")) {
            WRITE_TO(defn, "%S</b> ... <i>", mr.exp[0]);
            Regexp::replace(mr.exp[1], L"<b>", NULL, REP_REPEATING);
            Regexp::replace(mr.exp[1], L"</b>", NULL, REP_REPEATING);
            WRITE_TO(defn, "%S</i>", mr.exp[1]);
        }

This code is used in §21.

§21.5. <Specially set and specially index a phrase to decide a condition 21.5> =

        if ((Regexp::match(&mr, defn, L"<b>if (%c*)")) &&
            (Regexp::match(NULL, defn, L"%c*a condition%c*"))) {
            WRITE_TO(defn, "<i>if</i> <b>%S", mr.exp[0]);
            Regexp::replace(index_as, L"if ", NULL, REP_ATSTART);
            text_stream *index_alph = NULL;
            if (Regexp::match(&mr, index_as, L"%(%c*?%) (%c*)")) index_alph = mr.exp[0];
            TEMPORARY_TEXT(term);
            WRITE_TO(term, "%S=___=!if-definition", index_as);
            Indexes::mark_index_term(term, V, S, anchor, NULL, NULL, index_alph);
            DISCARD_TEXT(term);
        }

This code is used in §21.

§21.6. <Specially set and specially index a text substitution 21.6> =

        if (Regexp::match(&mr, defn, L"<b>say \"%[(%c*)%]\"</b>")) {
            WRITE_TO(defn, "say \"[<b>%S</b>]\"", mr.exp[0]);
            Regexp::replace(index_as, L"say ", NULL, REP_ATSTART);
            text_stream *index_alph = NULL;
            if (Regexp::match(&mr, index_as, L"%[%(?:%(%c*?%) %)?(%c*)%]")) index_alph = mr.exp[0];
            TEMPORARY_TEXT(term);
            WRITE_TO(term, "%S=___=!say-definition", index_as);
            Indexes::mark_index_term(term, V, S, anchor, NULL, NULL, index_alph);
            DISCARD_TEXT(term);
        }

This code is used in §21.

§21.7. <Tidy up the definition paragraph 21.7> =

        Regexp::replace(defn, L"<b></b>", NULL, REP_REPEATING);
        Regexp::replace(defn, L"</b><b>", NULL, REP_REPEATING);

        TEMPORARY_TEXT(star);
        Str::copy(star, defn);
        Str::clear(defn);
        int bl = 0;
        while (Regexp::match(&mr, star, L"(%c)(%c*)")) {
            text_stream *ch = mr.exp[0]; Str::copy(star, mr.exp[1]);
            if (Str::eq(ch, I"(")) { if (bl == 0) WRITE_TO(defn, "</b>"); bl++; }
            WRITE_TO(defn, "%S", ch);
            if (Str::eq(ch, I")")) { bl--; if (bl == 0) WRITE_TO(defn, "<b>"); }
        }
        DISCARD_TEXT(star);

This code is used in §21.

§22. Topping and tailing. If we're preparing HTML for use in a website, we probably want our documentation content to be just one <div> in a larger page whose design indoc knows nothing about. The following therefore lets us use an external file as the prototype, cutting it up into a head before the text [TEXT] and a tail afterwards.

    void HTMLUtilities::read_top_and_tail_matter(filename *F, int main_flag) {
        tt_helper_state tths;
        tths.pretext = TRUE;
        if (main_flag) {
            DSET_main_top_matter = Str::new();
            DSET_main_tail_matter = Str::new();
            tths.top_text = DSET_main_top_matter;
            tths.tail_text = DSET_main_tail_matter;
        } else {
            DSET_section_top_matter = Str::new();
            DSET_section_tail_matter = Str::new();
            tths.top_text = DSET_section_top_matter;
            tths.tail_text = DSET_section_tail_matter;
        }
        TextFiles::read(F, FALSE, "can't read top and tail file",
            TRUE, HTMLUtilities::tt_helper, NULL, &tths);
    }

The function HTMLUtilities::read_top_and_tail_matter is used in §24.

§23.

    typedef struct tt_helper_state {
        int pretext;
        text_stream *top_text;
        text_stream *tail_text;
    } tt_helper_state;

    void HTMLUtilities::tt_helper(text_stream *line, text_file_position *tfp, void *v_tths) {
        tt_helper_state *tths = (tt_helper_state *) v_tths;
        Str::trim_white_space_at_end(line);
        match_results mr = Regexp::create_mr();
        if (Regexp::match(&mr, line, L"(%c*?)%[TEXT%](%c*)")) {
            WRITE_TO(tths->top_text, "%S", mr.exp[0]);
            HTML::begin_div_with_id(tths->top_text, "bodytext");
            HTML::end_div(tths->tail_text);
            WRITE_TO(tths->tail_text, "%S\n", mr.exp[1]);
            Regexp::dispose_of(&mr);
            tths->pretext = FALSE;
        } else {
            if (tths->pretext) WRITE_TO(tths->top_text, "%S\n", line);
            else WRITE_TO(tths->tail_text, "%S\n", line);
        }
    }

The function HTMLUtilities::tt_helper is used in §22.

The structure tt_helper_state is private to this section.

§24. And this routine caches requests for tops or tails.

    void HTMLUtilities::get_tt_matter(OUTPUT_STREAM, int top, int main) {
        if (first_request_for_tt) {
            if (indoc_settings->top_and_tail_sections)
                HTMLUtilities::read_top_and_tail_matter(indoc_settings->top_and_tail_sections, FALSE);
            if (indoc_settings->top_and_tail)
                HTMLUtilities::read_top_and_tail_matter(indoc_settings->top_and_tail, TRUE);
        }
        first_request_for_tt = FALSE;
        if ((top == 1) && (main == 1)) WRITE("%S", DSET_main_top_matter);
        if ((top == 1) && (main == 0)) WRITE("%S", DSET_main_tail_matter);
        if ((top == 0) && (main == 1)) WRITE("%S", DSET_section_top_matter);
        if ((top == 0) && (main == 0)) WRITE("%S", DSET_section_tail_matter);
    }

The function HTMLUtilities::get_tt_matter is used in 2/rnd (§16.2, §16.1.1), 3/iu (§1, §2), 4/cm (§9.1, §9.2).