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