xref: /openbsd/gnu/usr.bin/texinfo/makeinfo/toc.c (revision a1acfa9b)
1 /* toc.c -- table of contents handling.
2    $Id: toc.c,v 1.1.1.3 2006/07/17 16:03:48 espie Exp $
3 
4    Copyright (C) 1999, 2000, 2001, 2002, 2003 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    Originally 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 #include "xml.h"
34 
35 /* array of toc entries */
36 static TOC_ENTRY_ELT **toc_entry_alist = NULL;
37 
38 /* toc_counter start from 0 ... n for every @chapter, @section ... */
39 static int toc_counter = 0;
40 
41 /* Routine to add an entry to the table of contents */
42 int
toc_add_entry(char * tocname,int level,char * node_name,char * anchor)43 toc_add_entry (char *tocname, int level, char *node_name, char *anchor)
44 {
45   char *tocname_and_node, *expanded_node, *d;
46   char *s = NULL;
47   char *filename = NULL;
48 
49   if (!node_name)
50     node_name = "";
51 
52   /* I assume that xrealloc behaves like xmalloc if toc_entry_alist is
53      NULL */
54   toc_entry_alist = xrealloc (toc_entry_alist,
55                               (toc_counter + 1) * sizeof (TOC_ENTRY_ELT *));
56 
57   toc_entry_alist[toc_counter] = xmalloc (sizeof (TOC_ENTRY_ELT));
58 
59   if (html)
60     {
61       /* We need to insert the expanded node name into the toc, so
62          that when we eventually output the toc, its <a ref= link will
63          point to the <a name= tag created by cm_node in the navigation
64          bar.  We cannot expand the containing_node member, for the
65          reasons explained in the WARNING below.  We also cannot wait
66          with the node name expansion until the toc is actually output,
67          since by that time the macro definitions may have been changed.
68          So instead we store in the tocname member the expanded node
69          name and the toc name concatenated together (with the necessary
70          html markup), since that's how they are output.  */
71       if (!anchor)
72         s = expanded_node = expand_node_name (node_name);
73       else
74         expanded_node = anchor;
75       if (splitting)
76 	{
77 	  if (!anchor)
78 	    filename = nodename_to_filename (expanded_node);
79 	  else
80 	    filename = filename_part (current_output_filename);
81 	}
82       /* Sigh...  Need to HTML-escape the expanded node name like
83          add_anchor_name does, except that we are not writing this to
84          the output, so can't use add_anchor_name...  */
85       /* The factor 5 in the next allocation is because the maximum
86          expansion of HTML-escaping is for the & character, which is
87          output as "&amp;".  2 is for "> that separates node from tocname.  */
88       d = tocname_and_node = (char *)xmalloc (2 + 5 * strlen (expanded_node)
89                                               + strlen (tocname) + 1);
90       if (!anchor)
91         {
92           for (; *s; s++)
93             {
94               if (cr_or_whitespace (*s))
95                 *d++ = '-';
96               else if (! URL_SAFE_CHAR (*s))
97                 {
98                   sprintf (d, "_00%x", (unsigned char) *s);
99                   /* do this manually since sprintf returns char * on
100                      SunOS 4 and other old systems.  */
101                   while (*d)
102                     d++;
103                 }
104               else
105                 *d++ = *s;
106             }
107           strcpy (d, "\">");
108         }
109       else
110         /* Section outside any node, they provided explicit anchor.  */
111         strcpy (d, anchor);
112       strcat (d, tocname);
113       free (tocname);       /* it was malloc'ed by substring() */
114       free (expanded_node);
115       toc_entry_alist[toc_counter]->name = tocname_and_node;
116     }
117   else
118     toc_entry_alist[toc_counter]->name = tocname;
119   /* WARNING!  The node name saved in containing_node member must
120      be the node name with _only_ macros expanded (the macros in
121      the node name are expanded by cm_node when it grabs the name
122      from the @node directive).  Non-macros, like @value, @@ and
123      other @-commands must NOT be expanded in containing_node,
124      because toc_find_section_of_node looks up the node name where
125      they are also unexpanded.  You *have* been warned!  */
126   toc_entry_alist[toc_counter]->containing_node = xstrdup (node_name);
127   toc_entry_alist[toc_counter]->level = level;
128   toc_entry_alist[toc_counter]->number = toc_counter;
129   toc_entry_alist[toc_counter]->html_file = filename;
130 
131   /* have to be done at least */
132   return toc_counter++;
133 }
134 
135 /* Return the name of a chapter/section/subsection etc. that
136    corresponds to the node NODE.  If the node isn't found,
137    return NULL.
138 
139    WARNING!  This function relies on NODE being unexpanded
140    except for macros (i.e., @value, @@, and other non-macros
141    should NOT be expanded), because the containing_node member
142    stores unexpanded node names.
143 
144    Note that this function returns the first section whose
145    containing node is NODE.  Thus, they will lose if they use
146    more than a single chapter structioning command in a node,
147    or if they have a node without any structuring commands.  */
148 char *
toc_find_section_of_node(char * node)149 toc_find_section_of_node (char *node)
150 {
151   int i;
152 
153   if (!node)
154     node = "";
155   for (i = 0; i < toc_counter; i++)
156     if (STREQ (node, toc_entry_alist[i]->containing_node))
157       return toc_entry_alist[i]->name;
158 
159   return NULL;
160 }
161 
162 /* free up memory used by toc entries */
163 void
toc_free(void)164 toc_free (void)
165 {
166   int i;
167 
168   if (toc_counter)
169     {
170       for (i = 0; i < toc_counter; i++)
171         {
172           free (toc_entry_alist[i]->name);
173           free (toc_entry_alist[i]->containing_node);
174           free (toc_entry_alist[i]);
175         }
176 
177       free (toc_entry_alist);
178       toc_entry_alist = NULL; /* to be sure ;-) */
179       toc_counter = 0; /* to be absolutley sure ;-) */
180     }
181 }
182 
183 /* Print table of contents in HTML.  */
184 
185 static void
contents_update_html(void)186 contents_update_html (void)
187 {
188   int i;
189   int k;
190   int last_level;
191 
192   /* does exist any toc? */
193   if (!toc_counter)
194       /* no, so return to sender ;-) */
195       return;
196 
197   add_html_block_elt_args ("\n<div class=\"contents\">\n<h2>%s</h2>\n<ul>\n", _("Table of Contents"));
198 
199   last_level = toc_entry_alist[0]->level;
200 
201   for (i = 0; i < toc_counter; i++)
202     {
203       if (toc_entry_alist[i]->level > last_level)
204         {
205           /* unusual, but it is possible
206              @chapter ...
207              @subsubsection ...      ? */
208           for (k = 0; k < (toc_entry_alist[i]->level-last_level); k++)
209             add_html_block_elt ("<ul>\n");
210         }
211       else if (toc_entry_alist[i]->level < last_level)
212         {
213           /* @subsubsection ...
214              @chapter ... this IS usual.*/
215           for (k = 0; k < (last_level-toc_entry_alist[i]->level); k++)
216             add_word ("</li></ul>\n");
217         }
218 
219       /* No double entries in TOC.  */
220       if (!(i && strcmp (toc_entry_alist[i]->name,
221 			 toc_entry_alist[i-1]->name) == 0))
222         {
223           /* each toc entry is a list item.  */
224           add_word ("<li>");
225 
226           /* Insert link -- to an external file if splitting, or
227              within the current document if not splitting.  */
228 	  add_word ("<a ");
229           /* For chapters (only), insert an anchor that the short contents
230              will link to.  */
231           if (toc_entry_alist[i]->level == 0)
232 	    {
233 	      char *p = toc_entry_alist[i]->name;
234 
235 	      /* toc_entry_alist[i]->name has the form `foo">bar',
236 		 that is, it includes both the node name and anchor
237 		 text.  We need to find where `foo', the node name,
238 		 ends, and use that in toc_FOO.  */
239 	      while (*p && *p != '"')
240 		p++;
241 	      add_word_args ("name=\"toc_%.*s\" ",
242 		       p - toc_entry_alist[i]->name, toc_entry_alist[i]->name);
243 	    }
244 	  add_word_args ("href=\"%s#%s</a>\n",
245 		   splitting ? toc_entry_alist[i]->html_file : "",
246 		   toc_entry_alist[i]->name);
247         }
248 
249       last_level = toc_entry_alist[i]->level;
250     }
251 
252   /* Go back to start level. */
253   if (toc_entry_alist[0]->level < last_level)
254     for (k = 0; k < (last_level-toc_entry_alist[0]->level); k++)
255       add_word ("</li></ul>\n");
256 
257   add_word ("</li></ul>\n</div>\n\n");
258 }
259 
260 /* print table of contents in ASCII (--no-headers)
261    May be we should create a new command line switch --ascii ? */
262 static void
contents_update_info(void)263 contents_update_info (void)
264 {
265   int i;
266   int k;
267 
268   if (!toc_counter)
269       return;
270 
271   insert_string ((char *) _("Table of Contents"));
272   insert ('\n');
273   for (i = 0; i < strlen (_("Table of Contents")); i++)
274     insert ('*');
275   insert_string ("\n\n");
276 
277   for (i = 0; i < toc_counter; i++)
278     {
279       if (toc_entry_alist[i]->level == 0)
280         add_char ('\n');
281 
282       /* indention with two spaces per level, should this
283          changed? */
284       for (k = 0; k < toc_entry_alist[i]->level; k++)
285         insert_string ("  ");
286 
287       insert_string (toc_entry_alist[i]->name);
288       insert ('\n');
289     }
290   insert_string ("\n\n");
291 }
292 
293 /* shortcontents in HTML; Should this produce a standalone file? */
294 static void
shortcontents_update_html(char * contents_filename)295 shortcontents_update_html (char *contents_filename)
296 {
297   int i;
298   char *toc_file = NULL;
299 
300   /* does exist any toc? */
301   if (!toc_counter)
302     return;
303 
304   add_html_block_elt_args ("\n<div class=\"shortcontents\">\n<h2>%s</h2>\n<ul>\n", _("Short Contents"));
305 
306   if (contents_filename)
307     toc_file = filename_part (contents_filename);
308 
309   for (i = 0; i < toc_counter; i++)
310     {
311       char *name = toc_entry_alist[i]->name;
312 
313       if (toc_entry_alist[i]->level == 0)
314 	{
315 	  if (contents_filename)
316 	    add_word_args ("<li><a href=\"%s#toc_%s</a></li>\n",
317 		     splitting ? toc_file : "", name);
318 	  else
319 	    add_word_args ("<a href=\"%s#%s</a>\n",
320 		     splitting ? toc_entry_alist[i]->html_file : "", name);
321 	}
322     }
323   add_word ("</ul>\n</div>\n\n");
324   if (contents_filename)
325     free (toc_file);
326 }
327 
328 /* short contents in ASCII (--no-headers).  */
329 static void
shortcontents_update_info(void)330 shortcontents_update_info (void)
331 {
332   int i;
333 
334   if (!toc_counter)
335       return;
336 
337   insert_string ((char *) _("Short Contents"));
338   insert ('\n');
339   for (i = 0; i < strlen (_("Short Contents")); i++)
340     insert ('*');
341   insert_string ("\n\n");
342 
343   for (i = 0; i < toc_counter; i++)
344     {
345       if (toc_entry_alist[i]->level == 0)
346         {
347           insert_string (toc_entry_alist[i]->name);
348           insert ('\n');
349         }
350     }
351   insert_string ("\n\n");
352 }
353 
354 void
cm_contents(int arg)355 cm_contents (int arg)
356 {
357   /* the file where we found the @contents directive */
358   static char *contents_filename;
359 
360   /* No need to mess with delayed stuff for XML and Docbook.  */
361   if (xml)
362     {
363       if (arg == START)
364         {
365           int elt = STREQ (command, "contents") ? CONTENTS : SHORTCONTENTS;
366           xml_insert_element (elt, START);
367           xml_insert_element (elt, END);
368         }
369     }
370   else if (!handling_delayed_writes)
371     {
372       register_delayed_write (STREQ (command, "contents")
373           ? "@contents" : "@shortcontents");
374 
375       if (html && STREQ (command, "contents"))
376         {
377           if (contents_filename)
378             free (contents_filename);
379           contents_filename = xstrdup (current_output_filename);
380         }
381     }
382   else if (html)
383     STREQ (command, "contents")
384       ? contents_update_html () : shortcontents_update_html (contents_filename);
385   else if (no_headers)
386     STREQ (command, "contents")
387       ? contents_update_info () : shortcontents_update_info ();
388 }
389