// -*- C++ -*- /* Copyright (C) 1989, 1990, 1991, 1992, 2000, 2001, 2002, 2003, 2004, 2005 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, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "table.h" #define MAX_POINT_SIZE 99 #define MAX_VERTICAL_SPACING 72 extern "C" const char *Version_string; int compatible_flag = 0; class table_input { FILE *fp; enum { START, MIDDLE, REREAD_T, REREAD_TE, REREAD_E, LEADER_1, LEADER_2, LEADER_3, LEADER_4, 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("invalid input character code 0"); break; } } return c; } break; case MIDDLE: // handle line continuation and uninterpreted leader character if ((c = getc(fp)) == '\\') { c = getc(fp); if (c == '\n') c = getc(fp); // perhaps state ought to be START now else if (c == 'a' && compatible_flag) { state = LEADER_1; return '\\'; } 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("invalid 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 LEADER_1: state = LEADER_2; return '*'; case LEADER_2: state = LEADER_3; return '('; case LEADER_3: state = LEADER_4; return PREFIX_CHAR; case LEADER_4: state = MIDDLE; return LEADER_CHAR; 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), linesize(0), tab_char('\t'), decimal_point_char('.') { 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 (!csalpha(*p) && *p != '\0') p++; if (*p == '\0') break; char *q = p; while (csalpha(*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 an argument"); opt->flags |= table::CENTER; } else if (strieq(p, "expand")) { if (arg) error("`expand' option does not take an argument"); opt->flags |= table::EXPAND; } else if (strieq(p, "box") || strieq(p, "frame")) { if (arg) error("`box' option does not take an argument"); opt->flags |= table::BOX; } else if (strieq(p, "doublebox") || strieq(p, "doubleframe")) { if (arg) error("`doublebox' option does not take an argument"); opt->flags |= table::DOUBLEBOX; } else if (strieq(p, "allbox")) { if (arg) error("`allbox' option does not take an argument"); opt->flags |= table::ALLBOX; } else if (strieq(p, "nokeep")) { if (arg) error("`nokeep' option does not take an argument"); opt->flags |= table::NOKEEP; } else if (strieq(p, "nospaces")) { if (arg) error("`nospaces' option does not take an argument"); opt->flags |= table::NOSPACES; } 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); } if (!macro.empty()) { putc('m', stderr); put_string(macro, 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 = FORMAT_LEFT; 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 'x': case 'X': do { c = in.get(); } while (c == ' ' || c == '\t'); if (c == EOF) { error("missing macro name"); break; } if (c == '(') { for (;;) { c = in.get(); if (c == EOF || c == ' ' || c == '\t') { error("missing `)'"); break; } if (c == ')') { c = in.get(); break; } list->macro += char(c); } } else { list->macro = c; char cc = c; c = in.get(); if (!csdigit(cc) && c != EOF && c != ' ' && c != '\t' && c != '.' && c != '\n') { list->macro += 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 vertical spacing"); 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 cnt; for (cnt = 0; cnt < ncolumns; cnt++) { entry_format *e = f->entry[format_index] + cnt; if (e->type != FORMAT_HLINE && e->type != FORMAT_DOUBLE_HLINE // Unfortunately tbl treats a span as needing data. // && e->type != FORMAT_SPAN ) break; } if (cnt < ncolumns) break; for (cnt = 0; cnt < ncolumns; cnt++) tbl->add_entry(current_row, cnt, input_entry, f->entry[format_index] + cnt, 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; if ((opt->flags & table::NOSPACES)) input_entry.remove_spaces(); 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 ((opt->flags & table::NOSPACES)) { while (c == ' ') c = in.get(); if (c == EOF) break; } if (c == '\n' || c == tab_char) state = END; else { input_entry += 'T'; input_entry += '}'; input_entry += c; state = 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] == 'l' && 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) { 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 (in.get() != EOF) ; } delete opt; delete form; if (!in.ended()) error("premature end of file"); } static void usage(FILE *stream) { fprintf(stream, "usage: %s [ -vC ] [ files... ]\n", program_name); } int main(int argc, char **argv) { program_name = argv[0]; static char stderr_buf[BUFSIZ]; setbuf(stderr, stderr_buf); int opt; static const struct option long_options[] = { { "help", no_argument, 0, CHAR_MAX + 1 }, { "version", no_argument, 0, 'v' }, { NULL, 0, 0, 0 } }; while ((opt = getopt_long(argc, argv, "vCT:", long_options, NULL)) != EOF) switch (opt) { case 'C': compatible_flag = 1; break; case 'v': { printf("GNU tbl (groff) version %s\n", Version_string); exit(0); break; } case 'T': // I'm sick of getting bug reports from IRIX users break; case CHAR_MAX + 1: // --help usage(stdout); exit(0); break; case '?': usage(stderr); exit(1); 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) fatal("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; }