X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/halibut/blobdiff_plain/75a96e919e6ff75522ab16fad137fd1516452813..12f0ee84ca3e9810b49601347e9ecc0a0d97e948:/bk_html.c diff --git a/bk_html.c b/bk_html.c index 596b026..4da897f 100644 --- a/bk_html.c +++ b/bk_html.c @@ -10,11 +10,6 @@ * sensible. Perhaps for the topmost section in the file, no * fragment should be used? (Though it should probably still be * _there_ even if unused.) - * - * - nonbreaking spaces. - * - * - free up all the data we have allocated while running this - * backend. */ #include @@ -50,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; @@ -89,7 +85,7 @@ struct htmlsect { paragraph *title, *text; enum { NORMAL, TOP, INDEX } type; int contents_depth; - char *fragment; + char **fragments; }; typedef struct { @@ -155,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 @@ -174,9 +171,10 @@ static void element_attr(htmloutput *ho, char const *name, char const *value); static void element_attr_w(htmloutput *ho, char const *name, wchar_t const *value); static void html_text(htmloutput *ho, wchar_t const *str); +static void html_text_nbsp(htmloutput *ho, wchar_t const *str); static void html_text_limit(htmloutput *ho, wchar_t const *str, int maxlen); static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, - int maxlen, int quote_quotes); + int maxlen, int quote_quotes, int nbsp); static void html_nl(htmloutput *ho); static void html_raw(htmloutput *ho, char *text); static void html_raw_as_attr(htmloutput *ho, char *text); @@ -221,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; @@ -271,13 +271,9 @@ static htmlconfig html_configure(paragraph *source) { k++; /* treat `xhtml-' and `html-' the same */ if (!ustricmp(k, L"html-restrict-charset")) { - char *csname = utoa_dup(uadv(k), CS_ASCII); - ret.restrict_charset = charset_from_localenc(csname); - sfree(csname); + ret.restrict_charset = charset_from_ustr(&p->fpos, uadv(k)); } else if (!ustricmp(k, L"html-output-charset")) { - char *csname = utoa_dup(uadv(k), CS_ASCII); - ret.output_charset = charset_from_localenc(csname); - sfree(csname); + ret.output_charset = charset_from_ustr(&p->fpos, uadv(k)); } else if (!ustricmp(k, L"html-version")) { wchar_t *vername = uadv(k); static const struct { @@ -313,8 +309,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")) { @@ -466,11 +478,13 @@ paragraph *html_config_filename(char *filename) } void html_backend(paragraph *sourceform, keywordlist *keywords, - indexdata *idx, void *unused) { + indexdata *idx, void *unused) +{ paragraph *p; htmlconfig conf; htmlfilelist files = { NULL, NULL, NULL, NULL, NULL }; htmlsectlist sects = { NULL, NULL }, nonsects = { NULL, NULL }; + int has_index; IGNORE(unused); @@ -496,20 +510,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)) { @@ -520,7 +533,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); @@ -534,21 +547,32 @@ 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. */ - sect = html_new_sect(§s, NULL); - sect->text = NULL; - sect->type = INDEX; - sect->parent = topsect; - 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); - files.index = sect->file; + /* And the index, if we have one. */ + has_index = (count234(idx->entries) > 0); + if (has_index) { + 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->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; + } } /* @@ -593,7 +617,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; @@ -602,11 +626,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]); } } } @@ -828,6 +852,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); @@ -867,15 +911,16 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, if (f != files.head) element_close(&ho, "a"); - html_text(&ho, conf.nav_separator); - - if (f != files.index) { - element_open(&ho, "a"); - element_attr(&ho, "href", files.index->filename); + if (has_index) { + html_text(&ho, conf.nav_separator); + if (f != files.index) { + element_open(&ho, "a"); + element_attr(&ho, "href", files.index->filename); + } + html_text(&ho, conf.index_text); + if (f != files.index) + element_close(&ho, "a"); } - html_text(&ho, conf.index_text); - if (f != files.index) - element_close(&ho, "a"); html_text(&ho, conf.nav_separator); @@ -893,7 +938,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 +1021,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 +1086,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 +1229,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 +1248,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; @@ -1402,8 +1463,81 @@ void html_backend(paragraph *sourceform, keywordlist *keywords, } /* - * FIXME: Free all the working data. + * Free all the working data. */ + { + htmlfragment *frag; + while ( (frag = (htmlfragment *)delpos234(files.frags, 0)) != NULL ) { + /* + * frag->fragment is dynamically allocated, but will be + * freed when we process the htmlsect structure which + * it is attached to. + */ + sfree(frag); + } + freetree234(files.frags); + } + { + htmlsect *sect, *tmp; + sect = sects.head; + while (sect) { + int i; + tmp = sect->next; + 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; + for (i=0; i < conf.ntfragments; i++) + sfree(sect->fragments[i]); + sfree(sect->fragments); + sfree(sect); + sect = tmp; + } + } + { + htmlfile *file, *tmp; + file = files.head; + while (file) { + tmp = file->next; + sfree(file->filename); + sfree(file); + file = tmp; + } + } + { + int i; + indexentry *entry; + for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) { + htmlindex *hi = (htmlindex *)entry->backend_data; + sfree(hi); + } + } + { + paragraph *p; + word *w; + for (p = sourceform; p; p = p->next) + for (w = p->words; w; w = w->next) + if (w->type == word_IndexRef) { + htmlindexref *hr = (htmlindexref *)w->private_data; + assert(hr != NULL); + sfree(hr->fragment); + 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, @@ -1501,7 +1635,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); @@ -1517,6 +1652,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; } @@ -1540,12 +1682,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: @@ -1593,8 +1739,8 @@ static void html_words(htmloutput *ho, word *words, int flags, else html_text(ho, cfg->rquote); } else { - if (cvt_ok(ho->restrict_charset, w->text) || !w->alt) - html_text(ho, w->text); + if (!w->alt || cvt_ok(ho->restrict_charset, w->text)) + html_text_nbsp(ho, w->text); else html_words(ho, w->alt, flags, file, keywords, cfg); } @@ -1669,11 +1815,9 @@ static void html_charset_cleanup(htmloutput *ho) fwrite(outbuf, 1, bytes, ho->fp); } -static void return_to_neutral(htmloutput *ho) +static void return_mostly_to_neutral(htmloutput *ho) { - if (ho->state == HO_IN_TEXT) { - html_charset_cleanup(ho); - } else if (ho->state == HO_IN_EMPTY_TAG && is_xhtml(ho->ver)) { + 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, ">"); @@ -1682,6 +1826,15 @@ static void return_to_neutral(htmloutput *ho) ho->state = HO_NEUTRAL; } +static void return_to_neutral(htmloutput *ho) +{ + if (ho->state == HO_IN_TEXT) { + html_charset_cleanup(ho); + } + + return_mostly_to_neutral(ho); +} + static void element_open(htmloutput *ho, char const *name) { return_to_neutral(ho); @@ -1734,24 +1887,31 @@ static void element_attr_w(htmloutput *ho, char const *name, { html_charset_cleanup(ho); fprintf(ho->fp, " %s=\"", name); - html_text_limit_internal(ho, value, 0, TRUE); + html_text_limit_internal(ho, value, 0, TRUE, FALSE); html_charset_cleanup(ho); fputc('"', ho->fp); } static void html_text(htmloutput *ho, wchar_t const *text) { - html_text_limit(ho, text, 0); + return_mostly_to_neutral(ho); + html_text_limit_internal(ho, text, 0, FALSE, FALSE); +} + +static void html_text_nbsp(htmloutput *ho, wchar_t const *text) +{ + return_mostly_to_neutral(ho); + html_text_limit_internal(ho, text, 0, FALSE, TRUE); } static void html_text_limit(htmloutput *ho, wchar_t const *text, int maxlen) { - return_to_neutral(ho); - html_text_limit_internal(ho, text, maxlen, FALSE); + return_mostly_to_neutral(ho); + html_text_limit_internal(ho, text, maxlen, FALSE, FALSE); } static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, - int maxlen, int quote_quotes) + int maxlen, int quote_quotes, int nbsp) { int textlen = ustrlen(text); char outbuf[256]; @@ -1767,7 +1927,8 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, if (text[lenbefore] == L'<' || text[lenbefore] == L'>' || text[lenbefore] == L'&' || - (text[lenbefore] == L'"' && quote_quotes)) + (text[lenbefore] == L'"' && quote_quotes) || + (text[lenbefore] == L' ' && nbsp)) break; lenafter = lenbefore; bytes = charset_from_unicode(&text, &lenafter, outbuf, lenof(outbuf), @@ -1797,7 +1958,10 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text, fprintf(ho->fp, "&"); else if (*text == L'"') fprintf(ho->fp, """); - else + else if (*text == L' ') { + assert(nbsp); + fprintf(ho->fp, " "); + } else assert(!"Can't happen"); text++, textlen--; } @@ -1881,6 +2045,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) { @@ -1934,6 +2099,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 @@ -1964,13 +2136,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++; } @@ -1978,10 +2161,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,