xref: /openbsd/gnu/usr.bin/texinfo/makeinfo/html.c (revision 1dc4902c)
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 
34 typedef struct
35 {
36   unsigned size;    /* allocated */
37   unsigned length;  /* used */
38   char *buffer;
39 } buffer_type;
40 
41 static buffer_type *
init_buffer(void)42 init_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 
52 static void
append_char(buffer_type * buf,int c)53 append_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.  */
69 static char *
process_css_file(char * filename)70 process_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 
189 HSTACK *htmlstack = NULL;
190 
191 /* See html.h.  */
192 int html_output_head_p = 0;
193 int html_title_written = 0;
194 
195 void
html_output_head(void)196 html_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/\" \
270 rel=\"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. */
344 char *
escape_string(char * string)345 escape_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.  */
406 static void
push_tag(char * tag,char * attribs)407 push_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.  */
418 static void
pop_tag(void)419 pop_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.  */
437 int
rollback_empty_tag(char * tag)438 rollback_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. */
495 void
496 #if defined (VA_FPRINTF) && __STDC__
insert_html_tag_with_attribute(int start_or_end,char * tag,char * format,...)497 insert_html_tag_with_attribute (int start_or_end, char *tag, char *format, ...)
498 #else
499 insert_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 
575 void
insert_html_tag(int start_or_end,char * tag)576 insert_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. */
583 void
add_link(char * nodename,char * attributes)584 add_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 
606 void
add_escaped_anchor_name(char * name,int old)607 add_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.   */
646 void
add_anchor_name(char * nodename,int href)647 add_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 */
670 void
add_url_name(char * nodename,int href)671 add_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.  */
679 static void
fix_filename(char * filename)680 fix_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.  */
714 static char *
nodename_to_filename_1(char * nodename,int href)715 nodename_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.  */
810 void
add_nodename_to_filename(char * nodename,int href)811 add_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 
819 char *
nodename_to_filename(char * nodename)820 nodename_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