/*
   <h3>CSV library functions.</h3>
        
   <p>This library reads and writes comma separated variable formatted text
   files. When the file is read, all fields are decoded into strings. 
   A number of field output functions are provided to allow strings, integers
   and real numbers to be output.  Alternatively, it can accept strings
   containing comma separated fields for decoding and it can assemble strings
   of comma separated fields for use elsewhere in the program.</p>

   <p>The rules defining a validly formatted csv file are:</p>
   <ul>
   <li>Fields are separated by commas unless a different 
      separator is set.  Comma is the default separator.</li>
   <li>The separator may not be set to null, newline or double quote.</li>
   <li>A field may be enclosed in double quote characters "..". </li>
   <li>A quoted field may contain field separators but not newlines.</li>
   <li>A quoted field may contain double-quote characters, which are 
      represented in the file by "".</li>
   <li>Fields may be empty: "" and an empty string (,,) both 
      represent an empty field.</li>
   <li>Leading and trailing white space is preserved.</li>
   </ul>

 
   <p>There are a number of possible quote modes. Changes to the quote mode
   only affect output and take effect immediately. 
   The quote modes are:</p>
   <ul>
   <li>NORMAL  - on output strings are quoted.  Numeric values  
                  are unquoted.  This is the default setting.</li>
   <li>ALWAYS  - all output fields are quoted.</li>
   <li>NEVER   - no fields are quoted.</li>
   <li>MINIMAL - fields are only quoted if they contain the 
                  field separator.</li>
   </ul>
 
   <p>The NEVER quote mode is intended to be combined with a nonstandard
   separator to allow the creating of files suitable for input to
   database loaders and to the creation of tab-separated files.</p>

   <p>Many csv files may be open at once. Each file has a 
   <i>CSV</i> control block that contains the resources and settings
   applicable to the file. The control block will exist if 
   <i>csvopen()</i> was able to allocate and initialise memory for it. 
   All other functions can be called 
   if the control block pointer is NULL or points to a valid control block,
   so the pointer should be initialised to NULL when it is declared. 
   Functions passed a NULL control block pointer will return an error code.
   </p>

   <p>This package provides the ability to change both field and 
   line delimiters. It also provides control over the amount
   of field quoting to be applied to the file. All changes to
   run conditions should be made by using the <i>csvsetxxxx()</i>
   functions. All settings are specific to a single file and are
   stored in the CSV control block.  Internal fields within the
   control block are subject to change without notice. Because of
   this they should not be accessed directly and must not be
   modified by the calling program except via the functions
   provided for this purpose.</p>

   <p>Functions that return an integer but have no explicit meaning
   described for the return value should be assumed to return zero
   for success and a negative value if an error has occurred.</p>
	
   <p>In all cases a call to <i>csvgeterr()</i> after another function
   will return zero if no error ocurred and a negative value if an
   error has ocurred. This can be translated into a displayable
   string by calling <i>csvgeterrtext()</i>. The current error status
   is held in a hidden global variable that is common to all active
   csv files, so it is good practise to check the error status, with
   <i>csvgeterr()</i> if necessary, after every csv function call and
   to translate and report the error immediately.</p>

   <h3>Compilation</h3>
        
   <p>The calling programs should include <i>csv.h</i> and must be
   linked using <i>-lenviron</i>.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public License.
   
   This program 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 Lesser GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "csv.h"

/*
   Private constants and globals
   -----------------------------
*/

static int debug = 0;     /* debug control variable          */
static int error = 0;     /* csv library internal error code */

static char *errtext[] =  /* CVS error text                  */
{
   /*   0 */ "ok",
   /*  -1 */ "can't allocate csv control block",
   /*  -2 */ "can't allocate filename storage",
   /*  -3 */ "invalid file mode",
   /*  -4 */ "can't close file",
   /*  -5 */ "csv control block not allocated",
   /*  -6 */ "invalid quote mode",
   /*  -7 */ "invalid field separator",
   /*  -8 */ "invalid line terminator",
   /*  -9 */ "can't allocate storage for line",
   /* -10 */ "can't extend storage for line",
   /* -11 */ "can't extend field storage",
   /* -12 */ "can't allocate quoted string storage",
   /* -13 */ "processing a string",
   /* -14 */ "a file is open",
   /* -99 */ "Dummy text"
};

static char *quotetext[] =  /* text describing quote modes   */ 
{
   "UNSET",
   "NORMAL",
   "ALWAYS",
   "NEVER",
   "MINIMAL"
};

static char *nltext[] =     /* text describing quote modes   */ 
{
   "UNSET",
   "CRLF",
   "CR",
   "LF"
};

enum { MAXERROR = -8,
       MAXSTRING = 160
     };

enum { FALSE = 0,
       TRUE  = 1
     };

/*
   Private function declarations
   -----------------------------
*/
static void reset(CSV *cb);
static void resetout(CSV *cb);
static void disphex(char *h);
static int endofline(FILE *f, int c);
static int split(CSV *f);
static char *advquoted(CSV *f, char *p);
int outputstr(CSV *f, char *val, int quote);
int appendout(CSV *f, char *val);
char *prepreal(double val);

/*
   Public functions
   ----------------
*/

/*
   <p>Open a file for csv use, returning a CSV control block
   pointer that is specific to the file that has just been
   opened. Name is the file name, mode the file opening mode,
   which must be "r" or "w".</p>

   <p>If the file name is null, stdin or stdout is opened
   depending on whether the opening mode is "r" or "w".</p>

   <p>If the file name is null and the mode is "s" no file will
   be opened because it is assumed that input and output will
   be to strings.</p>

   <p>Returns a pointer to the CSV control block on success
   and null if an error ocurred.</p>
*/
CSV *csvopen(char *name, char *mode)
{
   CSV *cb;
   int namesize;

   if (debug > 1)
      printf("called csvopen(%s,%s)\n", name, mode);

   error = errno = 0;
   cb = (CSV*)malloc(sizeof(CSV));
   if (cb)
   {
      cb->sused = FALSE;
      cb->f = NULL;
      if (name)
         namesize = strlen(name);
      else
         namesize = strlen("stdout");
         
      cb->name = (char*)malloc(namesize + 1);
      if (cb->name)
      {
         if (strcmp(mode, "r") == 0 || strcmp(mode, "w") == 0)
         {
            if (name)
            {
               strcpy(cb->name, name);
               cb->f = fopen(name, mode);
            }
            else
            {
               if (strcmp(mode, "r") == 0)
               {
                  strcpy(cb->name, "stdin");
                  cb->f = fdopen(0, "r");
               }
               else
               if (strcmp(mode, "w") == 0)
               {
                  strcpy(cb->name, "stdout");
                  cb->f = fdopen(1, "w");
               }
            }
            
            if (!cb->f)
               error = errno;
         }
         else
         if (strcmp("s", mode) == 0)
         {
            cb->sused = TRUE;
            strcpy(cb->name, "string");
            cb->f = NULL;
         }
         else
         {
            strcpy(cb->name, "-");
            error = -3;
         }
      }
      else
      {
         strcpy(cb->name, "-");
         error = -2;
      }

      reset(cb);        /* Initialise all CSV fields */
      resetout(cb);     /* Reset the output line     */
      if (debug > 1)
         printf("Opened(%s,%s) reply=%d\n",
                cb->name,
                mode,
                error);
   }
   else
      error = -1;

   if (debug)
      printf("%p = csvopen(%s,%s)\n",
             cb,
             ( name ? name : "NULL"),
             mode);

   return cb; 
}

/*
   Close a CSV file and delete the CSV control block, releasing
   the space it occupies. Returns null.
*/
CSV *csvclose(CSV *cb)
{
   error = errno = 0;
   if (cb)
   {
      if (!cb->sused)
      {
         if (cb->f)
         {
            if (fclose(cb->f) == 0)
               cb->f = NULL;
            else
              error = -4;
         }
      }
               
      if (cb->name)
         free(cb->name);
         
      if (cb->line)
         free(cb->line);

      if (cb->sline);
         free(cb->sline);

      if (cb->field)
         free(cb->field);
               
      resetout(cb);     /* release output line memory */
      free(cb);
   }
   else
      error = -5;
   
   if (debug)
      printf("NULL = csvclose(%p)\n", cb);

   return NULL;
}

/*
   Retrieve the current error code.
   Returns zero if there is no error. Errors are positive if they
   are standard system error codes and negative
   integers if they are specific to this library.
*/
int csvgeterr(void)
{
   if (debug)
      printf("%d = csvgeterr()\n", error);

   return error;
}

/*
   Return a pointer to a string containing a textual description
   of the current error. This always returns a string pointer, but
   the string is empty if there was no current error.
*/
char *csvgeterrtext(void)
{
   static char *e;
   
   if (error <=0 && error >= MAXERROR)
      e = errtext[-error];
   else
      e = strerror(errno);

   if (debug)
      printf("%s = csvgeterrtext()\n", e);

   return e;
}

/*
   Change the field separator character. 
   Returns zero if this is successful and non-zero 
   if the requested separator character is invalid.
   Valid separators are any printable ASCII character
   except newline, carriage return or double quote.  
   The change takes effect immediately. 
*/
int csvsetsep(CSV *f, int sepchar)
{
   char invalid[4];
   int  i;

   error = errno = 0;
   invalid[0] = '\n';
   invalid[1] = '\r';
   invalid[2] = '"';
   invalid[3] = 0;
   
   if (f)
   {
      if (isascii(sepchar) && strchr(invalid, sepchar))
         error = -7;
      else
      {
         f->sepchar[0] = sepchar;
         f->sepchar[1] = '\0';
      }
   }
   else
      error = -5;

   if (debug)
   {
      printf("%d = csvsetsep(%p, 0x%02x) invalid=",
             error,
             f,
             sepchar);
      disphex(invalid);
      printf("\n");
   }
             
   return error;
}

/*
   Set the quote mode to one of the defined values.
   The quotation mode is one of the following:
   <ul>
   <li>NORMAL  (the default) only quotes text fields.</li>
   <li>ALWAYS  applies quotes to all fields</li>
   <li>NEVER   disables all field quote marks</li> 
   <li>MINIMAL only quotes text fields containing the 
      current field separator or a double quote mark.</li>
   </ul>
   Returns zero if this is successful and non-zero 
   if the requested quote mode is invalid.
   The change takes effect immediately. 
*/
int csvsetquote(CSV *f, int quotemode)
{
   char *t = quotetext[0];
   char buff[20];

   error = errno = 0;
   if (quotemode >= NORMAL && quotemode <= MINIMAL)
      t = quotetext[quotemode];
   else
   {
      sprintf(buff, "%d", quotemode);
      t = &buff[0];
   }
  
   if (f)
   {
      if (quotemode >= NORMAL && quotemode <= MINIMAL)
         f->quotemode = quotemode;
      else
         error = -6;
   }
   else
      error = -5;

   if (debug)
      printf("%d = csvsetquote(%p, %s)\n",
             error,
             f,
             t);
             
   return error;
}

/*
   Set the type of line separator to be output.
   The value must be one of the following:
   <ul>
   <li>CRLF    use carriage return + linefeed</li>
   <li>CR      use a carriage return</li>
   <li>LF      use a linefeed</li>
   </ul>
   The default is LF, which is the normal line delimiter for
   the system your program is running on. 
   Returns zero if the change succeeded and non-zero if
   it failed. The change takes effect immediately. 
*/
int csvsetnl(CSV *f, int nlcode)
{
   int  i;
   char *t = nltext[0];
   char buff[20];

   error = errno = 0;
   if (nlcode>=CRLF && nlcode <=LF)
      t = nltext[nlcode];
   else
   {
      sprintf(buff, "%d", nlcode);  
      t = &buff[0];
   }
   
   if (f)
   {
      switch (nlcode)
      {
      	case CRLF: f->nl[0] = '\r';
      	           f->nl[1] = '\n';
      	           f->nl[2] = 0;
      	           break;
      	case CR:   f->nl[0] = '\r';
      	           f->nl[1] = 0;
      	           break;
      	case LF:   f->nl[0] = '\n';
      	           f->nl[1] = 0;
      	           break;
      	default:
                   error = -8;
      }
   }
   else
      error = -5;

   if (debug)
      printf("%d = csvsetnl(%p, %s)\n", error, f, t);
      
   return error;
}

/*
   This reads one line from the open file <u>f</u>.
   It assumes that all lines are terminated by carriage return,
   linefeed or EOF.
   It returns a pointer to the line with the terminator removed or 
   NULL if EOF was encountered. The line may be of any arbitrary length.
   NULL will be returned if the memory limit is exceeded while the line
   is being read. The line must be treated as read-only storage. The caller
   must make a copy to preserve or change its content.
*/
char *csvgetline(CSV *f)
{
   int  i;
   int  c;
   char *newl;
   char *news;
   char *reply;
   
   error = errno = 0;
   reply = NULL;
   if (debug > 1)
   {
   	printf("csvgetline(%p) called\n", f);
   }

   if (f)
   {
      if (f->sused)
      {
         error = -13;
      }
      else
      {
         if (f->line == NULL)             /* Line storage space allocated */
         {                                /* on the first call */
            f->maxline = f->maxfield = 1;
            f->line = (char*)malloc(f->maxline);
            f->sline = (char*)malloc(f->maxline);
            f->field = (char**) malloc(f->maxfield*sizeof(f->field[0]));
            if (f->line == NULL || f->sline == NULL || f->field == NULL)
            {
               reset(f);
               error = -9;
               reply = NULL; /* out of memory */
            }
         }

         if (!error)
         {
            for (i = 0; (c = getc(f->f)) != EOF && !endofline(f->f, c); i++)
            {
               if (i >= f->maxline-1)
               {                           /* grow line if needed    */
                  f->maxline *= 2;         /* double size each time  */
                  if (debug > 2)
                     printf("   line buffer is %d bytes\n", f->maxline);

                  newl = (char*)realloc(f->line, f->maxline);
                  news = (char*)realloc(f->sline, f->maxline);
                  if (newl == NULL || news == NULL)
                  {
                     reset(f);             /* out of memory: tidy up */
                     error -10;
                     reply = NULL;
                     break;
                  }

                  f->line = newl;
                  f->sline = news;
               }

               f->line[i] = c;
            }
         }
   
         if (!error)
         { 
            f->line[i] = '\0';
            if (debug > 1)
               printf("read %d = [%s]\n", strlen(f->line), f->line);
            
            if (split(f) < 0)              /* Parse line into fields */
            {                              /* out of memory splitting */
               reset(f);
               reply = NULL;
            }
         }

         error = errno;
         if (!error)
         {
            if (c == EOF && i == 0)
               reply = NULL;
            else
               reply = f->line;
         }
      }
   }
   else
      error = -5;
      
   if (debug)
      printf("%p = csvgetline(%p)\n", reply, f);
   
   return reply;
}

/*
   Returns a pointer to the the contents of the <u>n</u>th field in the
   last line read by <i>csvgetline()</i>. Fields are numbered from 0.
   It returns NULL if called before <i>csvgetline()</i>, 
   n<0 or is beyond the last field. Fields are separated
   by the current separator character. Fields may be surrounded by "..."; 
   such quotes are removed. Within "...", "" is replaced by a single
   double quote mark and the separator character has no special meaning. 
   In unquoted fields, quotes are regular characters.
   There can be an arbitrary number of fields of any length; NULL is returned
   if memory cannot be obtained to hold the current field or if the attempt to 
   unpack the field fails. 
   Fields must be treated as read-only storage; the caller must make a copy to
   preserve or change the field contents.
*/
char *csvgetfield(CSV *f, int n)
{
   char *reply = NULL;
   
   error = 0;
   if (f)
   {
      if (n >= 0 && n < f->nfield)
         reply = f->field[n];
   }
   else
      error = -5;

   if (debug)
      printf("%p = csvgetfield(%p, %d)\n", reply, f, n);
      
   return reply;
}

/*
   Returns the number of fields in the last line read by 
   <i>csvgetline()</i>.
   It returns -1 if it is called before <i>csvgetline()</i> 
   is called for the first time.
   Returns the number of fields in the last line read.
   Negative values indicate that an error has occurred.
*/
int csvgetn(CSV *f)
{
   int reply = 0;
   
   error = 0;
   if (f)
   {
      reply = f->nfield;
   }
   else
      error = -5;

   if (debug)
      printf("%d = csvgetn(%p)\n", reply, f);
      
   return reply;
}

/*
   Write the current output line to the file after appending the current 
   newline character(s). The line buffer is cleared. 
   Returns 0 if successful and a non-zero value otherwise.
*/
int csvputline(CSV *f)
{
   error = errno = 0;
   if (f)
   {
      if (!f->sused)
      {
         if (f->oline)
            fprintf(f->f, "%s%s", f->oline, f->nl);
         else
            fprintf(f->f, "%s", f->nl);

         f->nfield = 0;
   	 resetout(f);    /* Reset the output line */
      }
      else
         error = -13;
   }
   else
      error = -5;

   if (!error)
      error = errno;

   if (debug)
      printf("%d = csvputline(%p)\n", error, f);
   return error;
}

/*
   Appends <u>val</u>
   to the current line. If it is not the first field on the line it is
   preceeded by a separator character. Quoting depends on the current
   quote mode:
   <ul>
      <li>NORMAL  - the string will be quoted. Any double quote
               characters within the string will be replaced with "".</li>  
      <li>ALWAYS  - same as NORMAL</li>
      <li>NEVER   - the field is output without quotes. If it 
               contains a separator character an error will 
               be returned.</li>
      <li>MINIMAL - the string will only be quoted if it 
               contains the field separator.</li>
   </ul>

   Returns zero if the field is successfully processed and non-zero
   if errors were encountered.  
*/
int csvputstr(CSV *f, char *val)
{
   error = errno = 0;
   if (f)
   {
      if (f->nfield > 0)
         error = outputstr(f, f->sepchar, FALSE);

      if (!error)
      {
         f->nfield++;
         if (!errno)
         {   
            switch (f->quotemode)
            {
               case NORMAL:
               case ALWAYS:   error = outputstr(f, val, TRUE);
                              break;
               case NEVER:    error = outputstr(f, val, FALSE);
                              break;
               case MINIMAL:  error = outputstr(f,
                                        val,
                                        (strchr(val, f->sepchar[0]) != NULL));
                              break;
            }
         }
      }
   }
   else
      error = -5;

   if (!error)
      error = errno;

   if (debug)
      printf("%d = csvputstr(%p,%s)\n", error, f, val);

   return error;
}

/*
   Appends <u>val</u> to the current line as a signed numeric string. 
   If it is not the first field on the line it is
   preceeded by a separator character.
   Quoting depends on the current quote mode:
   <ul>
     <li>NORMAL  - the numeric string will not be quoted.</li>
     <li>ALWAYS  - the numeric string will be quoted.</li>
     <li>NEVER   - same as NORMAL</li>
     <li>MINIMAL - same as NORMAL</li>
   </ul>

   Signals an error if the separator character is found in the numeric string
   if the quote mode is NORMAL, NEVER or MINIMAL.
   Returns zero if the field is successfully processed and non-zero
   if errors were encountered.  
*/
int csvputint(CSV *f, int val)
{
   char numstr[20];

   error = errno = 0;
   sprintf(numstr, "%d", val);
   if (f)
   {
      if (f->nfield > 0)
         error = outputstr(f, f->sepchar, FALSE);

      if (!error)
      {
         f->nfield++;
         if (!errno)
         {   
            switch (f->quotemode)
            {
               case NORMAL:
               case MINIMAL:  error = outputstr(f,
                                       numstr,
                                       (strchr(numstr, f->sepchar[0]) != NULL));
                              break;
               case NEVER:    error = outputstr(f, numstr, FALSE);
                              break;
               case ALWAYS:   error = outputstr(f, numstr, TRUE);
                              break;
            }
         }
      }
   }
   else
      error = -5;

   if (!error)
      error = errno;

   if (debug)
      printf("%d = csvputint(%p,%d)\n", error, f, val);

   return error;
}

/*
   Identical with <i>csvputint()</i> except that a real number is output.
*/
int csvputreal(CSV *f, double val)
{
   char *numstr;

   error = errno = 0;
   if (f)
   {
      if (f->nfield > 0)
         error = outputstr(f, f->sepchar, FALSE);

      if (!error)
      {
         f->nfield++;
         if (!errno)
         {
            /*
               Prepare the number for printing with up to 17 significant
               digits. Determine how many leading digits there are
            */
            numstr = prepreal(val);
            switch (f->quotemode)
            {
               case NORMAL:
               case MINIMAL:  error = outputstr(f,
                                       numstr,
                                       (strchr(numstr, f->sepchar[0]) != NULL));
                              break;
               case NEVER:    error = outputstr(f, numstr, FALSE);
                              break;
               case ALWAYS:   error = outputstr(f, numstr, TRUE);
                              break;
            }
         }
      }
   }
   else
      error = -5;

   if (!error)
      error = errno;

   if (debug)
      printf("%d = csvputreal(%p,%f)\n", error, f, val);

   return error;
}

/*
   <p>Input a string containing a list of csv fields to be
   decoded. The string is terminated by a null and should not
   contain any newline characters.
   The string is stored internally and parsed into fields.
   Returns zero on success and non-zero if errors were encountered.</p>
*/
int csvstrin(CSV *f, char *str)
{
   int  i;
   int  c;
   char *newl;
   char *news;
   
   error = errno = 0;
   if (debug > 1)
   {
   	printf("csvstrin(%p,%s) called\n", f, str);
   }

   if (f)
   {
      if (f->sused)
      {
         if (f->line == NULL)             /* Line storage space allocated */
         {                                /* on the first call            */
            f->maxline = f->maxfield = 1;
            f->line = (char*)malloc(f->maxline);
            f->sline = (char*)malloc(f->maxline);
            f->field = (char**) malloc(f->maxfield*sizeof(f->field[0]));
            if (f->line == NULL || f->sline == NULL || f->field == NULL)
            {
               reset(f);      /* Out of memory - tidy up */
               error = -9;
            }
         }

         if (!error)
         {
            for (i = 0; (c = str[i]) != 0; i++)
            {
               if (i >= f->maxline-1)
               {                           /* grow line if needed    */
                  f->maxline *= 2;         /* double size each time  */
                  if (debug > 2)
                     printf("   line buffer is %d bytes\n", f->maxline);

                  newl = (char*)realloc(f->line, f->maxline);
                  news = (char*)realloc(f->sline, f->maxline);
                  if (newl == NULL || news == NULL)
                  {
                     reset(f);             /* out of memory: tidy up */
                     error -10;
                     break;
                  }

                  f->line = newl;
                  f->sline = news;
               }

               f->line[i] = c;
            }
         }
   
         if (!error)
         { 
            f->line[i] = '\0';
            if (debug > 1)
               printf("copied %d = [%s]\n", strlen(f->line), f->line);
            
            if (split(f) < 0)              /* Parse line into fields */
            {                              /* out of memory splitting */
               reset(f);
            }
         }
      }
      else
         error = -14;
   }
   else
      error = -5;
      
   if (debug)
      printf("%d = csvstrin(%p,%s)\n", error, f, str);
   
   return error;
}

/*
   Return a string containing a set of csv fields that has just 
   been built. The string will be null terminated and will not contain
   a trailing newline. <i>csvstrprep()</i> should be called before a set
   of fields are added to the output string.
   Returns null if errors were encountered.
*/
char *csvstrout(CSV *f)
{
   char *r = "";

   error = errno = 0;
   if (f)
      if (f->sused)
      {
         if (f->oline)
            r = f->oline;
      }
      else
         error = -14;
   else
      error = -5;

   if (debug)
      printf("[%s] = csvstrout(%p)\n", r, f);

   return r;
}

/*
   Prepare a receiving area to receive a string to be filled with csv 
   fields for later return by <i>csvstrout()</i>.  The separator and
   quoting control values are reset to their default values.
   Returns zero if the field was prepared successfully and non-zero
   if errors were encountered.
*/
int csvstrprep(CSV *f)
{
   error = errno = 0;
   if (f)
   {
      if (f->sused)
      {
         /*
            Reset the output line and then the control block.
            This is the reverse of the initial control, block
            creation to avoid memory leaks.
         */
         resetout(f);
         reset(f);
      }
      else
         error = -14;
   }
   else
      error = -5;

   if (debug)
      printf("%d = csvstrprep(%p)\n", f);

   return error;
}

/*
   Set CSV library debugging level. Debugging is global
   to all csv files and may be set at any time. Default
   is zero (no debugging), 1 traces all csv library calls,
   and 2 or greater traces internal function operations.

   Debugging level 1 is intended as a debugging aid for the calling
   program. Higher debugging levels are used to debug the internals
   of this package.
*/
void csvsetdebug(int d)
{
   debug = d;
}

/*
   Private functions
   -----------------
   reset: reset the control block
*/
static void reset(CSV *cb)
{
   cb->line       = NULL;
   cb->sline      = NULL;
   cb->oline      = NULL;
   cb->maxline    = 0;
   cb->field      = NULL;
   cb->maxfield   = 0;
   cb->nfield     = 0;
   cb->quotemode  = NORMAL;
   cb->sepchar[0] = ',';
   cb->sepchar[1] = '\0';
   cb->nl[0] = '\n';
   cb->nl[1] = 0;
   if (debug > 1)
   {
      printf("Control block for %s reset\n",
             ( cb->name ? cb->name : "UNSET"));
   }
}

/*
   resetout: reset the output line
*/
static void resetout(CSV *cb)
{
   if (cb->oline)
   {
      free(cb->oline);
      cb->oline = NULL;
   }
      
   if (debug > 1)
   {
      printf("Output line for %s reset\n",
             ( cb->name ? cb->name : "UNSET"));
   }
}

/*
   disphex: output a string in hex for debugging purposes
*/
static void disphex(char *h)
{
   int i;

   printf("0x");
   for (i = 0; i < strlen(h); i++)
      printf("%02x", h[i]);
}

/*
   endofline: check for and consume \r, \n, \r\n or EOF
*/
static int endofline(FILE *f, int c)
{
   int eol;
   int x;

   x = c;
   eol = (c == '\r' || c == '\n');
   if (c == '\r')
   {
      c = getc(f);
      if (c != '\n' && c != EOF)
         ungetc(c, f);            /* read too far: put c back */
   }

   if (debug > 3)
      printf("0x%02x = endofline(%p, 0x%02x)\n", eol, f, x);
   return eol;
}

/*
   split: split line into fields
*/
static int split(CSV *f)
{
   char *p;
   char **newf;
   char *sepp;                    /* pointer to temp. separator */
   int sepc;                      /* temporary separator        */
   int reply;

   if (debug > 1)
      printf("split(%p) called\n", f);
      
   f->nfield = 0;
   if (f->line[0] == '\0')
      reply = f->nfield;
   else
   {
      strcpy(f->sline, f->line);
      p = f->sline;
      do
      {
         if (f->nfield >= f->maxfield)
         {
            f->maxfield *= 2;     /* double number of fields */
            if (debug > 2)
               printf("   increasing fields to %d (%d bytes)\n",
                      f->maxfield,
                      f->maxfield * sizeof(f->field[0]));
               
            newf = (char**)realloc(f->field,
                                   f->maxfield*sizeof(f->field[0]));
            if (newf == NULL)
            {
               reply = -11;
               break;
            }
            
            f->field = newf;
         }

         if (*p == '"')
            sepp = advquoted(f, ++p);  /* skip initial quote */
         else
            sepp = p + strcspn(p, f->sepchar);

         sepc = sepp[0];       /* save last separator               */
         sepp[0] = '\0';       /* before null-terminating the field */
         f->field[f->nfield++] = p;
         if (debug > 1)
         {
            if (iscntrl(sepc))
               printf("   field %d = [%s] sepc=0x%02x\n", f->nfield, p, sepc);
            else
               printf("   field %d = [%s] sepc=%c\n", f->nfield, p, sepc);
         }
         
         p = sepp + 1;
      } while (sepc == f->sepchar[0]);
   }

   reply = f->nfield;
   if (debug > 1)
      printf("%d = split(%p)\n", reply, f);

   return reply;
}

/*
   advquoted: quoted field; return pointer to next separator
*/
static char *advquoted(CSV *f, char *p)
{
   int i;
   int j;
   int k;

   for (i = j = 0; p[j] != '\0'; i++, j++)
   {
      if (p[j] == '"' && p[++j] != '"')
      {
         k = strcspn(p+j, f->sepchar);
         memmove(p+i, p+j, k);
         i += k;
         j += k;
         break;
      }

      p[i] = p[j];
   }

   p[i] = '\0';
   if (debug > 1)
      printf("%p = advquoted(%p, %p)\n", p+j, f, p);

   return p + j;
}

/*
   outputstr: output a string, quoted if necessary and with protected
              quote marks if it is quoted
*/
int outputstr(CSV *f, char *val, int quote)
{
   char *buff;
   int  i;
   int  j;

   error = errno = 0;
   if (quote)
   {
      /*
        Put double quotes round the string.
        Internal double quotes are protected by duplicating them
      */
      buff = (char*)malloc(strlen(val)*2 + 3);
      if (buff)
      {
      	 for (i = 0, j = 1, buff[0] = '"'; i < strlen(val); i++, j++)
      	 {
      	    buff[j] = val[i]; 
      	    if (val[i] == '"')
      	       buff[++j] = '"';
	 }
      	    
      	 buff[j++] = '"';
         buff[j] = '\0';
         error = appendout(f, buff);
         free(buff);
      }
      else
         error = -12;
 
   }
   else
      error = appendout(f, val);

   error = (error ? error : errno );
   
   if (debug > 2)
      printf("%d = outputstr(%p,%s,%s) oline=[%s]\n",
             error,
             f,
             (quote ? "TRUE" : "FALSE"),
             val,
             f->oline);

   return error;
}

/*
   appendout - add a field to the output buffer
*/
int appendout(CSV *f, char *val)
{
   int   n;
   char  *tmp;

   error = errno = 0;
   if (f->oline)
   {
      n = strlen(f->oline) + strlen(val) + 2;
      tmp = (char*)realloc(f->oline, n);
      if (tmp)
      {
         f->oline = tmp;
         strcat(f->oline, val);
      }
      else
         error = -10;
   }
   else
   {
      f->oline = (char*)malloc(strlen(val) + 1);
      strcpy(f->oline, val);
   }

   error = (error ? error : errno);
   
   if (debug > 2)
      printf("%d = appendout(%p,%s) oline=[%s]\n",
             error,
             f,
             val,
             f->oline);

   return error;
}

/*
   prepreal: prepare a double for output
*/
char *prepreal(double val)
{
   static char numstr[30];
   char        fmt[10];
   double      scale;
   int         i;

   for (i = 1, scale=10.0; scale < val; i++)
      scale *= 10.0;

   sprintf(fmt, "%%17.%df", (17 - i));  /* generate format string */
   sprintf(numstr, fmt, val);           /* convert to a string and      */
                                        /* remove excess trailing zeros */
   for (i = strlen(numstr) -1; i > 0; i--)
   {
      if (numstr[i-1] == '.' || numstr[i] != '0')
         break;
      numstr[i] = '\0';
   }

   return numstr;
}      
