Back to index

tetex-bin  3.0
html.c
Go to the documentation of this file.
00001 /* html.c -- html-related utilities.
00002    $Id: html.c,v 1.28 2004/12/06 01:13:06 karl Exp $
00003 
00004    Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004 Free Software
00005    Foundation, Inc.
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2, or (at your option)
00010    any later version.
00011 
00012    This program is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015    GNU General Public License for more details.
00016 
00017    You should have received a copy of the GNU General Public License
00018    along with this program; if not, write to the Free Software Foundation,
00019    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
00020 
00021 #include "system.h"
00022 #include "cmds.h"
00023 #include "files.h"
00024 #include "html.h"
00025 #include "lang.h"
00026 #include "makeinfo.h"
00027 #include "node.h"
00028 #include "sectioning.h"
00029 
00030 
00031 /* Append CHAR to BUFFER, (re)allocating as necessary.  We don't handle
00032    null characters.  */
00033 
00034 typedef struct
00035 {
00036   unsigned size;    /* allocated */
00037   unsigned length;  /* used */
00038   char *buffer;
00039 } buffer_type;
00040 
00041 static buffer_type *
00042 init_buffer (void)
00043 {
00044   buffer_type *buf = xmalloc (sizeof (buffer_type));
00045   buf->length = 0;
00046   buf->size = 0;
00047   buf->buffer = NULL;
00048 
00049   return buf;
00050 }
00051 
00052 static void
00053 append_char (buffer_type *buf, int c)
00054 {
00055   buf->length++;
00056   if (buf->length >= buf->size)
00057     {
00058       buf->size += 100;
00059       buf->buffer = xrealloc (buf->buffer, buf->size);
00060     }
00061   buf->buffer[buf->length - 1] = c;
00062   buf->buffer[buf->length] = 0;
00063 }
00064 
00065 /* Read the cascading style-sheet file FILENAME.  Write out any @import
00066    commands, which must come first, by the definition of css.  If the
00067    file contains any actual css code following the @imports, return it;
00068    else return NULL.  */
00069 static char *
00070 process_css_file (char *filename)
00071 {
00072   int c;
00073   int lastchar = 0;
00074   FILE *f;
00075   buffer_type *import_text = init_buffer ();
00076   buffer_type *inline_text = init_buffer ();
00077   unsigned lineno = 1;
00078   enum { null_state, comment_state, import_state, inline_state } state
00079     = null_state, prev_state;
00080 
00081   prev_state = null_state;
00082 
00083   /* read from stdin if `-' is the filename.  */
00084   f = STREQ (filename, "-") ? stdin : fopen (filename, "r");
00085   if (!f)
00086     {
00087       error (_("%s: could not open --css-file: %s"), progname, filename);
00088       return NULL;
00089     }
00090 
00091   /* Read the file.  The @import statements must come at the beginning,
00092      with only whitespace and comments allowed before any inline css code.  */
00093   while ((c = getc (f)) >= 0)
00094     {
00095       if (c == '\n')
00096         lineno++;
00097 
00098       switch (state)
00099         {
00100         case null_state: /* between things */
00101           if (c == '@')
00102             { /* Only @import and @charset should switch into
00103                  import_state, other @-commands, such as @media, should
00104                  put us into inline_state.  I don't think any other css
00105                  @-commands start with `i' or `c', although of course
00106                  this will break when such a command is defined.  */
00107               int nextchar = getc (f);
00108               if (nextchar == 'i' || nextchar == 'c')
00109                 {
00110                   append_char (import_text, c);
00111                   state = import_state;
00112                 }
00113               else
00114                 {
00115                   ungetc (nextchar, f);  /* wasn't an @import */
00116                   state = inline_state;
00117                 }
00118             }
00119           else if (c == '/')
00120             { /* possible start of a comment */
00121               int nextchar = getc (f);
00122               if (nextchar == '*')
00123                 state = comment_state;
00124               else
00125                 {
00126                   ungetc (nextchar, f); /* wasn't a comment */
00127                   state = inline_state;
00128                 }
00129             }
00130           else if (isspace (c))
00131             ; /* skip whitespace; maybe should use c_isspace?  */
00132 
00133           else
00134             /* not an @import, not a comment, not whitespace: we must
00135                have started the inline text.  */
00136             state = inline_state;
00137 
00138           if (state == inline_state)
00139             append_char (inline_text, c);
00140 
00141           if (state != null_state)
00142             prev_state = null_state;
00143           break;
00144 
00145         case comment_state:
00146           if (c == '/' && lastchar == '*')
00147             state = prev_state;  /* end of comment */
00148           break;  /* else ignore this comment char */
00149 
00150         case import_state:
00151           append_char (import_text, c);  /* include this import char */
00152           if (c == ';')
00153             { /* done with @import */
00154               append_char (import_text, '\n');  /* make the output nice */
00155               state = null_state;
00156               prev_state = import_state;
00157             }
00158           break;
00159 
00160         case inline_state:
00161           /* No harm in writing out comments, so don't bother parsing
00162              them out, just append everything.  */
00163           append_char (inline_text, c);
00164           break;
00165         }
00166 
00167       lastchar = c;
00168     }
00169 
00170   /* Reached the end of the file.  We should not be still in a comment.  */
00171   if (state == comment_state)
00172     warning (_("%s:%d: --css-file ended in comment"), filename, lineno);
00173 
00174   /* Write the @import text, if any.  */
00175   if (import_text->buffer)
00176     {
00177       add_word (import_text->buffer);
00178       free (import_text->buffer);
00179       free (import_text);
00180     }
00181 
00182   /* We're wasting the buffer struct memory, but so what.  */
00183   return inline_text->buffer;
00184 }
00185 
00186 HSTACK *htmlstack = NULL;
00187 
00188 /* See html.h.  */
00189 int html_output_head_p = 0;
00190 int html_title_written = 0;
00191 
00192 void
00193 html_output_head (void)
00194 {
00195   static const char *html_title = NULL;
00196   char *encoding;
00197 
00198   if (html_output_head_p)
00199     return;
00200   html_output_head_p = 1;
00201 
00202   encoding = current_document_encoding ();
00203 
00204   /* The <title> should not have markup, so use text_expansion.  */
00205   if (!html_title)
00206     html_title = escape_string (title ?
00207         text_expansion (title) : (char *) _("Untitled"));
00208 
00209   /* Make sure this is the very first string of the output document.  */
00210   output_paragraph_offset = 0;
00211 
00212   add_html_block_elt_args ("<html lang=\"%s\">\n<head>\n",
00213       language_table[language_code].abbrev);
00214 
00215   /* When splitting, add current node's name to title if it's available and not
00216      Top.  */
00217   if (splitting && current_node && !STREQ (current_node, "Top"))
00218     add_word_args ("<title>%s - %s</title>\n",
00219         escape_string (xstrdup (current_node)), html_title);
00220   else
00221     add_word_args ("<title>%s</title>\n",  html_title);
00222 
00223   add_word ("<meta http-equiv=\"Content-Type\" content=\"text/html");
00224   if (encoding && *encoding)
00225     add_word_args ("; charset=%s", encoding);
00226                    
00227   add_word ("\">\n");
00228 
00229   if (!document_description)
00230     document_description = html_title;
00231 
00232   add_word_args ("<meta name=\"description\" content=\"%s\">\n",
00233                  document_description);
00234   add_word_args ("<meta name=\"generator\" content=\"makeinfo %s\">\n",
00235                  VERSION);
00236 
00237   /* Navigation bar links.  */
00238   if (!splitting)
00239     add_word ("<link title=\"Top\" rel=\"top\" href=\"#Top\">\n");
00240   else if (tag_table)
00241     {
00242       /* Always put a top link.  */
00243       add_word ("<link title=\"Top\" rel=\"start\" href=\"index.html#Top\">\n");
00244 
00245       /* We already have a top link, avoid duplication.  */
00246       if (tag_table->up && !STREQ (tag_table->up, "Top"))
00247         add_link (tag_table->up, "rel=\"up\"");
00248 
00249       if (tag_table->prev)
00250         add_link (tag_table->prev, "rel=\"prev\"");
00251 
00252       if (tag_table->next)
00253         add_link (tag_table->next, "rel=\"next\"");
00254 
00255       /* fixxme: Look for a way to put links to various indices in the
00256          document.  Also possible candidates to be added here are First and
00257          Last links.  */
00258     }
00259   else
00260     {
00261       /* We are splitting, but we neither have a tag_table.  So this must be
00262          index.html.  So put a link to Top. */
00263       add_word ("<link title=\"Top\" rel=\"start\" href=\"#Top\">\n");
00264     }
00265 
00266   add_word ("<link href=\"http://www.gnu.org/software/texinfo/\" \
00267 rel=\"generator-home\" title=\"Texinfo Homepage\">\n");
00268 
00269   if (copying_text)
00270     { /* It is not ideal that we include the html markup here within
00271          <head>, so we use text_expansion.  */
00272       insert_string ("<!--\n");
00273       insert_string (text_expansion (copying_text));
00274       insert_string ("-->\n");
00275     }
00276 
00277   /* Put the style definitions in a comment for the sake of browsers
00278      that don't support <style>.  */
00279   add_word ("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n");
00280   add_word ("<style type=\"text/css\"><!--\n");
00281 
00282   {
00283     char *css_inline = NULL;
00284 
00285     if (css_include)
00286       /* This writes out any @import commands from the --css-file,
00287          and returns any actual css code following the imports.  */
00288       css_inline = process_css_file (css_include);
00289 
00290     /* This seems cleaner than adding <br>'s at the end of each line for
00291        these "roman" displays.  It's hardly the end of the world if the
00292        browser doesn't do <style>s, in any case; they'll just come out in
00293        typewriter.  */
00294 #define CSS_FONT_INHERIT "font-family:inherit"
00295     add_word_args ("  pre.display { %s }\n", CSS_FONT_INHERIT);
00296     add_word_args ("  pre.format  { %s }\n", CSS_FONT_INHERIT);
00297 
00298     /* Alternatively, we could do <font size=-1> in insertion.c, but this
00299        way makes it easier to override.  */
00300 #define CSS_FONT_SMALLER "font-size:smaller"
00301     add_word_args ("  pre.smalldisplay { %s; %s }\n", CSS_FONT_INHERIT,
00302                    CSS_FONT_SMALLER);
00303     add_word_args ("  pre.smallformat  { %s; %s }\n", CSS_FONT_INHERIT,
00304                    CSS_FONT_SMALLER);
00305     add_word_args ("  pre.smallexample { %s }\n", CSS_FONT_SMALLER);
00306     add_word_args ("  pre.smalllisp    { %s }\n", CSS_FONT_SMALLER);
00307 
00308     /* Since HTML doesn't have a sc element, we use span with a bit of
00309        CSS spice instead.  */
00310 #define CSS_FONT_SMALL_CAPS "font-variant:small-caps"
00311     add_word_args ("  span.sc    { %s }\n", CSS_FONT_SMALL_CAPS);
00312 
00313     /* Roman (default) font class, closest we can come.  */
00314 #define CSS_FONT_ROMAN "font-family:serif; font-weight:normal;"
00315     add_word_args ("  span.roman { %s } \n", CSS_FONT_ROMAN);
00316 
00317     /* Sans serif font class.  */
00318 #define CSS_FONT_SANSSERIF "font-family:sans-serif; font-weight:normal;"
00319     add_word_args ("  span.sansserif { %s } \n", CSS_FONT_SANSSERIF);
00320 
00321     /* Write out any css code from the user's --css-file.  */
00322     if (css_inline)
00323       insert_string (css_inline);
00324 
00325     add_word ("--></style>\n");
00326   }
00327 
00328   add_word ("</head>\n<body>\n");
00329 
00330   if (title && !html_title_written && titlepage_cmd_present)
00331     {
00332       add_word_args ("<h1 class=\"settitle\">%s</h1>\n", html_title);
00333       html_title_written = 1;
00334     }
00335 
00336   free (encoding);
00337 }
00338 
00339 /* Escape HTML special characters in the string if necessary,
00340    returning a pointer to a possibly newly-allocated one. */
00341 char *
00342 escape_string (char *string)
00343 {
00344   char *newstring;
00345   int i = 0, newlen = 0;
00346 
00347   do
00348     {
00349       /* Find how much to allocate. */
00350       switch (string[i])
00351         {
00352         case '"':
00353           newlen += 6;          /* `&quot;' */
00354           break;
00355         case '&':
00356           newlen += 5;          /* `&amp;' */
00357           break;
00358         case '<':
00359         case '>':
00360           newlen += 4;          /* `&lt;', `&gt;' */
00361           break;
00362         default:
00363           newlen++;
00364         }
00365     }
00366   while (string[i++]);
00367 
00368   if (newlen == i) return string; /* Already OK. */
00369 
00370   newstring = xmalloc (newlen);
00371   i = 0;
00372   do
00373     {
00374       switch (string[i])
00375         {
00376         case '"':
00377           strcpy (newstring, "&quot;");
00378           newstring += 6;
00379           break;
00380         case '&':
00381           strcpy (newstring, "&amp;");
00382           newstring += 5;
00383           break;
00384         case '<':
00385           strcpy (newstring, "&lt;");
00386           newstring += 4;
00387           break;
00388         case '>':
00389           strcpy (newstring, "&gt;");
00390           newstring += 4;
00391           break;
00392         default:
00393           newstring[0] = string[i];
00394           newstring++;
00395         }
00396     }
00397   while (string[i++]);
00398   free (string);
00399   return newstring - newlen;
00400 }
00401 
00402 /* Save current tag.  */
00403 static void
00404 push_tag (char *tag, char *attribs)
00405 {
00406   HSTACK *newstack = xmalloc (sizeof (HSTACK));
00407 
00408   newstack->tag = tag;
00409   newstack->attribs = xstrdup (attribs);
00410   newstack->next = htmlstack;
00411   htmlstack = newstack;
00412 }
00413 
00414 /* Get last tag.  */
00415 static void
00416 pop_tag (void)
00417 {
00418   HSTACK *tos = htmlstack;
00419 
00420   if (!tos)
00421     {
00422       line_error (_("[unexpected] no html tag to pop"));
00423       return;
00424     }
00425 
00426   free (htmlstack->attribs);
00427 
00428   htmlstack = htmlstack->next;
00429   free (tos);
00430 }
00431 
00432 /* Check if tag is an empty or a whitespace only element.
00433    If so, remove it, keeping whitespace intact.  */
00434 int
00435 rollback_empty_tag (char *tag)
00436 {
00437   int check_position = output_paragraph_offset;
00438   int taglen = strlen (tag);
00439   int rollback_happened = 0;
00440   char *contents = "";
00441   char *contents_canon_white = "";
00442 
00443   /* If output_paragraph is empty, we cannot rollback :-\  */
00444   if (output_paragraph_offset <= 0)
00445     return 0;
00446 
00447   /* Find the end of the previous tag.  */
00448   while (output_paragraph[check_position-1] != '>' && check_position > 0)
00449     check_position--;
00450 
00451   /* Save stuff between tag's end to output_paragraph's end.  */
00452   if (check_position != output_paragraph_offset)
00453     {
00454       contents = xmalloc (output_paragraph_offset - check_position + 1);
00455       memcpy (contents, output_paragraph + check_position,
00456           output_paragraph_offset - check_position);
00457 
00458       contents[output_paragraph_offset - check_position] = '\0';
00459 
00460       contents_canon_white = xstrdup (contents);
00461       canon_white (contents_canon_white);
00462     }
00463 
00464   /* Find the start of the previous tag.  */
00465   while (output_paragraph[check_position-1] != '<' && check_position > 0)
00466     check_position--;
00467 
00468   /* Check to see if this is the tag.  */
00469   if (strncmp ((char *) output_paragraph + check_position, tag, taglen) == 0
00470       && (whitespace (output_paragraph[check_position + taglen])
00471           || output_paragraph[check_position + taglen] == '>'))
00472     {
00473       if (!contents_canon_white || !*contents_canon_white)
00474         {
00475           /* Empty content after whitespace removal, so roll it back.  */
00476           output_paragraph_offset = check_position - 1;
00477           rollback_happened = 1;
00478 
00479           /* Original contents may not be empty (whitespace.)  */
00480           if (contents && *contents)
00481             {
00482               insert_string (contents);
00483               free (contents);
00484             }
00485         }
00486     }
00487 
00488   return rollback_happened;
00489 }
00490 
00491 /* Open or close TAG according to START_OR_END. */
00492 void
00493 #if defined (VA_FPRINTF) && __STDC__
00494 insert_html_tag_with_attribute (int start_or_end, char *tag, char *format, ...)
00495 #else
00496 insert_html_tag_with_attribute (start_or_end, tag, format, va_alist)
00497      int start_or_end;
00498      char *tag;
00499      char *format;
00500      va_dcl
00501 #endif
00502 {
00503   char *old_tag = NULL;
00504   char *old_attribs = NULL;
00505   char formatted_attribs[2000]; /* xx no fixed limits */
00506   int do_return = 0;
00507   extern int in_html_elt;
00508 
00509   if (start_or_end != START)
00510     pop_tag ();
00511 
00512   if (htmlstack)
00513     {
00514       old_tag = htmlstack->tag;
00515       old_attribs = htmlstack->attribs;
00516     }
00517   
00518   if (format)
00519     {
00520 #ifdef VA_SPRINTF
00521       va_list ap;
00522 #endif
00523 
00524       VA_START (ap, format);
00525 #ifdef VA_SPRINTF
00526       VA_SPRINTF (formatted_attribs, format, ap);
00527 #else
00528       sprintf (formatted_attribs, format, a1, a2, a3, a4, a5, a6, a7, a8);
00529 #endif
00530       va_end (ap);
00531     }
00532   else
00533     formatted_attribs[0] = '\0';
00534 
00535   /* Exception: can nest multiple spans.  */
00536   if (htmlstack
00537       && STREQ (htmlstack->tag, tag)
00538       && !(STREQ (tag, "span") && STREQ (old_attribs, formatted_attribs)))
00539     do_return = 1;
00540 
00541   if (start_or_end == START)
00542     push_tag (tag, formatted_attribs);
00543 
00544   if (do_return)
00545     return;
00546 
00547   in_html_elt++;
00548 
00549   /* texinfo.tex doesn't support more than one font attribute
00550      at the same time.  */
00551   if ((start_or_end == START) && old_tag && *old_tag
00552       && !rollback_empty_tag (old_tag))
00553     add_word_args ("</%s>", old_tag);
00554 
00555   if (*tag)
00556     {
00557       if (start_or_end == START)
00558         add_word_args (format ? "<%s %s>" : "<%s>", tag, formatted_attribs);
00559       else if (!rollback_empty_tag (tag))
00560         /* Insert close tag only if we didn't rollback,
00561            in which case the opening tag is removed.  */
00562         add_word_args ("</%s>", tag);
00563     }
00564 
00565   if ((start_or_end != START) && old_tag && *old_tag)
00566     add_word_args (strlen (old_attribs) > 0 ? "<%s %s>" : "<%s>",
00567         old_tag, old_attribs);
00568 
00569   in_html_elt--;
00570 }
00571 
00572 void
00573 insert_html_tag (int start_or_end, char *tag)
00574 {
00575   insert_html_tag_with_attribute (start_or_end, tag, NULL);
00576 }
00577 
00578 /* Output an HTML <link> to the filename for NODE, including the
00579    other string as extra attributes. */
00580 void
00581 add_link (char *nodename, char *attributes)
00582 {
00583   if (nodename)
00584     {
00585       add_html_elt ("<link ");
00586       add_word_args ("%s", attributes);
00587       add_word_args (" href=\"");
00588       add_anchor_name (nodename, 1);
00589       add_word_args ("\" title=\"%s\">\n", nodename);
00590     }
00591 }
00592 
00593 /* Output NAME with characters escaped as appropriate for an anchor
00594    name, i.e., escape URL special characters with our _00hh convention
00595    if OLD is zero.  (See the manual for details on the new scheme.)
00596    
00597    If OLD is nonzero, generate the node name with the 4.6-and-earlier
00598    convention of %hh (and more special characters output as-is, notably
00599    - and *).  This is only so that external references to old names can
00600    still work with HTML generated by the new makeinfo; the gcc folks
00601    needed this.  Our own HTML does not refer to these names.  */
00602 
00603 void
00604 add_escaped_anchor_name (char *name, int old)
00605 {
00606   canon_white (name);
00607 
00608   if (!old && !strchr ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
00609                        *name))
00610     { /* XHTML does not allow anything but an ASCII letter to start an
00611          identifier.  Therefore kludge in this constant string if we
00612          have a nonletter.  */
00613       add_word ("g_t");
00614     }
00615 
00616   for (; *name; name++)
00617     {
00618       if (cr_or_whitespace (*name))
00619         add_char ('-');
00620 
00621       else if (!old && !URL_SAFE_CHAR (*name))
00622         /* Cast so characters with the high bit set are treated as >128,
00623            for example o-umlaut should be 246, not -10.  */
00624         add_word_args ("_00%x", (unsigned char) *name);
00625 
00626       else if (old && !URL_SAFE_CHAR (*name) && !OLD_URL_SAFE_CHAR (*name))
00627         /* Different output convention, but still cast as above.  */
00628         add_word_args ("%%%x", (unsigned char) *name);
00629 
00630       else
00631         add_char (*name);
00632     }
00633 }
00634 
00635 /* Insert the text for the name of a reference in an HTML anchor
00636    appropriate for NODENAME.
00637    
00638    If HREF is zero, generate text for name= in the new node name
00639      conversion convention.
00640    If HREF is negative, generate text for name= in the old convention.
00641    If HREF is positive, generate the name for an href= attribute, i.e.,
00642      including the `#' if it's an internal reference.   */
00643 void
00644 add_anchor_name (char *nodename, int href)
00645 {
00646   if (href > 0)
00647     {
00648       if (splitting)
00649        add_url_name (nodename, href);
00650       add_char ('#');
00651     }
00652   /* Always add NODENAME, so that the reference would pinpoint the
00653      exact node on its file.  This is so several nodes could share the
00654      same file, in case of file-name clashes, but also for more
00655      accurate browser positioning.  */
00656   if (strcasecmp (nodename, "(dir)") == 0)
00657     /* Strip the parens, but keep the original letter-case.  */
00658     add_word_args ("%.3s", nodename + 1);
00659   else if (strcasecmp (nodename, "top") == 0)
00660     add_word ("Top");
00661   else
00662     add_escaped_anchor_name (nodename, href < 0);
00663 }
00664 
00665 /* Insert the text for the name of a reference in an HTML url, aprropriate
00666    for NODENAME */
00667 void
00668 add_url_name (char *nodename, int href)
00669 {
00670     add_nodename_to_filename (nodename, href);
00671 }
00672 
00673 /* Convert non [A-Za-z0-9] to _00xx, where xx means the hexadecimal
00674    representation of the ASCII character.  Also convert spaces and
00675    newlines to dashes.  */
00676 static void
00677 fix_filename (char *filename)
00678 {
00679   int i;
00680   int len = strlen (filename);
00681   char *oldname = xstrdup (filename);
00682 
00683   *filename = '\0';
00684 
00685   for (i = 0; i < len; i++)
00686     {
00687       if (cr_or_whitespace (oldname[i]))
00688         strcat (filename, "-");
00689       else if (URL_SAFE_CHAR (oldname[i]))
00690         strncat (filename, (char *) oldname + i, 1);
00691       else
00692         {
00693           char *hexchar = xmalloc (6 * sizeof (char));
00694           sprintf (hexchar, "_00%x", (unsigned char) oldname[i]);
00695           strcat (filename, hexchar);
00696           free (hexchar);
00697         }
00698 
00699       /* Check if we are nearing boundaries.  */
00700       if (strlen (filename) >= PATH_MAX - 20)
00701         break;
00702     }
00703 
00704   free (oldname);
00705 }
00706 
00707 /* As we can't look-up a (forward-referenced) nodes' html filename
00708    from the tentry, we take the easy way out.  We assume that
00709    nodenames are unique, and generate the html filename from the
00710    nodename, that's always known.  */
00711 static char *
00712 nodename_to_filename_1 (char *nodename, int href)
00713 {
00714   char *p;
00715   char *filename;
00716   char dirname[PATH_MAX];
00717 
00718   if (strcasecmp (nodename, "Top") == 0)
00719     {
00720       /* We want to convert references to the Top node into
00721         "index.html#Top".  */
00722       if (href)
00723        filename = xstrdup ("index.html"); /* "#Top" is added by our callers */
00724       else
00725        filename = xstrdup ("Top");
00726     }
00727   else if (strcasecmp (nodename, "(dir)") == 0)
00728     /* We want to convert references to the (dir) node into
00729        "../index.html".  */
00730     filename = xstrdup ("../index.html");
00731   else
00732     {
00733       filename = xmalloc (PATH_MAX);
00734       dirname[0] = '\0';
00735       *filename = '\0';
00736 
00737       /* Check for external reference: ``(info-document)node-name''
00738         Assume this node lives at: ``../info-document/node-name.html''
00739 
00740         We need to handle the special case (sigh): ``(info-document)'',
00741         ie, an external top-node, which should translate to:
00742         ``../info-document/info-document.html'' */
00743 
00744       p = nodename;
00745       if (*nodename == '(')
00746        {
00747          int length;
00748 
00749          p = strchr (nodename, ')');
00750          if (p == NULL)
00751            {
00752              line_error (_("[unexpected] invalid node name: `%s'"), nodename);
00753              xexit (1);
00754            }
00755 
00756          length = p - nodename - 1;
00757          if (length > 5 &&
00758              FILENAME_CMPN (p - 5, ".info", 5) == 0)
00759            length -= 5;
00760          /* This is for DOS, and also for Windows and GNU/Linux
00761             systems that might have Info files copied from a DOS 8+3
00762             filesystem.  */
00763          if (length > 4 &&
00764              FILENAME_CMPN (p - 4, ".inf", 4) == 0)
00765            length -= 4;
00766          strcpy (filename, "../");
00767          strncpy (dirname, nodename + 1, length);
00768          *(dirname + length) = '\0';
00769          fix_filename (dirname);
00770          strcat (filename, dirname);
00771          strcat (filename, "/");
00772          p++;
00773        }
00774 
00775       /* In the case of just (info-document), there will be nothing
00776         remaining, and we will refer to ../info-document/, which will
00777         work fine.  */
00778       strcat (filename, p);
00779       if (*p)
00780        {
00781          /* Hmm */
00782          fix_filename (filename + strlen (filename) - strlen (p));
00783          strcat (filename, ".html");
00784        }
00785     }
00786 
00787   /* Produce a file name suitable for the underlying filesystem.  */
00788   normalize_filename (filename);
00789 
00790 #if 0
00791   /* We add ``#Nodified-filename'' anchor to external references to be
00792      prepared for non-split HTML support.  Maybe drop this. */
00793   if (href && *dirname)
00794     {
00795       strcat (filename, "#");
00796       strcat (filename, p);
00797       /* Hmm, again */
00798       fix_filename (filename + strlen (filename) - strlen (p));
00799     }
00800 #endif
00801 
00802   return filename;
00803 }
00804 
00805 /* If necessary, ie, if current filename != filename of node, output
00806    the node name.  */
00807 void
00808 add_nodename_to_filename (char *nodename, int href)
00809 {
00810   /* for now, don't check: always output filename */
00811   char *filename = nodename_to_filename_1 (nodename, href);
00812   add_word (filename);
00813   free (filename);
00814 }
00815 
00816 char *
00817 nodename_to_filename (char *nodename)
00818 {
00819   /* The callers of nodename_to_filename use the result to produce
00820      <a href=, so call nodename_to_filename_1 with last arg non-zero.  */
00821   return nodename_to_filename_1 (nodename, 1);
00822 }