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