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