1512 lines
31 KiB
C++
1512 lines
31 KiB
C++
// -*- C++ -*-
|
|
/* Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
|
|
Written by James Clark (jjc@jclark.com)
|
|
|
|
This file is part of groff.
|
|
|
|
groff is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 2, or (at your option) any later
|
|
version.
|
|
|
|
groff is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with groff; see the file COPYING. If not, write to the Free Software
|
|
Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
|
|
|
|
#include "table.h"
|
|
|
|
#define MAX_POINT_SIZE 99
|
|
#define MAX_VERTICAL_SPACING 72
|
|
|
|
static int compatible_flag = 0;
|
|
|
|
class table_input {
|
|
FILE *fp;
|
|
enum { START, MIDDLE, REREAD_T, REREAD_TE, REREAD_E, END, ERROR } state;
|
|
string unget_stack;
|
|
public:
|
|
table_input(FILE *);
|
|
int get();
|
|
int ended() { return unget_stack.empty() && state == END; }
|
|
void unget(char);
|
|
};
|
|
|
|
table_input::table_input(FILE *p)
|
|
: fp(p), state(START)
|
|
{
|
|
}
|
|
|
|
void table_input::unget(char c)
|
|
{
|
|
assert(c != '\0');
|
|
unget_stack += c;
|
|
if (c == '\n')
|
|
current_lineno--;
|
|
}
|
|
|
|
int table_input::get()
|
|
{
|
|
int len = unget_stack.length();
|
|
if (len != 0) {
|
|
unsigned char c = unget_stack[len - 1];
|
|
unget_stack.set_length(len - 1);
|
|
if (c == '\n')
|
|
current_lineno++;
|
|
return c;
|
|
}
|
|
int c;
|
|
for (;;) {
|
|
switch (state) {
|
|
case START:
|
|
if ((c = getc(fp)) == '.') {
|
|
if ((c = getc(fp)) == 'T') {
|
|
if ((c = getc(fp)) == 'E') {
|
|
if (compatible_flag) {
|
|
state = END;
|
|
return EOF;
|
|
}
|
|
else {
|
|
c = getc(fp);
|
|
if (c != EOF)
|
|
ungetc(c, fp);
|
|
if (c == EOF || c == ' ' || c == '\n') {
|
|
state = END;
|
|
return EOF;
|
|
}
|
|
state = REREAD_TE;
|
|
return '.';
|
|
}
|
|
}
|
|
else {
|
|
if (c != EOF)
|
|
ungetc(c, fp);
|
|
state = REREAD_T;
|
|
return '.';
|
|
}
|
|
}
|
|
else {
|
|
if (c != EOF)
|
|
ungetc(c, fp);
|
|
state = MIDDLE;
|
|
return '.';
|
|
}
|
|
}
|
|
else if (c == EOF) {
|
|
state = ERROR;
|
|
return EOF;
|
|
}
|
|
else {
|
|
if (c == '\n')
|
|
current_lineno++;
|
|
else {
|
|
state = MIDDLE;
|
|
if (c == '\0') {
|
|
error("illegal input character code 0");
|
|
break;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
break;
|
|
case MIDDLE:
|
|
// handle line continuation
|
|
if ((c = getc(fp)) == '\\') {
|
|
c = getc(fp);
|
|
if (c == '\n')
|
|
c = getc(fp); // perhaps state ought to be START now
|
|
else {
|
|
if (c != EOF)
|
|
ungetc(c, fp);
|
|
c = '\\';
|
|
}
|
|
}
|
|
if (c == EOF) {
|
|
state = ERROR;
|
|
return EOF;
|
|
}
|
|
else {
|
|
if (c == '\n') {
|
|
state = START;
|
|
current_lineno++;
|
|
}
|
|
else if (c == '\0') {
|
|
error("illegal input character code 0");
|
|
break;
|
|
}
|
|
return c;
|
|
}
|
|
case REREAD_T:
|
|
state = MIDDLE;
|
|
return 'T';
|
|
case REREAD_TE:
|
|
state = REREAD_E;
|
|
return 'T';
|
|
case REREAD_E:
|
|
state = MIDDLE;
|
|
return 'E';
|
|
case END:
|
|
case ERROR:
|
|
return EOF;
|
|
}
|
|
}
|
|
}
|
|
|
|
void process_input_file(FILE *);
|
|
void process_table(table_input &in);
|
|
|
|
void process_input_file(FILE *fp)
|
|
{
|
|
enum { START, MIDDLE, HAD_DOT, HAD_T, HAD_TS, HAD_l, HAD_lf } state;
|
|
state = START;
|
|
int c;
|
|
while ((c = getc(fp)) != EOF)
|
|
switch (state) {
|
|
case START:
|
|
if (c == '.')
|
|
state = HAD_DOT;
|
|
else {
|
|
if (c == '\n')
|
|
current_lineno++;
|
|
else
|
|
state = MIDDLE;
|
|
putchar(c);
|
|
}
|
|
break;
|
|
case MIDDLE:
|
|
if (c == '\n') {
|
|
current_lineno++;
|
|
state = START;
|
|
}
|
|
putchar(c);
|
|
break;
|
|
case HAD_DOT:
|
|
if (c == 'T')
|
|
state = HAD_T;
|
|
else if (c == 'l')
|
|
state = HAD_l;
|
|
else {
|
|
putchar('.');
|
|
putchar(c);
|
|
if (c == '\n') {
|
|
current_lineno++;
|
|
state = START;
|
|
}
|
|
else
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
case HAD_T:
|
|
if (c == 'S')
|
|
state = HAD_TS;
|
|
else {
|
|
putchar('.');
|
|
putchar('T');
|
|
putchar(c);
|
|
if (c == '\n') {
|
|
current_lineno++;
|
|
state = START;
|
|
}
|
|
else
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
case HAD_TS:
|
|
if (c == ' ' || c == '\n' || compatible_flag) {
|
|
putchar('.');
|
|
putchar('T');
|
|
putchar('S');
|
|
while (c != '\n') {
|
|
if (c == EOF) {
|
|
error("end of file at beginning of table");
|
|
return;
|
|
}
|
|
putchar(c);
|
|
c = getc(fp);
|
|
}
|
|
putchar('\n');
|
|
current_lineno++;
|
|
{
|
|
table_input input(fp);
|
|
process_table(input);
|
|
set_troff_location(current_filename, current_lineno);
|
|
if (input.ended()) {
|
|
fputs(".TE", stdout);
|
|
while ((c = getc(fp)) != '\n') {
|
|
if (c == EOF) {
|
|
putchar('\n');
|
|
return;
|
|
}
|
|
putchar(c);
|
|
}
|
|
putchar('\n');
|
|
current_lineno++;
|
|
}
|
|
}
|
|
state = START;
|
|
}
|
|
else {
|
|
fputs(".TS", stdout);
|
|
putchar(c);
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
case HAD_l:
|
|
if (c == 'f')
|
|
state = HAD_lf;
|
|
else {
|
|
putchar('.');
|
|
putchar('l');
|
|
putchar(c);
|
|
if (c == '\n') {
|
|
current_lineno++;
|
|
state = START;
|
|
}
|
|
else
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
case HAD_lf:
|
|
if (c == ' ' || c == '\n' || compatible_flag) {
|
|
string line;
|
|
while (c != EOF) {
|
|
line += c;
|
|
if (c == '\n') {
|
|
current_lineno++;
|
|
break;
|
|
}
|
|
c = getc(fp);
|
|
}
|
|
line += '\0';
|
|
interpret_lf_args(line.contents());
|
|
printf(".lf%s", line.contents());
|
|
state = START;
|
|
}
|
|
else {
|
|
fputs(".lf", stdout);
|
|
putchar(c);
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
switch(state) {
|
|
case START:
|
|
break;
|
|
case MIDDLE:
|
|
putchar('\n');
|
|
break;
|
|
case HAD_DOT:
|
|
fputs(".\n", stdout);
|
|
break;
|
|
case HAD_l:
|
|
fputs(".l\n", stdout);
|
|
break;
|
|
case HAD_T:
|
|
fputs(".T\n", stdout);
|
|
break;
|
|
case HAD_lf:
|
|
fputs(".lf\n", stdout);
|
|
break;
|
|
case HAD_TS:
|
|
fputs(".TS\n", stdout);
|
|
break;
|
|
}
|
|
if (fp != stdin)
|
|
fclose(fp);
|
|
}
|
|
|
|
struct options {
|
|
unsigned flags;
|
|
int linesize;
|
|
char delim[2];
|
|
char tab_char;
|
|
char decimal_point_char;
|
|
|
|
options();
|
|
};
|
|
|
|
options::options()
|
|
: flags(0), tab_char('\t'), decimal_point_char('.'), linesize(0)
|
|
{
|
|
delim[0] = delim[1] = '\0';
|
|
}
|
|
|
|
// Return non-zero if p and q are the same ignoring case.
|
|
|
|
int strieq(const char *p, const char *q)
|
|
{
|
|
for (; cmlower(*p) == cmlower(*q); p++, q++)
|
|
if (*p == '\0')
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
// return 0 if we should give up in this table
|
|
|
|
options *process_options(table_input &in)
|
|
{
|
|
options *opt = new options;
|
|
string line;
|
|
int level = 0;
|
|
for (;;) {
|
|
int c = in.get();
|
|
if (c == EOF) {
|
|
int i = line.length();
|
|
while (--i >= 0)
|
|
in.unget(line[i]);
|
|
return opt;
|
|
}
|
|
if (c == '\n') {
|
|
in.unget(c);
|
|
int i = line.length();
|
|
while (--i >= 0)
|
|
in.unget(line[i]);
|
|
return opt;
|
|
}
|
|
else if (c == '(')
|
|
level++;
|
|
else if (c == ')')
|
|
level--;
|
|
else if (c == ';' && level == 0) {
|
|
line += '\0';
|
|
break;
|
|
}
|
|
line += c;
|
|
}
|
|
if (line.empty())
|
|
return opt;
|
|
char *p = &line[0];
|
|
for (;;) {
|
|
while (csspace(*p) || *p == ',')
|
|
p++;
|
|
if (*p == '\0')
|
|
break;
|
|
char *q = p;
|
|
while (*q != ' ' && *q != '\0' && *q != '\t' && *q != ',' && *q != '(')
|
|
q++;
|
|
char *arg = 0;
|
|
if (*q != '(' && *q != '\0')
|
|
*q++ = '\0';
|
|
while (csspace(*q))
|
|
q++;
|
|
if (*q == '(') {
|
|
*q++ = '\0';
|
|
arg = q;
|
|
while (*q != ')' && *q != '\0')
|
|
q++;
|
|
if (*q == '\0')
|
|
error("missing `)'");
|
|
else
|
|
*q++ = '\0';
|
|
}
|
|
if (*p == '\0') {
|
|
if (arg)
|
|
error("argument without option");
|
|
}
|
|
else if (strieq(p, "tab")) {
|
|
if (!arg)
|
|
error("`tab' option requires argument in parentheses");
|
|
else {
|
|
if (arg[0] == '\0' || arg[1] != '\0')
|
|
error("argument to `tab' option must be a single character");
|
|
else
|
|
opt->tab_char = arg[0];
|
|
}
|
|
}
|
|
else if (strieq(p, "linesize")) {
|
|
if (!arg)
|
|
error("`linesize' option requires argument in parentheses");
|
|
else {
|
|
if (sscanf(arg, "%d", &opt->linesize) != 1)
|
|
error("bad linesize `%s'", arg);
|
|
else if (opt->linesize <= 0) {
|
|
error("linesize must be positive");
|
|
opt->linesize = 0;
|
|
}
|
|
}
|
|
}
|
|
else if (strieq(p, "delim")) {
|
|
if (!arg)
|
|
error("`delim' option requires argument in parentheses");
|
|
else if (arg[0] == '\0' || arg[1] == '\0' || arg[2] != '\0')
|
|
error("argument to `delim' option must be two characters");
|
|
else {
|
|
opt->delim[0] = arg[0];
|
|
opt->delim[1] = arg[1];
|
|
}
|
|
}
|
|
else if (strieq(p, "center") || strieq(p, "centre")) {
|
|
if (arg)
|
|
error("`center' option does not take a argument");
|
|
opt->flags |= table::CENTER;
|
|
}
|
|
else if (strieq(p, "expand")) {
|
|
if (arg)
|
|
error("`expand' option does not take a argument");
|
|
opt->flags |= table::EXPAND;
|
|
}
|
|
else if (strieq(p, "box") || strieq(p, "frame")) {
|
|
if (arg)
|
|
error("`box' option does not take a argument");
|
|
opt->flags |= table::BOX;
|
|
}
|
|
else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) {
|
|
if (arg)
|
|
error("`doublebox' option does not take a argument");
|
|
opt->flags |= table::DOUBLEBOX;
|
|
}
|
|
else if (strieq(p, "allbox")) {
|
|
if (arg)
|
|
error("`allbox' option does not take a argument");
|
|
opt->flags |= table::ALLBOX;
|
|
}
|
|
else if (strieq(p, "nokeep")) {
|
|
if (arg)
|
|
error("`nokeep' option does not take a argument");
|
|
opt->flags |= table::NOKEEP;
|
|
}
|
|
else if (strieq(p, "decimalpoint")) {
|
|
if (!arg)
|
|
error("`decimalpoint' option requires argument in parentheses");
|
|
else {
|
|
if (arg[0] == '\0' || arg[1] != '\0')
|
|
error("argument to `decimalpoint' option must be a single character");
|
|
else
|
|
opt->decimal_point_char = arg[0];
|
|
}
|
|
}
|
|
else {
|
|
error("unrecognised global option `%1'", p);
|
|
// delete opt;
|
|
// return 0;
|
|
}
|
|
p = q;
|
|
}
|
|
return opt;
|
|
}
|
|
|
|
entry_modifier::entry_modifier()
|
|
: vertical_alignment(CENTER), zero_width(0), stagger(0)
|
|
{
|
|
vertical_spacing.inc = vertical_spacing.val = 0;
|
|
point_size.inc = point_size.val = 0;
|
|
}
|
|
|
|
entry_modifier::~entry_modifier()
|
|
{
|
|
}
|
|
|
|
entry_format::entry_format() : type(FORMAT_LEFT)
|
|
{
|
|
}
|
|
|
|
entry_format::entry_format(format_type t) : type(t)
|
|
{
|
|
}
|
|
|
|
void entry_format::debug_print() const
|
|
{
|
|
switch (type) {
|
|
case FORMAT_LEFT:
|
|
putc('l', stderr);
|
|
break;
|
|
case FORMAT_CENTER:
|
|
putc('c', stderr);
|
|
break;
|
|
case FORMAT_RIGHT:
|
|
putc('r', stderr);
|
|
break;
|
|
case FORMAT_NUMERIC:
|
|
putc('n', stderr);
|
|
break;
|
|
case FORMAT_ALPHABETIC:
|
|
putc('a', stderr);
|
|
break;
|
|
case FORMAT_SPAN:
|
|
putc('s', stderr);
|
|
break;
|
|
case FORMAT_VSPAN:
|
|
putc('^', stderr);
|
|
break;
|
|
case FORMAT_HLINE:
|
|
putc('_', stderr);
|
|
break;
|
|
case FORMAT_DOUBLE_HLINE:
|
|
putc('=', stderr);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
if (point_size.val != 0) {
|
|
putc('p', stderr);
|
|
if (point_size.inc > 0)
|
|
putc('+', stderr);
|
|
else if (point_size.inc < 0)
|
|
putc('-', stderr);
|
|
fprintf(stderr, "%d ", point_size.val);
|
|
}
|
|
if (vertical_spacing.val != 0) {
|
|
putc('v', stderr);
|
|
if (vertical_spacing.inc > 0)
|
|
putc('+', stderr);
|
|
else if (vertical_spacing.inc < 0)
|
|
putc('-', stderr);
|
|
fprintf(stderr, "%d ", vertical_spacing.val);
|
|
}
|
|
if (!font.empty()) {
|
|
putc('f', stderr);
|
|
put_string(font, stderr);
|
|
putc(' ', stderr);
|
|
}
|
|
switch (vertical_alignment) {
|
|
case entry_modifier::CENTER:
|
|
break;
|
|
case entry_modifier::TOP:
|
|
putc('t', stderr);
|
|
break;
|
|
case entry_modifier::BOTTOM:
|
|
putc('d', stderr);
|
|
break;
|
|
}
|
|
if (zero_width)
|
|
putc('z', stderr);
|
|
if (stagger)
|
|
putc('u', stderr);
|
|
}
|
|
|
|
struct format {
|
|
int nrows;
|
|
int ncolumns;
|
|
int *separation;
|
|
string *width;
|
|
char *equal;
|
|
entry_format **entry;
|
|
char **vline;
|
|
|
|
format(int nr, int nc);
|
|
~format();
|
|
void add_rows(int n);
|
|
};
|
|
|
|
format::format(int nr, int nc) : nrows(nr), ncolumns(nc)
|
|
{
|
|
int i;
|
|
separation = ncolumns > 1 ? new int[ncolumns - 1] : 0;
|
|
for (i = 0; i < ncolumns-1; i++)
|
|
separation[i] = -1;
|
|
width = new string[ncolumns];
|
|
equal = new char[ncolumns];
|
|
for (i = 0; i < ncolumns; i++)
|
|
equal[i] = 0;
|
|
entry = new entry_format *[nrows];
|
|
for (i = 0; i < nrows; i++)
|
|
entry[i] = new entry_format[ncolumns];
|
|
vline = new char*[nrows];
|
|
for (i = 0; i < nrows; i++) {
|
|
vline[i] = new char[ncolumns+1];
|
|
for (int j = 0; j < ncolumns+1; j++)
|
|
vline[i][j] = 0;
|
|
}
|
|
}
|
|
|
|
void format::add_rows(int n)
|
|
{
|
|
int i;
|
|
char **old_vline = vline;
|
|
vline = new char*[nrows + n];
|
|
for (i = 0; i < nrows; i++)
|
|
vline[i] = old_vline[i];
|
|
a_delete old_vline;
|
|
for (i = 0; i < n; i++) {
|
|
vline[nrows + i] = new char[ncolumns + 1];
|
|
for (int j = 0; j < ncolumns + 1; j++)
|
|
vline[nrows + i][j] = 0;
|
|
}
|
|
entry_format **old_entry = entry;
|
|
entry = new entry_format *[nrows + n];
|
|
for (i = 0; i < nrows; i++)
|
|
entry[i] = old_entry[i];
|
|
a_delete old_entry;
|
|
for (i = 0; i < n; i++)
|
|
entry[nrows + i] = new entry_format[ncolumns];
|
|
nrows += n;
|
|
}
|
|
|
|
format::~format()
|
|
{
|
|
a_delete separation;
|
|
ad_delete(ncolumns) width;
|
|
a_delete equal;
|
|
for (int i = 0; i < nrows; i++) {
|
|
a_delete vline[i];
|
|
ad_delete(ncolumns) entry[i];
|
|
}
|
|
a_delete vline;
|
|
a_delete entry;
|
|
}
|
|
|
|
struct input_entry_format : public entry_format {
|
|
input_entry_format *next;
|
|
string width;
|
|
int separation;
|
|
int vline;
|
|
int pre_vline;
|
|
int last_column;
|
|
int equal;
|
|
input_entry_format(format_type, input_entry_format * = 0);
|
|
~input_entry_format();
|
|
void debug_print();
|
|
};
|
|
|
|
input_entry_format::input_entry_format(format_type t, input_entry_format *p)
|
|
: entry_format(t), next(p)
|
|
{
|
|
separation = -1;
|
|
last_column = 0;
|
|
vline = 0;
|
|
pre_vline = 0;
|
|
equal = 0;
|
|
}
|
|
|
|
input_entry_format::~input_entry_format()
|
|
{
|
|
}
|
|
|
|
void free_input_entry_format_list(input_entry_format *list)
|
|
{
|
|
while (list) {
|
|
input_entry_format *tem = list;
|
|
list = list->next;
|
|
delete tem;
|
|
}
|
|
}
|
|
|
|
void input_entry_format::debug_print()
|
|
{
|
|
int i;
|
|
for (i = 0; i < pre_vline; i++)
|
|
putc('|', stderr);
|
|
entry_format::debug_print();
|
|
if (!width.empty()) {
|
|
putc('w', stderr);
|
|
putc('(', stderr);
|
|
put_string(width, stderr);
|
|
putc(')', stderr);
|
|
}
|
|
if (equal)
|
|
putc('e', stderr);
|
|
if (separation >= 0)
|
|
fprintf(stderr, "%d", separation);
|
|
for (i = 0; i < vline; i++)
|
|
putc('|', stderr);
|
|
if (last_column)
|
|
putc(',', stderr);
|
|
}
|
|
|
|
// Return zero if we should give up on this table.
|
|
// If this is a continuation format line, current_format will be the current
|
|
// format line.
|
|
|
|
format *process_format(table_input &in, options *opt,
|
|
format *current_format = 0)
|
|
{
|
|
input_entry_format *list = 0;
|
|
int c = in.get();
|
|
for (;;) {
|
|
int pre_vline = 0;
|
|
int got_format = 0;
|
|
int got_period = 0;
|
|
format_type t;
|
|
for (;;) {
|
|
if (c == EOF) {
|
|
error("end of input while processing format");
|
|
free_input_entry_format_list(list);
|
|
return 0;
|
|
}
|
|
switch (c) {
|
|
case 'n':
|
|
case 'N':
|
|
t = FORMAT_NUMERIC;
|
|
got_format = 1;
|
|
break;
|
|
case 'a':
|
|
case 'A':
|
|
got_format = 1;
|
|
t = FORMAT_ALPHABETIC;
|
|
break;
|
|
case 'c':
|
|
case 'C':
|
|
got_format = 1;
|
|
t = FORMAT_CENTER;
|
|
break;
|
|
case 'l':
|
|
case 'L':
|
|
got_format = 1;
|
|
t = FORMAT_LEFT;
|
|
break;
|
|
case 'r':
|
|
case 'R':
|
|
got_format = 1;
|
|
t = FORMAT_RIGHT;
|
|
break;
|
|
case 's':
|
|
case 'S':
|
|
got_format = 1;
|
|
t = FORMAT_SPAN;
|
|
break;
|
|
case '^':
|
|
got_format = 1;
|
|
t = FORMAT_VSPAN;
|
|
break;
|
|
case '_':
|
|
case '-': // tbl also accepts this
|
|
got_format = 1;
|
|
t = FORMAT_HLINE;
|
|
break;
|
|
case '=':
|
|
got_format = 1;
|
|
t = FORMAT_DOUBLE_HLINE;
|
|
break;
|
|
case '.':
|
|
got_period = 1;
|
|
break;
|
|
case '|':
|
|
pre_vline++;
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
break;
|
|
default:
|
|
if (c == opt->tab_char)
|
|
break;
|
|
error("unrecognised format `%1'", char(c));
|
|
free_input_entry_format_list(list);
|
|
return 0;
|
|
}
|
|
if (got_period)
|
|
break;
|
|
c = in.get();
|
|
if (got_format)
|
|
break;
|
|
}
|
|
if (got_period)
|
|
break;
|
|
list = new input_entry_format(t, list);
|
|
if (pre_vline)
|
|
list->pre_vline = pre_vline;
|
|
int success = 1;
|
|
do {
|
|
switch (c) {
|
|
case 't':
|
|
case 'T':
|
|
c = in.get();
|
|
list->vertical_alignment = entry_modifier::TOP;
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
c = in.get();
|
|
list->vertical_alignment = entry_modifier::BOTTOM;
|
|
break;
|
|
case 'u':
|
|
case 'U':
|
|
c = in.get();
|
|
list->stagger = 1;
|
|
break;
|
|
case 'z':
|
|
case 'Z':
|
|
c = in.get();
|
|
list->zero_width = 1;
|
|
break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
{
|
|
int w = 0;
|
|
do {
|
|
w = w*10 + (c - '0');
|
|
c = in.get();
|
|
} while (c != EOF && csdigit(c));
|
|
list->separation = w;
|
|
}
|
|
break;
|
|
case 'f':
|
|
case 'F':
|
|
do {
|
|
c = in.get();
|
|
} while (c == ' ' || c == '\t');
|
|
if (c == EOF) {
|
|
error("missing font name");
|
|
break;
|
|
}
|
|
if (c == '(') {
|
|
for (;;) {
|
|
c = in.get();
|
|
if (c == EOF || c == ' ' || c == '\t') {
|
|
error("missing `)'");
|
|
break;
|
|
}
|
|
if (c == ')') {
|
|
c = in.get();
|
|
break;
|
|
}
|
|
list->font += char(c);
|
|
}
|
|
}
|
|
else {
|
|
list->font = c;
|
|
char cc = c;
|
|
c = in.get();
|
|
if (!csdigit(cc)
|
|
&& c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') {
|
|
list->font += char(c);
|
|
c = in.get();
|
|
}
|
|
}
|
|
break;
|
|
case 'v':
|
|
case 'V':
|
|
c = in.get();
|
|
list->vertical_spacing.val = 0;
|
|
list->vertical_spacing.inc = 0;
|
|
if (c == '+' || c == '-') {
|
|
list->vertical_spacing.inc = (c == '+' ? 1 : -1);
|
|
c = in.get();
|
|
}
|
|
if (c == EOF || !csdigit(c)) {
|
|
error("`v' modifier must be followed by number");
|
|
list->vertical_spacing.inc = 0;
|
|
}
|
|
else {
|
|
do {
|
|
list->vertical_spacing.val *= 10;
|
|
list->vertical_spacing.val += c - '0';
|
|
c = in.get();
|
|
} while (c != EOF && csdigit(c));
|
|
}
|
|
if (list->vertical_spacing.val > MAX_VERTICAL_SPACING
|
|
|| list->vertical_spacing.val < -MAX_VERTICAL_SPACING) {
|
|
error("unreasonable point size");
|
|
list->vertical_spacing.val = 0;
|
|
list->vertical_spacing.inc = 0;
|
|
}
|
|
break;
|
|
case 'p':
|
|
case 'P':
|
|
c = in.get();
|
|
list->point_size.val = 0;
|
|
list->point_size.inc = 0;
|
|
if (c == '+' || c == '-') {
|
|
list->point_size.inc = (c == '+' ? 1 : -1);
|
|
c = in.get();
|
|
}
|
|
if (c == EOF || !csdigit(c)) {
|
|
error("`p' modifier must be followed by number");
|
|
list->point_size.inc = 0;
|
|
}
|
|
else {
|
|
do {
|
|
list->point_size.val *= 10;
|
|
list->point_size.val += c - '0';
|
|
c = in.get();
|
|
} while (c != EOF && csdigit(c));
|
|
}
|
|
if (list->point_size.val > MAX_POINT_SIZE
|
|
|| list->point_size.val < -MAX_POINT_SIZE) {
|
|
error("unreasonable point size");
|
|
list->point_size.val = 0;
|
|
list->point_size.inc = 0;
|
|
}
|
|
break;
|
|
case 'w':
|
|
case 'W':
|
|
c = in.get();
|
|
while (c == ' ' || c == '\t')
|
|
c = in.get();
|
|
if (c == '(') {
|
|
list->width = "";
|
|
c = in.get();
|
|
while (c != ')') {
|
|
if (c == EOF || c == '\n') {
|
|
error("missing `)'");
|
|
free_input_entry_format_list(list);
|
|
return 0;
|
|
}
|
|
list->width += c;
|
|
c = in.get();
|
|
}
|
|
c = in.get();
|
|
}
|
|
else {
|
|
if (c == '+' || c == '-') {
|
|
list->width = char(c);
|
|
c = in.get();
|
|
}
|
|
else
|
|
list->width = "";
|
|
if (c == EOF || !csdigit(c))
|
|
error("bad argument for `w' modifier");
|
|
else {
|
|
do {
|
|
list->width += char(c);
|
|
c = in.get();
|
|
} while (c != EOF && csdigit(c));
|
|
}
|
|
}
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
c = in.get();
|
|
list->equal++;
|
|
break;
|
|
case '|':
|
|
c = in.get();
|
|
list->vline++;
|
|
break;
|
|
case 'B':
|
|
case 'b':
|
|
c = in.get();
|
|
list->font = "B";
|
|
break;
|
|
case 'I':
|
|
case 'i':
|
|
c = in.get();
|
|
list->font = "I";
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
c = in.get();
|
|
break;
|
|
default:
|
|
if (c == opt->tab_char)
|
|
c = in.get();
|
|
else
|
|
success = 0;
|
|
break;
|
|
}
|
|
} while (success);
|
|
if (list->vline > 2) {
|
|
list->vline = 2;
|
|
error("more than 2 vertical bars between key letters");
|
|
}
|
|
if (c == '\n' || c == ',') {
|
|
c = in.get();
|
|
list->last_column = 1;
|
|
}
|
|
}
|
|
if (c == '.') {
|
|
do {
|
|
c = in.get();
|
|
} while (c == ' ' || c == '\t');
|
|
if (c != '\n') {
|
|
error("`.' not last character on line");
|
|
free_input_entry_format_list(list);
|
|
return 0;
|
|
}
|
|
}
|
|
if (!list) {
|
|
error("no format");
|
|
free_input_entry_format_list(list);
|
|
return 0;
|
|
}
|
|
list->last_column = 1;
|
|
// now reverse the list so that the first row is at the beginning
|
|
input_entry_format *rev = 0;
|
|
while (list != 0) {
|
|
input_entry_format *tem = list->next;
|
|
list->next = rev;
|
|
rev = list;
|
|
list = tem;
|
|
}
|
|
list = rev;
|
|
input_entry_format *tem;
|
|
|
|
#if 0
|
|
for (tem = list; tem; tem = tem->next)
|
|
tem->debug_print();
|
|
putc('\n', stderr);
|
|
#endif
|
|
// compute number of columns and rows
|
|
int ncolumns = 0;
|
|
int nrows = 0;
|
|
int col = 0;
|
|
for (tem = list; tem; tem = tem->next) {
|
|
if (tem->last_column) {
|
|
if (col >= ncolumns)
|
|
ncolumns = col + 1;
|
|
col = 0;
|
|
nrows++;
|
|
}
|
|
else
|
|
col++;
|
|
}
|
|
int row;
|
|
format *f;
|
|
if (current_format) {
|
|
if (ncolumns > current_format->ncolumns) {
|
|
error("cannot increase the number of columns in a continued format");
|
|
free_input_entry_format_list(list);
|
|
return 0;
|
|
}
|
|
f = current_format;
|
|
row = f->nrows;
|
|
f->add_rows(nrows);
|
|
}
|
|
else {
|
|
f = new format(nrows, ncolumns);
|
|
row = 0;
|
|
}
|
|
col = 0;
|
|
for (tem = list; tem; tem = tem->next) {
|
|
f->entry[row][col] = *tem;
|
|
if (col < ncolumns-1) {
|
|
// use the greatest separation
|
|
if (tem->separation > f->separation[col]) {
|
|
if (current_format)
|
|
error("cannot change column separation in continued format");
|
|
else
|
|
f->separation[col] = tem->separation;
|
|
}
|
|
}
|
|
else if (tem->separation >= 0)
|
|
error("column separation specified for last column");
|
|
if (tem->equal && !f->equal[col]) {
|
|
if (current_format)
|
|
error("cannot change which columns are equal in continued format");
|
|
else
|
|
f->equal[col] = 1;
|
|
}
|
|
if (!tem->width.empty()) {
|
|
// use the last width
|
|
if (!f->width[col].empty() && f->width[col] != tem->width)
|
|
error("multiple widths for column %1", col+1);
|
|
f->width[col] = tem->width;
|
|
}
|
|
if (tem->pre_vline) {
|
|
assert(col == 0);
|
|
f->vline[row][col] = tem->pre_vline;
|
|
}
|
|
f->vline[row][col+1] = tem->vline;
|
|
if (tem->last_column) {
|
|
row++;
|
|
col = 0;
|
|
}
|
|
else
|
|
col++;
|
|
}
|
|
free_input_entry_format_list(list);
|
|
for (col = 0; col < ncolumns; col++) {
|
|
entry_format *e = f->entry[f->nrows-1] + col;
|
|
if (e->type != FORMAT_HLINE
|
|
&& e->type != FORMAT_DOUBLE_HLINE
|
|
&& e->type != FORMAT_SPAN)
|
|
break;
|
|
}
|
|
if (col >= ncolumns) {
|
|
error("last row of format is all lines");
|
|
delete f;
|
|
return 0;
|
|
}
|
|
return f;
|
|
}
|
|
|
|
table *process_data(table_input &in, format *f, options *opt)
|
|
{
|
|
char tab_char = opt->tab_char;
|
|
int ncolumns = f->ncolumns;
|
|
int current_row = 0;
|
|
int format_index = 0;
|
|
int give_up = 0;
|
|
enum { DATA_INPUT_LINE, TROFF_INPUT_LINE, SINGLE_HLINE, DOUBLE_HLINE } type;
|
|
table *tbl = new table(ncolumns, opt->flags, opt->linesize,
|
|
opt->decimal_point_char);
|
|
if (opt->delim[0] != '\0')
|
|
tbl->set_delim(opt->delim[0], opt->delim[1]);
|
|
for (;;) {
|
|
// first determine what type of line this is
|
|
int c = in.get();
|
|
if (c == EOF)
|
|
break;
|
|
if (c == '.') {
|
|
int d = in.get();
|
|
if (d != EOF && csdigit(d)) {
|
|
in.unget(d);
|
|
type = DATA_INPUT_LINE;
|
|
}
|
|
else {
|
|
in.unget(d);
|
|
type = TROFF_INPUT_LINE;
|
|
}
|
|
}
|
|
else if (c == '_' || c == '=') {
|
|
int d = in.get();
|
|
if (d == '\n') {
|
|
if (c == '_')
|
|
type = SINGLE_HLINE;
|
|
else
|
|
type = DOUBLE_HLINE;
|
|
}
|
|
else {
|
|
in.unget(d);
|
|
type = DATA_INPUT_LINE;
|
|
}
|
|
}
|
|
else {
|
|
type = DATA_INPUT_LINE;
|
|
}
|
|
switch (type) {
|
|
case DATA_INPUT_LINE:
|
|
{
|
|
string input_entry;
|
|
if (format_index >= f->nrows)
|
|
format_index = f->nrows - 1;
|
|
// A format row that is all lines doesn't use up a data line.
|
|
while (format_index < f->nrows - 1) {
|
|
int c;
|
|
for (c = 0; c < ncolumns; c++) {
|
|
entry_format *e = f->entry[format_index] + c;
|
|
if (e->type != FORMAT_HLINE
|
|
&& e->type != FORMAT_DOUBLE_HLINE
|
|
// Unfortunately tbl treats a span as needing data.
|
|
// && e->type != FORMAT_SPAN
|
|
)
|
|
break;
|
|
}
|
|
if (c < ncolumns)
|
|
break;
|
|
for (c = 0; c < ncolumns; c++)
|
|
tbl->add_entry(current_row, c, input_entry,
|
|
f->entry[format_index] + c, current_filename,
|
|
current_lineno);
|
|
tbl->add_vlines(current_row, f->vline[format_index]);
|
|
format_index++;
|
|
current_row++;
|
|
}
|
|
entry_format *line_format = f->entry[format_index];
|
|
int col = 0;
|
|
int row_comment = 0;
|
|
for (;;) {
|
|
if (c == tab_char || c == '\n') {
|
|
int ln = current_lineno;
|
|
if (c == '\n')
|
|
--ln;
|
|
while (col < ncolumns
|
|
&& line_format[col].type == FORMAT_SPAN) {
|
|
tbl->add_entry(current_row, col, "", &line_format[col],
|
|
current_filename, ln);
|
|
col++;
|
|
}
|
|
if (c == '\n' && input_entry.length() == 2
|
|
&& input_entry[0] == 'T' && input_entry[1] == '{') {
|
|
input_entry = "";
|
|
ln++;
|
|
enum {
|
|
START, MIDDLE, GOT_T, GOT_RIGHT_BRACE, GOT_DOT,
|
|
GOT_l, GOT_lf, END
|
|
} state = START;
|
|
while (state != END) {
|
|
c = in.get();
|
|
if (c == EOF)
|
|
break;
|
|
switch (state) {
|
|
case START:
|
|
if (c == 'T')
|
|
state = GOT_T;
|
|
else if (c == '.')
|
|
state = GOT_DOT;
|
|
else {
|
|
input_entry += c;
|
|
if (c != '\n')
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
case GOT_T:
|
|
if (c == '}')
|
|
state = GOT_RIGHT_BRACE;
|
|
else {
|
|
input_entry += 'T';
|
|
input_entry += c;
|
|
state = c == '\n' ? START : MIDDLE;
|
|
}
|
|
break;
|
|
case GOT_DOT:
|
|
if (c == 'l')
|
|
state = GOT_l;
|
|
else {
|
|
input_entry += '.';
|
|
input_entry += c;
|
|
state = c == '\n' ? START : MIDDLE;
|
|
}
|
|
break;
|
|
case GOT_l:
|
|
if (c == 'f')
|
|
state = GOT_lf;
|
|
else {
|
|
input_entry += ".l";
|
|
input_entry += c;
|
|
state = c == '\n' ? START : MIDDLE;
|
|
}
|
|
break;
|
|
case GOT_lf:
|
|
if (c == ' ' || c == '\n' || compatible_flag) {
|
|
string args;
|
|
input_entry += ".lf";
|
|
while (c != EOF) {
|
|
args += c;
|
|
if (c == '\n')
|
|
break;
|
|
c = in.get();
|
|
}
|
|
args += '\0';
|
|
interpret_lf_args(args.contents());
|
|
// remove the '\0'
|
|
args.set_length(args.length() - 1);
|
|
input_entry += args;
|
|
state = START;
|
|
}
|
|
else {
|
|
input_entry += ".lf";
|
|
input_entry += c;
|
|
state = MIDDLE;
|
|
}
|
|
break;
|
|
case GOT_RIGHT_BRACE:
|
|
if (c == '\n' || c == tab_char)
|
|
state = END;
|
|
else {
|
|
input_entry += 'T';
|
|
input_entry += '}';
|
|
input_entry += c;
|
|
state = c == '\n' ? START : MIDDLE;
|
|
}
|
|
break;
|
|
case MIDDLE:
|
|
if (c == '\n')
|
|
state = START;
|
|
input_entry += c;
|
|
break;
|
|
case END:
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
if (c == EOF) {
|
|
error("end of data in middle of text block");
|
|
give_up = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (col >= ncolumns) {
|
|
if (!input_entry.empty()) {
|
|
if (input_entry.length() >= 2
|
|
&& input_entry[0] == '\\'
|
|
&& input_entry[1] == '"')
|
|
row_comment = 1;
|
|
else if (!row_comment) {
|
|
if (c == '\n')
|
|
in.unget(c);
|
|
input_entry += '\0';
|
|
error("excess data entry `%1' discarded",
|
|
input_entry.contents());
|
|
if (c == '\n')
|
|
(void)in.get();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
tbl->add_entry(current_row, col, input_entry,
|
|
&line_format[col], current_filename, ln);
|
|
col++;
|
|
if (c == '\n')
|
|
break;
|
|
input_entry = "";
|
|
}
|
|
else
|
|
input_entry += c;
|
|
c = in.get();
|
|
if (c == EOF)
|
|
break;
|
|
}
|
|
if (give_up)
|
|
break;
|
|
input_entry = "";
|
|
for (; col < ncolumns; col++)
|
|
tbl->add_entry(current_row, col, input_entry, &line_format[col],
|
|
current_filename, current_lineno - 1);
|
|
tbl->add_vlines(current_row, f->vline[format_index]);
|
|
current_row++;
|
|
format_index++;
|
|
}
|
|
break;
|
|
case TROFF_INPUT_LINE:
|
|
{
|
|
string line;
|
|
int ln = current_lineno;
|
|
for (;;) {
|
|
line += c;
|
|
if (c == '\n')
|
|
break;
|
|
c = in.get();
|
|
if (c == EOF) {
|
|
break;
|
|
}
|
|
}
|
|
tbl->add_text_line(current_row, line, current_filename, ln);
|
|
if (line.length() >= 4
|
|
&& line[0] == '.' && line[1] == 'T' && line[2] == '&') {
|
|
format *newf = process_format(in, opt, f);
|
|
if (newf == 0)
|
|
give_up = 1;
|
|
else
|
|
f = newf;
|
|
}
|
|
if (line.length() >= 3
|
|
&& line[0] == '.' && line[1] == 'f' && line[2] == 'f') {
|
|
line += '\0';
|
|
interpret_lf_args(line.contents() + 3);
|
|
}
|
|
}
|
|
break;
|
|
case SINGLE_HLINE:
|
|
tbl->add_single_hline(current_row);
|
|
break;
|
|
case DOUBLE_HLINE:
|
|
tbl->add_double_hline(current_row);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
if (give_up)
|
|
break;
|
|
}
|
|
if (!give_up && current_row == 0) {
|
|
error("no real data");
|
|
give_up = 1;
|
|
}
|
|
if (give_up) {
|
|
delete tbl;
|
|
return 0;
|
|
}
|
|
// Do this here rather than at the beginning in case continued formats
|
|
// change it.
|
|
int i;
|
|
for (i = 0; i < ncolumns - 1; i++)
|
|
if (f->separation[i] >= 0)
|
|
tbl->set_column_separation(i, f->separation[i]);
|
|
for (i = 0; i < ncolumns; i++)
|
|
if (!f->width[i].empty())
|
|
tbl->set_minimum_width(i, f->width[i]);
|
|
for (i = 0; i < ncolumns; i++)
|
|
if (f->equal[i])
|
|
tbl->set_equal_column(i);
|
|
return tbl;
|
|
}
|
|
|
|
void process_table(table_input &in)
|
|
{
|
|
int c;
|
|
options *opt = 0;
|
|
format *form = 0;
|
|
table *tbl = 0;
|
|
if ((opt = process_options(in)) != 0
|
|
&& (form = process_format(in, opt)) != 0
|
|
&& (tbl = process_data(in, form, opt)) != 0) {
|
|
tbl->print();
|
|
delete tbl;
|
|
}
|
|
else {
|
|
error("giving up on this table");
|
|
while ((c = in.get()) != EOF)
|
|
;
|
|
}
|
|
delete opt;
|
|
delete form;
|
|
if (!in.ended())
|
|
error("premature end of file");
|
|
}
|
|
|
|
static void usage()
|
|
{
|
|
fprintf(stderr, "usage: %s [ -vC ] [ files... ]\n", program_name);
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
program_name = argv[0];
|
|
static char stderr_buf[BUFSIZ];
|
|
setbuf(stderr, stderr_buf);
|
|
int opt;
|
|
while ((opt = getopt(argc, argv, "vCT:")) != EOF)
|
|
switch (opt) {
|
|
case 'C':
|
|
compatible_flag = 1;
|
|
break;
|
|
case 'v':
|
|
{
|
|
extern const char *version_string;
|
|
fprintf(stderr, "GNU tbl version %s\n", version_string);
|
|
fflush(stderr);
|
|
break;
|
|
}
|
|
case 'T':
|
|
// I'm sick of getting bug reports from IRIX users
|
|
break;
|
|
case '?':
|
|
usage();
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
printf(".if !\\n(.g .ab GNU tbl requires GNU troff.\n"
|
|
".if !dTS .ds TS\n"
|
|
".if !dTE .ds TE\n");
|
|
if (argc > optind) {
|
|
for (int i = optind; i < argc; i++)
|
|
if (argv[i][0] == '-' && argv[i][1] == '\0') {
|
|
current_filename = "-";
|
|
current_lineno = 1;
|
|
printf(".lf 1 -\n");
|
|
process_input_file(stdin);
|
|
}
|
|
else {
|
|
errno = 0;
|
|
FILE *fp = fopen(argv[i], "r");
|
|
if (fp == 0) {
|
|
current_lineno = -1;
|
|
error("can't open `%1': %2", argv[i], strerror(errno));
|
|
}
|
|
else {
|
|
current_lineno = 1;
|
|
current_filename = argv[i];
|
|
printf(".lf 1 %s\n", current_filename);
|
|
process_input_file(fp);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
current_filename = "-";
|
|
current_lineno = 1;
|
|
printf(".lf 1 -\n");
|
|
process_input_file(stdin);
|
|
}
|
|
if (ferror(stdout) || fflush(stdout) < 0)
|
|
fatal("output error");
|
|
return 0;
|
|
}
|
|
|