X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/halibut/blobdiff_plain/cdb986cc9e29db157d58d06d02aa5e3c3d3c64a9..c885c2ffbb3aea5c255d3b2e767d4450b2bebcc6:/bk_html.c diff --git a/bk_html.c b/bk_html.c index 1bd573a..a1b46f6 100644 --- a/bk_html.c +++ b/bk_html.c @@ -45,7 +45,8 @@ typedef struct { char *index_filename; char *template_filename; char *single_filename; - char *template_fragment; + char **template_fragments; + int ntfragments; char *head_end, *body_start, *body_end, *addr_start, *addr_end; char *body_tag, *nav_attr; wchar_t *author, *description; @@ -84,7 +85,7 @@ struct htmlsect { paragraph *title, *text; enum { NORMAL, TOP, INDEX } type; int contents_depth; - char *fragment; + char **fragments; }; typedef struct { @@ -150,7 +151,8 @@ static void html_file_section(htmlconfig *cfg, htmlfilelist *files, htmlsect *sect, int depth); static htmlfile *html_new_file(htmlfilelist *list, char *filename); -static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title); +static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title, + htmlconfig *cfg); /* Flags for html_words() flags parameter */ #define NOTHING 0x00 @@ -217,7 +219,9 @@ static htmlconfig html_configure(paragraph *source) { ret.contents_filename = dupstr("Contents.html"); ret.index_filename = dupstr("IndexPage.html"); ret.template_filename = dupstr("%n.html"); - ret.template_fragment = dupstr("%b"); + ret.ntfragments = 1; + ret.template_fragments = snewn(ret.ntfragments, char *); + ret.template_fragments[0] = dupstr("%b"); ret.head_end = ret.body_tag = ret.body_start = ret.body_end = ret.addr_start = ret.addr_end = ret.nav_attr = NULL; ret.author = ret.description = NULL; @@ -255,6 +259,10 @@ static htmlconfig html_configure(paragraph *source) { ret.lquote = uadv(p->keyword); ret.rquote = uadv(ret.lquote); } + } else if (!ustricmp(p->keyword, L"index")) { + ret.index_text = uadv(p->keyword); + } else if (!ustricmp(p->keyword, L"contents")) { + ret.contents_text = uadv(p->keyword); } } } @@ -305,8 +313,24 @@ static htmlconfig html_configure(paragraph *source) { sfree(ret.template_filename); ret.template_filename = dupstr(adv(p->origkeyword)); } else if (!ustricmp(k, L"html-template-fragment")) { - sfree(ret.template_fragment); - ret.template_fragment = dupstr(adv(p->origkeyword)); + char *frag = adv(p->origkeyword); + if (*frag) { + while (ret.ntfragments--) + sfree(ret.template_fragments[ret.ntfragments]); + sfree(ret.template_fragments); + ret.template_fragments = NULL; + ret.ntfragments = 0; + while (*frag) { + ret.ntfragments++; + ret.template_fragments = + sresize(ret.template_fragments, + ret.ntfragments, char *); + ret.template_fragments[ret.ntfragments-1] = + dupstr(frag); + frag = adv(frag); + } + } else + error(err_cfginsufarg, &p->fpos, p->origkeyword, 1); } else if (!ustricmp(k, L"html-chapter-numeric")) { ret.achapter.just_numbers = utob(uadv(k)); } else if (!ustricmp(k, L"html-chapter-suffix")) { @@ -490,20 +514,19 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, * source form but needs to be consistently mentioned in * contents links. * - * While we're here, we'll also invent the HTML fragment name + * While we're here, we'll also invent the HTML fragment name(s) * for each section. */ { htmlsect *topsect, *sect; int d; - topsect = html_new_sect(§s, NULL); + topsect = html_new_sect(§s, NULL, &conf); topsect->type = TOP; topsect->title = NULL; topsect->text = sourceform; topsect->contents_depth = contents_depth(conf, 0); html_file_section(&conf, &files, topsect, -1); - topsect->fragment = NULL; for (p = sourceform; p; p = p->next) if (is_heading_type(p->type)) { @@ -514,7 +537,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, continue; } - sect = html_new_sect(§s, p); + sect = html_new_sect(§s, p, &conf); sect->text = p->next; sect->contents_depth = contents_depth(conf, d+1) - (d+1); @@ -528,23 +551,30 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, html_file_section(&conf, &files, sect, d); - sect->fragment = html_format(p, conf.template_fragment); - sect->fragment = html_sanitise_fragment(&files, sect->file, - sect->fragment); + { + int i; + for (i=0; i < conf.ntfragments; i++) { + sect->fragments[i] = + html_format(p, conf.template_fragments[i]); + sect->fragments[i] = + html_sanitise_fragment(&files, sect->file, + sect->fragments[i]); + } + } } /* And the index, if we have one. */ has_index = (count234(idx->entries) > 0); if (has_index) { - sect = html_new_sect(§s, NULL); + sect = html_new_sect(§s, NULL, &conf); sect->text = NULL; sect->type = INDEX; sect->parent = topsect; sect->contents_depth = 0; html_file_section(&conf, &files, sect, 0); /* peer of chapters */ - sect->fragment = utoa_dup(conf.index_text, CS_ASCII); - sect->fragment = html_sanitise_fragment(&files, sect->file, - sect->fragment); + sect->fragments[0] = utoa_dup(conf.index_text, CS_ASCII); + sect->fragments[0] = html_sanitise_fragment(&files, sect->file, + sect->fragments[0]); files.index = sect->file; } } @@ -591,7 +621,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, * won't attempt to add it to the contents or * anything weird like that). */ - sect = html_new_sect(&nonsects, p); + sect = html_new_sect(&nonsects, p, &conf); sect->file = parent->file; sect->parent = parent; p->private_data = sect; @@ -600,11 +630,11 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, * Fragment IDs for these paragraphs will simply be * `p' followed by an integer. */ - sect->fragment = snewn(40, char); - sprintf(sect->fragment, "p%d", + sect->fragments[0] = snewn(40, char); + sprintf(sect->fragments[0], "p%d", sect->file->last_fragment_number++); - sect->fragment = html_sanitise_fragment(&files, sect->file, - sect->fragment); + sect->fragments[0] = html_sanitise_fragment(&files, sect->file, + sect->fragments[0]); } } } @@ -737,6 +767,9 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, #define itemname(lt) ( (lt)==LI ? "li" : (lt)==DT ? "dt" : "dd" ) ho.fp = fopen(f->filename, "w"); + if (!ho.fp) + error(err_cantopenw, f->filename); + ho.charset = conf.output_charset; ho.restrict_charset = conf.restrict_charset; ho.cstate = charset_init_state; @@ -747,31 +780,38 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, /* . */ switch (conf.htmlver) { case HTML_3_2: - fprintf(ho.fp, "\n"); + if (ho.fp) + fprintf(ho.fp, "\n"); break; case HTML_4: - fprintf(ho.fp, "\n"); + if (ho.fp) + fprintf(ho.fp, "\n"); break; case ISO_HTML: - fprintf(ho.fp, "\n"); + if (ho.fp) + fprintf(ho.fp, "\n"); break; case XHTML_1_0_TRANSITIONAL: - fprintf(ho.fp, "\n", - charset_to_mimeenc(conf.output_charset)); - fprintf(ho.fp, "\n"); + if (ho.fp) { + fprintf(ho.fp, "\n", + charset_to_mimeenc(conf.output_charset)); + fprintf(ho.fp, "\n"); + } break; case XHTML_1_0_STRICT: - fprintf(ho.fp, "\n", - charset_to_mimeenc(conf.output_charset)); - fprintf(ho.fp, "\n"); + if (ho.fp) { + fprintf(ho.fp, "\n", + charset_to_mimeenc(conf.output_charset)); + fprintf(ho.fp, "\n"); + } break; } @@ -826,6 +866,26 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, if (conf.head_end) html_raw(&ho, conf.head_end); + /* + * Add any data defined in specific sections + * that go in this file. (This is mostly to allow tags for Mac online help.) + */ + for (s = sects.head; s; s = s->next) { + if (s->file == f && s->text) { + for (p = s->text; + p && (p == s->text || p->type == para_Title || + !is_heading_type(p->type)); + p = p->next) { + if (p->type == para_Config) { + if (!ustricmp(p->keyword, L"html-local-head")) { + html_raw(&ho, adv(p->origkeyword)); + } + } + } + } + } + element_close(&ho, "head"); html_nl(&ho); @@ -865,9 +925,8 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, if (f != files.head) element_close(&ho, "a"); - html_text(&ho, conf.nav_separator); - if (has_index) { + html_text(&ho, conf.nav_separator); if (f != files.index) { element_open(&ho, "a"); element_attr(&ho, "href", files.index->filename); @@ -893,7 +952,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, prevf = f; /* - * Write out a prefix TOC for the file. + * Write out a prefix TOC for the file (if a leaf file). * * We start by going through the section list and * collecting the sections which need to be added to @@ -976,6 +1035,12 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, */ displaying = TRUE; } else { + /* + * Doesn't belong in this file, but it may be + * a descendant of a section which does, in + * which case we should consider it for the + * main TOC of this file (for non-leaf files). + */ htmlsect *a, *ac; int depth, adepth; @@ -1035,14 +1100,18 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, element_open(&ho, htag); /* - * Provide anchor for cross-links to target. + * Provide anchor(s) for cross-links to target. * * (Also we'll have to do this separately in * other paragraph types - NumberedList and * BiblioCited.) */ - if (s->fragment) - html_fragment(&ho, s->fragment); + { + int i; + for (i=0; i < conf.ntfragments; i++) + if (s->fragments[i]) + html_fragment(&ho, s->fragments[i]); + } html_section_title(&ho, s, f, keywords, &conf, TRUE); @@ -1174,7 +1243,10 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, element_open(&ho, "p"); if (p->private_data) { htmlsect *s = (htmlsect *)p->private_data; - html_fragment(&ho, s->fragment); + int i; + for (i=0; i < conf.ntfragments; i++) + if (s->fragments[i]) + html_fragment(&ho, s->fragments[i]); } html_nl(&ho); html_words(&ho, p->kwtext, ALL, @@ -1190,7 +1262,10 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, element_open(&ho, "li"); if (p->private_data) { htmlsect *s = (htmlsect *)p->private_data; - html_fragment(&ho, s->fragment); + int i; + for (i=0; i < conf.ntfragments; i++) + if (s->fragments[i]) + html_fragment(&ho, s->fragments[i]); } html_nl(&ho); stackhead->itemtype = LI; @@ -1404,12 +1479,6 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, /* * Free all the working data. */ - sfree(conf.asect); - sfree(conf.single_filename); - sfree(conf.contents_filename); - sfree(conf.index_filename); - sfree(conf.template_filename); - sfree(conf.template_fragment); { htmlfragment *frag; while ( (frag = (htmlfragment *)delpos234(files.frags, 0)) != NULL ) { @@ -1426,15 +1495,21 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, htmlsect *sect, *tmp; sect = sects.head; while (sect) { + int i; tmp = sect->next; - sfree(sect->fragment); + for (i=0; i < conf.ntfragments; i++) + sfree(sect->fragments[i]); + sfree(sect->fragments); sfree(sect); sect = tmp; } sect = nonsects.head; while (sect) { + int i; tmp = sect->next; - sfree(sect->fragment); + for (i=0; i < conf.ntfragments; i++) + sfree(sect->fragments[i]); + sfree(sect->fragments); sfree(sect); sect = tmp; } @@ -1469,6 +1544,14 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, sfree(hr); } } + sfree(conf.asect); + sfree(conf.single_filename); + sfree(conf.contents_filename); + sfree(conf.index_filename); + sfree(conf.template_filename); + while (conf.ntfragments--) + sfree(conf.template_fragments[conf.ntfragments]); + sfree(conf.template_fragments); } static void html_file_section(htmlconfig *cfg, htmlfilelist *files, @@ -1566,7 +1649,8 @@ static htmlfile *html_new_file(htmlfilelist *list, char *filename) return ret; } -static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title) +static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title, + htmlconfig *cfg) { htmlsect *ret = snew(htmlsect); @@ -1582,6 +1666,13 @@ static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title) ret->parent = NULL; ret->type = NORMAL; + ret->fragments = snewn(cfg->ntfragments, char *); + { + int i; + for (i=0; i < cfg->ntfragments; i++) + ret->fragments[i] = NULL; + } + return ret; } @@ -1605,12 +1696,16 @@ static void html_words(htmloutput *ho, word *words, int flags, case word_LowerXref: if (flags & LINKS) { keyword *kwl = kw_lookup(keywords, w->text); - paragraph *p = kwl->para; - htmlsect *s = (htmlsect *)p->private_data; + paragraph *p; + htmlsect *s; + + assert(kwl); + p = kwl->para; + s = (htmlsect *)p->private_data; assert(s); - html_href(ho, file, s->file, s->fragment); + html_href(ho, file, s->file, s->fragments[0]); } break; case word_HyperEnd: @@ -1730,16 +1825,18 @@ static void html_charset_cleanup(htmloutput *ho) bytes = charset_from_unicode(NULL, NULL, outbuf, lenof(outbuf), ho->charset, &ho->cstate, NULL); - if (bytes > 0) + if (ho->fp && bytes > 0) fwrite(outbuf, 1, bytes, ho->fp); } static void return_mostly_to_neutral(htmloutput *ho) { - if (ho->state == HO_IN_EMPTY_TAG && is_xhtml(ho->ver)) { - fprintf(ho->fp, " />"); - } else if (ho->state == HO_IN_EMPTY_TAG || ho->state == HO_IN_TAG) { - fprintf(ho->fp, ">"); + if (ho->fp) { + if (ho->state == HO_IN_EMPTY_TAG && is_xhtml(ho->ver)) { + fprintf(ho->fp, " />"); + } else if (ho->state == HO_IN_EMPTY_TAG || ho->state == HO_IN_TAG) { + fprintf(ho->fp, ">"); + } } ho->state = HO_NEUTRAL; @@ -1757,58 +1854,68 @@ static void return_to_neutral(htmloutput *ho) static void element_open(htmloutput *ho, char const *name) { return_to_neutral(ho); - fprintf(ho->fp, "<%s", name); + if (ho->fp) + fprintf(ho->fp, "<%s", name); ho->state = HO_IN_TAG; } static void element_close(htmloutput *ho, char const *name) { return_to_neutral(ho); - fprintf(ho->fp, "", name); + if (ho->fp) + fprintf(ho->fp, "", name); ho->state = HO_NEUTRAL; } static void element_empty(htmloutput *ho, char const *name) { return_to_neutral(ho); - fprintf(ho->fp, "<%s", name); + if (ho->fp) + fprintf(ho->fp, "<%s", name); ho->state = HO_IN_EMPTY_TAG; } static void html_nl(htmloutput *ho) { return_to_neutral(ho); - fputc('\n', ho->fp); + if (ho->fp) + fputc('\n', ho->fp); } static void html_raw(htmloutput *ho, char *text) { return_to_neutral(ho); - fputs(text, ho->fp); + if (ho->fp) + fputs(text, ho->fp); } static void html_raw_as_attr(htmloutput *ho, char *text) { assert(ho->state == HO_IN_TAG || ho->state == HO_IN_EMPTY_TAG); - fputc(' ', ho->fp); - fputs(text, ho->fp); + if (ho->fp) { + fputc(' ', ho->fp); + fputs(text, ho->fp); + } } static void element_attr(htmloutput *ho, char const *name, char const *value) { html_charset_cleanup(ho); assert(ho->state == HO_IN_TAG || ho->state == HO_IN_EMPTY_TAG); - fprintf(ho->fp, " %s=\"%s\"", name, value); + if (ho->fp) + fprintf(ho->fp, " %s=\"%s\"", name, value); } static void element_attr_w(htmloutput *ho, char const *name, wchar_t const *value) { html_charset_cleanup(ho); - fprintf(ho->fp, " %s=\"", name); + if (ho->fp) + fprintf(ho->fp, " %s=\"", name); html_text_limit_internal(ho, value, 0, TRUE, FALSE); html_charset_cleanup(ho); - fputc('"', ho->fp); + if (ho->fp) + fputc('"', ho->fp); } static void html_text(htmloutput *ho, wchar_t const *text) @@ -1853,7 +1960,7 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, bytes = charset_from_unicode(&text, &lenafter, outbuf, lenof(outbuf), ho->charset, &ho->cstate, &err); textlen -= (lenbefore - lenafter); - if (bytes > 0) + if (bytes > 0 && ho->fp) fwrite(outbuf, 1, bytes, ho->fp); if (err) { /* @@ -1862,26 +1969,29 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, * we use an HTML numeric entity reference. */ assert(textlen > 0); - fprintf(ho->fp, "&#%ld;", (long int)*text); + if (ho->fp) + fprintf(ho->fp, "&#%ld;", (long int)*text); text++, textlen--; } else if (lenafter == 0 && textlen > 0) { /* * We have encountered a character which is special to * HTML. */ - if (*text == L'<') - fprintf(ho->fp, "<"); - else if (*text == L'>') - fprintf(ho->fp, ">"); - else if (*text == L'&') - fprintf(ho->fp, "&"); - else if (*text == L'"') - fprintf(ho->fp, """); - else if (*text == L' ') { - assert(nbsp); - fprintf(ho->fp, " "); - } else - assert(!"Can't happen"); + if (ho->fp) { + if (*text == L'<') + fprintf(ho->fp, "<"); + else if (*text == L'>') + fprintf(ho->fp, ">"); + else if (*text == L'&') + fprintf(ho->fp, "&"); + else if (*text == L'"') + fprintf(ho->fp, """); + else if (*text == L' ') { + assert(nbsp); + fprintf(ho->fp, " "); + } else + assert(!"Can't happen"); + } text++, textlen--; } } @@ -1890,7 +2000,8 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, static void cleanup(htmloutput *ho) { return_to_neutral(ho); - fclose(ho->fp); + if (ho->fp) + fclose(ho->fp); } static void html_href(htmloutput *ho, htmlfile *thisfile, @@ -1964,6 +2075,7 @@ static char *html_format(paragraph *p, char *template_string) } else if (p->keyword && *p->keyword && fmt == 'k') ws = p->keyword; else + /* %N comes here; also failure cases of other fmts */ w = p->words; if (ws) { @@ -2017,6 +2129,13 @@ static char *html_sanitise_fragment(htmlfilelist *files, htmlfile *file, *q = '\0'; } + /* If there's nothing left, make something valid up */ + if (!*text) { + static const char anonfrag[] = "anon"; + text = sresize(text, lenof(anonfrag), char); + strcpy(text, anonfrag); + } + /* * Now we check for clashes with other fragment names, and * adjust this one if necessary by appending a hyphen followed @@ -2047,13 +2166,24 @@ static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s, htmlfile *thisfile, keywordlist *keywords, htmlconfig *cfg) { + if (ho->contents_level >= depth && ho->contents_level > 0) { + element_close(ho, "li"); + html_nl(ho); + } + while (ho->contents_level > depth) { element_close(ho, "ul"); ho->contents_level--; + if (ho->contents_level > 0) { + element_close(ho, "li"); + } + html_nl(ho); } while (ho->contents_level < depth) { + html_nl(ho); element_open(ho, "ul"); + html_nl(ho); ho->contents_level++; } @@ -2061,10 +2191,10 @@ static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s, return; element_open(ho, "li"); - html_href(ho, thisfile, s->file, s->fragment); + html_href(ho, thisfile, s->file, s->fragments[0]); html_section_title(ho, s, thisfile, keywords, cfg, FALSE); element_close(ho, "a"); - element_close(ho, "li"); + /*
  • will be closed by a later invocation */ } static void html_section_title(htmloutput *ho, htmlsect *s, htmlfile *thisfile,