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
find_template_width(char ** params)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
select_output_environment(int n)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
setup_output_environment(int env_no,int width)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
setup_multitable_parameters(void)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
out_char(int ch)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
draw_horizontal_separator(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
do_multitable(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
nselect_next_environment(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
init_column(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
output_multitable_row(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
multitable_item(void)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
cm_tab(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
end_multitable(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