#ifndef lint
static char Rcs_Id[] = "$Id: fields.c,v 1.7 1994/01/06 05:26:37 geoff Exp $";
#endif

/*
 * $Log: fields.c,v $
 * Revision 1.7  1994/01/06  05:26:37  geoff
 * Get rid of all references to System V string routines, for portability
 * (sigh).
 *
 * Revision 1.6  1994/01/05  20:13:43  geoff
 * Add the maxf parameter
 *
 * Revision 1.5  1994/01/04  02:40:21  geoff
 * Make the increments settable (field_line_inc and field_field_inc).
 * Add support for the FLD_NOSHRINK flag.
 *
 * Revision 1.4  1993/09/27  17:48:02  geoff
 * Fix some lint complaints and some parenthesization errors.
 *
 * Revision 1.3  1993/09/09  01:11:11  geoff
 * Add a return value to fieldwrite.  Add support for backquotes and for
 * unstripped backslashes.
 *
 * Revision 1.2  1993/08/26  00:02:50  geoff
 * Fix a stupid null-pointer bug
 *
 * Revision 1.1  1993/08/25  21:32:05  geoff
 * Initial revision
 *
 */

#include "fields.h"

#include <stdio.h>

#include "config.h"

field_t *fieldread P((FILE * file, char *delims, int flags, int maxf));
/* Read a line with fields from a file */
field_t *fieldmake P((char *line, int allocated, char *delims, int flags, int maxf));
/* Make a field structure from a line */
static field_t *fieldparse P((field_t * fieldp, char *line, char *delims, int flags, int maxf));
/* Parse the fields in a line */
static int fieldbackch P((char *str, char **out, int strip));
/* Process backslash sequences */
int fieldwrite P((FILE * file, field_t *fieldp, int delim));
/* Write a line with fields to a file */
void fieldfree P((field_t * fieldp));
/* Free a field returned by fieldread */

unsigned int field_field_inc = 20; /* Increment to increase # fields by */
unsigned int field_line_inc = 512; /* Incr to increase line length by */

#ifndef USG
#define strchr index
#endif /* USG */

extern void free();
extern char *malloc();
extern char *realloc();
extern char *strchr();
extern int strlen();

/*
 * Read one line of the given file into a buffer, break it up into
 * fields, and return them to the caller.  The field_t structure
 * returned must eventually be freed with fieldfree.
 */
field_t *fieldread(file, delims, flags, maxf) FILE *file; /* File to read lines from */
char *delims;                                             /* Characters to use for field delimiters */
int flags;                                                /* Option flags;  see fields.h */
int maxf;                                                 /* Maximum number of fields to parse */
{
  register char *linebuf; /* Buffer to hold the line read in */
  int linemax;            /* Maximum line buffer size */
  int linesize;           /* Current line buffer size */

  linebuf = (char *)malloc(field_line_inc);
  if (linebuf == NULL) return NULL;
  linemax = field_line_inc;
  linesize = 0;
  /*
   * Read in the line.
   */
  while (fgets(&linebuf[linesize], linemax - linesize, file) != NULL) {
    linesize += strlen(&linebuf[linesize]);
    if (linebuf[linesize - 1] == '\n')
      break;
    else {
      linemax += field_line_inc;
      linebuf = (char *)realloc(linebuf, linemax);
      if (linebuf == NULL) return NULL;
    }
  }
  if (linesize == 0) {
    free(linebuf);
    return NULL;
  }
  return fieldmake(linebuf, 1, delims, flags, maxf);
}

field_t *fieldmake(line, allocated, delims, flags, maxf) char *line; /* Line to make into a field structure */
int allocated;                                                       /* NZ if line allocated with malloc */
char *delims;                                                        /* Characters to use for field delimiters */
int flags;                                                           /* Option flags;  see fields.h */
int maxf;                                                            /* Maximum number of fields to parse */
{
  register field_t *fieldp; /* Structure describing the fields */
  int linesize;             /* Current line buffer size */

  fieldp = (field_t *)malloc(sizeof(field_t));
  if (fieldp == NULL) return NULL;
  fieldp->nfields = 0;
  fieldp->linebuf = allocated ? line : NULL;
  fieldp->fields = NULL;
  fieldp->hadnl = 0;
  linesize = strlen(line);
  if (line[linesize - 1] == '\n') {
    line[--linesize] = '\0';
    fieldp->hadnl = 1;
  }
  /*
   * Shrink the line buffer if necessary.
   */
  if (allocated && (flags & FLD_NOSHRINK) == 0) {
    line = fieldp->linebuf = (char *)realloc(fieldp->linebuf, linesize + 1);
    if (fieldp->linebuf == NULL) {
      fieldfree(fieldp);
      return NULL;
    }
  }
  return fieldparse(fieldp, line, delims, flags, maxf);
}

static field_t *fieldparse(fieldp, line, delims, flags,
                           maxf) register field_t *fieldp; /* Field structure to parse into */
register char *line;                                       /* Line to be parsed */
char *delims;                                              /* Characters to use for field delimiters */
int flags;                                                 /* Option flags;  see fields.h */
int maxf;                                                  /* Maximum number of fields to parse */
{
  int fieldmax;  /* Max size of fields array */
  char *lineout; /* Where to store xlated char in line */
  char quote;    /* Quote character in use */

  fieldp->nfields = 0;
  fieldmax = (maxf != 0 && maxf < field_field_inc) ? maxf + 2 : field_field_inc;
  fieldp->fields = (char **)malloc(fieldmax * sizeof(char *));
  if (fieldp->fields == NULL) {
    fieldfree(fieldp);
    return NULL;
  }
  if ((flags & (FLD_SHQUOTES | FLD_SNGLQUOTES | FLD_BACKQUOTES | FLD_DBLQUOTES)) == FLD_SHQUOTES)
    flags |= FLD_SNGLQUOTES | FLD_BACKQUOTES | FLD_DBLQUOTES;
  while (1) {
    if (flags & FLD_RUNS) {
      while (*line != '\0' && strchr(delims, *line) != NULL) line++; /* Skip runs of delimiters */
      if (*line == '\0') break;
    }
    fieldp->fields[fieldp->nfields] = lineout = line;
    /*
     * Skip to the next delimiter.  At the end of skipping, "line" will
     * point to either a delimiter or a null byte.
     */
    if (flags & (FLD_SHQUOTES | FLD_SNGLQUOTES | FLD_BACKQUOTES | FLD_DBLQUOTES | FLD_BACKSLASH)) {
      while (*line != '\0') {
        if (strchr(delims, *line) != NULL)
          break;
        else if (((flags & FLD_SNGLQUOTES) && *line == '\'') || ((flags & FLD_BACKQUOTES) && *line == '`') ||
                 ((flags & FLD_DBLQUOTES) && *line == '"')) {
          if ((flags & FLD_SHQUOTES) == 0 && line != fieldp->fields[fieldp->nfields])
            quote = '\0';
          else
            quote = *line;
        } else
          quote = '\0';
        if (quote == '\0') {
          if (*line == '\\' && (flags & FLD_BACKSLASH)) {
            line++;
            if (*line == '\0') break;
            line += fieldbackch(line, &lineout, flags & FLD_STRIPQUOTES);
          } else
            *lineout++ = *line++;
        } else {
          /* Process quoted string */
          if ((flags & FLD_STRIPQUOTES) == 0) *lineout++ = quote;
          ++line;
          while (*line != '\0') {
            if (*line == quote) {
              if ((flags & FLD_STRIPQUOTES) == 0) *lineout++ = quote;
              line++; /* Go on past quote */
              if ((flags & FLD_SHQUOTES) == 0) {
                while (*line != '\0' && strchr(delims, *line) == NULL) line++; /* Skip to delimiter */
              }
              break;
            } else if (*line == '\\') {
              if (flags & FLD_BACKSLASH) {
                line++;
                if (*line == '\0')
                  break;
                else
                  line += fieldbackch(line, &lineout, flags & FLD_STRIPQUOTES);
              } else {
                *lineout++ = '\\';
                if (*++line == '\0') break;
                *lineout++ = *line;
              }
            } else
              *lineout++ = *line++;
          }
        }
      }
    } else {
      while (*line != '\0' && strchr(delims, *line) == NULL) line++; /* Skip to delimiter */
      lineout = line;
    }
    fieldp->nfields++;
    if (*line++ == '\0') break;
    if (maxf != 0 && fieldp->nfields > maxf) break;
    *lineout = '\0';
    if (fieldp->nfields >= fieldmax) {
      fieldmax += field_field_inc;
      fieldp->fields = (char **)realloc(fieldp->fields, fieldmax * sizeof(char *));
      if (fieldp->fields == NULL) {
        fieldfree(fieldp);
        return NULL;
      }
    }
  }
  /*
   * Shrink the field pointers and return the field structure.
   */
  if ((flags & FLD_NOSHRINK) == 0 && fieldp->nfields >= fieldmax) {
    fieldp->fields = (char **)realloc(fieldp->fields, (fieldp->nfields + 1) * sizeof(char *));
    if (fieldp->fields == NULL) {
      fieldfree(fieldp);
      return NULL;
    }
  }
  fieldp->fields[fieldp->nfields] = NULL;
  return fieldp;
}

static int fieldbackch(str, out, strip) register char *str; /* First char of backslash sequence */
register char **out;                                        /* Where to store result */
int strip;                                                  /* NZ to convert the sequence */
{
  register int ch; /* Character being developed */
  char *origstr;   /* Original value of str */

  if (!strip) {
    *(*out)++ = '\\';
    if (*str != 'x' && *str != 'X' && (*str < '0' || *str > '7')) {
      *(*out)++ = *str;
      return *str != '\0';
    }
  }
  switch (*str) {
    case '\0':
      *(*out)++ = '\0';
      return 0;
    case 'a':
      *(*out)++ = '\007';
      return 1;
    case 'b':
      *(*out)++ = '\b';
      return 1;
    case 'f':
      *(*out)++ = '\f';
      return 1;
    case 'n':
      *(*out)++ = '\n';
      return 1;
    case 'r':
      *(*out)++ = '\r';
      return 1;
    case 'v':
      *(*out)++ = '\v';
      return 1;
    case 'X':
    case 'x':
      /* Hexadecimal sequence */
      origstr = str++;
      ch = 0;
      if (*str >= '0' && *str <= '9')
        ch = *str++ - '0';
      else if (*str >= 'a' && *str <= 'f')
        ch = *str++ - 'a' + 0xa;
      else if (*str >= 'A' && *str <= 'F')
        ch = *str++ - 'A' + 0xa;
      if (*str >= '0' && *str <= '9')
        ch = (ch << 4) | (*str++ - '0');
      else if (*str >= 'a' && *str <= 'f')
        ch = (ch << 4) | (*str++ - 'a' + 0xa);
      else if (*str >= 'A' && *str <= 'F')
        ch = (ch << 4) | (*str++ - 'A' + 0xa);
      break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
      /* Octal sequence */
      origstr = str;
      ch = *str++ - '0';
      if (*str >= '0' && *str <= '7') ch = (ch << 3) | (*str++ - '0');
      if (*str >= '0' && *str <= '7') ch = (ch << 3) | (*str++ - '0');
      break;
    default:
      *(*out)++ = *str;
      return 1;
  }
  if (strip) {
    *(*out)++ = ch;
    return str - origstr;
  } else {
    for (ch = 0; origstr < str; ch++) *(*out)++ = *origstr++;
    return ch;
  }
}

int fieldwrite(file, fieldp, delim) FILE *file; /* File to write to */
register field_t *fieldp;                       /* Field structure to write */
int delim;                                      /* Delimiter to place between fields */
{
  int error;            /* NZ if an error occurs */
  register int fieldno; /* Number of field being written */

  error = 0;
  for (fieldno = 0; fieldno < fieldp->nfields; fieldno++) {
    if (fieldno != 0) error |= putc(delim, file) == EOF;
    error |= fputs(fieldp->fields[fieldno], file) == EOF;
  }
  if (fieldp->hadnl) error |= putc('\n', file) == EOF;
  return error;
}

void fieldfree(fieldp) register field_t *fieldp; /* Field structure to free */
{
  if (fieldp == NULL) return;
  if (fieldp->linebuf != NULL) free((char *)fieldp->linebuf);
  if (fieldp->fields != NULL) free((char *)fieldp->fields);
  free((char *)fieldp);
}
