Revert to "C" locale for LC_NUMERIC, so that PDFs won't be corrupted
[sgt/halibut] / bk_html.c
index d0e555e..41a9f76 100644 (file)
--- a/bk_html.c
+++ b/bk_html.c
@@ -10,9 +10,6 @@
  *    sensible. Perhaps for the topmost section in the file, no
  *    fragment should be used? (Though it should probably still be
  *    _there_ even if unused.)
- * 
- *  - free up all the data we have allocated while running this
- *    backend.
  */
 
 #include <stdio.h>
@@ -48,7 +45,8 @@ typedef struct {
     char *index_filename;
     char *template_filename;
     char *single_filename;
-    char *template_fragment;
+    char **template_fragments;
+    int ntfragments;
     char *head_end, *body_start, *body_end, *addr_start, *addr_end;
     char *body_tag, *nav_attr;
     wchar_t *author, *description;
@@ -87,7 +85,7 @@ struct htmlsect {
     paragraph *title, *text;
     enum { NORMAL, TOP, INDEX } type;
     int contents_depth;
-    char *fragment;
+    char **fragments;
 };
 
 typedef struct {
@@ -153,7 +151,8 @@ static void html_file_section(htmlconfig *cfg, htmlfilelist *files,
                              htmlsect *sect, int depth);
 
 static htmlfile *html_new_file(htmlfilelist *list, char *filename);
-static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title);
+static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title,
+                              htmlconfig *cfg);
 
 /* Flags for html_words() flags parameter */
 #define NOTHING 0x00
@@ -220,7 +219,9 @@ static htmlconfig html_configure(paragraph *source) {
     ret.contents_filename = dupstr("Contents.html");
     ret.index_filename = dupstr("IndexPage.html");
     ret.template_filename = dupstr("%n.html");
-    ret.template_fragment = dupstr("%b");
+    ret.ntfragments = 1;
+    ret.template_fragments = snewn(ret.ntfragments, char *);
+    ret.template_fragments[0] = dupstr("%b");
     ret.head_end = ret.body_tag = ret.body_start = ret.body_end =
        ret.addr_start = ret.addr_end = ret.nav_attr = NULL;
     ret.author = ret.description = NULL;
@@ -270,13 +271,9 @@ static htmlconfig html_configure(paragraph *source) {
                k++;                /* treat `xhtml-' and `html-' the same */
 
            if (!ustricmp(k, L"html-restrict-charset")) {
-               char *csname = utoa_dup(uadv(k), CS_ASCII);
-               ret.restrict_charset = charset_from_localenc(csname);
-               sfree(csname);
+               ret.restrict_charset = charset_from_ustr(&p->fpos, uadv(k));
            } else if (!ustricmp(k, L"html-output-charset")) {
-               char *csname = utoa_dup(uadv(k), CS_ASCII);
-               ret.output_charset = charset_from_localenc(csname);
-               sfree(csname);
+               ret.output_charset = charset_from_ustr(&p->fpos, uadv(k));
            } else if (!ustricmp(k, L"html-version")) {
                wchar_t *vername = uadv(k);
                static const struct {
@@ -312,8 +309,24 @@ static htmlconfig html_configure(paragraph *source) {
                sfree(ret.template_filename);
                ret.template_filename = dupstr(adv(p->origkeyword));
            } else if (!ustricmp(k, L"html-template-fragment")) {
-               sfree(ret.template_fragment);
-               ret.template_fragment = dupstr(adv(p->origkeyword));
+               char *frag = adv(p->origkeyword);
+               if (*frag) {
+                   while (ret.ntfragments--)
+                       sfree(ret.template_fragments[ret.ntfragments]);
+                   sfree(ret.template_fragments);
+                   ret.template_fragments = NULL;
+                   ret.ntfragments = 0;
+                   while (*frag) {
+                       ret.ntfragments++;
+                       ret.template_fragments =
+                           sresize(ret.template_fragments,
+                                   ret.ntfragments, char *);
+                       ret.template_fragments[ret.ntfragments-1] =
+                           dupstr(frag);
+                       frag = adv(frag);
+                   }
+               } else
+                   error(err_cfginsufarg, &p->fpos, p->origkeyword, 1);
            } else if (!ustricmp(k, L"html-chapter-numeric")) {
                ret.achapter.just_numbers = utob(uadv(k));
            } else if (!ustricmp(k, L"html-chapter-suffix")) {
@@ -465,11 +478,13 @@ paragraph *html_config_filename(char *filename)
 }
 
 void html_backend(paragraph *sourceform, keywordlist *keywords,
-                 indexdata *idx, void *unused) {
+                 indexdata *idx, void *unused)
+{
     paragraph *p;
     htmlconfig conf;
     htmlfilelist files = { NULL, NULL, NULL, NULL, NULL };
     htmlsectlist sects = { NULL, NULL }, nonsects = { NULL, NULL };
+    int has_index;
 
     IGNORE(unused);
 
@@ -495,20 +510,19 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
      * source form but needs to be consistently mentioned in
      * contents links.
      * 
-     * While we're here, we'll also invent the HTML fragment name
+     * While we're here, we'll also invent the HTML fragment name(s)
      * for each section.
      */
     {
        htmlsect *topsect, *sect;
        int d;
 
-       topsect = html_new_sect(&sects, NULL);
+       topsect = html_new_sect(&sects, 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)) {
@@ -519,7 +533,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                    continue;
                }
 
-               sect = html_new_sect(&sects, p);
+               sect = html_new_sect(&sects, p, &conf);
                sect->text = p->next;
 
                sect->contents_depth = contents_depth(conf, d+1) - (d+1);
@@ -533,21 +547,32 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
 
                html_file_section(&conf, &files, sect, d);
 
-               sect->fragment = html_format(p, conf.template_fragment);
-               sect->fragment = html_sanitise_fragment(&files, sect->file,
-                                                       sect->fragment);
+               {
+                   int i;
+                   for (i=0; i < conf.ntfragments; i++) {
+                       sect->fragments[i] =
+                           html_format(p, conf.template_fragments[i]);
+                       sect->fragments[i] =
+                           html_sanitise_fragment(&files, sect->file,
+                                                  sect->fragments[i]);
+                   }
+               }
            }
 
-       /* And the index. */
-       sect = html_new_sect(&sects, NULL);
-       sect->text = NULL;
-       sect->type = INDEX;
-       sect->parent = topsect;
-       html_file_section(&conf, &files, sect, 0);   /* peer of chapters */
-       sect->fragment = utoa_dup(conf.index_text, CS_ASCII);
-       sect->fragment = html_sanitise_fragment(&files, sect->file,
-                                               sect->fragment);
-       files.index = sect->file;
+       /* And the index, if we have one. */
+       has_index = (count234(idx->entries) > 0);
+       if (has_index) {
+           sect = html_new_sect(&sects, 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;
+       }
     }
 
     /*
@@ -592,7 +617,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                 * won't attempt to add it to the contents or
                 * anything weird like that).
                 */
-               sect = html_new_sect(&nonsects, p);
+               sect = html_new_sect(&nonsects, p, &conf);
                sect->file = parent->file;
                sect->parent = parent;
                p->private_data = sect;
@@ -601,11 +626,11 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                 * Fragment IDs for these paragraphs will simply be
                 * `p' followed by an integer.
                 */
-               sect->fragment = snewn(40, char);
-               sprintf(sect->fragment, "p%d",
+               sect->fragments[0] = snewn(40, char);
+               sprintf(sect->fragments[0], "p%d",
                        sect->file->last_fragment_number++);
-               sect->fragment = html_sanitise_fragment(&files, sect->file,
-                                                       sect->fragment);
+               sect->fragments[0] = html_sanitise_fragment(&files, sect->file,
+                                                           sect->fragments[0]);
            }
        }
     }
@@ -827,6 +852,26 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
            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);
 
@@ -866,15 +911,16 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                if (f != files.head)
                    element_close(&ho, "a");
 
-               html_text(&ho, conf.nav_separator);
-
-               if (f != files.index) {
-                   element_open(&ho, "a");
-                   element_attr(&ho, "href", files.index->filename);
+               if (has_index) {
+                   html_text(&ho, conf.nav_separator);
+                   if (f != files.index) {
+                       element_open(&ho, "a");
+                       element_attr(&ho, "href", files.index->filename);
+                   }
+                   html_text(&ho, conf.index_text);
+                   if (f != files.index)
+                       element_close(&ho, "a");
                }
-               html_text(&ho, conf.index_text);
-               if (f != files.index)
-                   element_close(&ho, "a");
 
                html_text(&ho, conf.nav_separator);
 
@@ -892,7 +938,7 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
            prevf = f;
 
            /*
-            * Write out a prefix TOC for the file.
+            * Write out a prefix TOC for the file (if a leaf file).
             * 
             * We start by going through the section list and
             * collecting the sections which need to be added to
@@ -975,6 +1021,12 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                     */
                    displaying = TRUE;
                } else {
+                   /*
+                    * Doesn't belong in this file, but it may be
+                    * a descendant of a section which does, in
+                    * which case we should consider it for the
+                    * main TOC of this file (for non-leaf files).
+                    */
                    htmlsect *a, *ac;
                    int depth, adepth;
 
@@ -1034,14 +1086,18 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                    element_open(&ho, htag);
 
                    /*
-                    * Provide anchor for cross-links to target.
+                    * Provide anchor(s) for cross-links to target.
                     * 
                     * (Also we'll have to do this separately in
                     * other paragraph types - NumberedList and
                     * BiblioCited.)
                     */
-                   if (s->fragment)
-                       html_fragment(&ho, s->fragment);
+                   {
+                       int i;
+                       for (i=0; i < conf.ntfragments; i++)
+                           if (s->fragments[i])
+                               html_fragment(&ho, s->fragments[i]);
+                   }
 
                    html_section_title(&ho, s, f, keywords, &conf, TRUE);
 
@@ -1173,7 +1229,10 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                                element_open(&ho, "p");
                                if (p->private_data) {
                                    htmlsect *s = (htmlsect *)p->private_data;
-                                   html_fragment(&ho, s->fragment);
+                                   int i;
+                                   for (i=0; i < conf.ntfragments; i++)
+                                       if (s->fragments[i])
+                                           html_fragment(&ho, s->fragments[i]);
                                }
                                html_nl(&ho);
                                html_words(&ho, p->kwtext, ALL,
@@ -1189,7 +1248,10 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
                                element_open(&ho, "li");
                                if (p->private_data) {
                                    htmlsect *s = (htmlsect *)p->private_data;
-                                   html_fragment(&ho, s->fragment);
+                                   int i;
+                                   for (i=0; i < conf.ntfragments; i++)
+                                       if (s->fragments[i])
+                                           html_fragment(&ho, s->fragments[i]);
                                }
                                html_nl(&ho);
                                stackhead->itemtype = LI;
@@ -1401,8 +1463,81 @@ void html_backend(paragraph *sourceform, keywordlist *keywords,
     }
 
     /*
-     * FIXME: Free all the working data.
+     * Free all the working data.
      */
+    {
+       htmlfragment *frag;
+       while ( (frag = (htmlfragment *)delpos234(files.frags, 0)) != NULL ) {
+           /*
+            * frag->fragment is dynamically allocated, but will be
+            * freed when we process the htmlsect structure which
+            * it is attached to.
+            */
+           sfree(frag);
+       }
+       freetree234(files.frags);
+    }
+    {
+       htmlsect *sect, *tmp;
+       sect = sects.head;
+       while (sect) {
+           int i;
+           tmp = sect->next;
+           for (i=0; i < conf.ntfragments; i++)
+               sfree(sect->fragments[i]);
+           sfree(sect->fragments);
+           sfree(sect);
+           sect = tmp;
+       }
+       sect = nonsects.head;
+       while (sect) {
+           int i;
+           tmp = sect->next;
+           for (i=0; i < conf.ntfragments; i++)
+               sfree(sect->fragments[i]);
+           sfree(sect->fragments);
+           sfree(sect);
+           sect = tmp;
+       }
+    }
+    {
+       htmlfile *file, *tmp;
+       file = files.head;
+       while (file) {
+           tmp = file->next;
+           sfree(file->filename);
+           sfree(file);
+           file = tmp;
+       }
+    }
+    {
+       int i;
+       indexentry *entry;
+       for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
+           htmlindex *hi = (htmlindex *)entry->backend_data;
+           sfree(hi);
+       }
+    }
+    {
+       paragraph *p;
+       word *w;
+       for (p = sourceform; p; p = p->next)
+           for (w = p->words; w; w = w->next)
+               if (w->type == word_IndexRef) {
+                   htmlindexref *hr = (htmlindexref *)w->private_data;
+                   assert(hr != NULL);
+                   sfree(hr->fragment);
+                   sfree(hr);
+               }
+    }
+    sfree(conf.asect);
+    sfree(conf.single_filename);
+    sfree(conf.contents_filename);
+    sfree(conf.index_filename);
+    sfree(conf.template_filename);
+    while (conf.ntfragments--)
+       sfree(conf.template_fragments[conf.ntfragments]);
+    sfree(conf.template_fragments);
 }
 
 static void html_file_section(htmlconfig *cfg, htmlfilelist *files,
@@ -1500,7 +1635,8 @@ static htmlfile *html_new_file(htmlfilelist *list, char *filename)
     return ret;
 }
 
-static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title)
+static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title,
+                              htmlconfig *cfg)
 {
     htmlsect *ret = snew(htmlsect);
 
@@ -1516,6 +1652,13 @@ static htmlsect *html_new_sect(htmlsectlist *list, paragraph *title)
     ret->parent = NULL;
     ret->type = NORMAL;
 
+    ret->fragments = snewn(cfg->ntfragments, char *);
+    {
+       int i;
+       for (i=0; i < cfg->ntfragments; i++)
+           ret->fragments[i] = NULL;
+    }
+
     return ret;
 }
 
@@ -1539,12 +1682,16 @@ static void html_words(htmloutput *ho, word *words, int flags,
       case word_LowerXref:
        if (flags & LINKS) {
            keyword *kwl = kw_lookup(keywords, w->text);
-           paragraph *p = kwl->para;
-           htmlsect *s = (htmlsect *)p->private_data;
+           paragraph *p;
+           htmlsect *s;
+
+           assert(kwl);
+           p = kwl->para;
+           s = (htmlsect *)p->private_data;
 
            assert(s);
 
-           html_href(ho, file, s->file, s->fragment);
+           html_href(ho, file, s->file, s->fragments[0]);
        }
        break;
       case word_HyperEnd:
@@ -1898,6 +2045,7 @@ static char *html_format(paragraph *p, char *template_string)
            } else if (p->keyword && *p->keyword && fmt == 'k')
                ws = p->keyword;
            else
+               /* %N comes here; also failure cases of other fmts */
                w = p->words;
 
            if (ws) {
@@ -1951,6 +2099,13 @@ static char *html_sanitise_fragment(htmlfilelist *files, htmlfile *file,
        *q = '\0';
     }
 
+    /* If there's nothing left, make something valid up */
+    if (!*text) {
+       static const char anonfrag[] = "anon";
+       text = sresize(text, lenof(anonfrag), char);
+       strcpy(text, anonfrag);
+    }
+
     /*
      * Now we check for clashes with other fragment names, and
      * adjust this one if necessary by appending a hyphen followed
@@ -1981,13 +2136,24 @@ static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s,
                                htmlfile *thisfile, keywordlist *keywords,
                                htmlconfig *cfg)
 {
+    if (ho->contents_level >= depth && ho->contents_level > 0) {
+       element_close(ho, "li");
+       html_nl(ho);
+    }
+
     while (ho->contents_level > depth) {
        element_close(ho, "ul");
        ho->contents_level--;
+       if (ho->contents_level > 0) {
+           element_close(ho, "li");
+       }
+       html_nl(ho);
     }
 
     while (ho->contents_level < depth) {
+       html_nl(ho);
        element_open(ho, "ul");
+       html_nl(ho);
        ho->contents_level++;
     }
 
@@ -1995,10 +2161,10 @@ static void html_contents_entry(htmloutput *ho, int depth, htmlsect *s,
        return;
 
     element_open(ho, "li");
-    html_href(ho, thisfile, s->file, s->fragment);
+    html_href(ho, thisfile, s->file, s->fragments[0]);
     html_section_title(ho, s, thisfile, keywords, cfg, FALSE);
     element_close(ho, "a");
-    element_close(ho, "li");
+    /* <li> will be closed by a later invocation */
 }
 
 static void html_section_title(htmloutput *ho, htmlsect *s, htmlfile *thisfile,