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 "&". 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, "&"); 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