[HTMLUtilities::] HTML and JavaScript. HTML utility code. @ 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; CLASS_DEFINITION } image_usage; typedef struct image_source { struct pathname *src; /* a location from which images can be taken */ CLASS_DEFINITION } image_source; @ 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; @h 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(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(Pathnames::from_text(I"images"), leafname); WRITE("%/f", F); } iu->resolved_to = F; } @ 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); } @ 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; } @ 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::down(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(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); } } } } @h 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); HTML::comment(OUT, I"Generated by indoc"); } void HTMLUtilities::write_title(OUTPUT_STREAM, text_stream *title) { WRITE("%S\n", title); } @ 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); } @ 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); } @ 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"); } @h Miscellaneous HTML. Horizontal ruled lines. = void HTMLUtilities::ruled_line(OUTPUT_STREAM) { if (indoc_settings->format == HTML_FORMAT) HTML_TAG("hr") else WRITE("----------------------------------------------------------------------\n"); } @ 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 "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 |
|, as follows: = void HTMLUtilities::extra_div_open(OUTPUT_STREAM, int id) { HTML_OPEN_WITH("div", "id=\"extra%d\" style=\"display: none;\"", id); } @ 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(" "); } void HTMLUtilities::all_extras_link(OUTPUT_STREAM, text_stream *from) { WRITE(" "); 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"); } @ 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"); } @ 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) } @ 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; ivol_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; ivol_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"); } } @ 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"); WRITE(" var myProject = window.Project;\n\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"); WRITE(" var myProject = window.Project;\n\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); } @h 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"); } @ 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(); @; TEMPORARY_TEXT(defn) Str::copy(defn, given_defn); TEMPORARY_TEXT(index_as) @; if (indoc_settings->inform_definitions_mode) { @; @; @; @; @; WRITE("%S", defn); } else { HTML_OPEN_WITH("span", "class=\"definitionterm\""); WRITE("%S", defn); HTML_CLOSE("span"); } Regexp::dispose_of(&mr); } @ = if (Regexp::match(&mr, given_defn, U"(%c*?) & (%c*)")) { HTMLUtilities::defn_unpack(OUT, mr.exp[0], V, S, anchor); HTML_TAG("br"); WRITE("or:   "); HTMLUtilities::defn_unpack(OUT, mr.exp[1], V, S, anchor); Regexp::dispose_of(&mr); return; } @ = Str::copy(index_as, given_defn); if (indoc_settings->inform_definitions_mode) Regexp::replace(index_as, U" ...%c*", NULL, 0); Regexp::replace(index_as, U": *", NULL, 0); WRITE_TO(index_as, "=___=!definition"); Indexes::mark_index_term(index_as, V, S, anchor, NULL, NULL, NULL); @ = TEMPORARY_TEXT(proto) HTML::open(proto, "p", I"class='defnprototype'", __FILE__, __LINE__); /* given alternative wordings, put only the first in boldface */ Regexp::replace(defn, U"(%i+?)/(%C+)", U"%0/%1", REP_REPEATING); WRITE_TO(proto, "%S", defn); HTML::close(proto, "p", __FILE__, __LINE__); Str::copy(defn, proto); @ = if (Regexp::match(&mr, defn, U"(%c*) ... (%c*?)")) { WRITE_TO(defn, "%S ... ", mr.exp[0]); Regexp::replace(mr.exp[1], U"", NULL, REP_REPEATING); Regexp::replace(mr.exp[1], U"", NULL, REP_REPEATING); WRITE_TO(defn, "%S", mr.exp[1]); } @ = if ((Regexp::match(&mr, defn, U"if (%c*)")) && (Regexp::match(NULL, defn, U"%c*a condition%c*"))) { WRITE_TO(defn, "if %S", mr.exp[0]); Regexp::replace(index_as, U"if ", NULL, REP_ATSTART); text_stream *index_alph = NULL; if (Regexp::match(&mr, index_as, U"%(%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) } @ = if (Regexp::match(&mr, defn, U"say \"%[(%c*)%]\"")) { WRITE_TO(defn, "say \"[%S]\"", mr.exp[0]); Regexp::replace(index_as, U"say ", NULL, REP_ATSTART); text_stream *index_alph = NULL; if (Regexp::match(&mr, index_as, U"%[%(?:%(%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) } @ = Regexp::replace(defn, U"", NULL, REP_REPEATING); Regexp::replace(defn, U"", NULL, REP_REPEATING); TEMPORARY_TEXT(star) Str::copy(star, defn); Str::clear(defn); int bl = 0; while (Regexp::match(&mr, star, U"(%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, ""); bl++; } WRITE_TO(defn, "%S", ch); if (Str::eq(ch, I")")) { bl--; if (bl == 0) WRITE_TO(defn, ""); } } DISCARD_TEXT(star) @h Topping and tailing. If we're preparing HTML for use in a website, we probably want our documentation content to be just one |
| 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); } @ = 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, U"(%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); } } @ 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); }