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