Add missing "return(error)".
[dragonfly.git] / contrib / texinfo / makeinfo / toc.c
1 /* toc.c -- table of contents handling.
2    $Id: toc.c,v 1.22 2002/04/01 14:07:11 karl Exp $
3
4    Copyright (C) 1999, 2000, 01, 02 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20    Written by Karl Heinz Marbaise <kama@hippo.fido.de>.  */
21
22 #include "system.h"
23 #include "makeinfo.h"
24 #include "cmds.h"
25 #include "files.h"
26 #include "macro.h"
27 #include "node.h"
28 #include "html.h"
29 #include "lang.h"
30 #include "makeinfo.h"
31 #include "sectioning.h"
32 #include "toc.h"
33
34
35 \f
36 /* array of toc entries */
37 static TOC_ENTRY_ELT **toc_entry_alist = NULL;
38
39 /* toc_counter start from 0 ... n for every @chapter, @section ... */
40 static int toc_counter = 0;
41
42 /* the file where we found the @contents directive */
43 char *contents_filename;
44
45 /* the file where we found the @shortcontents directive */
46 char *shortcontents_filename;
47
48 static const char contents_placebo[] = "\n...Table of Contents...\n";
49 static const char shortcontents_placebo[] = "\n...Short Contents...\n";
50 static const char lots_of_stars[] =
51 "***************************************************************************";
52
53 \f
54 /* Routine to add an entry to the table of contents */
55 int
56 toc_add_entry (tocname, level, node_name, anchor)
57      char *tocname;
58      int level;
59      char *node_name;
60      char *anchor;
61 {
62   char *tocname_and_node, *expanded_node, *s, *d;
63   char *filename = NULL;
64
65   if (!node_name)
66     node_name = "";
67
68   /* I assume that xrealloc behaves like xmalloc if toc_entry_alist is
69      NULL */
70   toc_entry_alist = xrealloc (toc_entry_alist,
71                               (toc_counter + 1) * sizeof (TOC_ENTRY_ELT *));
72
73   toc_entry_alist[toc_counter] = xmalloc (sizeof (TOC_ENTRY_ELT));
74
75   if (html)
76     {
77       /* We need to insert the expanded node name into the toc, so
78          that when we eventually output the toc, its <a ref= link will
79          point to the <a name= tag created by cm_node in the navigation
80          bar.  We cannot expand the containing_node member, for the
81          reasons explained in the WARNING below.  We also cannot wait
82          with the node name expansion until the toc is actually output,
83          since by that time the macro definitions may have been changed.
84          So instead we store in the tocname member the expanded node
85          name and the toc name concatenated together (with the necessary
86          html markup), since that's how they are output.  */
87       if (!anchor)
88         s = expanded_node = expand_node_name (node_name);
89       else
90         expanded_node = anchor;
91       if (splitting)
92         {
93           if (!anchor)
94             filename = nodename_to_filename (expanded_node);
95           else
96             filename = filename_part (current_output_filename);
97         }
98       /* Sigh...  Need to HTML-escape the expanded node name like
99          add_anchor_name does, except that we are not writing this to
100          the output, so can't use add_anchor_name...  */
101       /* The factor 5 in the next allocation is because the maximum
102          expansion of HTML-escaping is for the & character, which is
103          output as "&amp;".  2 is for "> that separates node from tocname.  */
104       d = tocname_and_node = (char *)xmalloc (2 + 5 * strlen (expanded_node)
105                                               + strlen (tocname) + 1);
106       if (!anchor)
107         {
108           for (; *s; s++)
109             {
110               if (*s == '&')
111                 {
112                   strcpy (d, "&amp;");
113                   d += 5;
114                 }
115               else if (! URL_SAFE_CHAR (*s))
116                 {
117                   sprintf (d, "%%%x", (unsigned char) *s);
118                   /* do this manually since sprintf returns char * on
119                      SunOS 4 and other old systems.  */
120                   while (*d)
121                     d++;
122                 }
123               else
124                 *d++ = *s;
125             }
126           strcpy (d, "\">");
127         }
128       else
129         /* Section outside any node, they provided explicit anchor.  */
130         strcpy (d, anchor);
131       strcat (d, tocname);
132       free (tocname);       /* it was malloc'ed by substring() */
133       free (expanded_node);
134       toc_entry_alist[toc_counter]->name = tocname_and_node;
135     }
136   else
137     toc_entry_alist[toc_counter]->name = tocname;
138   /* WARNING!  The node name saved in containing_node member must
139      be the node name with _only_ macros expanded (the macros in
140      the node name are expanded by cm_node when it grabs the name
141      from the @node directive).  Non-macros, like @value, @@ and
142      other @-commands must NOT be expanded in containing_node,
143      because toc_find_section_of_node looks up the node name where
144      they are also unexpanded.  You *have* been warned!  */
145   toc_entry_alist[toc_counter]->containing_node = xstrdup (node_name);
146   toc_entry_alist[toc_counter]->level = level;
147   toc_entry_alist[toc_counter]->number = toc_counter;
148   toc_entry_alist[toc_counter]->html_file = filename;
149
150   /* have to be done at least */
151   return toc_counter++;
152 }
153
154 /* Return the name of a chapter/section/subsection etc. that
155    corresponds to the node NODE.  If the node isn't found,
156    return NULL.
157
158    WARNING!  This function relies on NODE being unexpanded
159    except for macros (i.e., @value, @@, and other non-macros
160    should NOT be expanded), because the containing_node member
161    stores unexpanded node names.
162
163    Note that this function returns the first section whose
164    containing node is NODE.  Thus, they will lose if they use
165    more than a single chapter structioning command in a node,
166    or if they have a node without any structuring commands.  */
167 char *
168 toc_find_section_of_node (node)
169      char *node;
170 {
171   int i;
172
173   if (!node)
174     node = "";
175   for (i = 0; i < toc_counter; i++)
176     if (STREQ (node, toc_entry_alist[i]->containing_node))
177       return toc_entry_alist[i]->name;
178
179   return NULL;
180 }
181
182 /* free up memory used by toc entries */
183 void
184 toc_free ()
185 {
186   int i;
187
188   if (toc_counter)
189     {
190       for (i = 0; i < toc_counter; i++)
191         {
192           free (toc_entry_alist[i]->name);
193           free (toc_entry_alist[i]->containing_node);
194           free (toc_entry_alist[i]);
195         }
196
197       free (toc_entry_alist);
198       toc_entry_alist = NULL; /* to be sure ;-) */
199       toc_counter = 0; /* to be absolutley sure ;-) */
200     }
201 }
202
203 \f
204 /* Print table of contents in HTML.  */
205
206 static void
207 contents_update_html (fp)
208      FILE *fp;
209 {
210   int i;
211   int k;
212   int last_level;
213
214   /* does exist any toc? */
215   if (!toc_counter)
216       /* no, so return to sender ;-) */
217       return;
218
219   flush_output ();      /* in case we are writing stdout */
220
221   fprintf (fp, "\n<h2>%s</h2>\n<ul>\n", _("Table of Contents"));
222
223   last_level = toc_entry_alist[0]->level;
224
225   for (i = 0; i < toc_counter; i++)
226     {
227       if (toc_entry_alist[i]->level > last_level)
228         {
229           /* unusual, but it is possible
230              @chapter ...
231              @subsubsection ...      ? */
232           for (k = 0; k < (toc_entry_alist[i]->level-last_level); k++)
233             fputs ("<ul>\n", fp);
234         }
235       else if (toc_entry_alist[i]->level < last_level)
236         {
237           /* @subsubsection ...
238              @chapter ... this IS usual.*/
239           for (k = 0; k < (last_level-toc_entry_alist[i]->level); k++)
240             fputs ("</ul>\n", fp);
241         }
242
243       /* No double entries in TOC.  */
244       if (!(i && strcmp (toc_entry_alist[i]->name,
245                          toc_entry_alist[i-1]->name) == 0))
246         {
247           /* each toc entry is a list item.  */
248           fputs ("<li>", fp);
249
250           /* For chapters (only), insert an anchor that the short contents
251              will link to.  */
252           if (toc_entry_alist[i]->level == 0)
253             {
254               char *p = toc_entry_alist[i]->name;
255
256               /* toc_entry_alist[i]->name has the form `foo">bar',
257                  that is, it includes both the node name and anchor
258                  text.  We need to find where `foo', the node name,
259                  ends, and use that in toc_FOO.  */
260               while (*p && *p != '"')
261                 p++;
262               fprintf (fp, "<a name=\"toc_%.*s\"></a>\n    ",
263                        p - toc_entry_alist[i]->name, toc_entry_alist[i]->name);
264             }
265
266           /* Insert link -- to an external file if splitting, or
267              within the current document if not splitting.  */
268           fprintf (fp, "<a href=\"%s#%s</a>\n",
269                    splitting ? toc_entry_alist[i]->html_file : "",
270                    toc_entry_alist[i]->name);
271         }
272
273       last_level = toc_entry_alist[i]->level;
274     }
275
276   /* Go back to start level. */
277   if (toc_entry_alist[0]->level < last_level)
278     for (k = 0; k < (last_level-toc_entry_alist[0]->level); k++)
279       fputs ("</ul>\n", fp);
280
281   fputs ("</ul>\n\n", fp);
282 }
283
284 /* print table of contents in ASCII (--no-headers)
285    May be we should create a new command line switch --ascii ? */
286 static void
287 contents_update_info (fp)
288      FILE *fp;
289 {
290   int i;
291   int k;
292
293   if (!toc_counter)
294       return;
295
296   flush_output ();      /* in case we are writing stdout */
297
298   fprintf (fp, "%s\n%.*s\n\n", _("Table of Contents"),
299            (int) strlen (_("Table of Contents")), lots_of_stars);
300
301   for (i = 0; i < toc_counter; i++)
302     {
303       if (toc_entry_alist[i]->level == 0)
304         fputs ("\n", fp);
305
306       /* indention with two spaces per level, should this
307          changed? */
308       for (k = 0; k < toc_entry_alist[i]->level; k++)
309         fputs ("  ", fp);
310
311       fprintf (fp, "%s\n", toc_entry_alist[i]->name);
312     }
313   fputs ("\n\n", fp);
314 }
315
316 /* shortcontents in HTML; Should this produce a standalone file? */
317 static void
318 shortcontents_update_html (fp)
319      FILE *fp;
320 {
321   int i;
322   char *toc_file;
323
324   /* does exist any toc? */
325   if (!toc_counter)
326     return;
327
328   flush_output ();      /* in case we are writing stdout */
329
330   fprintf (fp, "\n<h2>%s</h2>\n<ul>\n", _("Short Contents"));
331
332   if (contents_filename)
333     toc_file = filename_part (contents_filename);
334
335   for (i = 0; i < toc_counter; i++)
336     {
337       char *name = toc_entry_alist[i]->name;
338
339       if (toc_entry_alist[i]->level == 0)
340         {
341           if (contents_filename)
342             fprintf (fp, "<li><a href=\"%s#toc_%s</a>\n",
343                      splitting ? toc_file : "", name);
344           else
345             fprintf (fp, "<a href=\"%s#%s</a>\n",
346                      splitting ? toc_entry_alist[i]->html_file : "", name);
347         }
348     }
349   fputs ("</ul>\n\n", fp);
350   if (contents_filename)
351     free (toc_file);
352 }
353
354 /* short contents in ASCII (--no-headers).  */
355 static void
356 shortcontents_update_info (fp)
357      FILE *fp;
358 {
359   int i;
360
361   if (!toc_counter)
362       return;
363
364   flush_output ();      /* in case we are writing stdout */
365
366   fprintf (fp, "%s\n%.*s\n\n", _("Short Contents"),
367            (int) strlen (_("Short Contents")), lots_of_stars);
368
369   for (i = 0; i < toc_counter; i++)
370     {
371       if (toc_entry_alist[i]->level == 0)
372         fprintf (fp, "%s\n", toc_entry_alist[i]->name);
373     }
374   fputs ("\n\n", fp);
375 }
376
377 \f
378 static FILE *toc_fp;
379 static char *toc_buf;
380
381 static int
382 rewrite_top (fname, placebo)
383      const char *fname, *placebo;
384 {
385   int idx;
386
387   /* Can't rewrite standard output or the null device.  No point in
388      complaining.  */
389   if (STREQ (fname, "-")
390       || FILENAME_CMP (fname, NULL_DEVICE) == 0
391       || FILENAME_CMP (fname, ALSO_NULL_DEVICE) == 0)
392     return -1;
393
394   toc_buf = find_and_load (fname);
395
396   if (!toc_buf)
397     {
398       fs_error (fname);
399       return -1;
400     }
401
402   idx = search_forward (placebo, 0);
403
404   if (idx < 0)
405     {
406       error (_("%s: TOC should be here, but it was not found"), fname);
407       return -1;
408     }
409
410   toc_fp = fopen (fname, "w");
411   if (!toc_fp)
412     {
413       fs_error (fname);
414       return -1;
415     }
416
417   if (fwrite (toc_buf, 1, idx, toc_fp) != idx)
418     {
419       fs_error (fname);
420       return -1;
421     }
422
423   return idx + strlen (placebo);
424 }
425
426 static void
427 contents_update ()
428 {
429   int cont_idx = rewrite_top (contents_filename, contents_placebo);
430
431   if (cont_idx < 0)
432     return;
433
434   if (html)
435     contents_update_html (toc_fp);
436   else
437     contents_update_info (toc_fp);
438
439   if (fwrite (toc_buf + cont_idx, 1, input_text_length - cont_idx, toc_fp)
440       != input_text_length - cont_idx
441       || fclose (toc_fp) != 0)
442     fs_error (contents_filename);
443 }
444
445 static void
446 shortcontents_update ()
447 {
448   int cont_idx = rewrite_top (shortcontents_filename, shortcontents_placebo);
449
450   if (cont_idx < 0)
451     return;
452
453   if (html)
454     shortcontents_update_html (toc_fp);
455   else
456     shortcontents_update_info (toc_fp);
457
458   if (fwrite (toc_buf + cont_idx, 1, input_text_length - cont_idx - 1, toc_fp)
459       != input_text_length - cont_idx - 1
460       || fclose (toc_fp) != 0)
461     fs_error (shortcontents_filename);
462 }
463
464 void
465 toc_update ()
466 {
467   if (!html && !no_headers)
468     return;
469
470   if (contents_filename)
471     contents_update ();
472   if (shortcontents_filename)
473     shortcontents_update ();
474 }
475
476 void
477 cm_contents (arg)
478      int arg;
479 {
480   if ((html || no_headers) && arg == START)
481     {
482       if (contents_filename)
483         {
484           free (contents_filename);
485           contents_filename = NULL;
486         }
487
488       if (contents_filename && STREQ (contents_filename, "-"))
489         {
490           if (html)
491             contents_update_html (stdout);
492           else
493             contents_update_info (stdout);
494         }
495       else
496         {
497           if (!executing_string && html)
498             html_output_head ();
499           contents_filename = xstrdup (current_output_filename);
500           insert_string (contents_placebo); /* just mark it, for now */
501         }
502     }
503 }
504
505 void
506 cm_shortcontents (arg)
507      int arg;
508 {
509   if ((html || no_headers) && arg == START)
510     {
511       if (shortcontents_filename)
512         {
513           free (shortcontents_filename);
514           shortcontents_filename = NULL;
515         }
516
517       if (shortcontents_filename && STREQ (shortcontents_filename, "-"))
518         {
519           if (html)
520             shortcontents_update_html (stdout);
521           else
522             shortcontents_update_info (stdout);
523         }
524       else
525         {
526           if (!executing_string && html)
527             html_output_head ();
528           shortcontents_filename = xstrdup (current_output_filename);
529           insert_string (shortcontents_placebo); /* just mark it, for now */
530         }
531     }
532 }