source: trunk/third/texinfo/makeinfo/multi.c @ 18945

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