Support for the MS HTML Help system in the HTML back end. As yet I
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 11 Dec 2006 19:43:10 +0000 (19:43 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Mon, 11 Dec 2006 19:43:10 +0000 (19:43 +0000)
don't know how to write out a .CHM directly, but I am at least able
to have the HTML back end write out the three auxiliary files which
enable a .CHM to be generated using the MS HTML Help compiler.

git-svn-id: svn://svn.tartarus.org/sgt/halibut@6991 cda61777-01e9-0310-a592-d414129be87e

bk_html.c
doc/Makefile
doc/chm.but [new file with mode: 0644]
doc/index.but
doc/intro.but
doc/output.but
doc/running.but
error.c
halibut.h
inputs/test.but

index a1b46f6..11d37d3 100644 (file)
--- a/bk_html.c
+++ b/bk_html.c
  *    sensible. Perhaps for the topmost section in the file, no
  *    fragment should be used? (Though it should probably still be
  *    _there_ even if unused.)
  *    sensible. Perhaps for the topmost section in the file, no
  *    fragment should be used? (Though it should probably still be
  *    _there_ even if unused.)
+ * 
+ *  - 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>
  */
 
 #include <stdio.h>
@@ -41,10 +48,12 @@ typedef struct {
     int ncdepths;
     int address_section, visible_version_id;
     int leaf_contains_contents, leaf_smallest_contents;
     int ncdepths;
     int address_section, visible_version_id;
     int leaf_contains_contents, leaf_smallest_contents;
+    int navlinks;
     char *contents_filename;
     char *index_filename;
     char *template_filename;
     char *single_filename;
     char *contents_filename;
     char *index_filename;
     char *template_filename;
     char *single_filename;
+    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 **template_fragments;
     int ntfragments;
     char *head_end, *body_start, *body_end, *addr_start, *addr_end;
@@ -77,6 +86,13 @@ struct htmlfile {
     int last_fragment_number;
     int min_heading_depth;
     htmlsect *first, *last;           /* first/last highest-level sections */
     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 {
 };
 
 struct htmlsect {
@@ -92,6 +108,7 @@ typedef struct {
     htmlfile *head, *tail;
     htmlfile *single, *index;
     tree234 *frags;
     htmlfile *head, *tail;
     htmlfile *single, *index;
     tree234 *frags;
+    tree234 *files;
 } htmlfilelist;
 
 typedef struct {
 } htmlfilelist;
 
 typedef struct {
@@ -127,6 +144,8 @@ typedef struct {
     enum {
        HO_NEUTRAL, HO_IN_TAG, HO_IN_EMPTY_TAG, HO_IN_TEXT
     } state;
     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
     /*
      * Stuff beyond here deals with the higher syntactic level: it
      * tracks how many levels of <ul> are currently open when
@@ -135,6 +154,21 @@ typedef struct {
     int contents_level;
 } htmloutput;
 
     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;
 static int html_fragment_compare(void *av, void *bv)
 {
     htmlfragment *a = (htmlfragment *)av;
@@ -147,6 +181,14 @@ static int html_fragment_compare(void *av, void *bv)
        return strcmp(a->fragment, b->fragment);
 }
 
        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 void html_file_section(htmlconfig *cfg, htmlfilelist *files,
                              htmlsect *sect, int depth);
 
@@ -187,6 +229,7 @@ static void html_fragment(htmloutput *ho, char const *fragment);
 static char *html_format(paragraph *p, char *template_string);
 static char *html_sanitise_fragment(htmlfilelist *files, htmlfile *file,
                                    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,
 
 static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s,
                                htmlfile *thisfile, keywordlist *keywords,
@@ -215,10 +258,13 @@ static htmlconfig html_configure(paragraph *source) {
     ret.address_section = TRUE;
     ret.leaf_contains_contents = FALSE;
     ret.leaf_smallest_contents = 4;
     ret.address_section = TRUE;
     ret.leaf_contains_contents = FALSE;
     ret.leaf_smallest_contents = 4;
+    ret.navlinks = 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.single_filename = dupstr("Manual.html");
     ret.contents_filename = dupstr("Contents.html");
     ret.index_filename = dupstr("IndexPage.html");
     ret.template_filename = dupstr("%n.html");
+    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.ntfragments = 1;
     ret.template_fragments = snewn(ret.ntfragments, char *);
     ret.template_fragments[0] = dupstr("%b");
@@ -333,10 +379,18 @@ static htmlconfig html_configure(paragraph *source) {
                    error(err_cfginsufarg, &p->fpos, p->origkeyword, 1);
            } else if (!ustricmp(k, L"html-chapter-numeric")) {
                ret.achapter.just_numbers = utob(uadv(k));
                    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-suppress-navlinks")) {
+               ret.navlinks = !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")) {
            } 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;
            } else if (!ustricmp(k, L"html-section-numeric")) {
                wchar_t *q = uadv(k);
                int n = 0;
@@ -446,11 +500,42 @@ static htmlconfig html_configure(paragraph *source) {
                ret.pre_versionid = uadv(k);
            } else if (!ustricmp(k, L"html-post-versionid")) {
                ret.post_versionid = uadv(k);
                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)) &&
      * Now process fallbacks on quote characters.
      */
     while (*uadv(ret.rquote) && *uadv(uadv(ret.rquote)) &&
@@ -485,9 +570,11 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                  indexdata *idx, void *unused)
 {
     paragraph *p;
                  indexdata *idx, void *unused)
 {
     paragraph *p;
+    htmlsect *topsect;
     htmlconfig conf;
     htmlconfig conf;
-    htmlfilelist files = { NULL, NULL, NULL, NULL, NULL };
+    htmlfilelist files = { NULL, NULL, NULL, NULL, NULL, NULL };
     htmlsectlist sects = { NULL, NULL }, nonsects = { NULL, NULL };
     htmlsectlist sects = { NULL, NULL }, nonsects = { NULL, NULL };
+    char *hhk_filename;
     int has_index;
 
     IGNORE(unused);
     int has_index;
 
     IGNORE(unused);
@@ -504,6 +591,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
        p->private_data = NULL;
 
     files.frags = newtree234(html_fragment_compare);
        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
 
     /*
      * Start by figuring out into which file each piece of the
@@ -518,7 +606,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
      * for each section.
      */
     {
      * for each section.
      */
     {
-       htmlsect *topsect, *sect;
+       htmlsect *sect;
        int d;
 
        topsect = html_new_sect(&sects, NULL, &conf);
        int d;
 
        topsect = html_new_sect(&sects, NULL, &conf);
@@ -563,9 +651,13 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                }
            }
 
                }
            }
 
-       /* And the index, if we have one. */
+       /*
+        * 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);
        has_index = (count234(idx->entries) > 0);
-       if (has_index) {
+       if (has_index && !conf.hhk_filename) {
            sect = html_new_sect(&sects, NULL, &conf);
            sect->text = NULL;
            sect->type = INDEX;
            sect = html_new_sect(&sects, NULL, &conf);
            sect->text = NULL;
            sect->type = INDEX;
@@ -776,6 +868,8 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
            ho.ver = conf.htmlver;
            ho.state = HO_NEUTRAL;
            ho.contents_level = 0;
            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) {
 
            /* <!DOCTYPE>. */
            switch (conf.htmlver) {
@@ -902,7 +996,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
             * Write out a nav bar. Special case: we don't do this
             * if there is only one file.
             */
             * 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);
                element_open(&ho, "p");
                if (conf.nav_attr)
                    html_raw_as_attr(&ho, conf.nav_attr);
@@ -925,7 +1019,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                if (f != files.head)
                    element_close(&ho, "a");
 
                if (f != files.head)
                    element_close(&ho, "a");
 
-               if (has_index) {
+               if (has_index && files.index) {
                    html_text(&ho, conf.nav_separator);
                    if (f != files.index) {
                        element_open(&ho, "a");
                    html_text(&ho, conf.nav_separator);
                    if (f != files.index) {
                        element_open(&ho, "a");
@@ -1379,7 +1473,8 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                 */
                int done_version_ids = FALSE;
 
                 */
                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);
 
                if (conf.body_end)
                    html_raw(&ho, conf.body_end);
@@ -1460,6 +1555,295 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
     }
 
     /*
     }
 
     /*
+     * 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.
+     */
+    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"
+               "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\",,,,,,"
+               "0x42520,,0x3876,[271,372,593,566],,,,,,,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.
      * 
      * Go through and check that no index fragments were referenced
      * without being generated, or indeed vice versa.
      * 
@@ -1491,6 +1875,11 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
        }
        freetree234(files.frags);
     }
        }
        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;
     {
        htmlsect *sect, *tmp;
        sect = sects.head;
@@ -1584,8 +1973,12 @@ 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.
         * 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
            /*
             * We know that sect->parent cannot be NULL. The only
             * circumstance in which it can be is if sect is at
@@ -1641,7 +2034,8 @@ static htmlfile *html_new_file(htmlfilelist *list, char *filename)
        list->head = ret;
     list->tail = ret;
 
        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;
     ret->last_fragment_number = 0;
     ret->min_heading_depth = INT_MAX;
     ret->first = ret->last = NULL;
@@ -1943,8 +2337,16 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text,
     char outbuf[256];
     int bytes, err;
 
     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 (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. */
 
     while (textlen > 0) {
        /* Scan ahead for characters we really can't display in HTML. */
@@ -1978,19 +2380,25 @@ static void html_text_limit_internal(htmloutput *ho, wchar_t const *text,
             * HTML.
             */
            if (ho->fp) {
             * HTML.
             */
            if (ho->fp) {
-               if (*text == L'<')
-                   fprintf(ho->fp, "&lt;");
-               else if (*text == L'>')
-                   fprintf(ho->fp, "&gt;");
-               else if (*text == L'&')
-                   fprintf(ho->fp, "&amp;");
-               else if (*text == L'"')
-                   fprintf(ho->fp, "&quot;");
-               else if (*text == L' ') {
-                   assert(nbsp);
-                   fprintf(ho->fp, "&nbsp;");
-               } else
-                   assert(!"Can't happen");
+               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, "&lt;");
+                   else if (*text == L'>')
+                       fprintf(ho->fp, "&gt;");
+                   else if (*text == L'&')
+                       fprintf(ho->fp, "&amp;");
+                   else if (*text == L'"')
+                       fprintf(ho->fp, "&quot;");
+                   else if (*text == L' ') {
+                       assert(nbsp);
+                       fprintf(ho->fp, "&nbsp;");
+                   } else
+                       assert(!"Can't happen");
+               }
            }
            text++, textlen--;
        }
            }
            text++, textlen--;
        }
@@ -2162,6 +2570,69 @@ static char *html_sanitise_fragment(htmlfilelist *files, htmlfile *file,
     return text;
 }
 
     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)
 static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s,
                                htmlfile *thisfile, keywordlist *keywords,
                                htmlconfig *cfg)
index cfdd943..63299ad 100644 (file)
@@ -20,4 +20,8 @@ install:
        $(INSTALL) -m 644 halibut.1 $(man1dir)/halibut.1
 
 clean:
        $(INSTALL) -m 644 halibut.1 $(man1dir)/halibut.1
 
 clean:
-       rm -f *.html *.txt *.hlp *.cnt *.1 *.info* *.ps *.pdf
+       rm -f *.html *.txt *.hlp *.cnt *.1 *.info* *.ps *.pdf *.hh* *.chm
+
+chm: halibut.hhp
+halibut.hhp: $(INPUTS) $(HALIBUT) chm.but
+       $(HALIBUT) --html $(INPUTS) chm.but
diff --git a/doc/chm.but b/doc/chm.but
new file mode 100644 (file)
index 0000000..510baf8
--- /dev/null
@@ -0,0 +1,19 @@
+\# File containing the magic HTML configuration directives to create
+\# an MS HTML Help project. We put this on the end of the Halibut
+\# docs build command line to build the HHP and friends.
+
+\cfg{html-leaf-level}{infinite}
+\cfg{html-leaf-contains-contents}{false}
+\cfg{html-suppress-navlinks}{true}
+\cfg{html-suppress-address}{true}
+
+\cfg{html-contents-filename}{index.html}
+\cfg{html-template-filename}{%k.html}
+\cfg{html-template-fragment}{%k}
+
+\cfg{html-mshtmlhelp-chm}{halibut.chm}
+\cfg{html-mshtmlhelp-project}{halibut.hhp}
+\cfg{html-mshtmlhelp-contents}{halibut.hhc}
+\cfg{html-mshtmlhelp-index}{halibut.hhk}
+
+\versionid $Id$
index b059b47..b8587c7 100644 (file)
@@ -3,6 +3,12 @@
 \IM{Windows Help} Windows Help
 \IM{Windows Help} Help, Windows
 
 \IM{Windows Help} Windows Help
 \IM{Windows Help} Help, Windows
 
+\IM{HTML Help} HTML Help
+\IM{HTML Help} Windows HTML Help
+\IM{HTML Help} MS HTML Help
+\IM{HTML Help} Microsoft HTML Help
+\IM{HTML Help} \cw{.chm} files
+
 \IM{Help compiler} Help compiler, lack of need for
 
 \IM{plain text} plain text
 \IM{Help compiler} Help compiler, lack of need for
 
 \IM{plain text} plain text
@@ -326,6 +332,11 @@ configuration directive
 \IM{\\cfg\{html-suppress-address\}}
 \cw{\\cfg\{html-suppress-address\}}
 
 \IM{\\cfg\{html-suppress-address\}}
 \cw{\\cfg\{html-suppress-address\}}
 
+\IM{\\cfg\{html-suppress-navlinks\}} \c{html-suppress-navlinks}
+configuration directive
+\IM{\\cfg\{html-suppress-navlinks\}}
+\cw{\\cfg\{html-suppress-navlinks\}}
+
 \IM{\\cfg\{html-author\}} \c{html-author} configuration directive
 \IM{\\cfg\{html-author\}} \cw{\\cfg\{html-author\}}
 
 \IM{\\cfg\{html-author\}} \c{html-author} configuration directive
 \IM{\\cfg\{html-author\}} \cw{\\cfg\{html-author\}}
 
@@ -333,6 +344,22 @@ configuration directive
 directive
 \IM{\\cfg\{html-description\}} \cw{\\cfg\{html-description\}}
 
 directive
 \IM{\\cfg\{html-description\}} \cw{\\cfg\{html-description\}}
 
+\IM{\\cfg\{html-mshtmlhelp-project\}} \c{html-mshtmlhelp-project}
+configuration directive
+\IM{\\cfg\{html-mshtmlhelp-project\}} \cw{\\cfg\{html-mshtmlhelp-project\}}
+
+\IM{\\cfg\{html-mshtmlhelp-chm\}} \c{html-mshtmlhelp-chm}
+configuration directive
+\IM{\\cfg\{html-mshtmlhelp-chm\}} \cw{\\cfg\{html-mshtmlhelp-chm\}}
+
+\IM{\\cfg\{html-mshtmlhelp-contents\}} \c{html-mshtmlhelp-contents}
+configuration directive
+\IM{\\cfg\{html-mshtmlhelp-contents\}} \cw{\\cfg\{html-mshtmlhelp-contents\}}
+
+\IM{\\cfg\{html-mshtmlhelp-index\}} \c{html-mshtmlhelp-index}
+configuration directive
+\IM{\\cfg\{html-mshtmlhelp-index\}} \cw{\\cfg\{html-mshtmlhelp-index\}}
+
 \IM{\\cfg\{winhelp-topic\}} \c{winhelp-topic} configuration directive
 \IM{\\cfg\{winhelp-topic\}} \cw{\\cfg\{winhelp-topic\}}
 
 \IM{\\cfg\{winhelp-topic\}} \c{winhelp-topic} configuration directive
 \IM{\\cfg\{winhelp-topic\}} \cw{\\cfg\{winhelp-topic\}}
 
index 2898d17..ae63dd3 100644 (file)
@@ -19,8 +19,6 @@ Currently Halibut supports the following output formats:
 
 \b HTML.
 
 
 \b HTML.
 
-\b Windows Help.
-
 \b Unix \cw{man} page format.
 
 \b GNU \c{info} format.
 \b Unix \cw{man} page format.
 
 \b GNU \c{info} format.
@@ -29,6 +27,11 @@ Currently Halibut supports the following output formats:
 
 \b PostScript.
 
 
 \b PostScript.
 
+\b Old-style Windows Help (\cw{.HLP}).
+
+(By setting suitable options, the HTML output can also be made
+suitable for feeding to the newer-style Windows HTML Help compiler.)
+
 \H{intro-features} Features supported by Halibut
 
 Here's a list of Halibut's notable features.
 \H{intro-features} Features supported by Halibut
 
 Here's a list of Halibut's notable features.
index 1145fbe..df4580e 100644 (file)
@@ -442,6 +442,10 @@ parameter after the command-line option \i\c{--html} (see
 and so Halibut assumes you want the whole document to be placed in
 that file.
 
 and so Halibut assumes you want the whole document to be placed in
 that file.
 
+You can also specify the special name \c{infinity} (or \c{infinite}
+or \c{inf}) if you want to ensure that \e{every} section and
+subsection ends up in a separate file no matter how deep you go.
+
 }
 
 \dt \I{\cw{\\cfg\{html-contents-depth\}}}\cw{\\cfg\{html-contents-depth\}\{}\e{level}\cw{\}\{}\e{depth}\cw{\}}
 }
 
 \dt \I{\cw{\\cfg\{html-contents-depth\}}}\cw{\\cfg\{html-contents-depth\}\{}\e{level}\cw{\}\{}\e{depth}\cw{\}}
@@ -765,6 +769,11 @@ visibly in the \i\cw{<ADDRESS>} section at the bottom of each HTML
 file. If it is set to \c{false}, they will only be included as HTML
 comments.
 
 file. If it is set to \c{false}, they will only be included as HTML
 comments.
 
+\dt \I{\cw{\\cfg\{html-suppress-navlinks\}}}\cw{\\cfg\{html-suppress-navlinks\}\{}\e{boolean}\cw{\}}
+
+\dd If this is set to \c{true}, the usual \i{navigation links} at the
+top of each HTML file will be suppressed.
+
 \dt \I{\cw{\\cfg\{html-suppress-address\}}}\cw{\\cfg\{html-suppress-address\}\{}\e{boolean}\cw{\}}
 
 \dd If this is set to \c{true}, the \i\cw{<ADDRESS>} section at the
 \dt \I{\cw{\\cfg\{html-suppress-address\}}}\cw{\\cfg\{html-suppress-address\}\{}\e{boolean}\cw{\}}
 
 \dd If this is set to \c{true}, the \i\cw{<ADDRESS>} section at the
@@ -784,6 +793,108 @@ name="description">} tag in the output HTML files, so that browsers
 which support this can easily pick out a brief \I{description, of
 document}description of the document.
 
 which support this can easily pick out a brief \I{description, of
 document}description of the document.
 
+\S{output-html-mshtmlhelp} Generating MS Windows \i{HTML Help}
+
+The HTML files output from Halibut's HTML back end can be used as
+input to the MS Windows HTML Help compiler. In order to do this, you
+also need some auxiliary files: a project file, and (probably) a
+contents file and an index file. Halibut can optionally generate
+those as well.
+
+To enable the generation of MS HTML Help auxiliary files, use the
+following configuration directives:
+
+\dt \I\cw{\\cfg\{html-mshtmlhelp-project\}}\cw{\\cfg\{html-mshtmlhelp-project\}\{}\e{filename}\cw{\}}
+
+\dd Instructs Halibut to output an HTML Help project file with the
+specified name. You will almost certainly want the filename to end
+in the extension \c{.hhp} (although Halibut will not enforce this).
+If you use this option, you must also use the
+\cw{html-mshtmlhelp-chm} option to specify the desired name of the
+compiled help file.
+
+\dt \I\cw{\\cfg\{html-mshtmlhelp-chm\}}\cw{\\cfg\{html-mshtmlhelp-chm\}\{}\e{filename}\cw{\}}
+
+\dd Specifies the desired name of the compiled HTML Help file. You
+will almost certainly want this to have the extension \c{.chm}
+(although Halibut will not enforce this). The name you specify here
+will be written into the help project file. If you specify this
+option, you must also use the \cw{html-mshtmlhelp-project} option to
+request a help project file in the first place.
+
+\dt \I\cw{\\cfg\{html-mshtmlhelp-contents\}}\cw{\\cfg\{html-mshtmlhelp-contents\}\{}\e{filename}\cw{\}}
+
+\dd Instructs Halibut to output an HTML Help contents file with the
+specified name, and refer to it in the help project file. You will
+almost certainly want the filename to end in the extension \c{.hhc}
+(although Halibut will not enforce this). This option will be
+ignored if you have not also specified a help project file.
+
+\lcont{
+
+Creating a contents file like this causes the HTML Help viewer to
+display a contents tree in the pane to the left of the main text
+window. You can choose to generate an HTML Help project without this
+feature, in which case the user will still be able to navigate
+around the document by using the ordinary internal links in the HTML
+files themselves just as if it were a web page. However, using a
+contents file is recommended.
+
+}
+
+\dt \I\cw{\\cfg\{html-mshtmlhelp-index\}}\cw{\\cfg\{html-mshtmlhelp-index\}\{}\e{filename}\cw{\}}
+
+\dd Instructs Halibut to output an HTML Help index file with the
+specified name, and refer to it in the help project file. You will
+almost certainly want the filename to end in the extension \c{.hhk}
+(although Halibut will not enforce this). This option will be
+ignored if you have not also specified a help project file.
+
+\lcont{
+
+Specifying this option suppresses the generation of an HTML-based
+index file (see \cw{\\cfg\{html-index-filename\}} in
+\k{output-html-file}).
+
+Creating an index file like this causes the HTML Help viewer to
+provide a list of index terms in a pane to the left of the main text
+window. You can choose to generate an HTML Help project without this
+feature, in which case a conventional HTML index will be generated
+instead (assuming you have any index terms at all defined) and the
+user will still be able to use that. However, using an index file is
+recommended.
+
+Halibut will not output an index file at all, or link to one from
+the help project file, if your document contains no index entries.
+
+}
+
+If you use the above options, Halibut will output a help project
+file which you should be able to feed straight to the command-line
+MS HTML Help compiler (\cw{HHC.EXE}), or load into the MS HTML Help
+Workshop (\cw{HHW.EXE}).
+
+You may also wish to alter other HTML configuration options to make
+the resulting help file look more like a help file and less like a
+web page. A suggested set of additional configuration options for
+HTML Help is as follows:
+
+\b \cw{\\cfg\{html-leaf-level\}\{infinite\}}, because HTML Help
+works best with lots of small files (\q{topics}) rather than a few
+large ones. In particular, the contents and index mechanisms can
+only reference files, not subsections within files.
+
+\b \cw{\\cfg\{html-leaf-contains-contents\}\{false\}}, to suppress
+the contents list above the main text of each bottom-level file.
+
+\b \cw{\\cfg\{html-suppress-navlinks\}\{true\}}, because HTML Help
+has its own navigation facilities and it looks a bit strange to
+duplicate them.
+
+\b \cw{\\cfg\{html-suppress-address\}\{true\}}, because the
+\cw{<address>} section makes less sense in a help file than it does
+on a web page.
+
 \S{output-html-defaults} Default settings
 
 The \i{default settings} for Halibut's HTML output format are:
 \S{output-html-defaults} Default settings
 
 The \i{default settings} for Halibut's HTML output format are:
@@ -847,12 +958,19 @@ The \i{default settings} for Halibut's HTML output format are:
 \H{output-whlp} Windows Help
 
 This output format generates data that can be used by the \i{Windows
 \H{output-whlp} Windows Help
 
 This output format generates data that can be used by the \i{Windows
-Help} program \cw{WINHELP.EXE}. There are two actual files
+Help} program \cw{WINHLP32.EXE}. There are two actual files
 generated, one ending in \c{.hlp} and the other ending in \c{.cnt}.
 
 generated, one ending in \c{.hlp} and the other ending in \c{.cnt}.
 
-Currently, the output is hardcoded to be in the \q{\i{Win1252}}
-character set. (If anyone knows how character sets are encoded in
-Windows Help, we'd appreciate help.)
+Note that as of 2006, MS is discontinuing the Windows Help format in
+favour of the newer HTML Help format (\c{.chm} files). Halibut is
+not currently able to generate \c{.chm} files directly, but its HTML
+back end can write out project files suitable for use as input to
+the MS HTML Help compiler. See \k{output-html-mshtmlhelp} for more
+information on this.
+
+Currently, the Windows Help output is hardcoded to be in the
+\q{\i{Win1252}} character set. (If anyone knows how character sets
+are encoded in Windows Help files, we'd appreciate help.)
 
 The Windows Help output format supports the following configuration
 directives:
 
 The Windows Help output format supports the following configuration
 directives:
index 884c33f..8574708 100644 (file)
@@ -14,18 +14,20 @@ This will generate a large set of \i{output files}:
 \b \i\c{output.txt} will be a \i{plain text} version of the input
 document.
 
 \b \i\c{output.txt} will be a \i{plain text} version of the input
 document.
 
-\b \i\c{output.hlp} and \i\c{output.cnt} will be a \i{Windows Help}
-version of the same thing. (Most of the text is in \c{output.hlp};
-\c{output.cnt} contains additional contents data used by the Windows
-help topic selector. If you lose the latter, the former should still
-be usable, but it will look less modern.)
+\b \i\c{output.hlp} and \i\c{output.cnt} will be an old-style
+\i{Windows Help} version of the same thing. (Most of the text is in
+\c{output.hlp}; \c{output.cnt} contains additional contents data
+used by the Windows help topic selector. If you lose the latter, the
+former should still be usable, but it will look less modern.)
 
 \lcont{
 
 \lcont{
-Note that Halibut does not require any external software such as a
-\i{Help compiler}. It \e{directly} generates Windows Help files, and
-therefore it doesn't need to be run on Windows to do so: it can
-generate them even when run from an automated script on a Unix
-machine.
+
+Note that to do this Halibut does not require any external software
+such as a \i{Help compiler}. It \e{directly} generates old-style
+Windows Help files, and therefore it doesn't need to be run on
+Windows to do so: it can generate them even when run from an
+automated script on a Unix machine.
+
 }
 
 \b \c{output.1} will be a Unix \i{\cw{man} page}.
 }
 
 \b \c{output.1} will be a Unix \i{\cw{man} page}.
@@ -81,9 +83,10 @@ line, using the \c{-C} option).
 
 \dt \i\cw{--winhelp}[\cw{=}\e{filename}]
 
 
 \dt \i\cw{--winhelp}[\cw{=}\e{filename}]
 
-\dd Specifies that you want to generate Windows Help output. You can
-optionally specify a file name (e.g. \c{--winhelp=myfile.hlp}), in
-which case Halibut will change the name of the output file as well.
+\dd Specifies that you want to generate old-style Windows Help
+output. You can optionally specify a file name (e.g.
+\c{--winhelp=myfile.hlp}), in which case Halibut will change the
+name of the output file as well.
 
 \lcont{
 
 
 \lcont{
 
diff --git a/error.c b/error.c
index 2dae220..7fe97ec 100644 (file)
--- a/error.c
+++ b/error.c
@@ -333,9 +333,14 @@ static void do_error(int code, va_list ap) {
       case err_pfnoafm:
        fpos = *va_arg(ap, filepos *);
        sp = va_arg(ap, char *);
       case err_pfnoafm:
        fpos = *va_arg(ap, filepos *);
        sp = va_arg(ap, char *);
-       sprintf(error, "No metrics available for Type 1 font '%.200s'", sp);
+       sprintf(error, "no metrics available for Type 1 font '%.200s'", sp);
        flags = FILEPOS;
        break;
        flags = FILEPOS;
        break;
+      case err_chmnames:
+       sprintf(error, "only one of html-mshtmlhelp-chm and "
+               "html-mshtmlhelp-hhp found");
+       flags = PREFIX;
+       break;
       case err_whatever:
        sp = va_arg(ap, char *);
         vsprintf(error, sp, ap);
       case err_whatever:
        sp = va_arg(ap, char *);
         vsprintf(error, sp, ap);
index 3cb1ed7..1ff014e 100644 (file)
--- a/halibut.h
+++ b/halibut.h
@@ -252,6 +252,7 @@ enum {
     err_pfhead,                               /* bad Type 1 header line */
     err_pfbad,                        /* otherwise invalide Type 1 font */
     err_pfnoafm,                      /* Type 1 font but no AFM */
     err_pfhead,                               /* bad Type 1 header line */
     err_pfbad,                        /* otherwise invalide Type 1 font */
     err_pfnoafm,                      /* Type 1 font but no AFM */
+    err_chmnames,                     /* need both or neither of hhp+chm */
     err_whatever                       /* random error of another type */
 };
 
     err_whatever                       /* random error of another type */
 };
 
index f52d4b8..ecd5a2d 100644 (file)
@@ -279,6 +279,11 @@ Umm.
 
 Ahh.
 
 
 Ahh.
 
+\H{app-\\two} Section with inconvenient keyword
+
+If you apply this file together with \cw{doc/chm.but}, this section
+should test \cw{html_sanitise_filename()}.
+
 \U Bibliography
 
 \B{book} Some text describing a book.
 \U Bibliography
 
 \B{book} Some text describing a book.