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