Home | History | Annotate | Line # | Download | only in makeinfo
      1  1.2       rin /*	$NetBSD: html.c,v 1.2 2020/06/05 12:47:28 rin Exp $	*/
      2  1.1  christos 
      3  1.1  christos /* html.c -- html-related utilities.
      4  1.1  christos    Id: html.c,v 1.28 2004/12/06 01:13:06 karl Exp
      5  1.1  christos 
      6  1.1  christos    Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Free Software
      7  1.1  christos    Foundation, Inc.
      8  1.1  christos 
      9  1.1  christos    This program is free software; you can redistribute it and/or modify
     10  1.1  christos    it under the terms of the GNU General Public License as published by
     11  1.1  christos    the Free Software Foundation; either version 2, or (at your option)
     12  1.1  christos    any later version.
     13  1.1  christos 
     14  1.1  christos    This program is distributed in the hope that it will be useful,
     15  1.1  christos    but WITHOUT ANY WARRANTY; without even the implied warranty of
     16  1.1  christos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17  1.1  christos    GNU General Public License for more details.
     18  1.1  christos 
     19  1.1  christos    You should have received a copy of the GNU General Public License
     20  1.1  christos    along with this program; if not, write to the Free Software Foundation,
     21  1.1  christos    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
     22  1.1  christos 
     23  1.1  christos #include "system.h"
     24  1.1  christos #include "cmds.h"
     25  1.1  christos #include "files.h"
     26  1.1  christos #include "html.h"
     27  1.1  christos #include "lang.h"
     28  1.1  christos #include "makeinfo.h"
     29  1.1  christos #include "node.h"
     30  1.1  christos #include "sectioning.h"
     31  1.1  christos 
     32  1.1  christos 
     33  1.1  christos /* Append CHAR to BUFFER, (re)allocating as necessary.  We don't handle
     35  1.1  christos    null characters.  */
     36  1.1  christos 
     37  1.1  christos typedef struct
     38  1.1  christos {
     39  1.1  christos   unsigned size;    /* allocated */
     40  1.1  christos   unsigned length;  /* used */
     41  1.1  christos   char *buffer;
     42  1.1  christos } buffer_type;
     43  1.1  christos 
     44  1.1  christos static buffer_type *
     45  1.1  christos init_buffer (void)
     46  1.1  christos {
     47  1.1  christos   buffer_type *buf = xmalloc (sizeof (buffer_type));
     48  1.1  christos   buf->length = 0;
     49  1.1  christos   buf->size = 0;
     50  1.1  christos   buf->buffer = NULL;
     51  1.1  christos 
     52  1.1  christos   return buf;
     53  1.1  christos }
     54  1.1  christos 
     55  1.1  christos static void
     56  1.1  christos append_char (buffer_type *buf, int c)
     57  1.1  christos {
     58  1.1  christos   buf->length++;
     59  1.1  christos   if (buf->length >= buf->size)
     60  1.1  christos     {
     61  1.1  christos       buf->size += 100;
     62  1.1  christos       buf->buffer = xrealloc (buf->buffer, buf->size);
     63  1.1  christos     }
     64  1.1  christos   buf->buffer[buf->length - 1] = c;
     65  1.1  christos   buf->buffer[buf->length] = 0;
     66  1.1  christos }
     67  1.1  christos 
     68  1.1  christos /* Read the cascading style-sheet file FILENAME.  Write out any @import
     69  1.1  christos    commands, which must come first, by the definition of css.  If the
     70  1.1  christos    file contains any actual css code following the @imports, return it;
     71  1.1  christos    else return NULL.  */
     72  1.1  christos static char *
     73  1.1  christos process_css_file (char *filename)
     74  1.1  christos {
     75  1.1  christos   int c;
     76  1.1  christos   int lastchar = 0;
     77  1.1  christos   FILE *f;
     78  1.1  christos   buffer_type *import_text = init_buffer ();
     79  1.1  christos   buffer_type *inline_text = init_buffer ();
     80  1.1  christos   unsigned lineno = 1;
     81  1.1  christos   enum { null_state, comment_state, import_state, inline_state } state
     82  1.1  christos     = null_state, prev_state;
     83  1.1  christos 
     84  1.1  christos   prev_state = null_state;
     85  1.1  christos 
     86  1.1  christos   /* read from stdin if `-' is the filename.  */
     87  1.1  christos   f = STREQ (filename, "-") ? stdin : fopen (filename, "r");
     88  1.1  christos   if (!f)
     89  1.1  christos     {
     90  1.1  christos       error (_("%s: could not open --css-file: %s"), progname, filename);
     91  1.1  christos       return NULL;
     92  1.1  christos     }
     93  1.1  christos 
     94  1.1  christos   /* Read the file.  The @import statements must come at the beginning,
     95  1.1  christos      with only whitespace and comments allowed before any inline css code.  */
     96  1.1  christos   while ((c = getc (f)) >= 0)
     97  1.1  christos     {
     98  1.1  christos       if (c == '\n')
     99  1.1  christos         lineno++;
    100  1.1  christos 
    101  1.1  christos       switch (state)
    102  1.1  christos         {
    103  1.1  christos         case null_state: /* between things */
    104  1.1  christos           if (c == '@')
    105  1.1  christos             { /* Only @import and @charset should switch into
    106  1.1  christos                  import_state, other @-commands, such as @media, should
    107  1.1  christos                  put us into inline_state.  I don't think any other css
    108  1.1  christos                  @-commands start with `i' or `c', although of course
    109  1.1  christos                  this will break when such a command is defined.  */
    110  1.1  christos               int nextchar = getc (f);
    111  1.1  christos               if (nextchar == 'i' || nextchar == 'c')
    112  1.1  christos                 {
    113  1.1  christos                   append_char (import_text, c);
    114  1.1  christos                   state = import_state;
    115  1.1  christos                 }
    116  1.1  christos               else
    117  1.1  christos                 {
    118  1.1  christos                   ungetc (nextchar, f);  /* wasn't an @import */
    119  1.1  christos                   state = inline_state;
    120  1.1  christos                 }
    121  1.1  christos             }
    122  1.1  christos           else if (c == '/')
    123  1.1  christos             { /* possible start of a comment */
    124  1.1  christos               int nextchar = getc (f);
    125  1.1  christos               if (nextchar == '*')
    126  1.1  christos                 state = comment_state;
    127  1.1  christos               else
    128  1.1  christos                 {
    129  1.1  christos                   ungetc (nextchar, f); /* wasn't a comment */
    130  1.1  christos                   state = inline_state;
    131  1.1  christos                 }
    132  1.1  christos             }
    133  1.1  christos           else if (isspace (c))
    134  1.1  christos             ; /* skip whitespace; maybe should use c_isspace?  */
    135  1.1  christos 
    136  1.1  christos           else
    137  1.1  christos             /* not an @import, not a comment, not whitespace: we must
    138  1.1  christos                have started the inline text.  */
    139  1.1  christos             state = inline_state;
    140  1.1  christos 
    141  1.1  christos           if (state == inline_state)
    142  1.1  christos             append_char (inline_text, c);
    143  1.1  christos 
    144  1.1  christos           if (state != null_state)
    145  1.1  christos             prev_state = null_state;
    146  1.1  christos           break;
    147  1.1  christos 
    148  1.1  christos         case comment_state:
    149  1.1  christos           if (c == '/' && lastchar == '*')
    150  1.1  christos             state = prev_state;  /* end of comment */
    151  1.1  christos           break;  /* else ignore this comment char */
    152  1.1  christos 
    153  1.1  christos         case import_state:
    154  1.1  christos           append_char (import_text, c);  /* include this import char */
    155  1.1  christos           if (c == ';')
    156  1.1  christos             { /* done with @import */
    157  1.1  christos               append_char (import_text, '\n');  /* make the output nice */
    158  1.1  christos               state = null_state;
    159  1.1  christos               prev_state = import_state;
    160  1.1  christos             }
    161  1.1  christos           break;
    162  1.1  christos 
    163  1.1  christos         case inline_state:
    164  1.1  christos           /* No harm in writing out comments, so don't bother parsing
    165  1.1  christos              them out, just append everything.  */
    166  1.1  christos           append_char (inline_text, c);
    167  1.1  christos           break;
    168  1.1  christos         }
    169  1.1  christos 
    170  1.1  christos       lastchar = c;
    171  1.1  christos     }
    172  1.1  christos 
    173  1.1  christos   /* Reached the end of the file.  We should not be still in a comment.  */
    174  1.1  christos   if (state == comment_state)
    175  1.1  christos     warning (_("%s:%d: --css-file ended in comment"), filename, lineno);
    176  1.1  christos 
    177  1.1  christos   /* Write the @import text, if any.  */
    178  1.1  christos   if (import_text->buffer)
    179  1.1  christos     {
    180  1.1  christos       add_word (import_text->buffer);
    181  1.1  christos       free (import_text->buffer);
    182  1.1  christos       free (import_text);
    183  1.1  christos     }
    184  1.1  christos 
    185  1.1  christos   /* We're wasting the buffer struct memory, but so what.  */
    186  1.1  christos   return inline_text->buffer;
    187  1.1  christos }
    188  1.1  christos 
    189  1.1  christos HSTACK *htmlstack = NULL;
    191  1.1  christos 
    192  1.1  christos /* See html.h.  */
    193  1.1  christos int html_output_head_p = 0;
    194  1.1  christos int html_title_written = 0;
    195  1.1  christos 
    196  1.1  christos void
    197  1.1  christos html_output_head (void)
    198  1.1  christos {
    199  1.1  christos   static const char *html_title = NULL;
    200  1.1  christos   char *encoding;
    201  1.1  christos 
    202  1.1  christos   if (html_output_head_p)
    203  1.1  christos     return;
    204  1.1  christos   html_output_head_p = 1;
    205  1.1  christos 
    206  1.1  christos   encoding = current_document_encoding ();
    207  1.1  christos 
    208  1.1  christos   /* The <title> should not have markup, so use text_expansion.  */
    209  1.1  christos   if (!html_title)
    210  1.1  christos     html_title = escape_string (title ?
    211  1.1  christos         text_expansion (title) : (char *) _("Untitled"));
    212  1.1  christos 
    213  1.1  christos   /* Make sure this is the very first string of the output document.  */
    214  1.1  christos   output_paragraph_offset = 0;
    215  1.1  christos 
    216  1.1  christos   add_html_block_elt_args ("<html lang=\"%s\">\n<head>\n",
    217  1.1  christos       language_table[language_code].abbrev);
    218  1.1  christos 
    219  1.1  christos   /* When splitting, add current node's name to title if it's available and not
    220  1.1  christos      Top.  */
    221  1.1  christos   if (splitting && current_node && !STREQ (current_node, "Top"))
    222  1.1  christos     add_word_args ("<title>%s - %s</title>\n",
    223  1.1  christos         escape_string (xstrdup (current_node)), html_title);
    224  1.1  christos   else
    225  1.1  christos     add_word_args ("<title>%s</title>\n",  html_title);
    226  1.1  christos 
    227  1.1  christos   add_word ("<meta http-equiv=\"Content-Type\" content=\"text/html");
    228  1.1  christos   if (encoding && *encoding)
    229  1.1  christos     add_word_args ("; charset=%s", encoding);
    230  1.1  christos 
    231  1.1  christos   add_word ("\">\n");
    232  1.1  christos 
    233  1.1  christos   if (!document_description)
    234  1.1  christos     document_description = html_title;
    235  1.1  christos 
    236  1.1  christos   add_word_args ("<meta name=\"description\" content=\"%s\">\n",
    237  1.1  christos                  document_description);
    238  1.1  christos   add_word_args ("<meta name=\"generator\" content=\"makeinfo %s\">\n",
    239  1.1  christos                  VERSION);
    240  1.1  christos 
    241  1.1  christos   /* Navigation bar links.  */
    242  1.1  christos   if (!splitting)
    243  1.1  christos     add_word ("<link title=\"Top\" rel=\"top\" href=\"#Top\">\n");
    244  1.1  christos   else if (tag_table)
    245  1.1  christos     {
    246  1.1  christos       /* Always put a top link.  */
    247  1.1  christos       add_word ("<link title=\"Top\" rel=\"start\" href=\"index.html#Top\">\n");
    248  1.1  christos 
    249  1.1  christos       /* We already have a top link, avoid duplication.  */
    250  1.1  christos       if (tag_table->up && !STREQ (tag_table->up, "Top"))
    251  1.1  christos         add_link (tag_table->up, "rel=\"up\"");
    252  1.1  christos 
    253  1.1  christos       if (tag_table->prev)
    254  1.1  christos         add_link (tag_table->prev, "rel=\"prev\"");
    255  1.1  christos 
    256  1.1  christos       if (tag_table->next)
    257  1.1  christos         add_link (tag_table->next, "rel=\"next\"");
    258  1.1  christos 
    259  1.1  christos       /* fixxme: Look for a way to put links to various indices in the
    260  1.1  christos          document.  Also possible candidates to be added here are First and
    261  1.1  christos          Last links.  */
    262  1.1  christos     }
    263  1.1  christos   else
    264  1.1  christos     {
    265  1.1  christos       /* We are splitting, but we neither have a tag_table.  So this must be
    266  1.1  christos          index.html.  So put a link to Top. */
    267  1.1  christos       add_word ("<link title=\"Top\" rel=\"start\" href=\"#Top\">\n");
    268  1.1  christos     }
    269  1.1  christos 
    270  1.1  christos   add_word ("<link href=\"http://www.gnu.org/software/texinfo/\" \
    271  1.1  christos rel=\"generator-home\" title=\"Texinfo Homepage\">\n");
    272  1.1  christos 
    273  1.1  christos   if (copying_text)
    274  1.1  christos     { /* It is not ideal that we include the html markup here within
    275  1.1  christos          <head>, so we use text_expansion.  */
    276  1.1  christos       insert_string ("<!--\n");
    277  1.1  christos       insert_string (text_expansion (copying_text));
    278  1.1  christos       insert_string ("-->\n");
    279  1.1  christos     }
    280  1.1  christos 
    281  1.1  christos   /* Put the style definitions in a comment for the sake of browsers
    282  1.1  christos      that don't support <style>.  */
    283  1.1  christos   add_word ("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n");
    284  1.1  christos   add_word ("<style type=\"text/css\"><!--\n");
    285  1.1  christos 
    286  1.1  christos   {
    287  1.1  christos     char *css_inline = NULL;
    288  1.1  christos 
    289  1.1  christos     if (css_include)
    290  1.1  christos       /* This writes out any @import commands from the --css-file,
    291  1.1  christos          and returns any actual css code following the imports.  */
    292  1.1  christos       css_inline = process_css_file (css_include);
    293  1.1  christos 
    294  1.1  christos     /* This seems cleaner than adding <br>'s at the end of each line for
    295  1.1  christos        these "roman" displays.  It's hardly the end of the world if the
    296  1.1  christos        browser doesn't do <style>s, in any case; they'll just come out in
    297  1.1  christos        typewriter.  */
    298  1.1  christos #define CSS_FONT_INHERIT "font-family:inherit"
    299  1.1  christos     add_word_args ("  pre.display { %s }\n", CSS_FONT_INHERIT);
    300  1.1  christos     add_word_args ("  pre.format  { %s }\n", CSS_FONT_INHERIT);
    301  1.1  christos 
    302  1.1  christos     /* Alternatively, we could do <font size=-1> in insertion.c, but this
    303  1.1  christos        way makes it easier to override.  */
    304  1.1  christos #define CSS_FONT_SMALLER "font-size:smaller"
    305  1.1  christos     add_word_args ("  pre.smalldisplay { %s; %s }\n", CSS_FONT_INHERIT,
    306  1.1  christos                    CSS_FONT_SMALLER);
    307  1.1  christos     add_word_args ("  pre.smallformat  { %s; %s }\n", CSS_FONT_INHERIT,
    308  1.1  christos                    CSS_FONT_SMALLER);
    309  1.1  christos     add_word_args ("  pre.smallexample { %s }\n", CSS_FONT_SMALLER);
    310  1.1  christos     add_word_args ("  pre.smalllisp    { %s }\n", CSS_FONT_SMALLER);
    311  1.1  christos 
    312  1.1  christos     /* Since HTML doesn't have a sc element, we use span with a bit of
    313  1.1  christos        CSS spice instead.  */
    314  1.1  christos #define CSS_FONT_SMALL_CAPS "font-variant:small-caps"
    315  1.1  christos     add_word_args ("  span.sc    { %s }\n", CSS_FONT_SMALL_CAPS);
    316  1.1  christos 
    317  1.1  christos     /* Roman (default) font class, closest we can come.  */
    318  1.1  christos #define CSS_FONT_ROMAN "font-family:serif; font-weight:normal;"
    319  1.1  christos     add_word_args ("  span.roman { %s } \n", CSS_FONT_ROMAN);
    320  1.1  christos 
    321  1.1  christos     /* Sans serif font class.  */
    322  1.1  christos #define CSS_FONT_SANSSERIF "font-family:sans-serif; font-weight:normal;"
    323  1.1  christos     add_word_args ("  span.sansserif { %s } \n", CSS_FONT_SANSSERIF);
    324  1.1  christos 
    325  1.1  christos     /* Write out any css code from the user's --css-file.  */
    326  1.1  christos     if (css_inline)
    327  1.1  christos       insert_string (css_inline);
    328  1.1  christos 
    329  1.1  christos     add_word ("--></style>\n");
    330  1.1  christos   }
    331  1.1  christos 
    332  1.1  christos   add_word ("</head>\n<body>\n");
    333  1.1  christos 
    334  1.1  christos   if (title && !html_title_written && titlepage_cmd_present)
    335  1.1  christos     {
    336  1.1  christos       add_word_args ("<h1 class=\"settitle\">%s</h1>\n", html_title);
    337  1.1  christos       html_title_written = 1;
    338  1.1  christos     }
    339  1.1  christos 
    340  1.1  christos   free (encoding);
    341  1.1  christos }
    342  1.1  christos 
    343  1.1  christos /* Escape HTML special characters in the string if necessary,
    345  1.1  christos    returning a pointer to a possibly newly-allocated one. */
    346  1.1  christos char *
    347  1.1  christos escape_string (char *string)
    348  1.1  christos {
    349  1.1  christos   char *newstring;
    350  1.1  christos   int i = 0, newlen = 0;
    351  1.1  christos 
    352  1.1  christos   do
    353  1.1  christos     {
    354  1.1  christos       /* Find how much to allocate. */
    355  1.1  christos       switch (string[i])
    356  1.1  christos         {
    357  1.1  christos         case '"':
    358  1.1  christos           newlen += 6;          /* `&quot;' */
    359  1.1  christos           break;
    360  1.1  christos         case '&':
    361  1.1  christos           newlen += 5;          /* `&amp;' */
    362  1.1  christos           break;
    363  1.1  christos         case '<':
    364  1.1  christos         case '>':
    365  1.1  christos           newlen += 4;          /* `&lt;', `&gt;' */
    366  1.1  christos           break;
    367  1.1  christos         default:
    368  1.1  christos           newlen++;
    369  1.1  christos         }
    370  1.1  christos     }
    371  1.1  christos   while (string[i++]);
    372  1.1  christos 
    373  1.1  christos   if (newlen == i) return string; /* Already OK. */
    374  1.1  christos 
    375  1.1  christos   newstring = xmalloc (newlen);
    376  1.1  christos   i = 0;
    377  1.1  christos   do
    378  1.1  christos     {
    379  1.1  christos       switch (string[i])
    380  1.1  christos         {
    381  1.1  christos         case '"':
    382  1.1  christos           strcpy (newstring, "&quot;");
    383  1.1  christos           newstring += 6;
    384  1.1  christos           break;
    385  1.1  christos         case '&':
    386  1.1  christos           strcpy (newstring, "&amp;");
    387  1.1  christos           newstring += 5;
    388  1.1  christos           break;
    389  1.1  christos         case '<':
    390  1.1  christos           strcpy (newstring, "&lt;");
    391  1.1  christos           newstring += 4;
    392  1.1  christos           break;
    393  1.1  christos         case '>':
    394  1.1  christos           strcpy (newstring, "&gt;");
    395  1.1  christos           newstring += 4;
    396  1.1  christos           break;
    397  1.1  christos         default:
    398  1.1  christos           newstring[0] = string[i];
    399  1.1  christos           newstring++;
    400  1.1  christos         }
    401  1.1  christos     }
    402  1.1  christos   while (string[i++]);
    403  1.1  christos   free (string);
    404  1.1  christos   return newstring - newlen;
    405  1.1  christos }
    406  1.1  christos 
    407  1.1  christos /* Save current tag.  */
    409  1.1  christos static void
    410  1.1  christos push_tag (char *tag, char *attribs)
    411  1.1  christos {
    412  1.1  christos   HSTACK *newstack = xmalloc (sizeof (HSTACK));
    413  1.1  christos 
    414  1.1  christos   newstack->tag = tag;
    415  1.1  christos   newstack->attribs = xstrdup (attribs);
    416  1.1  christos   newstack->next = htmlstack;
    417  1.1  christos   htmlstack = newstack;
    418  1.1  christos }
    419  1.1  christos 
    420  1.1  christos /* Get last tag.  */
    421  1.1  christos static void
    422  1.1  christos pop_tag (void)
    423  1.1  christos {
    424  1.1  christos   HSTACK *tos = htmlstack;
    425  1.1  christos 
    426  1.1  christos   if (!tos)
    427  1.1  christos     {
    428  1.1  christos       line_error (_("[unexpected] no html tag to pop"));
    429  1.1  christos       return;
    430  1.1  christos     }
    431  1.1  christos 
    432  1.1  christos   free (htmlstack->attribs);
    433  1.1  christos 
    434  1.1  christos   htmlstack = htmlstack->next;
    435  1.1  christos   free (tos);
    436  1.1  christos }
    437  1.1  christos 
    438  1.1  christos /* Check if tag is an empty or a whitespace only element.
    439  1.1  christos    If so, remove it, keeping whitespace intact.  */
    440  1.1  christos int
    441  1.1  christos rollback_empty_tag (char *tag)
    442  1.1  christos {
    443  1.1  christos   int check_position = output_paragraph_offset;
    444  1.1  christos   int taglen = strlen (tag);
    445  1.1  christos   int rollback_happened = 0;
    446  1.1  christos   char *contents = "";
    447  1.1  christos   char *contents_canon_white = "";
    448  1.1  christos 
    449  1.1  christos   /* If output_paragraph is empty, we cannot rollback :-\  */
    450  1.2       rin   if (output_paragraph_offset <= 0)
    451  1.1  christos     return 0;
    452  1.1  christos 
    453  1.1  christos   /* Find the end of the previous tag.  */
    454  1.1  christos   while (check_position > 0 && output_paragraph[check_position-1] != '>')
    455  1.1  christos     check_position--;
    456  1.1  christos 
    457  1.1  christos   /* Save stuff between tag's end to output_paragraph's end.  */
    458  1.1  christos   if (check_position != output_paragraph_offset)
    459  1.1  christos     {
    460  1.1  christos       contents = xmalloc (output_paragraph_offset - check_position + 1);
    461  1.1  christos       memcpy (contents, output_paragraph + check_position,
    462  1.1  christos           output_paragraph_offset - check_position);
    463  1.1  christos 
    464  1.1  christos       contents[output_paragraph_offset - check_position] = '\0';
    465  1.1  christos 
    466  1.1  christos       contents_canon_white = xstrdup (contents);
    467  1.2       rin       canon_white (contents_canon_white);
    468  1.1  christos     }
    469  1.1  christos 
    470  1.1  christos   /* Find the start of the previous tag.  */
    471  1.1  christos   while (check_position > 0 && output_paragraph[check_position-1] != '<')
    472  1.1  christos     check_position--;
    473  1.1  christos 
    474  1.1  christos   /* Check to see if this is the tag.  */
    475  1.1  christos   if (strncmp ((char *) output_paragraph + check_position, tag, taglen) == 0
    476  1.1  christos       && (whitespace (output_paragraph[check_position + taglen])
    477  1.1  christos           || output_paragraph[check_position + taglen] == '>'))
    478  1.1  christos     {
    479  1.1  christos       if (!contents_canon_white || !*contents_canon_white)
    480  1.1  christos         {
    481  1.1  christos           /* Empty content after whitespace removal, so roll it back.  */
    482  1.1  christos           output_paragraph_offset = check_position - 1;
    483  1.1  christos           rollback_happened = 1;
    484  1.1  christos 
    485  1.1  christos           /* Original contents may not be empty (whitespace.)  */
    486  1.1  christos           if (contents && *contents)
    487  1.1  christos             {
    488  1.1  christos               insert_string (contents);
    489  1.1  christos               free (contents);
    490  1.1  christos             }
    491  1.1  christos         }
    492  1.1  christos     }
    493  1.1  christos 
    494  1.1  christos   return rollback_happened;
    495  1.1  christos }
    496  1.1  christos 
    497  1.1  christos /* Open or close TAG according to START_OR_END. */
    498  1.1  christos void
    499  1.1  christos #if defined (VA_FPRINTF) && __STDC__
    500  1.1  christos insert_html_tag_with_attribute (int start_or_end, char *tag, char *format, ...)
    501  1.1  christos #else
    502  1.1  christos insert_html_tag_with_attribute (start_or_end, tag, format, va_alist)
    503  1.1  christos      int start_or_end;
    504  1.1  christos      char *tag;
    505  1.1  christos      char *format;
    506  1.1  christos      va_dcl
    507  1.1  christos #endif
    508  1.1  christos {
    509  1.1  christos   char *old_tag = NULL;
    510  1.1  christos   char *old_attribs = NULL;
    511  1.1  christos   char formatted_attribs[2000]; /* xx no fixed limits */
    512  1.1  christos   int do_return = 0;
    513  1.1  christos   extern int in_html_elt;
    514  1.1  christos 
    515  1.1  christos   if (start_or_end != START)
    516  1.1  christos     pop_tag ();
    517  1.1  christos 
    518  1.1  christos   if (htmlstack)
    519  1.1  christos     {
    520  1.1  christos       old_tag = htmlstack->tag;
    521  1.1  christos       old_attribs = htmlstack->attribs;
    522  1.1  christos     }
    523  1.1  christos 
    524  1.1  christos   if (format)
    525  1.1  christos     {
    526  1.1  christos #ifdef VA_SPRINTF
    527  1.1  christos       va_list ap;
    528  1.1  christos #endif
    529  1.1  christos 
    530  1.1  christos       VA_START (ap, format);
    531  1.1  christos #ifdef VA_SPRINTF
    532  1.1  christos       VA_SPRINTF (formatted_attribs, format, ap);
    533  1.1  christos #else
    534  1.1  christos       sprintf (formatted_attribs, format, a1, a2, a3, a4, a5, a6, a7, a8);
    535  1.1  christos #endif
    536  1.1  christos       va_end (ap);
    537  1.1  christos     }
    538  1.1  christos   else
    539  1.1  christos     formatted_attribs[0] = '\0';
    540  1.1  christos 
    541  1.1  christos   /* Exception: can nest multiple spans.  */
    542  1.1  christos   if (htmlstack
    543  1.1  christos       && STREQ (htmlstack->tag, tag)
    544  1.1  christos       && !(STREQ (tag, "span") && STREQ (old_attribs, formatted_attribs)))
    545  1.1  christos     do_return = 1;
    546  1.1  christos 
    547  1.1  christos   if (start_or_end == START)
    548  1.1  christos     push_tag (tag, formatted_attribs);
    549  1.1  christos 
    550  1.1  christos   if (do_return)
    551  1.1  christos     return;
    552  1.1  christos 
    553  1.1  christos   in_html_elt++;
    554  1.1  christos 
    555  1.1  christos   /* texinfo.tex doesn't support more than one font attribute
    556  1.1  christos      at the same time.  */
    557  1.1  christos   if ((start_or_end == START) && old_tag && *old_tag
    558  1.1  christos       && !rollback_empty_tag (old_tag))
    559  1.1  christos     add_word_args ("</%s>", old_tag);
    560  1.1  christos 
    561  1.1  christos   if (*tag)
    562  1.1  christos     {
    563  1.1  christos       if (start_or_end == START)
    564  1.1  christos         add_word_args (format ? "<%s %s>" : "<%s>", tag, formatted_attribs);
    565  1.1  christos       else if (!rollback_empty_tag (tag))
    566  1.1  christos         /* Insert close tag only if we didn't rollback,
    567  1.1  christos            in which case the opening tag is removed.  */
    568  1.1  christos         add_word_args ("</%s>", tag);
    569  1.1  christos     }
    570  1.1  christos 
    571  1.1  christos   if ((start_or_end != START) && old_tag && *old_tag)
    572  1.1  christos     add_word_args (strlen (old_attribs) > 0 ? "<%s %s>" : "<%s>",
    573  1.1  christos         old_tag, old_attribs);
    574  1.1  christos 
    575  1.1  christos   in_html_elt--;
    576  1.1  christos }
    577  1.1  christos 
    578  1.1  christos void
    579  1.1  christos insert_html_tag (int start_or_end, char *tag)
    580  1.1  christos {
    581  1.1  christos   insert_html_tag_with_attribute (start_or_end, tag, NULL);
    582  1.1  christos }
    583  1.1  christos 
    584  1.1  christos /* Output an HTML <link> to the filename for NODE, including the
    586  1.1  christos    other string as extra attributes. */
    587  1.1  christos void
    588  1.1  christos add_link (char *nodename, char *attributes)
    589  1.1  christos {
    590  1.1  christos   if (nodename)
    591  1.1  christos     {
    592  1.1  christos       add_html_elt ("<link ");
    593  1.1  christos       add_word_args ("%s", attributes);
    594  1.1  christos       add_word_args (" href=\"");
    595  1.1  christos       add_anchor_name (nodename, 1);
    596  1.1  christos       add_word_args ("\" title=\"%s\">\n", nodename);
    597  1.1  christos     }
    598  1.1  christos }
    599  1.1  christos 
    600  1.1  christos /* Output NAME with characters escaped as appropriate for an anchor
    601  1.1  christos    name, i.e., escape URL special characters with our _00hh convention
    602  1.1  christos    if OLD is zero.  (See the manual for details on the new scheme.)
    603  1.1  christos 
    604  1.1  christos    If OLD is nonzero, generate the node name with the 4.6-and-earlier
    605  1.1  christos    convention of %hh (and more special characters output as-is, notably
    606  1.1  christos    - and *).  This is only so that external references to old names can
    607  1.1  christos    still work with HTML generated by the new makeinfo; the gcc folks
    608  1.1  christos    needed this.  Our own HTML does not refer to these names.  */
    609  1.1  christos 
    610  1.1  christos void
    611  1.1  christos add_escaped_anchor_name (char *name, int old)
    612  1.1  christos {
    613  1.1  christos   canon_white (name);
    614  1.1  christos 
    615  1.1  christos   if (!old && !strchr ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    616  1.1  christos                        *name))
    617  1.1  christos     { /* XHTML does not allow anything but an ASCII letter to start an
    618  1.1  christos          identifier.  Therefore kludge in this constant string if we
    619  1.1  christos          have a nonletter.  */
    620  1.1  christos       add_word ("g_t");
    621  1.1  christos     }
    622  1.1  christos 
    623  1.1  christos   for (; *name; name++)
    624  1.1  christos     {
    625  1.1  christos       if (cr_or_whitespace (*name))
    626  1.1  christos         add_char ('-');
    627  1.1  christos 
    628  1.1  christos       else if (!old && !URL_SAFE_CHAR (*name))
    629  1.1  christos         /* Cast so characters with the high bit set are treated as >128,
    630  1.1  christos            for example o-umlaut should be 246, not -10.  */
    631  1.1  christos         add_word_args ("_00%x", (unsigned char) *name);
    632  1.1  christos 
    633  1.1  christos       else if (old && !URL_SAFE_CHAR (*name) && !OLD_URL_SAFE_CHAR (*name))
    634  1.1  christos         /* Different output convention, but still cast as above.  */
    635  1.1  christos         add_word_args ("%%%x", (unsigned char) *name);
    636  1.1  christos 
    637  1.1  christos       else
    638  1.1  christos         add_char (*name);
    639  1.1  christos     }
    640  1.1  christos }
    641  1.1  christos 
    642  1.1  christos /* Insert the text for the name of a reference in an HTML anchor
    643  1.1  christos    appropriate for NODENAME.
    644  1.1  christos 
    645  1.1  christos    If HREF is zero, generate text for name= in the new node name
    646  1.1  christos      conversion convention.
    647  1.1  christos    If HREF is negative, generate text for name= in the old convention.
    648  1.1  christos    If HREF is positive, generate the name for an href= attribute, i.e.,
    649  1.1  christos      including the `#' if it's an internal reference.   */
    650  1.1  christos void
    651  1.1  christos add_anchor_name (char *nodename, int href)
    652  1.1  christos {
    653  1.1  christos   if (href > 0)
    654  1.1  christos     {
    655  1.1  christos       if (splitting)
    656  1.1  christos 	add_url_name (nodename, href);
    657  1.1  christos       add_char ('#');
    658  1.1  christos     }
    659  1.1  christos   /* Always add NODENAME, so that the reference would pinpoint the
    660  1.1  christos      exact node on its file.  This is so several nodes could share the
    661  1.1  christos      same file, in case of file-name clashes, but also for more
    662  1.1  christos      accurate browser positioning.  */
    663  1.1  christos   if (strcasecmp (nodename, "(dir)") == 0)
    664  1.1  christos     /* Strip the parens, but keep the original letter-case.  */
    665  1.1  christos     add_word_args ("%.3s", nodename + 1);
    666  1.1  christos   else if (strcasecmp (nodename, "top") == 0)
    667  1.1  christos     add_word ("Top");
    668  1.1  christos   else
    669  1.1  christos     add_escaped_anchor_name (nodename, href < 0);
    670  1.1  christos }
    671  1.1  christos 
    672  1.1  christos /* Insert the text for the name of a reference in an HTML url, aprropriate
    673  1.1  christos    for NODENAME */
    674  1.1  christos void
    675  1.1  christos add_url_name (char *nodename, int href)
    676  1.1  christos {
    677  1.1  christos     add_nodename_to_filename (nodename, href);
    678  1.1  christos }
    679  1.1  christos 
    680  1.1  christos /* Convert non [A-Za-z0-9] to _00xx, where xx means the hexadecimal
    681  1.1  christos    representation of the ASCII character.  Also convert spaces and
    682  1.1  christos    newlines to dashes.  */
    683  1.1  christos static void
    684  1.1  christos fix_filename (char *filename)
    685  1.1  christos {
    686  1.1  christos   int i;
    687  1.1  christos   int len = strlen (filename);
    688  1.1  christos   char *oldname = xstrdup (filename);
    689  1.1  christos 
    690  1.1  christos   *filename = '\0';
    691  1.1  christos 
    692  1.1  christos   for (i = 0; i < len; i++)
    693  1.1  christos     {
    694  1.1  christos       if (cr_or_whitespace (oldname[i]))
    695  1.1  christos         strcat (filename, "-");
    696  1.1  christos       else if (URL_SAFE_CHAR (oldname[i]))
    697  1.1  christos         strncat (filename, (char *) oldname + i, 1);
    698  1.1  christos       else
    699  1.1  christos         {
    700  1.1  christos           char *hexchar = xmalloc (6 * sizeof (char));
    701  1.1  christos           sprintf (hexchar, "_00%x", (unsigned char) oldname[i]);
    702  1.1  christos           strcat (filename, hexchar);
    703  1.1  christos           free (hexchar);
    704  1.1  christos         }
    705  1.1  christos 
    706  1.1  christos       /* Check if we are nearing boundaries.  */
    707  1.1  christos       if (strlen (filename) >= PATH_MAX - 20)
    708  1.1  christos         break;
    709  1.1  christos     }
    710  1.1  christos 
    711  1.1  christos   free (oldname);
    712  1.1  christos }
    713  1.1  christos 
    714  1.1  christos /* As we can't look-up a (forward-referenced) nodes' html filename
    715  1.1  christos    from the tentry, we take the easy way out.  We assume that
    716  1.1  christos    nodenames are unique, and generate the html filename from the
    717  1.1  christos    nodename, that's always known.  */
    718  1.1  christos static char *
    719  1.1  christos nodename_to_filename_1 (char *nodename, int href)
    720  1.1  christos {
    721  1.1  christos   char *p;
    722  1.1  christos   char *filename;
    723  1.1  christos   char dirname[PATH_MAX];
    724  1.1  christos 
    725  1.1  christos   if (strcasecmp (nodename, "Top") == 0)
    726  1.1  christos     {
    727  1.1  christos       /* We want to convert references to the Top node into
    728  1.1  christos 	 "index.html#Top".  */
    729  1.1  christos       if (href)
    730  1.1  christos 	filename = xstrdup ("index.html"); /* "#Top" is added by our callers */
    731  1.1  christos       else
    732  1.1  christos 	filename = xstrdup ("Top");
    733  1.1  christos     }
    734  1.1  christos   else if (strcasecmp (nodename, "(dir)") == 0)
    735  1.1  christos     /* We want to convert references to the (dir) node into
    736  1.1  christos        "../index.html".  */
    737  1.1  christos     filename = xstrdup ("../index.html");
    738  1.1  christos   else
    739  1.1  christos     {
    740  1.1  christos       filename = xmalloc (PATH_MAX);
    741  1.1  christos       dirname[0] = '\0';
    742  1.1  christos       *filename = '\0';
    743  1.1  christos 
    744  1.1  christos       /* Check for external reference: ``(info-document)node-name''
    745  1.1  christos 	 Assume this node lives at: ``../info-document/node-name.html''
    746  1.1  christos 
    747  1.1  christos 	 We need to handle the special case (sigh): ``(info-document)'',
    748  1.1  christos 	 ie, an external top-node, which should translate to:
    749  1.1  christos 	 ``../info-document/info-document.html'' */
    750  1.1  christos 
    751  1.1  christos       p = nodename;
    752  1.1  christos       if (*nodename == '(')
    753  1.1  christos 	{
    754  1.1  christos 	  int length;
    755  1.1  christos 
    756  1.1  christos 	  p = strchr (nodename, ')');
    757  1.1  christos 	  if (p == NULL)
    758  1.1  christos 	    {
    759  1.1  christos 	      line_error (_("[unexpected] invalid node name: `%s'"), nodename);
    760  1.1  christos 	      xexit (1);
    761  1.1  christos 	    }
    762  1.1  christos 
    763  1.1  christos 	  length = p - nodename - 1;
    764  1.1  christos 	  if (length > 5 &&
    765  1.1  christos 	      FILENAME_CMPN (p - 5, ".info", 5) == 0)
    766  1.1  christos 	    length -= 5;
    767  1.1  christos 	  /* This is for DOS, and also for Windows and GNU/Linux
    768  1.1  christos 	     systems that might have Info files copied from a DOS 8+3
    769  1.1  christos 	     filesystem.  */
    770  1.1  christos 	  if (length > 4 &&
    771  1.1  christos 	      FILENAME_CMPN (p - 4, ".inf", 4) == 0)
    772  1.1  christos 	    length -= 4;
    773  1.1  christos 	  strcpy (filename, "../");
    774  1.1  christos 	  strncpy (dirname, nodename + 1, length);
    775  1.1  christos 	  *(dirname + length) = '\0';
    776  1.1  christos 	  fix_filename (dirname);
    777  1.1  christos 	  strcat (filename, dirname);
    778  1.1  christos 	  strcat (filename, "/");
    779  1.1  christos 	  p++;
    780  1.1  christos 	}
    781  1.1  christos 
    782  1.1  christos       /* In the case of just (info-document), there will be nothing
    783  1.1  christos 	 remaining, and we will refer to ../info-document/, which will
    784  1.1  christos 	 work fine.  */
    785  1.1  christos       strcat (filename, p);
    786  1.1  christos       if (*p)
    787  1.1  christos 	{
    788  1.1  christos 	  /* Hmm */
    789  1.1  christos 	  fix_filename (filename + strlen (filename) - strlen (p));
    790  1.1  christos 	  strcat (filename, ".html");
    791  1.1  christos 	}
    792  1.1  christos     }
    793  1.1  christos 
    794  1.1  christos   /* Produce a file name suitable for the underlying filesystem.  */
    795  1.1  christos   normalize_filename (filename);
    796  1.1  christos 
    797  1.1  christos #if 0
    798  1.1  christos   /* We add ``#Nodified-filename'' anchor to external references to be
    799  1.1  christos      prepared for non-split HTML support.  Maybe drop this. */
    800  1.1  christos   if (href && *dirname)
    801  1.1  christos     {
    802  1.1  christos       strcat (filename, "#");
    803  1.1  christos       strcat (filename, p);
    804  1.1  christos       /* Hmm, again */
    805  1.1  christos       fix_filename (filename + strlen (filename) - strlen (p));
    806  1.1  christos     }
    807  1.1  christos #endif
    808  1.1  christos 
    809  1.1  christos   return filename;
    810  1.1  christos }
    811  1.1  christos 
    812  1.1  christos /* If necessary, ie, if current filename != filename of node, output
    813  1.1  christos    the node name.  */
    814  1.1  christos void
    815  1.1  christos add_nodename_to_filename (char *nodename, int href)
    816  1.1  christos {
    817  1.1  christos   /* for now, don't check: always output filename */
    818  1.1  christos   char *filename = nodename_to_filename_1 (nodename, href);
    819  1.1  christos   add_word (filename);
    820  1.1  christos   free (filename);
    821  1.1  christos }
    822  1.1  christos 
    823  1.1  christos char *
    824  1.1  christos nodename_to_filename (char *nodename)
    825                {
    826                  /* The callers of nodename_to_filename use the result to produce
    827                     <a href=, so call nodename_to_filename_1 with last arg non-zero.  */
    828                  return nodename_to_filename_1 (nodename, 1);
    829                }
    830