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