1 /* multi.c -- multiple-column tables (@multitable) for makeinfo. 2 $Id: multi.c,v 1.7 2006/07/17 16:12:36 espie Exp $ 3 4 Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004 Free Software 5 Foundation, Inc. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2, or (at your option) 10 any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program; if not, write to the Free Software Foundation, 19 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 21 Originally written by phr@gnu.org (Paul Rubin). */ 22 23 #include "system.h" 24 #include "cmds.h" 25 #include "insertion.h" 26 #include "makeinfo.h" 27 #include "multi.h" 28 #include "xml.h" 29 30 #define MAXCOLS 100 /* remove this limit later @@ */ 31 32 33 /* 34 * Output environments. This is a hack grafted onto existing 35 * structure. The "output environment" used to consist of the 36 * global variables `output_paragraph', `fill_column', etc. 37 * Routines like add_char would manipulate these variables. 38 * 39 * Now, when formatting a multitable, we maintain separate environments 40 * for each column. That way we can build up the columns separately 41 * and write them all out at once. The "current" output environment" 42 * is still kept in those global variables, so that the old output 43 * routines don't have to change. But we provide routines to save 44 * and restore these variables in an "environment table". The 45 * `select_output_environment' function switches from one output 46 * environment to another. 47 * 48 * Environment #0 (i.e., element #0 of the table) is the regular 49 * environment that is used when we're not formatting a multitable. 50 * 51 * Environment #N (where N = 1,2,3,...) is the env. for column #N of 52 * the table, when a multitable is active. 53 */ 54 55 /* contents of an output environment */ 56 /* some more vars may end up being needed here later @@ */ 57 struct env 58 { 59 unsigned char *output_paragraph; 60 int output_paragraph_offset; 61 int meta_char_pos; 62 int output_column; 63 int paragraph_is_open; 64 int current_indent; 65 int fill_column; 66 } envs[MAXCOLS]; /* the environment table */ 67 68 /* index in environment table of currently selected environment */ 69 static int current_env_no; 70 71 /* current column number */ 72 static int current_column_no; 73 74 /* We need to make a difference between template based widths and 75 @columnfractions for HTML tables' sake. Sigh. */ 76 static int seen_column_fractions; 77 78 /* column number of last column in current multitable */ 79 static int last_column; 80 81 /* flags indicating whether horizontal and vertical separators need 82 to be drawn, separating rows and columns in the current multitable. */ 83 static int hsep, vsep; 84 85 /* whether this is the first row. */ 86 static int first_row; 87 88 /* Called to handle a {...} template on the @multitable line. 89 We're at the { and our first job is to find the matching }; as a side 90 effect, we change *PARAMS to point to after it. Our other job is to 91 expand the template text and return the width of that string. */ 92 static unsigned 93 find_template_width (char **params) 94 { 95 char *template, *xtemplate; 96 unsigned len; 97 char *start = *params; 98 int brace_level = 0; 99 100 /* The first character should be a {. */ 101 if (!params || !*params || **params != '{') 102 { 103 line_error ("find_template width internal error: passed %s", 104 params ? *params : "null"); 105 return 0; 106 } 107 108 do 109 { 110 if (**params == '{' && (*params == start || (*params)[-1] != '@')) 111 brace_level++; 112 else if (**params == '}' && (*params)[-1] != '@') 113 brace_level--; 114 else if (**params == 0) 115 { 116 line_error (_("Missing } in @multitable template")); 117 return 0; 118 } 119 (*params)++; 120 } 121 while (brace_level > 0); 122 123 template = substring (start + 1, *params - 1); /* omit braces */ 124 xtemplate = expansion (template, 0); 125 len = strlen (xtemplate); 126 127 free (template); 128 free (xtemplate); 129 130 return len; 131 } 132 133 /* Direct current output to environment number N. Used when 134 switching work from one column of a multitable to the next. 135 Returns previous environment number. */ 136 static int 137 select_output_environment (int n) 138 { 139 struct env *e = &envs[current_env_no]; 140 int old_env_no = current_env_no; 141 142 /* stash current env info from global vars into the old environment */ 143 e->output_paragraph = output_paragraph; 144 e->output_paragraph_offset = output_paragraph_offset; 145 e->meta_char_pos = meta_char_pos; 146 e->output_column = output_column; 147 e->paragraph_is_open = paragraph_is_open; 148 e->current_indent = current_indent; 149 e->fill_column = fill_column; 150 151 /* now copy new environment into global vars */ 152 current_env_no = n; 153 e = &envs[current_env_no]; 154 output_paragraph = e->output_paragraph; 155 output_paragraph_offset = e->output_paragraph_offset; 156 meta_char_pos = e->meta_char_pos; 157 output_column = e->output_column; 158 paragraph_is_open = e->paragraph_is_open; 159 current_indent = e->current_indent; 160 fill_column = e->fill_column; 161 return old_env_no; 162 } 163 164 /* Initialize environment number ENV_NO, of width WIDTH. 165 The idea is that we're going to use one environment for each column of 166 a multitable, so we can build them up separately and print them 167 all out at the end. */ 168 static int 169 setup_output_environment (int env_no, int width) 170 { 171 int old_env = select_output_environment (env_no); 172 173 /* clobber old environment and set width of new one */ 174 init_paragraph (); 175 176 /* make our change */ 177 fill_column = width; 178 179 /* Save new environment and restore previous one. */ 180 select_output_environment (old_env); 181 182 return env_no; 183 } 184 185 /* Read the parameters for a multitable from the current command 186 line, save the parameters away, and return the 187 number of columns. */ 188 static int 189 setup_multitable_parameters (void) 190 { 191 char *params = insertion_stack->item_function; 192 int nchars; 193 float columnfrac; 194 char command[200]; /* xx no fixed limits */ 195 int i = 1; 196 197 /* We implement @hsep and @vsep even though TeX doesn't. 198 We don't get mixing of @columnfractions and templates right, 199 but TeX doesn't either. */ 200 hsep = vsep = 0; 201 202 /* Assume no @columnfractions per default. */ 203 seen_column_fractions = 0; 204 205 while (*params) { 206 while (whitespace (*params)) 207 params++; 208 209 if (*params == '@') { 210 sscanf (params, "%199s", command); 211 nchars = strlen (command); 212 params += nchars; 213 if (strcmp (command, "@hsep") == 0) 214 hsep++; 215 else if (strcmp (command, "@vsep") == 0) 216 vsep++; 217 else if (strcmp (command, "@columnfractions") == 0) { 218 seen_column_fractions = 1; 219 /* Clobber old environments and create new ones, starting at #1. 220 Environment #0 is the normal output, so don't mess with it. */ 221 for ( ; i <= MAXCOLS; i++) { 222 if (sscanf (params, "%f", &columnfrac) < 1) 223 goto done; 224 /* Unfortunately, can't use %n since m68k-hp-bsd libc (at least) 225 doesn't support it. So skip whitespace (preceding the 226 number) and then non-whitespace (the number). */ 227 while (*params && (*params == ' ' || *params == '\t')) 228 params++; 229 /* Hmm, but what about @columnfractions 3foo. Oh well, 230 it's invalid input anyway. */ 231 while (*params && *params != ' ' && *params != '\t' 232 && *params != '\n' && *params != '@') 233 params++; 234 235 { 236 /* For html/xml/docbook, translate fractions into integer 237 percentages, adding .005 to avoid rounding problems. For 238 info, we want the character width. */ 239 int width = xml || html ? (columnfrac + .005) * 100 240 : (columnfrac * (fill_column - current_indent) + .5); 241 setup_output_environment (i, width); 242 } 243 } 244 } 245 246 } else if (*params == '{') { 247 unsigned template_width = find_template_width (¶ms); 248 249 /* This gives us two spaces between columns. Seems reasonable. 250 How to take into account current_indent here? */ 251 setup_output_environment (i++, template_width + 2); 252 253 } else { 254 warning (_("ignoring stray text `%s' after @multitable"), params); 255 break; 256 } 257 } 258 259 done: 260 flush_output (); 261 inhibit_output_flushing (); 262 263 last_column = i - 1; 264 return last_column; 265 } 266 267 /* Output a row. Calls insert, but also flushes the buffered output 268 when we see a newline, since in multitable every line is a separate 269 paragraph. */ 270 static void 271 out_char (int ch) 272 { 273 if (html || xml) 274 add_char (ch); 275 else 276 { 277 int env = select_output_environment (0); 278 insert (ch); 279 if (ch == '\n') 280 { 281 uninhibit_output_flushing (); 282 flush_output (); 283 inhibit_output_flushing (); 284 } 285 select_output_environment (env); 286 } 287 } 288 289 290 static void 291 draw_horizontal_separator (void) 292 { 293 int i, j, s; 294 295 if (html) 296 { 297 add_word ("<hr>"); 298 return; 299 } 300 if (xml) 301 return; 302 303 for (s = 0; s < envs[0].current_indent; s++) 304 out_char (' '); 305 if (vsep) 306 out_char ('+'); 307 for (i = 1; i <= last_column; i++) { 308 for (j = 0; j <= envs[i].fill_column; j++) 309 out_char ('-'); 310 if (vsep) 311 out_char ('+'); 312 } 313 out_char (' '); 314 out_char ('\n'); 315 } 316 317 318 /* multitable strategy: 319 for each item { 320 for each column in an item { 321 initialize a new paragraph 322 do ordinary formatting into the new paragraph 323 save the paragraph away 324 repeat if there are more paragraphs in the column 325 } 326 dump out the saved paragraphs and free the storage 327 } 328 329 For HTML we construct a simple HTML 3.2 table with <br>s inserted 330 to help non-tables browsers. `@item' inserts a <tr> and `@tab' 331 inserts <td>; we also try to close <tr>. The only real 332 alternative is to rely on the info formatting engine and present 333 preformatted text. */ 334 335 void 336 do_multitable (void) 337 { 338 int ncolumns; 339 340 if (multitable_active) 341 { 342 line_error ("Multitables cannot be nested"); 343 return; 344 } 345 346 close_single_paragraph (); 347 348 if (xml) 349 { 350 xml_no_para = 1; 351 if (output_paragraph[output_paragraph_offset-1] == '\n') 352 output_paragraph_offset--; 353 } 354 355 /* scan the current item function to get the field widths 356 and number of columns, and set up the output environment list 357 accordingly. */ 358 ncolumns = setup_multitable_parameters (); 359 first_row = 1; 360 361 /* <p> for non-tables browsers. @multitable implicitly ends the 362 current paragraph, so this is ok. */ 363 if (html) 364 add_html_block_elt ("<p><table summary=\"\">"); 365 /* else if (docbook)*/ /* 05-08 */ 366 else if (xml) 367 { 368 int *widths = xmalloc (ncolumns * sizeof (int)); 369 int i; 370 for (i=0; i<ncolumns; i++) 371 widths[i] = envs[i+1].fill_column; 372 xml_begin_multitable (ncolumns, widths); 373 free (widths); 374 } 375 376 if (hsep) 377 draw_horizontal_separator (); 378 379 /* The next @item command will direct stdout into the first column 380 and start processing. @tab will then switch to the next column, 381 and @item will flush out the saved output and return to the first 382 column. Environment #1 is the first column. (Environment #0 is 383 the normal output) */ 384 385 ++multitable_active; 386 } 387 388 /* advance to the next environment number */ 389 static void 390 nselect_next_environment (void) 391 { 392 if (current_env_no >= last_column) { 393 line_error (_("Too many columns in multitable item (max %d)"), last_column); 394 return; 395 } 396 select_output_environment (current_env_no + 1); 397 } 398 399 400 /* do anything needed at the beginning of processing a 401 multitable column. */ 402 static void 403 init_column (void) 404 { 405 /* don't indent 1st paragraph in the item */ 406 cm_noindent (); 407 408 /* throw away possible whitespace after @item or @tab command */ 409 skip_whitespace (); 410 } 411 412 static void 413 output_multitable_row (void) 414 { 415 /* offset in the output paragraph of the next char needing 416 to be output for that column. */ 417 int offset[MAXCOLS]; 418 int i, j, s, remaining; 419 int had_newline = 0; 420 421 for (i = 0; i <= last_column; i++) 422 offset[i] = 0; 423 424 /* select the current environment, to make sure the env variables 425 get updated */ 426 select_output_environment (current_env_no); 427 428 #define CHAR_ADDR(n) (offset[i] + (n)) 429 #define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)]) 430 431 /* remove trailing whitespace from each column */ 432 for (i = 1; i <= last_column; i++) { 433 while (envs[i].output_paragraph_offset && 434 cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1))) 435 envs[i].output_paragraph_offset--; 436 437 if (i == current_env_no) 438 output_paragraph_offset = envs[i].output_paragraph_offset; 439 } 440 441 /* read the current line from each column, outputting them all 442 pasted together. Do this til all lines are output from all 443 columns. */ 444 for (;;) { 445 remaining = 0; 446 /* first, see if there is any work to do */ 447 for (i = 1; i <= last_column; i++) { 448 if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) { 449 remaining = 1; 450 break; 451 } 452 } 453 if (!remaining) 454 break; 455 456 for (s = 0; s < envs[0].current_indent; s++) 457 out_char (' '); 458 459 if (vsep) 460 out_char ('|'); 461 462 for (i = 1; i <= last_column; i++) { 463 for (s = 0; s < envs[i].current_indent; s++) 464 out_char (' '); 465 for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) { 466 if (CHAR_AT (j) == '\n') 467 break; 468 out_char (CHAR_AT (j)); 469 } 470 offset[i] += j + 1; /* skip last text plus skip the newline */ 471 472 /* Do not output trailing blanks if we're in the last column and 473 there will be no trailing |. */ 474 if (i < last_column && !vsep) 475 for (; j <= envs[i].fill_column; j++) 476 out_char (' '); 477 if (vsep) 478 out_char ('|'); /* draw column separator */ 479 } 480 out_char ('\n'); /* end of line */ 481 had_newline = 1; 482 } 483 484 /* If completely blank item, get blank line despite no other output. */ 485 if (!had_newline) 486 out_char ('\n'); /* end of line */ 487 488 if (hsep) 489 draw_horizontal_separator (); 490 491 /* Now dispose of the buffered output. */ 492 for (i = 1; i <= last_column; i++) { 493 select_output_environment (i); 494 init_paragraph (); 495 } 496 } 497 498 int after_headitem = 0; 499 int headitem_row = 0; 500 501 /* start a new item (row) of a multitable */ 502 int 503 multitable_item (void) 504 { 505 if (!multitable_active) { 506 line_error ("multitable_item internal error: no active multitable"); 507 xexit (1); 508 } 509 510 current_column_no = 1; 511 512 if (html) 513 { 514 if (!first_row) 515 /* <br> for non-tables browsers. */ 516 add_word_args ("<br></%s></tr>", after_headitem ? "th" : "td"); 517 518 if (seen_column_fractions) 519 add_word_args ("<tr align=\"left\"><%s valign=\"top\" width=\"%d%%\">", 520 headitem_flag ? "th" : "td", 521 envs[current_column_no].fill_column); 522 else 523 add_word_args ("<tr align=\"left\"><%s valign=\"top\">", 524 headitem_flag ? "th" : "td"); 525 526 if (headitem_flag) 527 after_headitem = 1; 528 else 529 after_headitem = 0; 530 first_row = 0; 531 headitem_row = headitem_flag; 532 headitem_flag = 0; 533 return 0; 534 } 535 /* else if (docbook)*/ /* 05-08 */ 536 else if (xml) 537 { 538 xml_end_multitable_row (first_row); 539 if (headitem_flag) 540 after_headitem = 1; 541 else 542 after_headitem = 0; 543 first_row = 0; 544 headitem_flag = 0; 545 return 0; 546 } 547 first_row = 0; 548 549 if (current_env_no > 0) { 550 output_multitable_row (); 551 } 552 /* start at column 1 */ 553 select_output_environment (1); 554 if (!output_paragraph) { 555 line_error (_("[unexpected] cannot select column #%d in multitable"), 556 current_env_no); 557 xexit (1); 558 } 559 560 init_column (); 561 562 if (headitem_flag) 563 hsep = 1; 564 else 565 hsep = 0; 566 567 if (headitem_flag) 568 after_headitem = 1; 569 else 570 after_headitem = 0; 571 headitem_flag = 0; 572 573 return 0; 574 } 575 576 #undef CHAR_AT 577 #undef CHAR_ADDR 578 579 /* select a new column in current row of multitable */ 580 void 581 cm_tab (void) 582 { 583 if (!multitable_active) 584 error (_("ignoring @tab outside of multitable")); 585 586 current_column_no++; 587 588 if (html) 589 { 590 if (seen_column_fractions) 591 add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">", 592 headitem_row ? "th" : "td", 593 headitem_row ? "th" : "td", 594 envs[current_column_no].fill_column); 595 else 596 add_word_args ("</%s><%s valign=\"top\">", 597 headitem_row ? "th" : "td", 598 headitem_row ? "th" : "td"); 599 } 600 /* else if (docbook)*/ /* 05-08 */ 601 else if (xml) 602 xml_end_multitable_column (); 603 else 604 nselect_next_environment (); 605 606 init_column (); 607 } 608 609 /* close a multitable, flushing its output and resetting 610 whatever needs resetting */ 611 void 612 end_multitable (void) 613 { 614 if (!html && !docbook) 615 output_multitable_row (); 616 617 /* Multitables cannot be nested. Otherwise, we'd have to save the 618 previous output environment number on a stack somewhere, and then 619 restore to that environment. */ 620 select_output_environment (0); 621 multitable_active = 0; 622 uninhibit_output_flushing (); 623 close_insertion_paragraph (); 624 625 if (html) 626 add_word_args ("<br></%s></tr></table>\n", headitem_row ? "th" : "td"); 627 /* else if (docbook)*/ /* 05-08 */ 628 else if (xml) 629 xml_end_multitable (); 630 631 #if 0 632 printf (_("** Multicolumn output from last row:\n")); 633 for (i = 1; i <= last_column; i++) { 634 select_output_environment (i); 635 printf (_("* column #%d: output = %s\n"), i, output_paragraph); 636 } 637 #endif 638 } 639