| /* $Id: tbl_layout.c,v 1.22 2011/09/18 14:14:15 schwarze Exp $ */ |
| /* |
| * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
| * |
| * Permission to use, copy, modify, and distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #include "mandoc.h" |
| #include "libmandoc.h" |
| #include "libroff.h" |
| |
| struct tbl_phrase { |
| char name; |
| enum tbl_cellt key; |
| }; |
| |
| /* |
| * FIXME: we can make this parse a lot nicer by, when an error is |
| * encountered in a layout key, bailing to the next key (i.e. to the |
| * next whitespace then continuing). |
| */ |
| |
| #define KEYS_MAX 11 |
| |
| static const struct tbl_phrase keys[KEYS_MAX] = { |
| { 'c', TBL_CELL_CENTRE }, |
| { 'r', TBL_CELL_RIGHT }, |
| { 'l', TBL_CELL_LEFT }, |
| { 'n', TBL_CELL_NUMBER }, |
| { 's', TBL_CELL_SPAN }, |
| { 'a', TBL_CELL_LONG }, |
| { '^', TBL_CELL_DOWN }, |
| { '-', TBL_CELL_HORIZ }, |
| { '_', TBL_CELL_HORIZ }, |
| { '=', TBL_CELL_DHORIZ }, |
| { '|', TBL_CELL_VERT } |
| }; |
| |
| static int mods(struct tbl_node *, struct tbl_cell *, |
| int, const char *, int *); |
| static int cell(struct tbl_node *, struct tbl_row *, |
| int, const char *, int *); |
| static void row(struct tbl_node *, int, const char *, int *); |
| static struct tbl_cell *cell_alloc(struct tbl_node *, |
| struct tbl_row *, enum tbl_cellt); |
| static void head_adjust(const struct tbl_cell *, |
| struct tbl_head *); |
| |
| static int |
| mods(struct tbl_node *tbl, struct tbl_cell *cp, |
| int ln, const char *p, int *pos) |
| { |
| char buf[5]; |
| int i; |
| |
| /* Not all types accept modifiers. */ |
| |
| switch (cp->pos) { |
| case (TBL_CELL_DOWN): |
| /* FALLTHROUGH */ |
| case (TBL_CELL_HORIZ): |
| /* FALLTHROUGH */ |
| case (TBL_CELL_DHORIZ): |
| /* FALLTHROUGH */ |
| case (TBL_CELL_VERT): |
| /* FALLTHROUGH */ |
| case (TBL_CELL_DVERT): |
| return(1); |
| default: |
| break; |
| } |
| |
| mod: |
| /* |
| * XXX: since, at least for now, modifiers are non-conflicting |
| * (are separable by value, regardless of position), we let |
| * modifiers come in any order. The existing tbl doesn't let |
| * this happen. |
| */ |
| switch (p[*pos]) { |
| case ('\0'): |
| /* FALLTHROUGH */ |
| case (' '): |
| /* FALLTHROUGH */ |
| case ('\t'): |
| /* FALLTHROUGH */ |
| case (','): |
| /* FALLTHROUGH */ |
| case ('.'): |
| return(1); |
| default: |
| break; |
| } |
| |
| /* Throw away parenthesised expression. */ |
| |
| if ('(' == p[*pos]) { |
| (*pos)++; |
| while (p[*pos] && ')' != p[*pos]) |
| (*pos)++; |
| if (')' == p[*pos]) { |
| (*pos)++; |
| goto mod; |
| } |
| mandoc_msg(MANDOCERR_TBLLAYOUT, |
| tbl->parse, ln, *pos, NULL); |
| return(0); |
| } |
| |
| /* Parse numerical spacing from modifier string. */ |
| |
| if (isdigit((unsigned char)p[*pos])) { |
| for (i = 0; i < 4; i++) { |
| if ( ! isdigit((unsigned char)p[*pos + i])) |
| break; |
| buf[i] = p[*pos + i]; |
| } |
| buf[i] = '\0'; |
| |
| /* No greater than 4 digits. */ |
| |
| if (4 == i) { |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, |
| ln, *pos, NULL); |
| return(0); |
| } |
| |
| *pos += i; |
| cp->spacing = (size_t)atoi(buf); |
| |
| goto mod; |
| /* NOTREACHED */ |
| } |
| |
| /* TODO: GNU has many more extensions. */ |
| |
| switch (tolower((unsigned char)p[(*pos)++])) { |
| case ('z'): |
| cp->flags |= TBL_CELL_WIGN; |
| goto mod; |
| case ('u'): |
| cp->flags |= TBL_CELL_UP; |
| goto mod; |
| case ('e'): |
| cp->flags |= TBL_CELL_EQUAL; |
| goto mod; |
| case ('t'): |
| cp->flags |= TBL_CELL_TALIGN; |
| goto mod; |
| case ('d'): |
| cp->flags |= TBL_CELL_BALIGN; |
| goto mod; |
| case ('w'): /* XXX for now, ignore minimal column width */ |
| goto mod; |
| case ('f'): |
| break; |
| case ('r'): |
| /* FALLTHROUGH */ |
| case ('b'): |
| /* FALLTHROUGH */ |
| case ('i'): |
| (*pos)--; |
| break; |
| default: |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, |
| ln, *pos - 1, NULL); |
| return(0); |
| } |
| |
| switch (tolower((unsigned char)p[(*pos)++])) { |
| case ('3'): |
| /* FALLTHROUGH */ |
| case ('b'): |
| cp->flags |= TBL_CELL_BOLD; |
| goto mod; |
| case ('2'): |
| /* FALLTHROUGH */ |
| case ('i'): |
| cp->flags |= TBL_CELL_ITALIC; |
| goto mod; |
| case ('1'): |
| /* FALLTHROUGH */ |
| case ('r'): |
| goto mod; |
| default: |
| break; |
| } |
| |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, |
| ln, *pos - 1, NULL); |
| return(0); |
| } |
| |
| static int |
| cell(struct tbl_node *tbl, struct tbl_row *rp, |
| int ln, const char *p, int *pos) |
| { |
| int i; |
| enum tbl_cellt c; |
| |
| /* Parse the column position (`r', `R', `|', ...). */ |
| |
| for (i = 0; i < KEYS_MAX; i++) |
| if (tolower((unsigned char)p[*pos]) == keys[i].name) |
| break; |
| |
| if (KEYS_MAX == i) { |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, |
| ln, *pos, NULL); |
| return(0); |
| } |
| |
| c = keys[i].key; |
| |
| /* |
| * If a span cell is found first, raise a warning and abort the |
| * parse. If a span cell is found and the last layout element |
| * isn't a "normal" layout, bail. |
| * |
| * FIXME: recover from this somehow? |
| */ |
| |
| if (TBL_CELL_SPAN == c) { |
| if (NULL == rp->first) { |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, |
| ln, *pos, NULL); |
| return(0); |
| } else if (rp->last) |
| switch (rp->last->pos) { |
| case (TBL_CELL_VERT): |
| case (TBL_CELL_DVERT): |
| case (TBL_CELL_HORIZ): |
| case (TBL_CELL_DHORIZ): |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, |
| ln, *pos, NULL); |
| return(0); |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * If a vertical spanner is found, we may not be in the first |
| * row. |
| */ |
| |
| if (TBL_CELL_DOWN == c && rp == tbl->first_row) { |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL); |
| return(0); |
| } |
| |
| (*pos)++; |
| |
| /* Extra check for the double-vertical. */ |
| |
| if (TBL_CELL_VERT == c && '|' == p[*pos]) { |
| (*pos)++; |
| c = TBL_CELL_DVERT; |
| } |
| |
| /* Disallow adjacent spacers. */ |
| |
| if (rp->last && (TBL_CELL_VERT == c || TBL_CELL_DVERT == c) && |
| (TBL_CELL_VERT == rp->last->pos || |
| TBL_CELL_DVERT == rp->last->pos)) { |
| mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL); |
| return(0); |
| } |
| |
| /* Allocate cell then parse its modifiers. */ |
| |
| return(mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos)); |
| } |
| |
| |
| static void |
| row(struct tbl_node *tbl, int ln, const char *p, int *pos) |
| { |
| struct tbl_row *rp; |
| |
| row: /* |
| * EBNF describing this section: |
| * |
| * row ::= row_list [:space:]* [.]?[\n] |
| * row_list ::= [:space:]* row_elem row_tail |
| * row_tail ::= [:space:]*[,] row_list | |
| * epsilon |
| * row_elem ::= [\t\ ]*[:alpha:]+ |
| */ |
| |
| rp = mandoc_calloc(1, sizeof(struct tbl_row)); |
| if (tbl->last_row) { |
| tbl->last_row->next = rp; |
| tbl->last_row = rp; |
| } else |
| tbl->last_row = tbl->first_row = rp; |
| |
| cell: |
| while (isspace((unsigned char)p[*pos])) |
| (*pos)++; |
| |
| /* Safely exit layout context. */ |
| |
| if ('.' == p[*pos]) { |
| tbl->part = TBL_PART_DATA; |
| if (NULL == tbl->first_row) |
| mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse, |
| ln, *pos, NULL); |
| (*pos)++; |
| return; |
| } |
| |
| /* End (and possibly restart) a row. */ |
| |
| if (',' == p[*pos]) { |
| (*pos)++; |
| goto row; |
| } else if ('\0' == p[*pos]) |
| return; |
| |
| if ( ! cell(tbl, rp, ln, p, pos)) |
| return; |
| |
| goto cell; |
| /* NOTREACHED */ |
| } |
| |
| int |
| tbl_layout(struct tbl_node *tbl, int ln, const char *p) |
| { |
| int pos; |
| |
| pos = 0; |
| row(tbl, ln, p, &pos); |
| |
| /* Always succeed. */ |
| return(1); |
| } |
| |
| static struct tbl_cell * |
| cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos) |
| { |
| struct tbl_cell *p, *pp; |
| struct tbl_head *h, *hp; |
| |
| p = mandoc_calloc(1, sizeof(struct tbl_cell)); |
| |
| if (NULL != (pp = rp->last)) { |
| rp->last->next = p; |
| rp->last = p; |
| } else |
| rp->last = rp->first = p; |
| |
| p->pos = pos; |
| |
| /* |
| * This is a little bit complicated. Here we determine the |
| * header the corresponds to a cell. We add headers dynamically |
| * when need be or re-use them, otherwise. As an example, given |
| * the following: |
| * |
| * 1 c || l |
| * 2 | c | l |
| * 3 l l |
| * 3 || c | l |. |
| * |
| * We first add the new headers (as there are none) in (1); then |
| * in (2) we insert the first spanner (as it doesn't match up |
| * with the header); then we re-use the prior data headers, |
| * skipping over the spanners; then we re-use everything and add |
| * a last spanner. Note that VERT headers are made into DVERT |
| * ones. |
| */ |
| |
| h = pp ? pp->head->next : tbl->first_head; |
| |
| if (h) { |
| /* Re-use data header. */ |
| if (TBL_HEAD_DATA == h->pos && |
| (TBL_CELL_VERT != p->pos && |
| TBL_CELL_DVERT != p->pos)) { |
| p->head = h; |
| return(p); |
| } |
| |
| /* Re-use spanner header. */ |
| if (TBL_HEAD_DATA != h->pos && |
| (TBL_CELL_VERT == p->pos || |
| TBL_CELL_DVERT == p->pos)) { |
| head_adjust(p, h); |
| p->head = h; |
| return(p); |
| } |
| |
| /* Right-shift headers with a new spanner. */ |
| if (TBL_HEAD_DATA == h->pos && |
| (TBL_CELL_VERT == p->pos || |
| TBL_CELL_DVERT == p->pos)) { |
| hp = mandoc_calloc(1, sizeof(struct tbl_head)); |
| hp->ident = tbl->opts.cols++; |
| hp->prev = h->prev; |
| if (h->prev) |
| h->prev->next = hp; |
| if (h == tbl->first_head) |
| tbl->first_head = hp; |
| h->prev = hp; |
| hp->next = h; |
| head_adjust(p, hp); |
| p->head = hp; |
| return(p); |
| } |
| |
| if (NULL != (h = h->next)) { |
| head_adjust(p, h); |
| p->head = h; |
| return(p); |
| } |
| |
| /* Fall through to default case... */ |
| } |
| |
| hp = mandoc_calloc(1, sizeof(struct tbl_head)); |
| hp->ident = tbl->opts.cols++; |
| |
| if (tbl->last_head) { |
| hp->prev = tbl->last_head; |
| tbl->last_head->next = hp; |
| tbl->last_head = hp; |
| } else |
| tbl->last_head = tbl->first_head = hp; |
| |
| head_adjust(p, hp); |
| p->head = hp; |
| return(p); |
| } |
| |
| static void |
| head_adjust(const struct tbl_cell *cellp, struct tbl_head *head) |
| { |
| if (TBL_CELL_VERT != cellp->pos && |
| TBL_CELL_DVERT != cellp->pos) { |
| head->pos = TBL_HEAD_DATA; |
| return; |
| } |
| |
| if (TBL_CELL_VERT == cellp->pos) |
| if (TBL_HEAD_DVERT != head->pos) |
| head->pos = TBL_HEAD_VERT; |
| |
| if (TBL_CELL_DVERT == cellp->pos) |
| head->pos = TBL_HEAD_DVERT; |
| } |
| |