/* toc.c -- table of contents handling.
$Id: toc.c,v 1.12 2008/05/19 18:26:48 karl Exp $
Copyright (C) 1999, 2000, 2001, 2002, 2003, 2007, 2008
Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Originally written by Karl Heinz Marbaise . */
#include "system.h"
#include "makeinfo.h"
#include "cmds.h"
#include "files.h"
#include "macro.h"
#include "node.h"
#include "html.h"
#include "lang.h"
#include "makeinfo.h"
#include "sectioning.h"
#include "toc.h"
#include "xml.h"
/* array of toc entries */
static TOC_ENTRY_ELT **toc_entry_alist = NULL;
/* toc_counter start from 0 ... n for every @chapter, @section ... */
static int toc_counter = 0;
/* Routine to add an entry to the table of contents */
int
toc_add_entry (char *tocname, int level, char *node_name, char *anchor)
{
char *expanded_node, *d;
char *s = NULL;
char *filename = NULL;
if (!node_name)
node_name = "";
/* I assume that xrealloc behaves like xmalloc if toc_entry_alist is
NULL */
toc_entry_alist = xrealloc (toc_entry_alist,
(toc_counter + 1) * sizeof (TOC_ENTRY_ELT *));
toc_entry_alist[toc_counter] = xmalloc (sizeof (TOC_ENTRY_ELT));
if (html)
{
/* We need to insert the expanded node name into the toc, so
that when we eventually output the toc, its which may be needed, and the tocname */
d = xrealloc (d, strlen (d) + strlen (tocname) + 3);
if (!anchor)
strcat (d, "\">");
strcat (d, tocname);
free (tocname); /* it was malloc'ed by substring() */
free (expanded_node);
toc_entry_alist[toc_counter]->name = d;
}
else
toc_entry_alist[toc_counter]->name = tocname;
/* WARNING! The node name saved in containing_node member must
be the node name with _only_ macros expanded (the macros in
the node name are expanded by cm_node when it grabs the name
from the @node directive). Non-macros, like @value, @@ and
other @-commands must NOT be expanded in containing_node,
because toc_find_section_of_node looks up the node name where
they are also unexpanded. You *have* been warned! */
toc_entry_alist[toc_counter]->containing_node = xstrdup (node_name);
toc_entry_alist[toc_counter]->level = level;
toc_entry_alist[toc_counter]->number = toc_counter;
toc_entry_alist[toc_counter]->html_file = filename;
/* have to be done at least */
return toc_counter++;
}
/* Return the name of a chapter/section/subsection etc. that
corresponds to the node NODE. If the node isn't found,
return NULL.
WARNING! This function relies on NODE being unexpanded
except for macros (i.e., @value, @@, and other non-macros
should NOT be expanded), because the containing_node member
stores unexpanded node names.
Note that this function returns the first section whose
containing node is NODE. Thus, they will lose if they use
more than a single chapter structioning command in a node,
or if they have a node without any structuring commands. */
char *
toc_find_section_of_node (char *node)
{
int i;
if (!node)
node = "";
for (i = 0; i < toc_counter; i++)
if (STREQ (node, toc_entry_alist[i]->containing_node))
return toc_entry_alist[i]->name;
return NULL;
}
/* free up memory used by toc entries */
void
toc_free (void)
{
int i;
if (toc_counter)
{
for (i = 0; i < toc_counter; i++)
{
free (toc_entry_alist[i]->name);
free (toc_entry_alist[i]->containing_node);
free (toc_entry_alist[i]);
}
free (toc_entry_alist);
toc_entry_alist = NULL; /* to be sure ;-) */
toc_counter = 0; /* to be absolutley sure ;-) */
}
}
/* Print table of contents in HTML. */
static void
contents_update_html (void)
{
int i;
int k;
int last_level;
/* does exist any toc? */
if (!toc_counter)
/* no, so return to sender ;-) */
return;
add_html_block_elt_args ("\n\n
%s
\n
\n", gdt("Table of Contents"));
last_level = toc_entry_alist[0]->level;
for (i = 0; i < toc_counter; i++)
{
if (toc_entry_alist[i]->level > last_level)
{
/* unusual, but it is possible
@chapter ...
@subsubsection ... ? */
for (k = 0; k < (toc_entry_alist[i]->level-last_level); k++)
add_html_block_elt ("\n");
}
else if (toc_entry_alist[i]->level < last_level)
{
/* @subsubsection ...
@chapter ... this IS usual.*/
for (k = 0; k < (last_level-toc_entry_alist[i]->level); k++)
add_word ("
\n");
}
/* No double entries in TOC. */
if (!(i && strcmp (toc_entry_alist[i]->name,
toc_entry_alist[i-1]->name) == 0))
{
/* each toc entry is a list item. */
add_word ("- ");
/* Insert link -- to an external file if splitting, or
within the current document if not splitting. */
add_word ("level == 0)
{
char *p = toc_entry_alist[i]->name;
/* toc_entry_alist[i]->name has the form `foo">bar',
that is, it includes both the node name and anchor
text. We need to find where `foo', the node name,
ends, and use that in toc_FOO. */
while (*p && *p != '"')
p++;
add_word_args ("name=\"toc_%.*s\" ",
p - toc_entry_alist[i]->name, toc_entry_alist[i]->name);
/* save the link if necessary */
if (internal_links_stream)
{
fprintf (internal_links_stream, "%s#toc_%.*s\ttoc\t%s\n",
splitting ? toc_entry_alist[i]->html_file : "",
p - toc_entry_alist[i]->name, toc_entry_alist[i]->name,
p + 2);
}
}
add_word_args ("href=\"%s#%s\n",
splitting ? toc_entry_alist[i]->html_file : "",
toc_entry_alist[i]->name);
}
last_level = toc_entry_alist[i]->level;
}
/* Go back to start level. */
if (toc_entry_alist[0]->level < last_level)
for (k = 0; k < (last_level-toc_entry_alist[0]->level); k++)
add_word ("
\n");
add_word ("\n
\n\n");
}
/* print table of contents in ASCII (--no-headers)
May be we should create a new command line switch --ascii ? */
static void
contents_update_info (void)
{
int i;
int k;
if (!toc_counter)
return;
insert_string ((char *) gdt("Table of Contents"));
insert ('\n');
for (i = 0; i < strlen (gdt("Table of Contents")); i++)
insert ('*');
insert_string ("\n\n");
for (i = 0; i < toc_counter; i++)
{
if (toc_entry_alist[i]->level == 0)
add_char ('\n');
/* indention with two spaces per level, should this
changed? */
for (k = 0; k < toc_entry_alist[i]->level; k++)
insert_string (" ");
insert_string (toc_entry_alist[i]->name);
insert ('\n');
}
insert_string ("\n\n");
}
/* shortcontents in HTML; Should this produce a standalone file? */
static void
shortcontents_update_html (char *contents_filename)
{
int i;
char *toc_file = NULL;
/* does exist any toc? */
if (!toc_counter)
return;
add_html_block_elt_args ("\n\n
%s
\n
\n", gdt("Short Contents"));
if (contents_filename)
toc_file = filename_part (contents_filename);
for (i = 0; i < toc_counter; i++)
{
char *name = toc_entry_alist[i]->name;
if (toc_entry_alist[i]->level == 0)
{
if (contents_filename)
add_word_args (" \n",
splitting ? toc_file : "", name);
else
add_word_args ("\n",
splitting ? toc_entry_alist[i]->html_file : "", name);
}
}
add_word ("
\n
\n\n");
if (contents_filename)
free (toc_file);
}
/* short contents in ASCII (--no-headers). */
static void
shortcontents_update_info (void)
{
int i;
if (!toc_counter)
return;
insert_string ((char *) gdt("Short Contents"));
insert ('\n');
for (i = 0; i < strlen (gdt("Short Contents")); i++)
insert ('*');
insert_string ("\n\n");
for (i = 0; i < toc_counter; i++)
{
if (toc_entry_alist[i]->level == 0)
{
insert_string (toc_entry_alist[i]->name);
insert ('\n');
}
}
insert_string ("\n\n");
}
void
cm_contents (int arg)
{
/* the file where we found the @contents directive */
static char *contents_filename;
/* No need to mess with delayed stuff for XML and Docbook. */
if (xml)
{
if (arg == START)
{
int elt = STREQ (command, "contents") ? CONTENTS : SHORTCONTENTS;
xml_insert_element (elt, START);
xml_insert_element (elt, END);
}
}
else if (!handling_delayed_writes)
{
register_delayed_write (STREQ (command, "contents")
? "@contents" : "@shortcontents");
if (html && STREQ (command, "contents"))
{
if (contents_filename)
free (contents_filename);
contents_filename = xstrdup (current_output_filename);
}
}
else if (html)
STREQ (command, "contents")
? contents_update_html () : shortcontents_update_html (contents_filename);
else if (no_headers)
STREQ (command, "contents")
? contents_update_info () : shortcontents_update_info ();
}