/*++++++++++++++
iIDENTIFICATION trcol.c
.LANGUAGE       C
.AUTHOR         Francois Ochsenbein [CDS]
.ENVIRONMENT    
.KEYWORDS       
.VERSION  1.0   31-Oct-1991 (rmcol)
.VERSION  1.1   23-Apr-1992
.VERSION  1.2   08-Jun-1993: Don't remove newlines !
.VERSION  1.3   28-Jul-1997: Bug in trc(...)...
.VERSION  1.4   21-Apr-2005: Added -a -A -l -r
.VERSION  1.41  02-Sep-2005: Bug in translation when transte at end-of-line
.VERSION  1.42  06-May-2007: Cosmetics
.VERSION  1.5   30-Nov-2008: -s option
.VERSION  1.51  27-Dec-2011: -t option, fill 2nd string with blanks
.VERSION  1.52  11-Jun-2012: -t option, accept \0 to remove characters
.COMMENTS       Translate just columns of a file
   NEW METHOD: Just zero the columns to ignore 
---------------*/

#define VERSION	"1.52"

/* Function Prototypes */
#ifndef _ARGS
#ifdef __STDC__
#define _ARGS(A)        A       /* ANSI */
#else
#define _ARGS(A)        ()      /* Traditional */
#define const
#endif
#endif
typedef int (*SORT_FCT)_ARGS((const void *, const void *)) ;

#include <stdio.h>
#include <fcntl.h>
#ifndef O_BINARY
#define O_BINARY 0
#endif
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>

#define MAXCOL 		0x7fffffff

#define MAX(x,y)	((x) >= (y) ? (x) : (y))
#define ITEMS(a)	sizeof(a)/sizeof(a[0])

typedef struct {	/* Specifies one operation */
	int col0;	/* First column	*/
	int col1;	/* Last  column */
	int prio;	/* Priority: 1=tr, 2=justify, 3=delete */
	int (*f)();	/* Application of Function */
	char *parm;	/* Parameter f  */
  } FIELD;
static FIELD *ff = (FIELD *)0;
static char *filename;
static int  line_length;
static long lineno = 0;
static long colno  = 0;
static int  non_sp = 0;

/* Vector of Functions */

static char pgm[] = "trcol";
static char usage[] = "\
Usage: %s [-[a|A|b|B|l|r|s]cols]... [-tcols s1 s2] [file...]\n\
";
static char help[] = "\
 -acols: transforms columns to lowercase\n\
 -Acols: transforms columns to UPPERCASE\n\
 -bcols: Delete columns with blank test\n\
 -Bcols: same as -b, but without error message (just status is set)\n\
 -dcols: Delete specified columns \n\
 -lcols: left-align specified columns\n\
 -rcols: right-align specified columns\n\
 -scols: squeeze blanks in specified columns\n\
 -tcols: Translate specified columns (accepts C-like controls; \\0 removes)\n\
   cols  are specified with commas and dashes. e.g. 1,2,5-10\n\
" ;

/*==================================================================
		Internal Routines
 *==================================================================*/
static int strloc(text, c)
/*++++++++++++++++
.PURPOSE  Locate specified character
.RETURNS  Index of located char
-----------------*/
  char *text;     /* IN: String to interpret */
  int  c;         /* IN: Char to look for */
{
  char *s;
    for (s = text; *s && (*s != c); s++) ;
    return(s-text);
}

static void error(t0, t1)
/*++++++++++++++++
.PURPOSE  Error exit
.RETURNS  -
-----------------*/
  char *t0;	/* IN: Text before error */
  char *t1;	/* IN: Text before error */
{
    if (t0)   fprintf(stderr, "*** %s: %s%s\n", pgm, t0, t1);
    fprintf(stderr, usage, pgm);
    exit(1);
}

/*==================================================================*/
static int trc(in, out)
/*++++++++++++++++
.PURPOSE  Interpret a string according to sed standards (a-z = a...z)
.RETURNS  Number of bytes in out
.REMARKS  \octal or \f \b \r \n ...
-----------------*/
  char *in;       /* IN: String to interpret */
  char *out;      /* OUT: Result of interpretation */
{
  char *p, *q, *e, c; int o;  c = 0177;

   for (p=in, q=out, e=out+256; *p && (q<e); p++, q++) {
	if ((*p == '-') && (c < p[1])) {
		for (++p; (++c <= *p) && (q<e); q++)  *q = c;
		c = 0177;
		--q;
		continue;
	}
	if (*p == '\\') switch(*++p) {
	    case 'b': *q = '\b'; continue;
	    case 'f': *q = '\f'; continue;
	    case 'n': *q = '\n'; continue;
	    case 'r': *q = '\r'; continue;
	    case 't': *q = '\t'; continue;
	    case 'E':
	    case 'e': *q = 033; continue;
	    default: 
		if (isdigit(*p)) {
		    for (o=0; isdigit(*p); p++) o = o * 8 + (*p - '0');
		    *q = o;
		    p--;
		    continue;
		} 
	}
	*q = c = *p;
    }
    if (q<e) *q = 0;
    return (q - out);
}

/*==================================================================*/
#if 0	/* For field sort (not used) */
static int diffield(f1, f2)
/*++++++++++++++++
.PURPOSE  Compare two FIELDs
.RETURNS  Difference f1 - f2
.REMARKS  Priority: 0=delete (last function)
-----------------*/
  FIELD *f1, *f2;	/* IN: Fields to compare	*/
{
  int d;
    d = f2->prio - f1->prio;  	if(d) return(d);
    d = f1->col1 - f2->col1;  	if(d) return(d);
    d = f2->col0 - f1->col0;  	return(d);
}
#endif

/*==================================================================*/
static FIELD *fields(string, fct, parm, prio)
/*++++++++++++++++
.PURPOSE  Interpret a set of fields
.RETURNS  Number of fields
-----------------*/
  char *string;	/* IN: ascii text to interpret */
  int (*fct)();	/* IN: Function to transmit	*/
  char *parm;	/* IN: Parameter to fct		*/
  int  prio;	/* IN: priority, see FIELD structure */
{
  char *p;
  int  nf;
  FIELD *f, *fp, *fx;

	/* Count the number of Fields. */

    nf = 0;
    if (ff) { /* Already Allocated Fields */
	for (fx=ff; fx->f; fx++) nf++;
    }
    for (nf+=2, p = string; *p; p++) nf += (*p == ',');
    fp = (FIELD *)malloc(nf*sizeof(FIELD));
    f = fp;
    if (ff) { /* Already Allocated Fields */
	for (fx=ff; fx->f; fx++, f++) *f = *fx;
	free(ff);
	ff = (FIELD *)0;
    }

	/* Interpret the string	*/

    for (p=string; *p; f++) {
	f->f = fct; f->parm = parm; f->prio = prio;
	f->col0 = 1; 
	if (isdigit(*p)) {
	    f->col0 = atoi(p);
	    while(isdigit(*p)) p++;
	}
	f->col1 = f->col0;
	if (*p == '-') {
	    p++;
	    f->col1 = atoi(p);
	    if (f->col1 == 0) f->col1 = MAXCOL;
	    if (f->col1 < f->col0) {
		fprintf(stderr, "    %s\n    ", string);
		while(string < p) fprintf(stderr, " "), string++;
		fprintf(stderr, "^\n"); 
		error("Invalid column range", "");
	    }
	    while(isdigit(*p)) p++;
	}
	if (*p == ',') { p++; continue; }
	if (*p) {
		fprintf(stderr, "    %s\n    ", string);
		while(string < p) fprintf(stderr, " "), string++;
		fprintf(stderr, "^\n"); 
		error("Invalid column definition", "");
	}
    }
    /* Accept the input as given */
    /* qsort(fp, nf-1, sizeof(FIELD), (SORT_FCT) diffield); */
    f->col0 = f->col1 = MAXCOL;
    f->f = (int (*)())0;
    f->parm = (char *)0;

    return(fp);
}

static char *getLine(FILE *input)
/*++++++++++++++++
.PURPOSE  Get a line (and forget the end of), and remove the \r\n
.RETURNS  the line
.REMARKS  The length of line is saved in the static line_length
-----------------*/
{
  static char *abuf; static int lbuf;
  int i;
    if (!lbuf) { lbuf = BUFSIZ-1; abuf = malloc(lbuf+1); abuf[lbuf] = 0; }
    if (! fgets(abuf, lbuf-1, input)) {	/* Error or EOF ? */
	if (ferror(input)) {
	    fprintf(stderr, "****%s: getLine: ", pgm);
	    perror(filename ? filename : "(stdin)");
	}
	return((char *)0);
    }
    i = strloc(abuf, '\n');
    while (!abuf[i]) {
	lbuf += BUFSIZ; abuf = realloc(abuf, lbuf);
	if (!fgets(abuf+i, BUFSIZ, input)) break;
	i = strloc(abuf, '\n');
    }
    abuf[i] = 0;
    line_length = i;	/* Save in static variable */
    return(abuf);
}


/*==================================================================*
	Functions which execute the various requirements
 *==================================================================*/
static int fct_b(ps, pd, len, tr)
/*++++++++++++++++
.PURPOSE  Zero the string with a blank
.RETURNS  0
-----------------*/
  char *ps;	/* IN: Source string	*/
  char *pd;	/* IN: Destination string	*/
  int  len;	/* IN: Length of zone to blank	*/
  char *tr;	/* IN: non-zero to print error	*/
{
  static char o[8];
  char *pe;
    for (pe = ps+len; ps<pe; ps++) {
	if (*ps == ' ') o[0] = 0;
	else if (isprint(*ps)) o[0] = *ps, o[1] = 0;
	else sprintf(o, "o%03o", *ps & 0xff);
	if (o[0] && tr)  non_sp++, fprintf(stderr, 
	    "+++ %s: Non-blank char <%s> deleted in line %ld, col %ld\n", 
	    pgm, o, lineno, colno);
	*(pd++) = 0;
    }
    return(0);
}

static int fct_d(ps, pd, len, tr)
/*++++++++++++++++
.PURPOSE  Delete byte
.RETURNS  0
-----------------*/
  char *ps;	/* IN: Source string	*/
  char *pd;	/* IN: Destination string	*/
  int  len;	/* IN: Length of zone to blank	*/
  char *tr;	/* IN: Unused parameter */
{
    while (--len >= 0) *(pd++) = 0;
    return(0);
}

static int fct_jl(ps, pd, len, tr)
/*++++++++++++++++
.PURPOSE  Left Justify 
.RETURNS  0
-----------------*/
  char *ps;	/* IN: Source string	*/
  char *pd;	/* IN: Destination string	*/
  int  len;	/* IN: Length of zone to blank	*/
  char *tr;	/* IN: for decimal-point adjustment (future use) */
{
  char *es, *ed;
    es = ps+len; ed = pd + len;
    while ((ps<es) && (*ps == ' ')) ps++;
    while (ps<es) *(pd++) = *(ps++);
    while (pd<ed) *(pd++) = ' ';
    return(0);
}

static int fct_jr(ps, pd, len, tr)
/*++++++++++++++++
.PURPOSE  Justify 
.RETURNS  Number of bytes processed
-----------------*/
  char *ps;	/* IN: Source string	*/
  char *pd;	/* IN: Destination string	*/
  int  len;	/* IN: Length of zone to blank	*/
  char *tr;	/* IN: for decimal-point adjustment (future use) */
{
  char *es, *ed;
    es = ps+len; ed = pd + len;
    for (--es; (es>=ps) && (*es == ' '); es--) ;
    while (es>=ps)  *(--ed) = *(es--);
    while (ed> pd)  *(--ed) = ' ';
    return(0);
}

static int fct_s(ps, pd, len, tr)
/*++++++++++++++++
.PURPOSE  Squeeze blanks
.RETURNS  Number of bytes processed
-----------------*/
  char *ps;	/* IN: Source string	*/
  char *pd;	/* IN: Destination string	 */
  int  len;	/* IN: Length of zone to squeeze */
  char *tr;	/* IN: for decimal-point adjustment (future use) */
{
  char *es, *ed; int ignore=1;
    es = ps+len; ed = pd + len;
    while (ps<es) {
	if (*ps == ' ') { if (ignore++ == 0) *(pd++) = ' '; }
	else { ignore = 0; *(pd++) = *ps; }
	ps++;
    }
    while (pd<ed) *(pd++) = ' ';
    return(0);
}

static int fct_t(ps, pd, len, tr)
/*++++++++++++++++
.PURPOSE  Translate
.RETURNS  1
.REMARKS  Zero bytes are removed.
-----------------*/
  char *ps;	/* IN: Source string	*/
  char *pd;	/* IN: Destination string	*/
  int  len;	/* IN: Length of zone to translate */
  char *tr;	/* IN: Translation table */
{
  char *pe, *p;
#ifdef DEBUG
  { int i;
    printf("... fct_t of %s  ", ps);
    for (i=0; i < 128; i++) if (i != tr[i]) printf(" %c=>%c", i, tr[i]);
    printf("\n");
  }
#endif
    for (pe = ps+len, p = pd; (ps<pe); ps++) {
        *(p++) = tr[*ps & 0xff];
	if (!*p) break;		/* End-of-line */
    }
    /* V1.52: Remove zero-bytes */
    pe = p;
    for (p=ps=pd; ps<pe; ps++) {
	if (*ps == 0) continue;
	*(p++) = *ps;
    }
    while (p<pe) *(p++) = ' ';	/* Fill with blanks */
    return(pe-pd);
}

/*==================================================================*/
static int trf(FILE *in, FILE *out)
/*++++++++++++++++
.PURPOSE  Execute translations on an opened file
.RETURNS  0
-----------------*/
{
  FIELD *fc;
  char *p0, *p1, *pe, *line;

    if (!ff) error("No -b/-d/-t Function", "");
    lineno = 0;
    while((line = getLine(in))) {
	lineno++;
	pe = line + line_length;
	for (fc = ff; fc->f; fc++) {
	    colno = fc->col0;
	    p0 = line + fc->col0-1;
	    p1 = line + fc->col1;
	    if (p0 >= pe) 	/* Shorter line */
		continue;
	    if (p1 >= pe) p1 = pe;
	    p1 += (*(fc->f))(p0, p0, p1-p0, fc->parm);
	}
	/* Write out the result */
	for (p1=line; p1<pe; p1++) {
	    if (*p1) fputc(*p1, out);
	}
	fputc('\n', out);
    }
    if (in != stdin) fclose(in);
    return(0);
}
	
/*==================================================================
		Program
 *==================================================================*/
int main (int argc, char **argv)
{
  int   nf, i, n;
  char  *p, *trt;
  char  s0[256], s1[256];
  FILE  *file;

	/* Check the Arguments */
  
    nf = 0;
    while (-- argc > 0) {
	p = *++argv;
	if (strcmp(p, "-help") == 0) { 		/* Help */
	    printf("%s%s", usage, help);
	    exit(0);
	}
	if (*p == '-') switch(*++p) {
	    case 'B':
		ff = fields(++p, fct_b, (char *)0, 0);
		continue;
	    case 'b':
		ff = fields(++p, fct_b, (char *)1, 0);
		continue;
	    case 'd':
		ff = fields(++p, fct_d, (char *)1, 0);
		continue;
	    case 'a':
		trt = malloc(256); 
		for (i=0; i < 256; i++) trt[i] = tolower(i);
		ff = fields(++p, fct_t, trt, 3);
		continue;
	    case 'A':
		trt = malloc(256); 
		for (i=0; i < 256; i++) trt[i] = toupper(i);
		ff = fields(++p, fct_t, trt, 3);
		continue;
	    case 't':
		if (argc < 2) error("-t requires TWO strings: ", p-1);
		trt = malloc(256);
  	    	for (i=0; i < 256; i++) trt[i] = i;
		ff = fields(++p, fct_t, trt, 3);
		n = trc(argv[1], s0);
		i = trc(argv[2], s1);
		/* Fill with blanks if not same length */
		while (i<n) s1[i++] = ' ';
		if (i != n) error("-t strings NOT of same length", "");
		for (i=0; i<n; i++) trt[s0[i]&0xff] = s1[i];
		argv += 2; argc -= 2;
		continue;
	    case 'l':
		ff = fields(++p, fct_jl, (char *)0, 2);	/* Justify Left	*/
		continue;
	    case 'r':
		ff = fields(++p, fct_jr, (char *)0, 2);	/* Justify Right */
		continue;
	    case 's':
		ff = fields(++p, fct_s , (char *)0, 2);	/* Justify Right */
		continue;
	    case 'v':
		if (strncmp(p, "ver", 3) == 0) {
		    printf("%s -- Version %s\n", pgm, VERSION);
		    exit(0);
		}
		/* NO BREAK */
	    default:
		error("Unknown option: ", p-1);
	}
	else {	/* A File */
	    filename = p;
	    file = strcmp(p, "-")==0 ? stdin : fopen(p, "r");
	    if (!file) { 
		fprintf(stderr, "****%s: ", pgm); 
		perror(p); 
		exit(2); 
	    }
	    nf++;
	    trf(file, stdout);
	}
    }

	/* Execute on stdin if necessary */

    if (nf == 0)	trf(stdin, stdout);

    exit(non_sp>0);
}
