473 lines
10 KiB
C
473 lines
10 KiB
C
|
/* $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;
|
||
|
}
|
||
|
|