1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-01 06:24:58 +03:00
inform7/indoc/Chapter 2/HTML and Javascript.w

588 lines
19 KiB
OpenEdge ABL

[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("<title>%S</title>\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 |<div>|, 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("&#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");
}
@ 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; 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");
}
}
@ 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();
@<Given alternates divided by an ampersand, recurse to unpack each in turn@>;
TEMPORARY_TEXT(defn)
Str::copy(defn, given_defn);
TEMPORARY_TEXT(index_as)
@<Work out an index mark for this definition@>;
if (indoc_settings->inform_definitions_mode) {
@<Rewrite definition as an HTML paragraph of class defnprototype@>;
@<Set the kind of the result of the phrase in italic, not bold@>;
@<Specially set and specially index a phrase to decide a condition@>;
@<Specially set and specially index a text substitution@>;
@<Tidy up the definition paragraph@>;
WRITE("%S", defn);
} else {
HTML_OPEN_WITH("span", "class=\"definitionterm\"");
WRITE("%S", defn);
HTML_CLOSE("span");
}
Regexp::dispose_of(&mr);
}
@<Given alternates divided by an ampersand, recurse to unpack each in turn@> =
if (Regexp::match(&mr, given_defn, U"(%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;
}
@<Work out an index mark for this definition@> =
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);
@<Rewrite definition as an HTML paragraph of class defnprototype@> =
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</b>/%1<b>", REP_REPEATING);
WRITE_TO(proto, "<b>%S</b>", defn);
HTML::close(proto, "p", __FILE__, __LINE__);
Str::copy(defn, proto);
@<Set the kind of the result of the phrase in italic, not bold@> =
if (Regexp::match(&mr, defn, U"(%c*) ... (%c*?)</b>")) {
WRITE_TO(defn, "%S</b> ... <i>", mr.exp[0]);
Regexp::replace(mr.exp[1], U"<b>", NULL, REP_REPEATING);
Regexp::replace(mr.exp[1], U"</b>", NULL, REP_REPEATING);
WRITE_TO(defn, "%S</i>", mr.exp[1]);
}
@<Specially set and specially index a phrase to decide a condition@> =
if ((Regexp::match(&mr, defn, U"<b>if (%c*)")) &&
(Regexp::match(NULL, defn, U"%c*a condition%c*"))) {
WRITE_TO(defn, "<i>if</i> <b>%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)
}
@<Specially set and specially index a text substitution@> =
if (Regexp::match(&mr, defn, U"<b>say \"%[(%c*)%]\"</b>")) {
WRITE_TO(defn, "say \"[<b>%S</b>]\"", 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)
}
@<Tidy up the definition paragraph@> =
Regexp::replace(defn, U"<b></b>", NULL, REP_REPEATING);
Regexp::replace(defn, U"</b><b>", 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, "</b>"); bl++; }
WRITE_TO(defn, "%S", ch);
if (Str::eq(ch, I")")) { bl--; if (bl == 0) WRITE_TO(defn, "<b>"); }
}
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 |<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);
}
@ =
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);
}