911 lines
28 KiB
OpenEdge ABL
911 lines
28 KiB
OpenEdge ABL
[HTML::] HTML.
|
|
|
|
Utility functions for writing HTML.
|
|
|
|
@h Header and footer.
|
|
|
|
=
|
|
void HTML::header(OUTPUT_STREAM, text_stream *title, filename *css, filename *js) {
|
|
HTML::declare_as_HTML(OUT, FALSE);
|
|
HTML::begin_head(OUT, NULL);
|
|
HTML::title(OUT, title);
|
|
HTML::incorporate_CSS(OUT, css);
|
|
HTML::incorporate_javascript(OUT, TRUE, js);
|
|
#ifdef ADDITIONAL_SCRIPTING_HTML_CALLBACK
|
|
ADDITIONAL_SCRIPTING_HTML_CALLBACK(OUT);
|
|
#endif
|
|
HTML::end_head(OUT);
|
|
HTML::begin_body(OUT, NULL);
|
|
HTML::comment(OUT, I"CONTENT BEGINS");
|
|
}
|
|
|
|
void HTML::footer(OUTPUT_STREAM) {
|
|
WRITE("\n");
|
|
HTML::comment(OUT, I"CONTENT ENDS");
|
|
HTML::end_body(OUT);
|
|
}
|
|
|
|
@h Abstraction.
|
|
Though the code below does nothing at all interesting, to put it mildly,
|
|
it's written a little defensively, to increase the chances that the client
|
|
is producing valid HTML with it. In particular, the client won't be
|
|
allowed to open a |p| tag, then open a |b| tag, then close the |p|, then
|
|
close the |b|: that would be wrongly nested. We want to throw errors like
|
|
that into the debugging log, so:
|
|
|
|
@d tag_error(x) { LOG("Tag error: %s\n", x); }
|
|
|
|
@ Any text stream can be declared as being HTML, and therefore subject to
|
|
this auditing. To do that, we atach an |HTML_file_state| object to the
|
|
text stream.
|
|
|
|
=
|
|
typedef struct HTML_file_state {
|
|
int XHTML_flag; /* writing strict XHTML for use in epubs */
|
|
struct lifo_stack *tag_stack; /* of |HTML_tag|: those currently open */
|
|
int CSS_included;
|
|
int JS_included;
|
|
CLASS_DEFINITION
|
|
} HTML_file_state;
|
|
|
|
void HTML::declare_as_HTML(OUTPUT_STREAM, int XHTML) {
|
|
HTML_file_state *hs = CREATE(HTML_file_state);
|
|
hs->XHTML_flag = XHTML;
|
|
hs->tag_stack = NEW_LIFO_STACK(HTML_tag);
|
|
hs->CSS_included = 0;
|
|
hs->JS_included = 0;
|
|
Streams::declare_as_HTML(OUT, hs);
|
|
}
|
|
|
|
@ What we require is that any tag "pushed" to the file must later be "pulled",
|
|
and in the right order. Thus we can't open body, open div, close body, because
|
|
that would be a div tag which was pushed but not pulled.
|
|
|
|
=
|
|
int unique_xref = 0;
|
|
typedef struct HTML_tag {
|
|
char *tag_name;
|
|
int tag_xref;
|
|
CLASS_DEFINITION
|
|
} HTML_tag;
|
|
|
|
int HTML::push_tag(OUTPUT_STREAM, char *tag) {
|
|
int u = unique_xref++;
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if (hs) {
|
|
HTML_tag *ht = CREATE(HTML_tag);
|
|
ht->tag_name = tag;
|
|
ht->tag_xref = u;
|
|
PUSH_TO_LIFO_STACK(ht, HTML_tag, hs->tag_stack);
|
|
}
|
|
return u;
|
|
}
|
|
|
|
@ =
|
|
void HTML::pop_tag(OUTPUT_STREAM, char *tag) {
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if (hs) {
|
|
if (LIFO_STACK_EMPTY(HTML_tag, hs->tag_stack)) {
|
|
LOG("{tag: %s}\n", tag);
|
|
tag_error("closed HTML tag which wasn't open");
|
|
} else {
|
|
HTML_tag *ht = TOP_OF_LIFO_STACK(HTML_tag, hs->tag_stack);
|
|
if (strcmp(tag, ht->tag_name) != 0) {
|
|
LOG("{expected to close tag %s (%d), but actually closed %s}\n",
|
|
ht->tag_name, ht->tag_xref, tag);
|
|
tag_error("closed HTML tag which wasn't open");
|
|
}
|
|
POP_LIFO_STACK(HTML_tag, hs->tag_stack);
|
|
}
|
|
}
|
|
}
|
|
|
|
@ At the end, therefore, no tags must remain unpulled.
|
|
|
|
=
|
|
void HTML::completed(OUTPUT_STREAM) {
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if ((hs) && (LIFO_STACK_EMPTY(HTML_tag, hs->tag_stack) == FALSE)) {
|
|
HTML_tag *ht;
|
|
int i = 0;
|
|
LOG("HTML tag stack: ");
|
|
LOOP_DOWN_LIFO_STACK(ht, HTML_tag, hs->tag_stack) {
|
|
if (i++ > 0) LOG(" in ");
|
|
LOG("%s (%d)", ht->tag_name, ht->tag_xref);
|
|
}
|
|
LOG("\n");
|
|
tag_error("HTML tags still open");
|
|
}
|
|
}
|
|
|
|
@ We will open and close all HTML tags using the following macros, two
|
|
of which are variadic and have to be written out the old-fashioned way:
|
|
|
|
@d HTML_TAG(tag) HTML::tag(OUT, tag, NULL);
|
|
@d HTML_OPEN(tag) HTML::open(OUT, tag, NULL);
|
|
@d HTML_CLOSE(tag) HTML::close(OUT, tag);
|
|
|
|
=
|
|
#define HTML_TAG_WITH(tag, args...) { \
|
|
TEMPORARY_TEXT(details) \
|
|
WRITE_TO(details, args); \
|
|
HTML::tag(OUT, tag, details); \
|
|
DISCARD_TEXT(details) \
|
|
}
|
|
|
|
#define HTML_OPEN_WITH(tag, args...) { \
|
|
TEMPORARY_TEXT(details) \
|
|
WRITE_TO(details, args); \
|
|
HTML::open(OUT, tag, details); \
|
|
DISCARD_TEXT(details) \
|
|
}
|
|
|
|
@ Which themselves depend on these routines:
|
|
|
|
=
|
|
void HTML::tag(OUTPUT_STREAM, char *tag, text_stream *details) {
|
|
WRITE("<%s", tag);
|
|
if (Str::len(details) > 0) WRITE(" %S", details);
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if ((hs) && (hs->XHTML_flag)) WRITE(" /");
|
|
WRITE(">");
|
|
if (HTML::tag_formatting(tag) >= 1) WRITE("\n");
|
|
}
|
|
|
|
void HTML::tag_sc(OUTPUT_STREAM, char *tag, text_stream *details) {
|
|
WRITE("<%s", tag);
|
|
if (Str::len(details) > 0) WRITE(" %S", details);
|
|
WRITE(" />");
|
|
if (HTML::tag_formatting(tag) >= 1) WRITE("\n");
|
|
}
|
|
|
|
int HTML::tag_formatting(char *tag) {
|
|
if (strcmp(tag, "meta") == 0) return 1;
|
|
if (strcmp(tag, "link") == 0) return 1;
|
|
if (strcmp(tag, "hr") == 0) return 1;
|
|
if (strcmp(tag, "br") == 0) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void HTML::open(OUTPUT_STREAM, char *tag, text_stream *details) {
|
|
int f = HTML::pair_formatting(tag);
|
|
HTML::push_tag(OUT, tag);
|
|
WRITE("<%s", tag);
|
|
if (Str::len(details) > 0) WRITE(" %S", details);
|
|
WRITE(">");
|
|
if (f >= 2) { WRITE("\n"); INDENT; }
|
|
}
|
|
|
|
void HTML::close(OUTPUT_STREAM, char *tag) {
|
|
int f = HTML::pair_formatting(tag);
|
|
if (f >= 3) WRITE("\n");
|
|
if (f >= 2) OUTDENT;
|
|
WRITE("</%s>", tag);
|
|
HTML::pop_tag(OUT, tag);
|
|
if (f >= 1) WRITE("\n");
|
|
}
|
|
|
|
void HTML::open_indented_p(OUTPUT_STREAM, int depth, char *class) {
|
|
int margin = depth;
|
|
if (margin < 1) internal_error("minimal HTML indentation is 1");
|
|
if (margin > 9) margin = 9;
|
|
HTML_OPEN_WITH("p", "class=\"%sin%d\"", class, margin);
|
|
while (depth > 9) { depth--; WRITE(" "); }
|
|
}
|
|
|
|
int HTML::pair_formatting(char *tag) {
|
|
if (strcmp(tag, "td") == 0) return 3;
|
|
|
|
if (strcmp(tag, "head") == 0) return 2;
|
|
if (strcmp(tag, "body") == 0) return 2;
|
|
if (strcmp(tag, "div") == 0) return 2;
|
|
if (strcmp(tag, "table") == 0) return 2;
|
|
if (strcmp(tag, "tr") == 0) return 2;
|
|
if (strcmp(tag, "script") == 0) return 2;
|
|
if (strcmp(tag, "style") == 0) return 2;
|
|
|
|
if (strcmp(tag, "html") == 0) return 1;
|
|
if (strcmp(tag, "p") == 0) return 1;
|
|
if (strcmp(tag, "title") == 0) return 1;
|
|
if (strcmp(tag, "blockquote") == 0) return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
@h Head.
|
|
|
|
=
|
|
void HTML::begin_head(OUTPUT_STREAM, filename *CSS_file) {
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if ((hs) && (hs->XHTML_flag)) {
|
|
WRITE("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ");
|
|
WRITE("\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n");
|
|
HTML_OPEN_WITH("html", "xmlns=\"http://www.w3.org/1999/xhtml\"");
|
|
} else {
|
|
WRITE("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" ");
|
|
WRITE("\"http://www.w3.org/TR/html4/loose.dtd\">\n");
|
|
HTML_OPEN("html");
|
|
}
|
|
WRITE("\n");
|
|
HTML_OPEN("head");
|
|
HTML_TAG_WITH("meta", "http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"");
|
|
if (CSS_file)
|
|
HTML_TAG_WITH("link", "href=\"%/f\" rel=\"stylesheet\" type=\"text/css\"", CSS_file);
|
|
}
|
|
|
|
void HTML::end_head(OUTPUT_STREAM) {
|
|
HTML_CLOSE("head");
|
|
}
|
|
|
|
@ =
|
|
void HTML::title(OUTPUT_STREAM, text_stream *title) {
|
|
HTML_OPEN("title");
|
|
WRITE("%S", title);
|
|
HTML_CLOSE("title");
|
|
}
|
|
|
|
@h Scripts and styles.
|
|
|
|
=
|
|
void HTML::open_javascript(OUTPUT_STREAM, int define_project) {
|
|
HTML_OPEN_WITH("script", "type=\"text/javascript\"");
|
|
if (define_project) {
|
|
WRITE("function project() {\n"); INDENT;
|
|
#ifdef WINDOWS_JAVASCRIPT
|
|
WRITE("return external.Project;\n");
|
|
#endif
|
|
#ifndef WINDOWS_JAVASCRIPT
|
|
WRITE("return window.Project;\n");
|
|
#endif
|
|
OUTDENT; WRITE("}\n");
|
|
}
|
|
}
|
|
|
|
void HTML::close_javascript(OUTPUT_STREAM) {
|
|
HTML_CLOSE("script");
|
|
}
|
|
|
|
void HTML::incorporate_javascript(OUTPUT_STREAM, int define_project, filename *M) {
|
|
HTML::open_javascript(OUT, define_project);
|
|
if (TextFiles::read(M, FALSE, NULL, FALSE, HTML::incorporate_helper, NULL, OUT) == FALSE) {
|
|
WRITE_TO(STDERR, "%f", M);
|
|
internal_error("Unable to open model JS material for reading");
|
|
}
|
|
HTML::close_javascript(OUT);
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if (hs) hs->JS_included++;
|
|
}
|
|
|
|
void HTML::open_CSS(OUTPUT_STREAM) {
|
|
HTML_OPEN_WITH("style", "type=\"text/css\"");
|
|
WRITE("<!--\n");
|
|
}
|
|
|
|
void HTML::close_CSS(OUTPUT_STREAM) {
|
|
WRITE("-->\n");
|
|
HTML_CLOSE("style");
|
|
}
|
|
|
|
void HTML::incorporate_CSS(OUTPUT_STREAM, filename *M) {
|
|
HTML::open_CSS(OUT);
|
|
if (TextFiles::read(M, FALSE, NULL, FALSE, HTML::incorporate_helper, NULL, OUT) == FALSE)
|
|
internal_error("Unable to open model CSS material for reading");
|
|
HTML::close_CSS(OUT);
|
|
HTML_file_state *hs = Streams::get_HTML_file_state(OUT);
|
|
if (hs) hs->CSS_included++;
|
|
}
|
|
|
|
void HTML::incorporate_HTML(OUTPUT_STREAM, filename *M) {
|
|
if (TextFiles::read(M, FALSE, NULL, FALSE, HTML::incorporate_helper, NULL, OUT) == FALSE)
|
|
internal_error("Unable to open model HTML material for reading");
|
|
}
|
|
|
|
@ The helper simply performs a textual copy:
|
|
|
|
=
|
|
void HTML::incorporate_helper(text_stream *line_of_template,
|
|
text_file_position *tfp, void *OUT) {
|
|
WRITE("%S\n", line_of_template);
|
|
}
|
|
|
|
@h Body.
|
|
|
|
=
|
|
void HTML::begin_body(OUTPUT_STREAM, text_stream *class) {
|
|
if (class) HTML_OPEN_WITH("body", "class=\"%S\"", class)
|
|
else HTML_OPEN("body");
|
|
}
|
|
|
|
void HTML::end_body(OUTPUT_STREAM) {
|
|
HTML_CLOSE("body");
|
|
HTML_CLOSE("html");
|
|
}
|
|
|
|
@h Divisions.
|
|
|
|
=
|
|
void HTML::begin_div_with_id(OUTPUT_STREAM, char *id) {
|
|
HTML_OPEN_WITH("div", "id=\"%s\"", id);
|
|
}
|
|
|
|
void HTML::begin_div_with_class(OUTPUT_STREAM, char *cl) {
|
|
HTML_OPEN_WITH("div", "class=\"%s\"", cl);
|
|
}
|
|
|
|
void HTML::begin_div_with_class_and_id(OUTPUT_STREAM, char *cl, char *id, int hide) {
|
|
if (hide) HTML_OPEN_WITH("div", "class=\"%s\" id=\"%s\" style=\"display: none;\"", cl, id)
|
|
else HTML_OPEN_WITH("div", "class=\"%s\" id=\"%s\"", cl, id);
|
|
}
|
|
|
|
void HTML::begin_div_with_id_S(OUTPUT_STREAM, text_stream *id) {
|
|
TEMPORARY_TEXT(details)
|
|
WRITE_TO(details, "id=\"%S\"", id);
|
|
HTML::open(OUT, "div", details);
|
|
DISCARD_TEXT(details)
|
|
}
|
|
|
|
void HTML::begin_div_with_class_S(OUTPUT_STREAM, text_stream *cl) {
|
|
TEMPORARY_TEXT(details)
|
|
WRITE_TO(details, "class=\"%S\"", cl);
|
|
HTML::open(OUT, "div", details);
|
|
DISCARD_TEXT(details)
|
|
}
|
|
|
|
void HTML::begin_div_with_class_and_id_S(OUTPUT_STREAM, text_stream *cl, text_stream *id, int hide) {
|
|
TEMPORARY_TEXT(details)
|
|
WRITE_TO(details, "class=\"%S\" id=\"%S\"", cl, id);
|
|
if (hide) WRITE_TO(details, " style=\"display: none;\"");
|
|
HTML::open(OUT, "div", details);
|
|
DISCARD_TEXT(details)
|
|
}
|
|
|
|
void HTML::end_div(OUTPUT_STREAM) {
|
|
HTML_CLOSE("div");
|
|
}
|
|
|
|
@h Images.
|
|
|
|
=
|
|
void HTML::image(OUTPUT_STREAM, filename *F) {
|
|
HTML_TAG_WITH("img", "src=\"%/f\"", F);
|
|
}
|
|
|
|
void HTML::image_to_dimensions(OUTPUT_STREAM, filename *F, int w, int h) {
|
|
if ((w > 0) && (h > 0)) {
|
|
HTML_TAG_WITH("img", "src=\"%/f\" alt=\"%S\" width=\"%d\" height=\"%d\"",
|
|
F, Filenames::get_leafname(F), w, h);
|
|
} else if (w > 0) {
|
|
HTML_TAG_WITH("img", "src=\"%/f\" alt=\"%S\" width=\"%d\"",
|
|
F, Filenames::get_leafname(F), w);
|
|
} else if (h > 0) {
|
|
HTML_TAG_WITH("img", "src=\"%/f\" alt=\"%S\" height=\"%d\"",
|
|
F, Filenames::get_leafname(F), h);
|
|
} else {
|
|
HTML_TAG_WITH("img", "src=\"%/f\" alt=\"%S\"",
|
|
F, Filenames::get_leafname(F));
|
|
}
|
|
}
|
|
|
|
@ Tooltips are the evanescent pop-up windows which appear, a little behind the
|
|
mouse arrow, when it is poised waiting over the icon. (Inform makes heavy use of
|
|
these in its World index, for instance, to clarify what abbreviations mean.)
|
|
|
|
=
|
|
void HTML::icon_with_tooltip(OUTPUT_STREAM, text_stream *icon_name,
|
|
text_stream *tip, text_stream *tip2) {
|
|
TEMPORARY_TEXT(img)
|
|
WRITE_TO(img, "border=0 src=%S ", icon_name);
|
|
if (tip) {
|
|
WRITE_TO(img, "title=\"%S", tip);
|
|
if (tip2) WRITE_TO(img, " %S", tip2);
|
|
WRITE_TO(img, "\"");
|
|
}
|
|
HTML_TAG_WITH("img", "%S", img);
|
|
DISCARD_TEXT(img)
|
|
}
|
|
|
|
@h Links.
|
|
|
|
=
|
|
void HTML::anchor(OUTPUT_STREAM, text_stream *id) {
|
|
HTML_OPEN_WITH("a", "id=\"%S\"", id); HTML_CLOSE("a");
|
|
}
|
|
|
|
void HTML::begin_link(OUTPUT_STREAM, text_stream *to) {
|
|
HTML_OPEN_WITH("a", "href=\"%S\"", to);
|
|
}
|
|
|
|
void HTML::begin_download_link(OUTPUT_STREAM, text_stream *to) {
|
|
HTML_OPEN_WITH("a", "href=\"%S\" download", to);
|
|
}
|
|
|
|
void HTML::begin_link_with_class(OUTPUT_STREAM, text_stream *cl, text_stream *to) {
|
|
HTML::begin_link_with_class_onclick(OUT, cl, to, NULL);
|
|
}
|
|
|
|
void HTML::begin_link_with_class_title(OUTPUT_STREAM, text_stream *cl, text_stream *to, text_stream *ti) {
|
|
HTML::begin_link_with_class_title_onclick(OUT, cl, to, ti, NULL);
|
|
}
|
|
|
|
void HTML::begin_link_with_class_onclick(OUTPUT_STREAM, text_stream *cl, text_stream *to, text_stream *on) {
|
|
HTML::begin_link_with_class_title_onclick(OUT, cl, to, NULL, on);
|
|
}
|
|
|
|
void HTML::begin_link_with_class_title_onclick(OUTPUT_STREAM, text_stream *cl, text_stream *to, text_stream *ti, text_stream *on) {
|
|
WRITE("<a href=\"%S\" class=\"%S\"", to, cl);
|
|
if (Str::len(ti) > 0) WRITE(" title=\"%S\"", ti);
|
|
if (Str::len(on) > 0) WRITE(" onclick=\"%S\"", on);
|
|
WRITE(">");
|
|
}
|
|
|
|
void HTML::end_link(OUTPUT_STREAM) {
|
|
HTML_CLOSE("a");
|
|
}
|
|
|
|
@ For convenience we keep a global setting for a prefix of a URL which
|
|
can be removed. None of that removal happens here; we're just the bookkeeper.
|
|
|
|
=
|
|
pathname *abbreviate_links_within = NULL;
|
|
void HTML::set_link_abbreviation_path(pathname *P) {
|
|
abbreviate_links_within = P;
|
|
}
|
|
pathname *HTML::get_link_abbreviation_path(void) {
|
|
return abbreviate_links_within;
|
|
}
|
|
|
|
@h Tables.
|
|
Opening a generic bland table with reasonable column spacing:
|
|
|
|
=
|
|
void HTML::begin_plain_html_table(OUTPUT_STREAM) {
|
|
HTML::begin_html_table(OUT, NULL, FALSE, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
void HTML::begin_wide_html_table(OUTPUT_STREAM) {
|
|
HTML::begin_html_table(OUT, NULL, TRUE, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
@ And some more general code:
|
|
|
|
=
|
|
void HTML::begin_html_table(OUTPUT_STREAM, char *colour, int full_width,
|
|
int border, int cellspacing, int cellpadding, int height, int width) {
|
|
TEMPORARY_TEXT(tab)
|
|
WRITE_TO(tab, "border=\"%d\" cellspacing=\"%d\" cellpadding=\"%d\"",
|
|
border, cellspacing, cellpadding);
|
|
if (colour) {
|
|
if (*colour == '*')
|
|
WRITE_TO(tab, " style=\"background-image:url('inform:/%s');\"", colour+1);
|
|
else
|
|
WRITE_TO(tab, " bgcolor=\"%s\"", colour);
|
|
}
|
|
if (full_width) WRITE_TO(tab, " width=100%%");
|
|
if (width > 0) WRITE_TO(tab, " width=\"%d\"", width);
|
|
if (height > 0) WRITE_TO(tab, " height=\"%d\"", height);
|
|
HTML_OPEN_WITH("table", "%S", tab);
|
|
DISCARD_TEXT(tab)
|
|
}
|
|
void HTML::begin_html_table_bg(OUTPUT_STREAM, char *colour, int full_width,
|
|
int border, int cellspacing, int cellpadding, int height, int width, char *bg) {
|
|
TEMPORARY_TEXT(tab)
|
|
WRITE_TO(tab, "border=\"%d\" cellspacing=\"%d\" cellpadding=\"%d\"",
|
|
border, cellspacing, cellpadding);
|
|
if (bg) WRITE_TO(tab, " background=\"inform:/map_icons/%s\"", bg);
|
|
if (colour) WRITE_TO(tab, " bgcolor=\"%s\"", colour);
|
|
if (full_width) WRITE_TO(tab, " width=100%%");
|
|
if (width > 0) WRITE_TO(tab, " width=\"%d\"", width);
|
|
if (height > 0) WRITE_TO(tab, " height=\"%d\"", height);
|
|
HTML_OPEN_WITH("table", "%S", tab);
|
|
DISCARD_TEXT(tab)
|
|
}
|
|
void HTML::first_html_column(OUTPUT_STREAM, int width) {
|
|
HTML_OPEN("tr");
|
|
if (width > 0) HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\"");
|
|
}
|
|
void HTML::first_html_column_nowrap(OUTPUT_STREAM, int width, char *colour) {
|
|
if (colour) HTML_OPEN_WITH("tr", "bgcolor=\"%s\"", colour) else HTML_OPEN("tr");
|
|
TEMPORARY_TEXT(col)
|
|
WRITE_TO(col, "style=\"white-space:nowrap;\" align=\"left\" valign=\"top\" height=\"20\"");
|
|
if (width > 0) WRITE_TO(col, " width=\"%d\"", width);
|
|
HTML_OPEN_WITH("td", "%S", col);
|
|
DISCARD_TEXT(col)
|
|
}
|
|
void HTML::first_html_column_spaced(OUTPUT_STREAM, int width) {
|
|
HTML_OPEN("tr");
|
|
TEMPORARY_TEXT(col)
|
|
WRITE_TO(col, "style=\"padding-top: 3px;\" align=\"left\" valign=\"top\"");
|
|
if (width > 0) WRITE_TO(col, " width=\"%d\"", width);
|
|
HTML_OPEN_WITH("td", "%S", col);
|
|
DISCARD_TEXT(col)
|
|
}
|
|
void HTML::first_html_column_coloured(OUTPUT_STREAM, int width, char *colour, int cs) {
|
|
if (colour) HTML_OPEN_WITH("tr", "bgcolor=\"%s\"", colour) else HTML_OPEN("tr");
|
|
TEMPORARY_TEXT(col)
|
|
WRITE_TO(col, "nowrap=\"nowrap\" align=\"left\" valign=\"top\"");
|
|
if (width > 0) WRITE_TO(col, " width=\"%d\"", width);
|
|
if (cs > 0) WRITE_TO(col, " colspan=\"%d\"", cs);
|
|
HTML_OPEN_WITH("td", "%S", col);
|
|
DISCARD_TEXT(col)
|
|
}
|
|
void HTML::next_html_column(OUTPUT_STREAM, int width) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\"");
|
|
}
|
|
void HTML::next_html_column_centred(OUTPUT_STREAM, int width) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "align=\"center\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "align=\"center\" valign=\"top\"");
|
|
}
|
|
void HTML::next_html_column_spanning(OUTPUT_STREAM, int width, int sp) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\" colspan=\"%d\" width=\"%d\"", sp, width)
|
|
else HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\" colspan=\"%d\"", sp);
|
|
}
|
|
void HTML::next_html_column_nowrap(OUTPUT_STREAM, int width) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "style=\"white-space:nowrap;\" align=\"left\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "style=\"white-space:nowrap;\" align=\"left\" valign=\"top\"");
|
|
}
|
|
void HTML::next_html_column_spaced(OUTPUT_STREAM, int width) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "style=\"padding-top: 3px;\" align=\"left\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "style=\"padding-top: 3px;\" align=\"left\" valign=\"top\"");
|
|
}
|
|
void HTML::next_html_column_nw(OUTPUT_STREAM, int width) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "nowrap=\"nowrap\" align=\"left\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "nowrap=\"nowrap\" align=\"left\" valign=\"top\"");
|
|
}
|
|
void HTML::next_html_column_w(OUTPUT_STREAM, int width) {
|
|
WRITE(" ");
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "align=\"left\" valign=\"top\"");
|
|
}
|
|
void HTML::next_html_column_right_justified(OUTPUT_STREAM, int width) {
|
|
HTML_CLOSE("td");
|
|
if (width > 0) HTML_OPEN_WITH("td", "align=\"right\" valign=\"top\" width=\"%d\"", width)
|
|
else HTML_OPEN_WITH("td", "align=\"right\" valign=\"top\"");
|
|
}
|
|
void HTML::end_html_row(OUTPUT_STREAM) {
|
|
HTML_CLOSE("td");
|
|
HTML_CLOSE("tr");
|
|
}
|
|
void HTML::end_html_table(OUTPUT_STREAM) {
|
|
HTML_CLOSE("table");
|
|
}
|
|
|
|
@h Round-rects.
|
|
|
|
@d CORNER_SIZE 8 /* measured in pixels */
|
|
@d ROUND_BOX_TOP 1
|
|
@d ROUND_BOX_BOTTOM 2
|
|
|
|
=
|
|
void HTML::open_coloured_box(OUTPUT_STREAM, char *html_colour, int rounding) {
|
|
HTML_OPEN_WITH("table",
|
|
"width=\"100%%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" "
|
|
"style=\"background-color: #%s\"", html_colour);
|
|
HTML_OPEN("tr");
|
|
HTML_OPEN_WITH("td", "width=\"%d\"", CORNER_SIZE);
|
|
if (rounding & ROUND_BOX_TOP) HTML::box_corner(OUT, html_colour, "tl");
|
|
HTML_CLOSE("td");
|
|
HTML_OPEN("td");
|
|
HTML_CLOSE("td");
|
|
HTML_OPEN_WITH("td", "width=\"%d\"", CORNER_SIZE);
|
|
if (rounding & ROUND_BOX_TOP) HTML::box_corner(OUT, html_colour, "tr");
|
|
HTML_CLOSE("td");
|
|
HTML_CLOSE("tr");
|
|
HTML_OPEN("tr");
|
|
HTML_OPEN_WITH("td", "width=\"%d\"", CORNER_SIZE);
|
|
HTML_CLOSE("td");
|
|
HTML_OPEN("td");
|
|
}
|
|
|
|
void HTML::close_coloured_box(OUTPUT_STREAM, char *html_colour, int rounding) {
|
|
HTML_CLOSE("td");
|
|
HTML_OPEN_WITH("td", "width=\"%d\"", CORNER_SIZE);
|
|
HTML_CLOSE("td");
|
|
HTML_CLOSE("tr");
|
|
HTML_OPEN("tr");
|
|
HTML_OPEN_WITH("td", "width=\"%d\"", CORNER_SIZE);
|
|
if (rounding & ROUND_BOX_BOTTOM) HTML::box_corner(OUT, html_colour, "bl");
|
|
HTML_CLOSE("td");
|
|
HTML_OPEN("td");
|
|
HTML_CLOSE("td");
|
|
HTML_OPEN_WITH("td", "width=\"%d\"", CORNER_SIZE);
|
|
if (rounding & ROUND_BOX_BOTTOM) HTML::box_corner(OUT, html_colour, "br");
|
|
HTML_CLOSE("td");
|
|
HTML_CLOSE("tr");
|
|
HTML::end_html_table(OUT);
|
|
}
|
|
|
|
void HTML::box_corner(OUTPUT_STREAM, char *html_colour, char *corner) {
|
|
HTML_TAG_WITH("img",
|
|
"src=\"inform:/bg_images/%s_corner_%s.gif\" "
|
|
"width=\"%d\" height=\"%d\" border=\"0\" alt=\"...\"",
|
|
corner, html_colour, CORNER_SIZE, CORNER_SIZE);
|
|
}
|
|
|
|
@h Miscellaneous.
|
|
|
|
=
|
|
void HTML::comment(OUTPUT_STREAM, text_stream *text) {
|
|
WRITE("<!--%S-->\n", text);
|
|
}
|
|
|
|
void HTML::heading(OUTPUT_STREAM, char *tag, text_stream *text) {
|
|
HTML_OPEN(tag);
|
|
WRITE("%S", text);
|
|
HTML_CLOSE(tag);
|
|
WRITE("\n");
|
|
}
|
|
|
|
void HTML::hr(OUTPUT_STREAM, char *class) {
|
|
if (class) HTML_TAG_WITH("hr", "class=\"%s\"", class)
|
|
else HTML_TAG("hr");
|
|
}
|
|
|
|
@h HTML colours.
|
|
Inform uses these when constructing the map in the World index.
|
|
|
|
=
|
|
typedef struct colour_translation {
|
|
wchar_t *chip_name;
|
|
wchar_t *html_colour;
|
|
} colour_translation;
|
|
|
|
colour_translation table_of_translations[] = {
|
|
{ L"Alice Blue", L"F0F8FF" },
|
|
{ L"Antique White", L"FAEBD7" },
|
|
{ L"Aqua", L"00FFFF" },
|
|
{ L"Aquamarine", L"7FFFD4" },
|
|
{ L"Azure", L"F0FFFF" },
|
|
{ L"Beige", L"F5F5DC" },
|
|
{ L"Bisque", L"FFE4C4" },
|
|
{ L"Black", L"000000" },
|
|
{ L"Blanched Almond", L"FFEBCD" },
|
|
{ L"Blue", L"0000FF" },
|
|
{ L"Blue Violet", L"8A2BE2" },
|
|
{ L"Brown", L"A52A2A" },
|
|
{ L"Burly Wood", L"DEB887" },
|
|
{ L"Cadet Blue", L"5F9EA0" },
|
|
{ L"Chartreuse", L"7FFF00" },
|
|
{ L"Chocolate", L"D2691E" },
|
|
{ L"Coral", L"FF7F50" },
|
|
{ L"Cornflower Blue", L"6495ED" },
|
|
{ L"Cornsilk", L"FFF8DC" },
|
|
{ L"Crimson", L"DC143C" },
|
|
{ L"Cyan", L"00FFFF" },
|
|
{ L"Dark Blue", L"00008B" },
|
|
{ L"Dark Cyan", L"008B8B" },
|
|
{ L"Dark Golden Rod", L"B8860B" },
|
|
{ L"Dark Gray", L"A9A9A9" },
|
|
{ L"Dark Green", L"006400" },
|
|
{ L"Dark Khaki", L"BDB76B" },
|
|
{ L"Dark Magenta", L"8B008B" },
|
|
{ L"Dark Olive Green", L"556B2F" },
|
|
{ L"Dark Orange", L"FF8C00" },
|
|
{ L"Dark Orchid", L"9932CC" },
|
|
{ L"Dark Red", L"8B0000" },
|
|
{ L"Dark Salmon", L"E9967A" },
|
|
{ L"Dark Sea Green", L"8FBC8F" },
|
|
{ L"Dark Slate Blue", L"483D8B" },
|
|
{ L"Dark Slate Gray", L"2F4F4F" },
|
|
{ L"Dark Turquoise", L"00CED1" },
|
|
{ L"Dark Violet", L"9400D3" },
|
|
{ L"Deep Pink", L"FF1493" },
|
|
{ L"Deep Sky Blue", L"00BFFF" },
|
|
{ L"Dim Gray", L"696969" },
|
|
{ L"Dodger Blue", L"1E90FF" },
|
|
{ L"Feldspar", L"D19275" },
|
|
{ L"Fire Brick", L"B22222" },
|
|
{ L"Floral White", L"FFFAF0" },
|
|
{ L"Forest Green", L"228B22" },
|
|
{ L"Fuchsia", L"FF00FF" },
|
|
{ L"Gainsboro", L"DCDCDC" },
|
|
{ L"Ghost White", L"F8F8FF" },
|
|
{ L"Gold", L"FFD700" },
|
|
{ L"Golden Rod", L"DAA520" },
|
|
{ L"Gray", L"808080" },
|
|
{ L"Green", L"008000" },
|
|
{ L"Green Yellow", L"ADFF2F" },
|
|
{ L"Honey Dew", L"F0FFF0" },
|
|
{ L"Hot Pink", L"FF69B4" },
|
|
{ L"Indian Red", L"CD5C5C" },
|
|
{ L"Indigo", L"4B0082" },
|
|
{ L"Ivory", L"FFFFF0" },
|
|
{ L"Khaki", L"F0E68C" },
|
|
{ L"Lavender", L"E6E6FA" },
|
|
{ L"Lavender Blush", L"FFF0F5" },
|
|
{ L"Lawn Green", L"7CFC00" },
|
|
{ L"Lemon Chiffon", L"FFFACD" },
|
|
{ L"Light Blue", L"ADD8E6" },
|
|
{ L"Light Coral", L"F08080" },
|
|
{ L"Light Cyan", L"E0FFFF" },
|
|
{ L"Light Golden Rod Yellow", L"FAFAD2" },
|
|
{ L"Light Grey", L"D3D3D3" },
|
|
{ L"Light Green", L"90EE90" },
|
|
{ L"Light Pink", L"FFB6C1" },
|
|
{ L"Light Salmon", L"FFA07A" },
|
|
{ L"Light Sea Green", L"20B2AA" },
|
|
{ L"Light Sky Blue", L"87CEFA" },
|
|
{ L"Light Slate Blue", L"8470FF" },
|
|
{ L"Light Slate Gray", L"778899" },
|
|
{ L"Light Steel Blue", L"B0C4DE" },
|
|
{ L"Light Yellow", L"FFFFE0" },
|
|
{ L"Lime", L"00FF00" },
|
|
{ L"Lime Green", L"32CD32" },
|
|
{ L"Linen", L"FAF0E6" },
|
|
{ L"Magenta", L"FF00FF" },
|
|
{ L"Maroon", L"800000" },
|
|
{ L"Medium Aquamarine", L"66CDAA" },
|
|
{ L"Medium Blue", L"0000CD" },
|
|
{ L"Medium Orchid", L"BA55D3" },
|
|
{ L"Medium Purple", L"9370D8" },
|
|
{ L"Medium Sea Green", L"3CB371" },
|
|
{ L"Medium Slate Blue", L"7B68EE" },
|
|
{ L"Medium Spring Green", L"00FA9A" },
|
|
{ L"Medium Turquoise", L"48D1CC" },
|
|
{ L"Medium Violet Red", L"CA226B" },
|
|
{ L"Midnight Blue", L"191970" },
|
|
{ L"Mint Cream", L"F5FFFA" },
|
|
{ L"Misty Rose", L"FFE4E1" },
|
|
{ L"Moccasin", L"FFE4B5" },
|
|
{ L"Navajo White", L"FFDEAD" },
|
|
{ L"Navy", L"000080" },
|
|
{ L"Old Lace", L"FDF5E6" },
|
|
{ L"Olive", L"808000" },
|
|
{ L"Olive Drab", L"6B8E23" },
|
|
{ L"Orange", L"FFA500" },
|
|
{ L"Orange Red", L"FF4500" },
|
|
{ L"Orchid", L"DA70D6" },
|
|
{ L"Pale Golden Rod", L"EEE8AA" },
|
|
{ L"Pale Green", L"98FB98" },
|
|
{ L"Pale Turquoise", L"AFEEEE" },
|
|
{ L"Pale Violet Red", L"D87093" },
|
|
{ L"Papaya Whip", L"FFEFD5" },
|
|
{ L"Peach Puff", L"FFDAB9" },
|
|
{ L"Peru", L"CD853F" },
|
|
{ L"Pink", L"FFC0CB" },
|
|
{ L"Plum", L"DDA0DD" },
|
|
{ L"Powder Blue", L"B0E0E6" },
|
|
{ L"Purple", L"800080" },
|
|
{ L"Red", L"FF0000" },
|
|
{ L"Rosy Brown", L"BC8F8F" },
|
|
{ L"Royal Blue", L"4169E1" },
|
|
{ L"Saddle Brown", L"8B4513" },
|
|
{ L"Salmon", L"FA8072" },
|
|
{ L"Sandy Brown", L"F4A460" },
|
|
{ L"Sea Green", L"2E8B57" },
|
|
{ L"Sea Shell", L"FFF5EE" },
|
|
{ L"Sienna", L"A0522D" },
|
|
{ L"Silver", L"C0C0C0" },
|
|
{ L"Sky Blue", L"87CEEB" },
|
|
{ L"Slate Blue", L"6A5ACD" },
|
|
{ L"Slate Gray", L"708090" },
|
|
{ L"Snow", L"FFFAFA" },
|
|
{ L"Spring Green", L"00FF7F" },
|
|
{ L"Steel Blue", L"4682B4" },
|
|
{ L"Tan", L"D2B48C" },
|
|
{ L"Teal", L"008080" },
|
|
{ L"Thistle", L"D8BFD8" },
|
|
{ L"Tomato", L"FF6347" },
|
|
{ L"Turquoise", L"40E0D0" },
|
|
{ L"Violet", L"EE82EE" },
|
|
{ L"Violet Red", L"D02090" },
|
|
{ L"Wheat", L"F5DEB3" },
|
|
{ L"White", L"FFFFFF" },
|
|
{ L"White Smoke", L"F5F5F5" },
|
|
{ L"Yellow", L"FFFF00" },
|
|
{ L"Yellow Green", L"9ACD32" },
|
|
{ L"", L"" }
|
|
};
|
|
|
|
@ The following is used only a handful of times, if at all, and does not
|
|
need to run quickly.
|
|
|
|
=
|
|
wchar_t *HTML::translate_colour_name(wchar_t *original) {
|
|
for (int j=0; Wide::cmp(table_of_translations[j].chip_name, L""); j++)
|
|
if (Wide::cmp(table_of_translations[j].chip_name, original) == 0)
|
|
return table_of_translations[j].html_colour;
|
|
return NULL;
|
|
}
|
|
|
|
@ =
|
|
void HTML::begin_colour(OUTPUT_STREAM, text_stream *col) {
|
|
HTML_OPEN_WITH("span", "style=\"color:#%S\"", col);
|
|
}
|
|
void HTML::end_colour(OUTPUT_STREAM) {
|
|
HTML_CLOSE("span");
|
|
}
|
|
|
|
@h Writing text.
|
|
To begin with, to XML:
|
|
|
|
=
|
|
void HTML::write_xml_safe_text(OUTPUT_STREAM, text_stream *txt) {
|
|
LOOP_THROUGH_TEXT(pos, txt) {
|
|
wchar_t c = Str::get(pos);
|
|
switch(c) {
|
|
case '&': WRITE("&"); break;
|
|
case '<': WRITE("<"); break;
|
|
case '>': WRITE(">"); break;
|
|
default: PUT(c); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@ And now to HTML. This would be very similar, except:
|
|
|
|
(a) if the |words| and |html| modules are both present, we recognise
|
|
|*source text*Source/story.ni*14*| as something which should expand to a
|
|
source code link -- except that the much less commonly occurring
|
|
|SOURCE_REF_CHAR| character code is used in place of the asterisk;
|
|
(b) if the |problems| module is present, we recognise |FORCE_NEW_PARA_CHAR|
|
|
as a paragraph break.
|
|
|
|
These two special case characters are lower and upper case Icelandic eth,
|
|
respectively. These do not occur in Inform source text.
|
|
|
|
@d SOURCE_REF_CHAR L'\xf0'
|
|
@d FORCE_NEW_PARA_CHAR L'\xd0'
|
|
|
|
=
|
|
text_stream *source_ref_fields[3] = { NULL, NULL, NULL }; /* paraphrase, filename, line */
|
|
int source_ref_field = -1; /* which field we are buffering */
|
|
|
|
void HTML::put(OUTPUT_STREAM, int charcode) {
|
|
@<Buffer into one of the source reference fields@>;
|
|
switch(charcode) {
|
|
case '"': WRITE("""); break;
|
|
case '<': WRITE("<"); break;
|
|
case '>': WRITE(">"); break;
|
|
case '&': WRITE("&"); break;
|
|
case NEWLINE_IN_STRING: HTML_TAG("br"); break;
|
|
|
|
#ifdef PROBLEMS_MODULE
|
|
case FORCE_NEW_PARA_CHAR: HTML_CLOSE("p"); HTML_OPEN_WITH("p", "class=\"in2\"");
|
|
HTML::icon_with_tooltip(OUT, I"inform:/doc_images/ornament_flower.png", NULL, NULL);
|
|
WRITE(" "); break;
|
|
#endif
|
|
|
|
#ifdef WORDS_MODULE
|
|
case SOURCE_REF_CHAR: @<Deal with a source reference field divider@>; break;
|
|
#endif
|
|
|
|
default: PUT(charcode); break;
|
|
}
|
|
}
|
|
|
|
@<Buffer into one of the source reference fields@> =
|
|
if ((source_ref_field >= 0) && (charcode != SOURCE_REF_CHAR)) {
|
|
PUT_TO(source_ref_fields[source_ref_field], charcode); return;
|
|
}
|
|
|
|
@<Deal with a source reference field divider@> =
|
|
source_ref_field++;
|
|
if (source_ref_field == 3) {
|
|
source_ref_field = -1;
|
|
source_location sl;
|
|
sl.file_of_origin = TextFromFiles::filename_to_source_file(source_ref_fields[1]);
|
|
sl.line_number = Str::atoi(source_ref_fields[2], 0);
|
|
#ifdef HTML_MODULE
|
|
SourceLinks::link(OUT, sl, TRUE);
|
|
#endif
|
|
} else {
|
|
if (source_ref_fields[source_ref_field] == NULL)
|
|
source_ref_fields[source_ref_field] = Str::new();
|
|
Str::clear(source_ref_fields[source_ref_field]);
|
|
}
|