* fragment should be used? (Though it should probably still be
* _there_ even if unused.)
*
- * - new configurability:
- * * index_text, contents_text, preamble_text, title_separator,
- * nav_prev_text, nav_next_text, nav_separator,
- * index_main_sep, index_multi_sep, pre_versionid,
- * post_versionid
- * * Some means of specifying the distinction between
- * restrict-charset and output-charset. It seems to me that
- * `html-charset' is output-charset, and that
- * restrict-charset usually wants to be either output-charset
- * or UTF-8 (the latter indicating that any Unicode character
- * is fair game and it will be specified using &#foo; if it
- * isn't in output-charset). However, since XHTML defaults to
- * UTF-8 and it's fiddly to tell it otherwise, it's just
- * possible that some user may need to set restrict-charset
- * to their charset of choice while leaving _output_-charset
- * at UTF-8. Figure out some configuration, and apply it.
- *
- * - nonbreaking spaces.
- *
- * - free up all the data we have allocated while running this
- * backend.
+ * - In HHK index mode: subsidiary hhk entries (as in replacing
+ * `foo, bar' with `foo\n\tbar') can be done by embedding
+ * sub-<UL>s in the hhk file. This requires me getting round to
+ * supporting that idiom in the rest of Halibut, but I thought
+ * I'd record how it's done here in case I turn out to have
+ * forgotten when I get there.
*/
#include <stdio.h>
(p)->type == para_Title ? -1 : 0 )
typedef struct {
- int just_numbers;
+ int number_at_all, just_numbers;
wchar_t *number_suffix;
} sectlevel;
int ncdepths;
int address_section, visible_version_id;
int leaf_contains_contents, leaf_smallest_contents;
+ int navlinks;
+ int rellinks;
char *contents_filename;
char *index_filename;
char *template_filename;
char *single_filename;
- char *template_fragment;
+ char *chm_filename, *hhp_filename, *hhc_filename, *hhk_filename;
+ 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;
wchar_t *index_text, *contents_text, *preamble_text, *title_separator;
- wchar_t *nav_prev_text, *nav_next_text, *nav_separator;
+ wchar_t *nav_prev_text, *nav_next_text, *nav_up_text, *nav_separator;
wchar_t *index_main_sep, *index_multi_sep;
wchar_t *pre_versionid, *post_versionid;
int restrict_charset, output_charset;
int last_fragment_number;
int min_heading_depth;
htmlsect *first, *last; /* first/last highest-level sections */
+ /*
+ * The `temp' field is available for use in individual passes
+ * over the file list. For example, the HHK index generation
+ * uses it to ensure no index term references the same file
+ * more than once.
+ */
+ int temp;
};
struct htmlsect {
paragraph *title, *text;
enum { NORMAL, TOP, INDEX } type;
int contents_depth;
- char *fragment;
+ char **fragments;
};
typedef struct {
htmlfile *head, *tail;
htmlfile *single, *index;
tree234 *frags;
+ tree234 *files;
} htmlfilelist;
typedef struct {
* level.
*/
FILE *fp;
- int charset;
+ int charset, restrict_charset;
charset_state cstate;
int ver;
enum {
HO_NEUTRAL, HO_IN_TAG, HO_IN_EMPTY_TAG, HO_IN_TEXT
} state;
+ int hackflags; /* used for icky .HH* stuff */
+ int hacklimit; /* text size limit, again for .HH* */
/*
* Stuff beyond here deals with the higher syntactic level: it
* tracks how many levels of <ul> are currently open when
int contents_level;
} htmloutput;
+/*
+ * Nasty hacks that modify the behaviour of htmloutput files. All
+ * of these are flag bits set in ho.hackflags. HO_HACK_QUOTEQUOTES
+ * has the same effect as the `quote_quotes' parameter to
+ * html_text_limit_internal, except that it's set globally on an
+ * entire htmloutput structure; HO_HACK_QUOTENOTHING suppresses
+ * quoting of any HTML special characters (for .HHP files);
+ * HO_HACK_OMITQUOTES completely suppresses the generation of
+ * double quotes at all (turning them into single quotes, for want
+ * of a better idea).
+ */
+#define HO_HACK_QUOTEQUOTES 1
+#define HO_HACK_QUOTENOTHING 2
+#define HO_HACK_OMITQUOTES 4
+
static int html_fragment_compare(void *av, void *bv)
{
htmlfragment *a = (htmlfragment *)av;
return strcmp(a->fragment, b->fragment);
}
+static int html_filename_compare(void *av, void *bv)
+{
+ char *a = (char *)av;
+ char *b = (char *)bv;
+
+ return strcmp(a, b);
+}
+
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
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);
static char *html_format(paragraph *p, char *template_string);
static char *html_sanitise_fragment(htmlfilelist *files, htmlfile *file,
char *text);
+static char *html_sanitise_filename(htmlfilelist *files, char *text);
static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s,
htmlfile *thisfile, keywordlist *keywords,
*/
ret.leaf_level = 2;
ret.achapter.just_numbers = FALSE;
+ ret.achapter.number_at_all = TRUE;
ret.achapter.number_suffix = L": ";
ret.nasect = 1;
ret.asect = snewn(ret.nasect, sectlevel);
ret.asect[0].just_numbers = TRUE;
+ ret.achapter.number_at_all = TRUE;
ret.asect[0].number_suffix = L" ";
ret.ncdepths = 0;
ret.contents_depths = 0;
ret.address_section = TRUE;
ret.leaf_contains_contents = FALSE;
ret.leaf_smallest_contents = 4;
+ ret.navlinks = TRUE;
+ ret.rellinks = TRUE;
ret.single_filename = dupstr("Manual.html");
ret.contents_filename = dupstr("Contents.html");
ret.index_filename = dupstr("IndexPage.html");
ret.template_filename = dupstr("%n.html");
- ret.template_fragment = dupstr("%b");
+ ret.chm_filename = ret.hhp_filename = NULL;
+ ret.hhc_filename = ret.hhk_filename = NULL;
+ 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;
- ret.restrict_charset = CS_ASCII;
+ ret.restrict_charset = CS_UTF8;
ret.output_charset = CS_ASCII;
ret.htmlver = HTML_4;
ret.index_text = L"Index";
ret.title_separator = L" - ";
ret.nav_prev_text = L"Previous";
ret.nav_next_text = L"Next";
+ ret.nav_up_text = L"Up";
ret.nav_separator = L" | ";
ret.index_main_sep = L": ";
ret.index_multi_sep = L", ";
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);
}
}
}
if (!ustrnicmp(k, L"xhtml-", 6))
k++; /* treat `xhtml-' and `html-' the same */
- if (!ustricmp(k, L"html-charset")) {
- char *csname = utoa_dup(uadv(k), CS_ASCII);
- ret.restrict_charset = ret.output_charset =
- charset_from_localenc(csname);
- sfree(csname);
+ if (!ustricmp(k, L"html-restrict-charset")) {
+ ret.restrict_charset = charset_from_ustr(&p->fpos, uadv(k));
+ } else if (!ustricmp(k, L"html-output-charset")) {
+ 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 {
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-shownumber")) {
+ ret.achapter.number_at_all = utob(uadv(k));
+ } else if (!ustricmp(k, L"html-suppress-navlinks")) {
+ ret.navlinks = !utob(uadv(k));
+ } else if (!ustricmp(k, L"html-rellinks")) {
+ ret.rellinks = utob(uadv(k));
} else if (!ustricmp(k, L"html-chapter-suffix")) {
ret.achapter.number_suffix = uadv(k);
} else if (!ustricmp(k, L"html-leaf-level")) {
- ret.leaf_level = utoi(uadv(k));
+ wchar_t *u = uadv(k);
+ if (!ustricmp(u, L"infinite") ||
+ !ustricmp(u, L"infinity") ||
+ !ustricmp(u, L"inf"))
+ ret.leaf_level = -1; /* represents infinity */
+ else
+ ret.leaf_level = utoi(u);
} else if (!ustricmp(k, L"html-section-numeric")) {
wchar_t *q = uadv(k);
int n = 0;
ret.nasect = n+1;
}
ret.asect[n].just_numbers = utob(q);
+ } else if (!ustricmp(k, L"html-section-shownumber")) {
+ wchar_t *q = uadv(k);
+ int n = 0;
+ if (uisdigit(*q)) {
+ n = utoi(q);
+ q = uadv(q);
+ }
+ if (n >= ret.nasect) {
+ int i;
+ ret.asect = sresize(ret.asect, n+1, sectlevel);
+ for (i = ret.nasect; i <= n; i++)
+ ret.asect[i] = ret.asect[ret.nasect-1];
+ ret.nasect = n+1;
+ }
+ ret.asect[n].number_at_all = utob(q);
} else if (!ustricmp(k, L"html-section-suffix")) {
wchar_t *q = uadv(k);
int n = 0;
ret.leaf_contains_contents = utob(uadv(k));
} else if (!ustricmp(k, L"html-leaf-smallest-contents")) {
ret.leaf_smallest_contents = utoi(uadv(k));
+ } else if (!ustricmp(k, L"html-index-text")) {
+ ret.index_text = uadv(k);
+ } else if (!ustricmp(k, L"html-contents-text")) {
+ ret.contents_text = uadv(k);
+ } else if (!ustricmp(k, L"html-preamble-text")) {
+ ret.preamble_text = uadv(k);
+ } else if (!ustricmp(k, L"html-title-separator")) {
+ ret.title_separator = uadv(k);
+ } else if (!ustricmp(k, L"html-nav-prev-text")) {
+ ret.nav_prev_text = uadv(k);
+ } else if (!ustricmp(k, L"html-nav-next-text")) {
+ ret.nav_next_text = uadv(k);
+ } else if (!ustricmp(k, L"html-nav-up-text")) {
+ ret.nav_up_text = uadv(k);
+ } else if (!ustricmp(k, L"html-nav-separator")) {
+ ret.nav_separator = uadv(k);
+ } else if (!ustricmp(k, L"html-index-main-separator")) {
+ ret.index_main_sep = uadv(k);
+ } else if (!ustricmp(k, L"html-index-multiple-separator")) {
+ ret.index_multi_sep = uadv(k);
+ } else if (!ustricmp(k, L"html-pre-versionid")) {
+ ret.pre_versionid = uadv(k);
+ } else if (!ustricmp(k, L"html-post-versionid")) {
+ ret.post_versionid = uadv(k);
+ } else if (!ustricmp(k, L"html-mshtmlhelp-chm")) {
+ sfree(ret.chm_filename);
+ ret.chm_filename = dupstr(adv(p->origkeyword));
+ } else if (!ustricmp(k, L"html-mshtmlhelp-project")) {
+ sfree(ret.hhp_filename);
+ ret.hhp_filename = dupstr(adv(p->origkeyword));
+ } else if (!ustricmp(k, L"html-mshtmlhelp-contents")) {
+ sfree(ret.hhc_filename);
+ ret.hhc_filename = dupstr(adv(p->origkeyword));
+ } else if (!ustricmp(k, L"html-mshtmlhelp-index")) {
+ sfree(ret.hhk_filename);
+ ret.hhk_filename = dupstr(adv(p->origkeyword));
}
}
}
/*
+ * Enforce that the CHM and HHP filenames must either be both
+ * present or both absent. If one is present but not the other,
+ * turn both off.
+ */
+ if (!ret.chm_filename ^ !ret.hhp_filename) {
+ error(err_chmnames);
+ sfree(ret.chm_filename); ret.chm_filename = NULL;
+ sfree(ret.hhp_filename); ret.hhp_filename = NULL;
+ }
+ /*
+ * And if we're not generating an HHP, there's no need for HHC
+ * or HHK.
+ */
+ if (!ret.hhp_filename) {
+ sfree(ret.hhc_filename); ret.hhc_filename = NULL;
+ sfree(ret.hhk_filename); ret.hhk_filename = NULL;
+ }
+
+ /*
* Now process fallbacks on quote characters.
*/
while (*uadv(ret.rquote) && *uadv(uadv(ret.rquote)) &&
}
void html_backend(paragraph *sourceform, keywordlist *keywords,
- indexdata *idx, void *unused) {
+ indexdata *idx, void *unused)
+{
paragraph *p;
+ htmlsect *topsect;
htmlconfig conf;
- htmlfilelist files = { NULL, NULL, NULL, NULL, NULL };
+ htmlfilelist files = { NULL, NULL, NULL, NULL, NULL, NULL };
htmlsectlist sects = { NULL, NULL }, nonsects = { NULL, NULL };
+ char *hhk_filename;
+ int has_index;
IGNORE(unused);
p->private_data = NULL;
files.frags = newtree234(html_fragment_compare);
+ files.files = newtree234(html_filename_compare);
/*
* Start by figuring out into which file each piece of the
* 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;
+ htmlsect *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)) {
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);
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. Note that we don't output
+ * an index as an HTML file if we're outputting one as a
+ * .HHK.
+ */
+ has_index = (count234(idx->entries) > 0);
+ if (has_index && !conf.hhk_filename) {
+ 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;
+ }
}
/*
* 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;
* 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]);
}
}
}
#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;
ho.ver = conf.htmlver;
ho.state = HO_NEUTRAL;
ho.contents_level = 0;
+ ho.hackflags = 0; /* none of these thankyouverymuch */
+ ho.hacklimit = -1;
/* <!DOCTYPE>. */
switch (conf.htmlver) {
case HTML_3_2:
- fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD "
- "HTML 3.2 Final//EN\">\n");
+ if (ho.fp)
+ fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD "
+ "HTML 3.2 Final//EN\">\n");
break;
case HTML_4:
- fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML"
- " 4.01//EN\"\n\"http://www.w3.org/TR/html4/"
- "strict.dtd\">\n");
+ if (ho.fp)
+ fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML"
+ " 4.01//EN\"\n\"http://www.w3.org/TR/html4/"
+ "strict.dtd\">\n");
break;
case ISO_HTML:
- fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"ISO/IEC "
- "15445:2000//DTD HTML//EN\">\n");
+ if (ho.fp)
+ fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"ISO/IEC "
+ "15445:2000//DTD HTML//EN\">\n");
break;
case XHTML_1_0_TRANSITIONAL:
- fprintf(ho.fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
- charset_to_mimeenc(conf.output_charset));
- fprintf(ho.fp, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML"
- " 1.0 Transitional//EN\"\n\"http://www.w3.org/TR/"
- "xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+ if (ho.fp) {
+ fprintf(ho.fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
+ charset_to_mimeenc(conf.output_charset));
+ fprintf(ho.fp, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML"
+ " 1.0 Transitional//EN\"\n\"http://www.w3.org/TR/"
+ "xhtml1/DTD/xhtml1-transitional.dtd\">\n");
+ }
break;
case XHTML_1_0_STRICT:
- fprintf(ho.fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
- charset_to_mimeenc(conf.output_charset));
- fprintf(ho.fp, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML"
- " 1.0 Strict//EN\"\n\"http://www.w3.org/TR/xhtml1/"
- "DTD/xhtml1-strict.dtd\">\n");
+ if (ho.fp) {
+ fprintf(ho.fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
+ charset_to_mimeenc(conf.output_charset));
+ fprintf(ho.fp, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML"
+ " 1.0 Strict//EN\"\n\"http://www.w3.org/TR/xhtml1/"
+ "DTD/xhtml1-strict.dtd\">\n");
+ }
break;
}
element_close(&ho, "title");
html_nl(&ho);
+ if (conf.rellinks) {
+
+ if (prevf) {
+ element_empty(&ho, "link");
+ element_attr(&ho, "rel", "previous");
+ element_attr(&ho, "href", prevf->filename);
+ html_nl(&ho);
+ }
+
+ if (f != files.head) {
+ element_empty(&ho, "link");
+ element_attr(&ho, "rel", "ToC");
+ element_attr(&ho, "href", files.head->filename);
+ html_nl(&ho);
+ }
+
+ if (conf.leaf_level > 0) {
+ htmlsect *p = f->first->parent;
+ assert(p == f->last->parent);
+ if (p) {
+ element_empty(&ho, "link");
+ element_attr(&ho, "rel", "up");
+ element_attr(&ho, "href", p->file->filename);
+ html_nl(&ho);
+ }
+ }
+
+ if (has_index && files.index && f != files.index) {
+ element_empty(&ho, "link");
+ element_attr(&ho, "rel", "index");
+ element_attr(&ho, "href", files.index->filename);
+ html_nl(&ho);
+ }
+
+ if (f->next) {
+ element_empty(&ho, "link");
+ element_attr(&ho, "rel", "next");
+ element_attr(&ho, "href", f->next->filename);
+ html_nl(&ho);
+ }
+
+ }
+
if (conf.head_end)
html_raw(&ho, conf.head_end);
+ /*
+ * Add any <head> data defined in specific sections
+ * that go in this file. (This is mostly to allow <meta
+ * name="AppleTitle"> 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);
* Write out a nav bar. Special case: we don't do this
* if there is only one file.
*/
- if (files.head != files.tail) {
+ if (conf.navlinks && files.head != files.tail) {
element_open(&ho, "p");
if (conf.nav_attr)
html_raw_as_attr(&ho, conf.nav_attr);
if (f != files.head)
element_close(&ho, "a");
- html_text(&ho, conf.nav_separator);
+ /* We don't bother with "Up" links for leaf-level 1,
+ * as they would be identical to the "Contents" links. */
+ if (conf.leaf_level >= 2) {
+ htmlsect *p = f->first->parent;
+ assert(p == f->last->parent);
+ html_text(&ho, conf.nav_separator);
+ if (p) {
+ element_open(&ho, "a");
+ element_attr(&ho, "href", p->file->filename);
+ }
+ html_text(&ho, conf.nav_up_text);
+ if (p) {
+ element_close(&ho, "a");
+ }
+ }
- if (f != files.index) {
- element_open(&ho, "a");
- element_attr(&ho, "href", files.index->filename);
+ if (has_index && files.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);
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
*/
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;
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);
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,
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;
*/
int done_version_ids = FALSE;
- element_empty(&ho, "hr");
+ if (conf.address_section)
+ element_empty(&ho, "hr");
if (conf.body_end)
html_raw(&ho, conf.body_end);
}
/*
+ * Before we start outputting the HTML Help files, check
+ * whether there's even going to _be_ an index file: we omit it
+ * if the index contains nothing.
+ */
+ hhk_filename = conf.hhk_filename;
+ if (hhk_filename) {
+ int ok = FALSE;
+ int i;
+ indexentry *entry;
+
+ for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
+ htmlindex *hi = (htmlindex *)entry->backend_data;
+
+ if (hi->nrefs > 0) {
+ ok = TRUE; /* found an index entry */
+ break;
+ }
+ }
+
+ if (!ok)
+ hhk_filename = NULL;
+ }
+
+ /*
+ * Output the MS HTML Help supporting files, if requested.
+ *
+ * A good unofficial reference for these is <http://chmspec.nongnu.org/>.
+ */
+ if (conf.hhp_filename) {
+ htmlfile *f;
+ htmloutput ho;
+
+ ho.charset = CS_CP1252; /* as far as I know, HHP files are */
+ ho.restrict_charset = CS_CP1252; /* hardwired to this charset */
+ ho.cstate = charset_init_state;
+ ho.ver = HTML_4; /* *shrug* */
+ ho.state = HO_NEUTRAL;
+ ho.contents_level = 0;
+ ho.hackflags = HO_HACK_QUOTENOTHING;
+
+ ho.fp = fopen(conf.hhp_filename, "w");
+ if (!ho.fp)
+ error(err_cantopenw, conf.hhp_filename);
+
+ fprintf(ho.fp,
+ "[OPTIONS]\n"
+ /* Binary TOC required for Next/Previous nav to work */
+ "Binary TOC=Yes\n"
+ "Compatibility=1.1 or later\n"
+ "Compiled file=%s\n"
+ "Default Window=main\n"
+ "Default topic=%s\n"
+ "Display compile progress=Yes\n"
+ "Full-text search=Yes\n"
+ "Title=", conf.chm_filename, files.head->filename);
+
+ ho.hacklimit = 255;
+ html_words(&ho, topsect->title->words, NOTHING,
+ NULL, keywords, &conf);
+
+ fprintf(ho.fp, "\n");
+
+ /*
+ * These two entries don't seem to be remotely necessary
+ * for a successful run of the help _compiler_, but
+ * omitting them causes the GUI Help Workshop to behave
+ * rather strangely if you try to load the help project
+ * into that and edit it.
+ */
+ if (conf.hhc_filename)
+ fprintf(ho.fp, "Contents file=%s\n", conf.hhc_filename);
+ if (hhk_filename)
+ fprintf(ho.fp, "Index file=%s\n", hhk_filename);
+
+ fprintf(ho.fp, "\n[WINDOWS]\nmain=\"");
+
+ ho.hackflags |= HO_HACK_OMITQUOTES;
+ ho.hacklimit = 255;
+ html_words(&ho, topsect->title->words, NOTHING,
+ NULL, keywords, &conf);
+
+ fprintf(ho.fp, "\",\"%s\",\"%s\",\"%s\",,,,,,"
+ /* This first magic number is fsWinProperties, controlling
+ * Navigation Pane options and the like.
+ * Constants HHWIN_PROP_* in htmlhelp.h. */
+ "0x62520,,"
+ /* This second number is fsToolBarFlags, mainly controlling
+ * toolbar buttons. Constants HHWIN_BUTTON_*.
+ * NOTE: there are two pairs of bits for Next/Previous
+ * buttons: 7/8 (which do nothing useful), and 21/22
+ * (which work). (Neither of these are exposed in the HHW
+ * UI, but they work fine in HH.) We use the latter. */
+ "0x60304e,,,,,,,,0\n",
+ conf.hhc_filename ? conf.hhc_filename : "",
+ hhk_filename ? hhk_filename : "",
+ files.head->filename);
+
+ /*
+ * The [FILES] section is also not necessary for
+ * compilation (hhc appears to build up a list of needed
+ * files just by following links from the given starting
+ * points), but useful for loading the project into HHW.
+ */
+ fprintf(ho.fp, "\n[FILES]\n");
+ for (f = files.head; f; f = f->next)
+ fprintf(ho.fp, "%s\n", f->filename);
+
+ fclose(ho.fp);
+ }
+ if (conf.hhc_filename) {
+ htmlfile *f;
+ htmlsect *s, *a;
+ htmloutput ho;
+ int currdepth = 0;
+
+ ho.fp = fopen(conf.hhc_filename, "w");
+ if (!ho.fp)
+ error(err_cantopenw, conf.hhc_filename);
+
+ ho.charset = CS_CP1252; /* as far as I know, HHC files are */
+ ho.restrict_charset = CS_CP1252; /* hardwired to this charset */
+ ho.cstate = charset_init_state;
+ ho.ver = HTML_4; /* *shrug* */
+ ho.state = HO_NEUTRAL;
+ ho.contents_level = 0;
+ ho.hackflags = HO_HACK_QUOTEQUOTES;
+
+ /*
+ * Magic DOCTYPE which seems to work for .HHC files. I'm
+ * wary of trying to change it!
+ */
+ fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
+ "<HTML><HEAD>\n"
+ "<META HTTP-EQUIV=\"Content-Type\" "
+ "CONTENT=\"text/html; charset=%s\">\n"
+ "</HEAD><BODY><UL>\n",
+ charset_to_mimeenc(conf.output_charset));
+
+ for (f = files.head; f; f = f->next) {
+ /*
+ * For each HTML file, write out a contents entry.
+ */
+ int depth, leaf = TRUE;
+
+ /*
+ * Determine the depth of this file in the contents
+ * tree.
+ *
+ * If the file contains no sections, it is assumed to
+ * have depth zero.
+ */
+ depth = 0;
+ if (f->first)
+ for (a = f->first->parent; a && a->type != TOP; a = a->parent)
+ depth++;
+
+ /*
+ * Determine if this file is a leaf file, by
+ * trawling the section list to see if there's any
+ * section with an ancestor in this file but which
+ * is not itself in this file.
+ *
+ * Special case: for contents purposes, the TOP
+ * file is not considered to be the parent of the
+ * chapter files, so it's always a leaf.
+ *
+ * A file with no sections in it is also a leaf.
+ */
+ if (f->first && f->first->type != TOP) {
+ for (s = f->first; s; s = s->next) {
+ htmlsect *a;
+
+ if (leaf && s->file != f) {
+ for (a = s; a; a = a->parent)
+ if (a->file == f) {
+ leaf = FALSE;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Now write out our contents entry.
+ */
+ while (currdepth < depth) {
+ fprintf(ho.fp, "<UL>\n");
+ currdepth++;
+ }
+ while (currdepth > depth) {
+ fprintf(ho.fp, "</UL>\n");
+ currdepth--;
+ }
+ /* fprintf(ho.fp, "<!-- depth=%d -->", depth); */
+ fprintf(ho.fp, "<LI><OBJECT TYPE=\"text/sitemap\">"
+ "<PARAM NAME=\"Name\" VALUE=\"");
+ ho.hacklimit = 255;
+ if (f->first->title)
+ html_words(&ho, f->first->title->words, NOTHING,
+ NULL, keywords, &conf);
+ else if (f->first->type == INDEX)
+ html_text(&ho, conf.index_text);
+ fprintf(ho.fp, "\"><PARAM NAME=\"Local\" VALUE=\"%s\">"
+ "<PARAM NAME=\"ImageNumber\" VALUE=\"%d\"></OBJECT>\n",
+ f->filename, leaf ? 11 : 1);
+ }
+
+ while (currdepth > 0) {
+ fprintf(ho.fp, "</UL>\n");
+ currdepth--;
+ }
+
+ fprintf(ho.fp, "</UL></BODY></HTML>\n");
+
+ cleanup(&ho);
+ }
+ if (hhk_filename) {
+ htmlfile *f;
+ htmloutput ho;
+ indexentry *entry;
+ int i;
+
+ /*
+ * First make a pass over all HTML files and set their
+ * `temp' fields to zero, because we're about to use them.
+ */
+ for (f = files.head; f; f = f->next)
+ f->temp = 0;
+
+ ho.fp = fopen(hhk_filename, "w");
+ if (!ho.fp)
+ error(err_cantopenw, hhk_filename);
+
+ ho.charset = CS_CP1252; /* as far as I know, HHK files are */
+ ho.restrict_charset = CS_CP1252; /* hardwired to this charset */
+ ho.cstate = charset_init_state;
+ ho.ver = HTML_4; /* *shrug* */
+ ho.state = HO_NEUTRAL;
+ ho.contents_level = 0;
+ ho.hackflags = HO_HACK_QUOTEQUOTES;
+
+ /*
+ * Magic DOCTYPE which seems to work for .HHK files. I'm
+ * wary of trying to change it!
+ */
+ fprintf(ho.fp, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
+ "<HTML><HEAD>\n"
+ "<META HTTP-EQUIV=\"Content-Type\" "
+ "CONTENT=\"text/html; charset=%s\">\n"
+ "</HEAD><BODY><UL>\n",
+ charset_to_mimeenc(conf.output_charset));
+
+ /*
+ * Go through the index terms and output each one.
+ */
+ for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
+ htmlindex *hi = (htmlindex *)entry->backend_data;
+ int j;
+
+ if (hi->nrefs > 0) {
+ fprintf(ho.fp, "<LI><OBJECT TYPE=\"text/sitemap\">\n"
+ "<PARAM NAME=\"Name\" VALUE=\"");
+ ho.hacklimit = 255;
+ html_words(&ho, entry->text, NOTHING,
+ NULL, keywords, &conf);
+ fprintf(ho.fp, "\">\n");
+
+ for (j = 0; j < hi->nrefs; j++) {
+ htmlindexref *hr =
+ (htmlindexref *)hi->refs[j]->private_data;
+
+ /*
+ * Use the temp field to ensure we don't
+ * reference the same file more than once.
+ */
+ if (!hr->section->file->temp) {
+ fprintf(ho.fp, "<PARAM NAME=\"Local\" VALUE=\"%s\">\n",
+ hr->section->file->filename);
+ hr->section->file->temp = 1;
+ }
+
+ hr->referenced = TRUE;
+ }
+
+ fprintf(ho.fp, "</OBJECT>\n");
+
+ /*
+ * Now go through those files and re-clear the temp
+ * fields ready for the _next_ index term.
+ */
+ for (j = 0; j < hi->nrefs; j++) {
+ htmlindexref *hr =
+ (htmlindexref *)hi->refs[j]->private_data;
+ hr->section->file->temp = 0;
+ }
+ }
+ }
+
+ fprintf(ho.fp, "</UL></BODY></HTML>\n");
+ cleanup(&ho);
+ }
+
+ /*
* Go through and check that no index fragments were referenced
* without being generated, or indeed vice versa.
*
}
/*
- * 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);
+ }
+ /*
+ * The strings in files.files are all owned by their containing
+ * htmlfile structures, so there's no need to free them here.
*/
+ freetree234(files.files);
+ {
+ 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,
* we invent a fresh file and put this section at its head.
* Otherwise, we put it in the same file as its parent
* section.
+ *
+ * Another special value of cfg->leaf_level is -1, which
+ * means infinity (i.e. it's considered to always be
+ * greater than depth).
*/
- if (ldepth > cfg->leaf_level) {
+ if (cfg->leaf_level > 0 && ldepth > cfg->leaf_level) {
/*
* We know that sect->parent cannot be NULL. The only
* circumstance in which it can be is if sect is at
list->head = ret;
list->tail = ret;
- ret->filename = dupstr(filename);
+ ret->filename = html_sanitise_filename(list, dupstr(filename));
+ add234(list->files, ret->filename);
ret->last_fragment_number = 0;
ret->min_heading_depth = INT_MAX;
ret->first = ret->last = NULL;
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);
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;
}
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:
else
html_text(ho, cfg->rquote);
} else {
- if (cvt_ok(ho->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);
}
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->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;
+}
+
static void return_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)) {
- fprintf(ho->fp, " />");
- } else if (ho->state == HO_IN_EMPTY_TAG || ho->state == HO_IN_TAG) {
- fprintf(ho->fp, ">");
}
- ho->state = HO_NEUTRAL;
+ return_mostly_to_neutral(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, "</%s>", name);
+ if (ho->fp)
+ fprintf(ho->fp, "</%s>", 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);
- html_text_limit_internal(ho, value, 0, TRUE);
+ 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)
{
- 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];
int bytes, err;
+ if (ho->hackflags & (HO_HACK_QUOTEQUOTES | HO_HACK_OMITQUOTES))
+ quote_quotes = TRUE; /* override the input value */
+
if (maxlen > 0 && textlen > maxlen)
textlen = maxlen;
+ if (ho->hacklimit >= 0) {
+ if (textlen > ho->hacklimit)
+ textlen = ho->hacklimit;
+ ho->hacklimit -= textlen;
+ }
while (textlen > 0) {
/* Scan ahead for characters we really can't display in HTML. */
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),
ho->charset, &ho->cstate, &err);
textlen -= (lenbefore - lenafter);
- if (bytes > 0)
+ if (bytes > 0 && ho->fp)
fwrite(outbuf, 1, bytes, ho->fp);
if (err) {
/*
* 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
- assert(!"Can't happen");
+ if (ho->fp) {
+ if (*text == L'"' && (ho->hackflags & HO_HACK_OMITQUOTES)) {
+ fputc('\'', ho->fp);
+ } else if (ho->hackflags & HO_HACK_QUOTENOTHING) {
+ fputc(*text, 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'"')
+ fprintf(ho->fp, """);
+ else if (*text == L' ') {
+ assert(nbsp);
+ fprintf(ho->fp, " ");
+ } else
+ assert(!"Can't happen");
+ }
+ }
text++, textlen--;
}
}
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,
} 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) {
*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
return text;
}
+static char *html_sanitise_filename(htmlfilelist *files, char *text)
+{
+ /*
+ * Unceremoniously rip out any character that might cause
+ * difficulty in some filesystem or another, or be otherwise
+ * inconvenient.
+ *
+ * That doesn't leave much punctuation. I permit alphanumerics
+ * and +-.=_ only.
+ */
+ char *p = text, *q = text;
+
+ while (*p) {
+ if ((*p>='A' && *p<='Z') ||
+ (*p>='a' && *p<='z') ||
+ (*p>='0' && *p<='9') ||
+ *p=='-' || *p=='_' || *p=='+' || *p=='.' || *p=='=')
+ *q++ = *p;
+ p++;
+ }
+ *q = '\0';
+
+ /* If there's nothing left, make something valid up */
+ if (!*text) {
+ static const char anonfrag[] = "anon.html";
+ text = sresize(text, lenof(anonfrag), char);
+ strcpy(text, anonfrag);
+ }
+
+ /*
+ * Now we check for clashes with other filenames, and adjust
+ * this one if necessary by appending a hyphen followed by a
+ * number just before the file extension (if any).
+ */
+ {
+ int len, extpos;
+ int suffix = 1;
+
+ p = NULL;
+
+ while (find234(files->files, text, NULL)) {
+ if (!p) {
+ len = strlen(text);
+ p = text;
+ text = snewn(len+20, char);
+
+ for (extpos = len; extpos > 0 && p[extpos-1] != '.'; extpos--);
+ if (extpos > 0)
+ extpos--;
+ else
+ extpos = len;
+ }
+
+ sprintf(text, "%.*s-%d%s", extpos, p, ++suffix, p+extpos);
+ }
+
+ if (p)
+ sfree(p);
+ }
+
+ return text;
+}
+
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++;
}
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");
+ /* <li> will be closed by a later invocation */
}
static void html_section_title(htmloutput *ho, htmlsect *s, htmlfile *thisfile,
else
sl = &cfg->asect[cfg->nasect-1];
- if (!sl)
+ if (!sl || !sl->number_at_all)
number = NULL;
else if (sl->just_numbers)
number = s->title->kwtext2;