multi.c 18 KB

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