xref: /openbsd-src/gnu/usr.bin/texinfo/makeinfo/toc.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /* toc.c -- table of contents handling.
2    $Id: toc.c,v 1.1.1.2 2002/06/10 13:21:22 espie 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 
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 
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 
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 
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 }
533