| 1 | /* toc.c -- table of contents handling. | 
|---|
| 2 | $Id: toc.c,v 1.6 2004/04/11 17:56:47 karl 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 |  | 
|---|
| 42 | /* Routine to add an entry to the table of contents */ | 
|---|
| 43 | int | 
|---|
| 44 | toc_add_entry (char *tocname, int level, char *node_name, char *anchor) | 
|---|
| 45 | { | 
|---|
| 46 | char *tocname_and_node, *expanded_node, *d; | 
|---|
| 47 | char *s = NULL; | 
|---|
| 48 | char *filename = NULL; | 
|---|
| 49 |  | 
|---|
| 50 | if (!node_name) | 
|---|
| 51 | node_name = ""; | 
|---|
| 52 |  | 
|---|
| 53 | /* I assume that xrealloc behaves like xmalloc if toc_entry_alist is | 
|---|
| 54 | NULL */ | 
|---|
| 55 | toc_entry_alist = xrealloc (toc_entry_alist, | 
|---|
| 56 | (toc_counter + 1) * sizeof (TOC_ENTRY_ELT *)); | 
|---|
| 57 |  | 
|---|
| 58 | toc_entry_alist[toc_counter] = xmalloc (sizeof (TOC_ENTRY_ELT)); | 
|---|
| 59 |  | 
|---|
| 60 | if (html) | 
|---|
| 61 | { | 
|---|
| 62 | /* We need to insert the expanded node name into the toc, so | 
|---|
| 63 | that when we eventually output the toc, its <a ref= link will | 
|---|
| 64 | point to the <a name= tag created by cm_node in the navigation | 
|---|
| 65 | bar.  We cannot expand the containing_node member, for the | 
|---|
| 66 | reasons explained in the WARNING below.  We also cannot wait | 
|---|
| 67 | with the node name expansion until the toc is actually output, | 
|---|
| 68 | since by that time the macro definitions may have been changed. | 
|---|
| 69 | So instead we store in the tocname member the expanded node | 
|---|
| 70 | name and the toc name concatenated together (with the necessary | 
|---|
| 71 | html markup), since that's how they are output.  */ | 
|---|
| 72 | if (!anchor) | 
|---|
| 73 | s = expanded_node = expand_node_name (node_name); | 
|---|
| 74 | else | 
|---|
| 75 | expanded_node = anchor; | 
|---|
| 76 | if (splitting) | 
|---|
| 77 | { | 
|---|
| 78 | if (!anchor) | 
|---|
| 79 | filename = nodename_to_filename (expanded_node); | 
|---|
| 80 | else | 
|---|
| 81 | filename = filename_part (current_output_filename); | 
|---|
| 82 | } | 
|---|
| 83 | /* Sigh...  Need to HTML-escape the expanded node name like | 
|---|
| 84 | add_anchor_name does, except that we are not writing this to | 
|---|
| 85 | the output, so can't use add_anchor_name...  */ | 
|---|
| 86 | /* The factor 5 in the next allocation is because the maximum | 
|---|
| 87 | expansion of HTML-escaping is for the & character, which is | 
|---|
| 88 | output as "&".  2 is for "> that separates node from tocname.  */ | 
|---|
| 89 | d = tocname_and_node = (char *)xmalloc (2 + 5 * strlen (expanded_node) | 
|---|
| 90 | + strlen (tocname) + 1); | 
|---|
| 91 | if (!anchor) | 
|---|
| 92 | { | 
|---|
| 93 | for (; *s; s++) | 
|---|
| 94 | { | 
|---|
| 95 | if (cr_or_whitespace (*s)) | 
|---|
| 96 | *d++ = '-'; | 
|---|
| 97 | else if (! URL_SAFE_CHAR (*s)) | 
|---|
| 98 | { | 
|---|
| 99 | sprintf (d, "_00%x", (unsigned char) *s); | 
|---|
| 100 | /* do this manually since sprintf returns char * on | 
|---|
| 101 | SunOS 4 and other old systems.  */ | 
|---|
| 102 | while (*d) | 
|---|
| 103 | d++; | 
|---|
| 104 | } | 
|---|
| 105 | else | 
|---|
| 106 | *d++ = *s; | 
|---|
| 107 | } | 
|---|
| 108 | strcpy (d, "\">"); | 
|---|
| 109 | } | 
|---|
| 110 | else | 
|---|
| 111 | /* Section outside any node, they provided explicit anchor.  */ | 
|---|
| 112 | strcpy (d, anchor); | 
|---|
| 113 | strcat (d, tocname); | 
|---|
| 114 | free (tocname);       /* it was malloc'ed by substring() */ | 
|---|
| 115 | free (expanded_node); | 
|---|
| 116 | toc_entry_alist[toc_counter]->name = tocname_and_node; | 
|---|
| 117 | } | 
|---|
| 118 | else | 
|---|
| 119 | toc_entry_alist[toc_counter]->name = tocname; | 
|---|
| 120 | /* WARNING!  The node name saved in containing_node member must | 
|---|
| 121 | be the node name with _only_ macros expanded (the macros in | 
|---|
| 122 | the node name are expanded by cm_node when it grabs the name | 
|---|
| 123 | from the @node directive).  Non-macros, like @value, @@ and | 
|---|
| 124 | other @-commands must NOT be expanded in containing_node, | 
|---|
| 125 | because toc_find_section_of_node looks up the node name where | 
|---|
| 126 | they are also unexpanded.  You *have* been warned!  */ | 
|---|
| 127 | toc_entry_alist[toc_counter]->containing_node = xstrdup (node_name); | 
|---|
| 128 | toc_entry_alist[toc_counter]->level = level; | 
|---|
| 129 | toc_entry_alist[toc_counter]->number = toc_counter; | 
|---|
| 130 | toc_entry_alist[toc_counter]->html_file = filename; | 
|---|
| 131 |  | 
|---|
| 132 | /* have to be done at least */ | 
|---|
| 133 | return toc_counter++; | 
|---|
| 134 | } | 
|---|
| 135 |  | 
|---|
| 136 | /* Return the name of a chapter/section/subsection etc. that | 
|---|
| 137 | corresponds to the node NODE.  If the node isn't found, | 
|---|
| 138 | return NULL. | 
|---|
| 139 |  | 
|---|
| 140 | WARNING!  This function relies on NODE being unexpanded | 
|---|
| 141 | except for macros (i.e., @value, @@, and other non-macros | 
|---|
| 142 | should NOT be expanded), because the containing_node member | 
|---|
| 143 | stores unexpanded node names. | 
|---|
| 144 |  | 
|---|
| 145 | Note that this function returns the first section whose | 
|---|
| 146 | containing node is NODE.  Thus, they will lose if they use | 
|---|
| 147 | more than a single chapter structioning command in a node, | 
|---|
| 148 | or if they have a node without any structuring commands.  */ | 
|---|
| 149 | char * | 
|---|
| 150 | toc_find_section_of_node (char *node) | 
|---|
| 151 | { | 
|---|
| 152 | int i; | 
|---|
| 153 |  | 
|---|
| 154 | if (!node) | 
|---|
| 155 | node = ""; | 
|---|
| 156 | for (i = 0; i < toc_counter; i++) | 
|---|
| 157 | if (STREQ (node, toc_entry_alist[i]->containing_node)) | 
|---|
| 158 | return toc_entry_alist[i]->name; | 
|---|
| 159 |  | 
|---|
| 160 | return NULL; | 
|---|
| 161 | } | 
|---|
| 162 |  | 
|---|
| 163 | /* free up memory used by toc entries */ | 
|---|
| 164 | void | 
|---|
| 165 | toc_free (void) | 
|---|
| 166 | { | 
|---|
| 167 | int i; | 
|---|
| 168 |  | 
|---|
| 169 | if (toc_counter) | 
|---|
| 170 | { | 
|---|
| 171 | for (i = 0; i < toc_counter; i++) | 
|---|
| 172 | { | 
|---|
| 173 | free (toc_entry_alist[i]->name); | 
|---|
| 174 | free (toc_entry_alist[i]->containing_node); | 
|---|
| 175 | free (toc_entry_alist[i]); | 
|---|
| 176 | } | 
|---|
| 177 |  | 
|---|
| 178 | free (toc_entry_alist); | 
|---|
| 179 | toc_entry_alist = NULL; /* to be sure ;-) */ | 
|---|
| 180 | toc_counter = 0; /* to be absolutley sure ;-) */ | 
|---|
| 181 | } | 
|---|
| 182 | } | 
|---|
| 183 |  | 
|---|
| 184 |  | 
|---|
| 185 | /* Print table of contents in HTML.  */ | 
|---|
| 186 |  | 
|---|
| 187 | static void | 
|---|
| 188 | contents_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 ? */ | 
|---|
| 264 | static void | 
|---|
| 265 | contents_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? */ | 
|---|
| 296 | static void | 
|---|
| 297 | shortcontents_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).  */ | 
|---|
| 331 | static void | 
|---|
| 332 | shortcontents_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 |  | 
|---|
| 356 | void | 
|---|
| 357 | cm_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 | } | 
|---|