source: trunk/essentials/sys-apps/texinfo/makeinfo/multi.c

Last change on this file was 2617, checked in by bird, 19 years ago

GNU Texinfo 4.8

File size: 17.5 KB
Line 
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
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/*
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 @@ */
58struct 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 */
70static int current_env_no;
71
72/* current column number */
73static int current_column_no;
74
75/* We need to make a difference between template based widths and
76 @columnfractions for HTML tables' sake. Sigh. */
77static int seen_column_fractions;
78
79/* column number of last column in current multitable */
80static 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. */
84static int hsep, vsep;
85
86/* whether this is the first row. */
87static int first_row;
88
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. */
94static unsigned
95find_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. */
138static int
139select_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. */
170static int
171setup_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. */
190static int
191setup_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 (&params);
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
261done:
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. */
272static void
273out_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
292static void
293draw_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
321/* multitable strategy:
322 for each item {
323 for each column in an item {
324 initialize a new paragraph
325 do ordinary formatting into the new paragraph
326 save the paragraph away
327 repeat if there are more paragraphs in the column
328 }
329 dump out the saved paragraphs and free the storage
330 }
331
332 For HTML we construct a simple HTML 3.2 table with <br>s inserted
333 to help non-tables browsers. `@item' inserts a <tr> and `@tab'
334 inserts <td>; we also try to close <tr>. The only real
335 alternative is to rely on the info formatting engine and present
336 preformatted text. */
337
338void
339do_multitable (void)
340{
341 int ncolumns;
342
343 if (multitable_active)
344 {
345 line_error ("Multitables cannot be nested");
346 return;
347 }
348
349 close_single_paragraph ();
350
351 if (xml)
352 {
353 xml_no_para = 1;
354 if (output_paragraph[output_paragraph_offset-1] == '\n')
355 output_paragraph_offset--;
356 }
357
358 /* scan the current item function to get the field widths
359 and number of columns, and set up the output environment list
360 accordingly. */
361 ncolumns = setup_multitable_parameters ();
362 first_row = 1;
363
364 /* <p> for non-tables browsers. @multitable implicitly ends the
365 current paragraph, so this is ok. */
366 if (html)
367 add_html_block_elt ("<p><table summary=\"\">");
368 /* else if (docbook)*/ /* 05-08 */
369 else if (xml)
370 {
371 int *widths = xmalloc (ncolumns * sizeof (int));
372 int i;
373 for (i=0; i<ncolumns; i++)
374 widths[i] = envs[i+1].fill_column;
375 xml_begin_multitable (ncolumns, widths);
376 free (widths);
377 }
378
379 if (hsep)
380 draw_horizontal_separator ();
381
382 /* The next @item command will direct stdout into the first column
383 and start processing. @tab will then switch to the next column,
384 and @item will flush out the saved output and return to the first
385 column. Environment #1 is the first column. (Environment #0 is
386 the normal output) */
387
388 ++multitable_active;
389}
390
391/* advance to the next environment number */
392static void
393nselect_next_environment (void)
394{
395 if (current_env_no >= last_column) {
396 line_error (_("Too many columns in multitable item (max %d)"), last_column);
397 return;
398 }
399 select_output_environment (current_env_no + 1);
400}
401
402
403
404/* do anything needed at the beginning of processing a
405 multitable column. */
406static void
407init_column (void)
408{
409 /* don't indent 1st paragraph in the item */
410 cm_noindent ();
411
412 /* throw away possible whitespace after @item or @tab command */
413 skip_whitespace ();
414}
415
416static void
417output_multitable_row (void)
418{
419 /* offset in the output paragraph of the next char needing
420 to be output for that column. */
421 int offset[MAXCOLS];
422 int i, j, s, remaining;
423 int had_newline = 0;
424
425 for (i = 0; i <= last_column; i++)
426 offset[i] = 0;
427
428 /* select the current environment, to make sure the env variables
429 get updated */
430 select_output_environment (current_env_no);
431
432#define CHAR_ADDR(n) (offset[i] + (n))
433#define CHAR_AT(n) (envs[i].output_paragraph[CHAR_ADDR(n)])
434
435 /* remove trailing whitespace from each column */
436 for (i = 1; i <= last_column; i++) {
437 if (envs[i].output_paragraph_offset)
438 while (cr_or_whitespace (CHAR_AT (envs[i].output_paragraph_offset - 1)))
439 envs[i].output_paragraph_offset--;
440
441 if (i == current_env_no)
442 output_paragraph_offset = envs[i].output_paragraph_offset;
443 }
444
445 /* read the current line from each column, outputting them all
446 pasted together. Do this til all lines are output from all
447 columns. */
448 for (;;) {
449 remaining = 0;
450 /* first, see if there is any work to do */
451 for (i = 1; i <= last_column; i++) {
452 if (CHAR_ADDR (0) < envs[i].output_paragraph_offset) {
453 remaining = 1;
454 break;
455 }
456 }
457 if (!remaining)
458 break;
459
460 for (s = 0; s < envs[0].current_indent; s++)
461 out_char (' ');
462
463 if (vsep)
464 out_char ('|');
465
466 for (i = 1; i <= last_column; i++) {
467 for (s = 0; s < envs[i].current_indent; s++)
468 out_char (' ');
469 for (j = 0; CHAR_ADDR (j) < envs[i].output_paragraph_offset; j++) {
470 if (CHAR_AT (j) == '\n')
471 break;
472 out_char (CHAR_AT (j));
473 }
474 offset[i] += j + 1; /* skip last text plus skip the newline */
475
476 /* Do not output trailing blanks if we're in the last column and
477 there will be no trailing |. */
478 if (i < last_column && !vsep)
479 for (; j <= envs[i].fill_column; j++)
480 out_char (' ');
481 if (vsep)
482 out_char ('|'); /* draw column separator */
483 }
484 out_char ('\n'); /* end of line */
485 had_newline = 1;
486 }
487
488 /* If completely blank item, get blank line despite no other output. */
489 if (!had_newline)
490 out_char ('\n'); /* end of line */
491
492 if (hsep)
493 draw_horizontal_separator ();
494
495 /* Now dispose of the buffered output. */
496 for (i = 1; i <= last_column; i++) {
497 select_output_environment (i);
498 init_paragraph ();
499 }
500}
501
502int after_headitem = 0;
503int headitem_row = 0;
504
505/* start a new item (row) of a multitable */
506int
507multitable_item (void)
508{
509 if (!multitable_active) {
510 line_error ("multitable_item internal error: no active multitable");
511 xexit (1);
512 }
513
514 current_column_no = 1;
515
516 if (html)
517 {
518 if (!first_row)
519 /* <br> for non-tables browsers. */
520 add_word_args ("<br></%s></tr>", after_headitem ? "th" : "td");
521
522 if (seen_column_fractions)
523 add_word_args ("<tr align=\"left\"><%s valign=\"top\" width=\"%d%%\">",
524 headitem_flag ? "th" : "td",
525 envs[current_column_no].fill_column);
526 else
527 add_word_args ("<tr align=\"left\"><%s valign=\"top\">",
528 headitem_flag ? "th" : "td");
529
530 if (headitem_flag)
531 after_headitem = 1;
532 else
533 after_headitem = 0;
534 first_row = 0;
535 headitem_row = headitem_flag;
536 headitem_flag = 0;
537 return 0;
538 }
539 /* else if (docbook)*/ /* 05-08 */
540 else if (xml)
541 {
542 xml_end_multitable_row (first_row);
543 if (headitem_flag)
544 after_headitem = 1;
545 else
546 after_headitem = 0;
547 first_row = 0;
548 headitem_flag = 0;
549 return 0;
550 }
551 first_row = 0;
552
553 if (current_env_no > 0) {
554 output_multitable_row ();
555 }
556 /* start at column 1 */
557 select_output_environment (1);
558 if (!output_paragraph) {
559 line_error (_("[unexpected] cannot select column #%d in multitable"),
560 current_env_no);
561 xexit (1);
562 }
563
564 init_column ();
565
566 if (headitem_flag)
567 hsep = 1;
568 else
569 hsep = 0;
570
571 if (headitem_flag)
572 after_headitem = 1;
573 else
574 after_headitem = 0;
575 headitem_flag = 0;
576
577 return 0;
578}
579
580#undef CHAR_AT
581#undef CHAR_ADDR
582
583/* select a new column in current row of multitable */
584void
585cm_tab (void)
586{
587 if (!multitable_active)
588 error (_("ignoring @tab outside of multitable"));
589
590 current_column_no++;
591
592 if (html)
593 {
594 if (seen_column_fractions)
595 add_word_args ("</%s><%s valign=\"top\" width=\"%d%%\">",
596 headitem_row ? "th" : "td",
597 headitem_row ? "th" : "td",
598 envs[current_column_no].fill_column);
599 else
600 add_word_args ("</%s><%s valign=\"top\">",
601 headitem_row ? "th" : "td",
602 headitem_row ? "th" : "td");
603 }
604 /* else if (docbook)*/ /* 05-08 */
605 else if (xml)
606 xml_end_multitable_column ();
607 else
608 nselect_next_environment ();
609
610 init_column ();
611}
612
613/* close a multitable, flushing its output and resetting
614 whatever needs resetting */
615void
616end_multitable (void)
617{
618 if (!html && !docbook)
619 output_multitable_row ();
620
621 /* Multitables cannot be nested. Otherwise, we'd have to save the
622 previous output environment number on a stack somewhere, and then
623 restore to that environment. */
624 select_output_environment (0);
625 multitable_active = 0;
626 uninhibit_output_flushing ();
627 close_insertion_paragraph ();
628
629 if (html)
630 add_word_args ("<br></%s></tr></table>\n", headitem_row ? "th" : "td");
631 /* else if (docbook)*/ /* 05-08 */
632 else if (xml)
633 xml_end_multitable ();
634
635#if 0
636 printf (_("** Multicolumn output from last row:\n"));
637 for (i = 1; i <= last_column; i++) {
638 select_output_environment (i);
639 printf (_("* column #%d: output = %s\n"), i, output_paragraph);
640 }
641#endif
642}
Note: See TracBrowser for help on using the repository browser.