    Title: STANDARD USAGE OF C LANGUAGE RECIO LIBRARY
Copyright: (C) 1994 William Pierpoint
  Version: 2.04
     Date: October 10, 1994



1.0 INTRODUCTION

The implementation descibed by this standard usage is a superset of the
recio specification.  Enhancements are noted in the text.


1.1 Mnemonics

The recio functions have been given a consistent mnemonic naming
convention.  All recio functions are in lower case and start with
the letter r.  Function names are analogous to <stdio.h> functions.
Mnemonics are as follows:

Single letter (field functions)               Multi-letter
----------------------------------------      -----------------
b - base (prefix)                             beg - beginning
c - column (prefix), character (suffix)       ch  - character
d - double (suffix)                           col - column
f - float (suffix)                            cxt - context
i - integer (suffix)                          eof - end of file
l - long (suffix)                             err - error
n - number                                    fld - field buffer
r - record pointer (first letter)             fn  - function
s - string pointer (suffix)                   no  - number
u - unsigned (suffix)                 	      rec - record buffer
					      siz - size of buffer
					      str - string
					      txt - text
1.2 Order

The order in which the prefix mnemonics appear indicates the order in which
the arguments appear in the function.  The suffix mnemonics of the input
functions tell you what the function returns.  The suffix mnemonics of the
output functions indicate the last argument and tell you what the function
outputs.  All output functions return an integer with a zero value if the
function executed successfully.

For example, the input function rbgetui():

    arguments:  r - record pointer
                b - base (radix) of input
      returns: ui - unsigned integer

The output function rbputui():

    arguments:  r - record pointer
                b - base (radix) of input
               ui - unsigned integer is output

Note: c is used in the prefix of a function's name only once even if there are
      two column arguments.  If the function inputs or outputs a character,
      there is only one column argument; otherwise there are two.



2.0 ERRORS AND WARNINGS

The functions declared in the header <recio.h> make use of the errno macro
defined in section 4.1.3 of ANSI X3.159-1989.  This mechanism was chosen
because (1) the <stdlib.h> conversion functions (strtod(), strtol(), etc.)
make use of this error reporting mechanism and (2) the <recio.h> functions
make use of the <stdlib.h> conversion functions.

In this implementation, errno can return the following macro constants:

         0 - No error.
    EACCES - permission denied.
    EINVAL - invalid argument (usually null record pointer).
    EMFILE - too many open files.
    ENOENT - no such file or directory.
    ENOMEM - out of memory.

Beginning with version 1.1, recio functions set errno when the record
pointer is invalid and set an internal error number when the record pointer
is valid.  The recio error number is accessed through the rerror function.

The rerror function can return the following macro constants:

         0 - No error.
  R_EINVAL - invalid argument (not the record pointer).
 R_EINVDAT - invalid data.
 R_EINVMOD - invalid mode.
 R_EMISDAT - missing data.
  R_ENOMEM - out of memory.
  R_ENOPUT - unable to write data to output.
  R_ERANGE - data out of range.

Beginning with version 2.0, recio functions set an internal warning number
when the record pointer is valid.  The recio library never sets the warning
number when the record pointer is invalid.  The recio warning number is
accessed through the rwarning function.

The rwarning function can return the following macro constants:

         0 - No warning.
 R_WEMPSTR - empty data string.
  R_WNOREG - unable to register exit function with atexit().
  R_WWIDTH - data too wide for columnar output.


2.1 Define Callback Error Function

First define a callback error function to be used by the recio functions.
You may give the function any name you wish.  In the sample function below,
the name rerrfn is used.  The function takes one argument, a record pointer
(REC *).  It returns nothing (void).  The function must first check for a
valid record pointer using the risvalid function.  Other than that, you can
customize it to do whatever you want.

Starting with version 2.04, a simple callback error function rerrmsg is now 
included in the RECIO library.  In the initial prototyping stages of 
development, you may wish to use this function rather than taking the time 
to develop your own function.  In the later stages of development you can 
substitute a more robust callback error function.

The recio functions use a callback error function in order to give the
most flexibility in handling errors.  This rerrfn function just sends
information to stderr.  You may wish to send information to a printer,
a file, a window, or a dialog box.  You might even want to give users
the ability to examine errors and enter corrections.  If the error is
corrected, you will want to call the rclearerr function before your
callback error function returns.

When your callback error function is invoked, check rerror() or errno
to determine the cause of the error.

Symbolic errno constants:

* EACCESS means that you don't have permission to access this file.  All
  MSDOS files have read permission.

* EINVAL indicates an invalid argument to a function, usually a NULL record
  pointer.  This resulted from a programming error.

* EMFILE means the program tried to open more files than the maximum allotted
  by ROPEN_MAX or FOPEN_MAX.  If your program is interactive, the user can
  close one or more open record streams.  Or you might decide that ROPEN_MAX
  or FOPEN_MAX needs to be a larger value.

* ENOENT says that ropen() could not find the requested file to open.
  Perhaps the name of the file was misspelled, or your program looked in
  wrong directory.  If your program was trying to read a configuration file,
  it could use internal default values when the configuration file does
  not exist.

* ENOMEM indicates that the program ran out of heap space.  You may be able
  to correct this if you are able to deallocate memory you no longer need.
  For example, you could reduce the size of buffers when the size only
  affects speed.  Such buffers need to be flushed first.  Buffers used by
  the recio library do not fit this criteria.

Symbolic rerror() constants:

* R_EINVDAT says the data is invalid.  Invalid data is caused by an
  unrecognized character in the field.  For example, rgetui() doesn't
  expect to see a negative sign, so a negative number will be flagged as
  invalid data.

* R_EINVMOD indicates that you opened a file in read mode, then used an output
  function or opened a file in write or append mode, then used an input
  function.

* R_EMISDAT says the data is missing.  Missing data means the field is empty.
  If you expect a number, you could substitute either zero or some unique
  number to indicate an empty field.

* R_ENOMEM indicates that the program ran out of heap space.  You may be able
  to correct this if you are able to deallocate memory you no longer need.
  For example, you could reduce the size of buffers when the size only
  affects speed.  Such buffers need to be flushed first.  Buffers used by
  the recio library do not fit this criteria.

* R_ENOPUT says the program was unable to write the data to output.  This
  could indicate that the disk is full.

* R_ERANGE tells you that the data is outside the range of the function.
  For instance, suppose you used rgeti() to get an integer and the data
  value is 32768.  If a 16-bit integer has an upper limit of 32767, the
  value is too large.  If the data is wrong, you can have the error
  function correct it.  If the data is right, you have to correct the
  data type in the program.

The main purpose of this sample callback error function is to show some of
kinds of things you can do in a callback error function.  Note that when an
error occurs, the column number indicator rcolno() has moved just beyond
the error.  To make it clearer to the user where the error occurred, rerrfn()
displays rcolno()-1, but not less than rbegcolno(), the column number for the
first column.

A more detailed callback error function is given in the source code for the
test program TESTCHG.C.  The test program callback error function makes use
of the rfix functions to fix up bad data (primarily overflows and underflows)
and continue processing.  If appropriate for your application, you can use
these functions as well.  They have been compiled into the recio libraries
for your potential use.

/* define callback error function */
void rerrfn(REC *rp)
{
    /* if rp is a valid record pointer */
    if (risvalid(rp)) {

      /* if reof indicator set */
      if (reof(rp)) {
          fprintf(stderr, "ERROR reading %s -- "
           "tried to read past end of file\n\n", rnames(rp));

      /* else rerror indicator set */
      } else {

          /* determine cause of error */
          switch (rerror(rp)) {

          /* input data errors */
          case R_ERANGE:
          case R_EINVDAT:
          case R_EMISDAT:
          /* output data errors */
          case R_ENOPUT:

              /* print location of error */
              fprintf(stderr, "DATA ERROR in FILE %s at LINE %ld,"
               " FIELD %u, COLUMN %u -- %s\n", rnames(rp), rrecno(rp),
               rfldno(rp), max(rcolno(rp)-1, rbegcolno(rp)), rerrstr(rp));
              break;

          /* fatal errors (R_EINVMOD, R_EINVAL, R_ENOMEM) */
          default:
            fprintf(errout, "FATAL ERROR reading FILE %s -- %s\n",
             rnames(rp), rerrstr(rp));
            abort();
            break;
          }
      }

    /* else invalid record pointer */
    } else {
        switch (errno) {

        /* non-fatal errors */
        case EACCES:
        case EMFILE:
          fprintf(errout, "WARNING: %s\n", strerror(errno));
          break;

        /* fatal errors (EINVAL, ENOMEM) */
        default:
          fprintf(errout, "FATAL ERROR: %s\n", strerror(errno));
          abort();
          break;
        }
    }
}


2.2 Define Callback Warning Function

Next define a callback warning function.  You may give the function any name
you want.  In the sample function below, the name rwarnfn is used.  The
function takes one argument, a record pointer (REC *).  It returns nothing
(void).

Starting with version 2.04, a simple callback warning function rwarnmsg is 
now included in the RECIO library.  In the initial prototyping stages of 
development, you may wish to use this function rather than taking the time 
to develop your own function.  In the later stages of development you can 
substitute a more robust callback warning function.

The recio functions use a callback warning function in order to give the
most flexibility in handling unusual conditions.  For example, the recio
library considers empty data strings to be legal but your application may
want to flag an empty data string as a data error.  You can do that by
checking for R_WEMPSTR warnings in your callback warning function.

When your callback warning function is invoked, check rwarning() to
determine the cause of the warning.

Symbolic warning constants:

* R_WEMPSTR says that an empty data string was input.  If you want to
  substitute another string, just use the rsetfldstr function to stuff the
  field buffer inside your callback warning function.

* R_WWIDTH indicates that on output, the data will not fit between the
  columns specified.  If the data is numeric, the recio output functions
  write asterisks to the output; if the data is a string, a truncated string
  is written.

* R_WNOREG means the program was unable to register the internal recio exit
  function with the ANSI atexit() function.  The internal recio exit
  function ensures that all open record streams are closed and all dynamic
  memory allocated by the recio library is deallocated.

You can also use your callback warning function to keep track of the number
of warnings, then print a summary of any warnings just before you close a
record stream.  You will find an example in the test program TESTCHP.C.

void rwarnfn(REC *rp)
{
  if (risvalid(rp)) {
    switch (rwarning(rp)) {
    case R_WNOREG:   /* atexit() full */
      fprintf (errout, "WARNING %s\n", rwarnstr(rp));
      break;
    case R_WEMPSTR:  /* empty data string */
      fprintf(errout, "WARNING reading %s at record %lu and field %u -- %s\n",
       rnames(rp), rrecno(rp), rfldno(rp), rwarnstr(rp));
      break;
    case R_WWIDTH:  /* data too wide for columns */
      fprintf(errout, "WARNING writing %s at record %lu and field %u -- %s\n",
       rnames(rp), rrecno(rp), rfldno(rp), rwarnstr(rp));
      break;
    }
  }
}

2.3 Register Callback Error and Warning Functions

Once you have written your callback error and warning functions, you must let
the other recio functions know that they exist.  You use the rseterrfn and
rsetwarnfn functions to register your callback functions.  Starting with 
version 2.04, these two functions are combined into the rinit function.  You 
may find use of rinit to be more convenient and easier to remember.

    /* register callback error and warning functions */
    rseterrfn(rerrfn);
    rsetwarnfn(rwarnfn);

OR

    /* register callback error and warning functions */
    rinit(rerrfn, rwarnfn);

OR to use the new simple built-in callback error/warning functions

    /* register built-in callback error and warning functions */
    rinit(rerrmsg, rwarnmsg);


OR to use the new simple built-in error function (but no warning function)

    /* register callback error function */
    rinit(rerrmsg, NULL);



3.0 OPEN FILE


3.1 Open File and Get Record Pointer

Use the ropen function to open the file you want to read, write, or append.
Store the record pointer returned by the ropen function.  Do not open recin,
recout, recerr, or recprn (MSDOS printer).  They are always open, so they do
not need to be opened or closed.

    REC *rp = ropen("FILENAME.DAT", "r");


3.2 Check Record Pointer

Following the ropen function, you need to check to see if the file was
opened correctly.  If ropen returned a NULL pointer, then the file was not
opened.

Errors other than ENOENT are reported to your callback error function.
ENOENT is not reported since you may want to use default values if the
data file is not available.

    /* if ropen() failed */
    if (!rp) {
        /* if it failed because file does not exist */
        if (errno==ENOENT) {
            /* action to take when file does not exist */
            ...
        }
    /* else ropen() succeeded */
    } else {
        /* set up stream (see sections 3.3 - 3.6) */
        ...
        /* read or write file (see sections 4 and 5) */
        ...
        /* close file (see section 6) */
        rclose(rp);
    }


3.3 Set Field and Text Delimiters

The space character is the default value for both the field and text
delimiters.  The space character matches any white space.  If you need to
use something else, you need to explicitly set the values.  Application
maintenance will be easier if you always set the values.

    rsetfldch(rp, ',');  /* set field delimiter character */
    rsettxtch(rp, '"');  /* set text delimiter character */


3.4 Set Field and Record Buffer Sizes

Setting the field and record buffer sizes is optional.  Buffers will be
automatically reallocated as necessary.  However if you set the field and
record sizes in advance to the maximum value needed, you can reduce memory
fragmentation.  The field and record buffers are not used for output.

    rsetfldsiz(rp, 41);  /* set size of field buffer */
    rsetrecsiz(rp, 133); /* set size of record buffer */


3.5 Set Context Number

If your application opens record streams with more than one data format, you
will want to set a context number.  You use the context number so that your
callback error function can determine (using the rcxtno function) which data
format it is dealing with.  Each context number must be a positive integer;
zero and negative numbers are reserved.  Predefined context symbolic
constants are RECIN, RECOUT, RECERR, and (for MSDOS) RECPRN.

#define SOILS_DB      1
#define BUILDINGS_DB  2

     rsetcxtno(rp, SOILS_DB); /* set context number */


3.6 Set Beginning Column Number

The first column number in the record buffer defaults to zero.  If you prefer
column numbering to start at one, use the rsetbegcolno function.  It is mainly
useful if using column delimited data.  If a number takes up the first ten
columns of the record, the column numbering will be 0 to 9 if rsetbegcolno()
is set to 0, or 1 to 10 is rsetbegcolno() is set to 1.

     rsetbegcolno(rp, 1); /* first column is column 1 */



4.0 RECORD FUNCTIONS

4.1 The rgetrec Function

If all the records in a data file have the same format, you will want to
loop through all the records until the end of file is reached.  If each
record has a different format, you must call the rgetrec function each
time you want to get the next record.  Calling rgetrec() is optional for
the first record.

    /* read all records in file */
    while (rgetrec(rp)) {
        /* Section 5 field functions go here ... */
    }


4.2 The rputrec Function

After you write all the fields in one record, use the rputrec function
to put the end-of-record newline character to the output and to reset the
internal recio library variables for the next record.

    /* write end-of-record */
    rputrec(rp);


4.3 The rrecs Macro

To get a pointer to the start of the record buffer, use the rrecs macro.

    /* echo record contents to stdout */
    printf("%s\n", rrecs(rp));


4.4 The rrecno Macro

To get the record number, use the rrecno macro.

    /* echo record number and record contents to stdout */
    printf("%ld: %s\n", rrecno(rp), rrecs(rp));


4.5 The rsetrecstr Function

There may be times when you will find it useful to stuff the record buffer
with your own string, then use the field input functions to scan the fields.
Use the rsetrecstr function for this.

4.6 The rresetrec Macro

The rresetrec macro resets internals so that you can start reading fields
from the beginning of the record buffer.  This allows you to scan through
the fields in the record buffer multiple times.  For example, you could
first use rskipnfld to determine the number of fields, use rresetrec to
reset, and then read in each field until you have reached the number of
fields.


5.0 GET AND PUT FIELD DATA

5.1 Field functions

There are 48 functions that can be used to read or write data.  A mnemonic
system makes it easy to construct the name of any function you want.  All you
need to remember is that there are four prefixes, two bodies, and eight
suffixes, and that the rb and rcb prefixes are used only with the i, l, ui,
and ul suffixes.

If the prefix contains the letter 'c', it is a column delimited field function.
If the prefix contains the letter 'b', it is a field function that reads or
writes an integral number in a specified base (radix).  The eight suffixes
indicate the eight data types: character, double, float, integer, long,
string, unsigned integer, and unsigned long.

    Prefix   Body   Suffix      Prefix   Body   Suffix
    ------   ----   ------      ------   ----   ------
      r       get      c          rb      get      i
      rc      put      d          rcb     put      l
                       f                          ui
                       i                          ul
                       l
                       s
                      ui
                      ul


5.2 The rskipfld Macro

If your application does not need to read the data in a field, you can skip
over the field by using the rskipfld macro.

    /* skip over a field */
    if (rskipfld(rp) != 1) printf("Unable to skip field.\n");


5.3 The rskipnfld Function

If your application does not need to read the data in several adjacent
fields, you can skip over the fields by using the rskipnfld function.

    /* skip over three fields */
    if (rskipnfld(rp, 3) != 3) printf("Unable to skip 3 fields.\n");


5.4 The ristxtfld Macro

You can use the ristxtfld macro to determine if the current field (the field
most recently input or output) was quoted with the text delimiter character
(provided the text delimiter character is not the space character).  For
example, if you need to write out a string field in the same format in
which was read, you can use this macro to determine if the original field
was quoted.

    /* write field in same format as read */
    rgets(recin);
    if (ristxtfld(recin)) rsettxtch(recout, '"');
    else rsettxtch(recout, ' ');
    rputs(recout, rflds(recin));


5.5 The reof Macro

Use the reof macro to determine when the record stream has reached the
end of file.

    /* if error or end of file reached */
    while (rgetrec(rp)) {
    ...
    }
    /* if end of file */
    if (reof(rp)) {
       ...
    /* else error */
    } else {
       ...
    }


5.6 The rerror Function

Use the rerror function to determine if an error has occurred on a record
stream.  You can access a text string for the error using the rerrstr
function.

    if (rerror(rp))
        printf("ERROR reading %s - %s\n",
         rnames(rp), rerrstr(rp));
    rclose(rp);

It is a good practice to check for any errors just before closing
a record stream.  If the error indicator is clear, you have additional
confidence that the stream was processed correctly.


5.7 The rseterr Function

If you write wrapper functions or other functions that interact with
recio functions, your code will need to handle errors.  If can use
the rseterr function to set the error number and to call the record
stream callback error function.

/* get integer and validate range */
int rrgeti(REC *rp, int min, int max) {
    int result;

    result = rgeti(rp);
    if (result < min || result > max) {
        rseterr(rp, R_ERANGE);
    }
    return result;
}


5.8 The rwarning Function

Use the rwarning macro to determine if a warning has occurred on a record
stream.

    if (rwarning(rp))
        printf("ERROR %s - %s\n", rnames(rp), rwarnstr(rp));

It is good practice to check for any warnings just before closing an output
record stream.


5.9 The rsetwarn Function

If you write wrapper functions or other functions that interact with recio
functions, you may need to provide warnings.  The rsetwarn function sets the
warning number and calls the record stream callback warning function.

    rsetwarn(rp, R_WWIDTH);


5.10 The rnumfld Function

You can determine the number of character delimited fields in the current
record of an input record stream with the rnumfld function.

    unsigned numfld;
    
    numfld = rnumfld(recin);
    

5.11 The rgetfldpos and rsetfldpos Functions

If you want to mark a specific position within the current record and later 
return to the same position, use the rgetfldpos and rsetfldpos functions. 
If the record number has changed between calls, a R_EINVAL error is generated 
and passed to the callback error function.  The pos argument is passed by 
reference, but a macro takes care of placing the & for you.


     rpos_t pos;
     
     rgetrec(recin);
     rgetfldpos(recin, pos);   /* get bookmark at start of record */
     rskipnfld(recin, 3);      /* skip three fields */
     rsetfldpos(recin, pos);   /* reset to bookmark position */



6.0 CLOSE FILE

6.1 Close File

When finished reading or writing a data file, close it.  Do not close recin,
recout, recerr, or recprn as they are always open.

    /* close record file */
    rclose(rp);


6.2 Close All Files

Rather than closing record files one at a time, one can close all open
record files at once using the rcloseall function.

    /* all done */
    rcloseall();



7.0 STRING FUNCTIONS

The recio library contains a small set of string functions.  Ideally these 
would be part of a larger separate string library, but are included here 
because the recio functions need them.  You may also find them useful for 
your application.


7.1 Trim a string

The scntrimbegs, scntrimends, and scntrims functions allow you to trim the 
same character from the beginning, end, and both ends of string up to the 
maximum number specified by the last argument.  The sctrimbegs, sctrimends, 
and sctrims functions let you to trim all multiple occurances of the same 
character from the beginning, end, and both ends of a string.  If you 
specify the space character, all whitespace is trimmed.  You can use the 
strimbegs, strimends, and strims macros to trim whitespace from the ends.

    char str[]="\t  Hello, World?!!!\n";
    
    strims(str);              /* trim whitespace from both ends of string */
    sctrimends(str, '!');     /* then trim all ! chars from end of string */
    scntrimends(str, '?', 1); /* finally trim one ? char from end of string */


7.2 Dynamically copy and concatenate a string

The scpys and scats macros are used to dynamically copy and concatenate a 
string.  To use these macros you must adhere to these points:

     1. Initialize pointers of destination strings to NULL.
     2. These macros pass the destination string by reference (and place
        the & for you).
     3. A pointer to the destination string is returned.
     4. Free any dynamically allocated strings when finished with them.
     
     char *dst=NULL;
     char src1[]="Hello ";
     char src2[]="world";
     
     scpys(dst, src1);
     puts(scats(dst, src2));  /* "Hello world" */

     /* however the following will not work here as in C++ where you can 
        declare a function to pass an argument by reference, i.e. 
        char *scats(char *&dst, const char *src)

     puts(scats(scpys(dst, src1), src2));                      */
     
     free(dst);



8.0 REFERENCES

1. ANSI X3.159-1989.  American National Standard for Information Systems -
   Programming Language - C.  American National Standards Institute,
   11 West 42nd Street, New York, NY 10036, 1990.



9.0 INDEX

errno macro ............ 2.0, 2.1, 3.2
rbegcolno macro ........ 2.1
rclearerr function ..... 2.1
rclose function ........ 6.1
rcloseall function ..... 6.2
rcolno macro ........... 2.1
rcxtno macro ........... 3.5
recin expression ....... 3.1, 6.1
reof macro ............. 2.1, 5.5
rerror function ........ 2.1, 5.6
rerrmsg function ....... 2.1, 2.3
rerrstr function ....... 2.1, 5.6
rfix functions ......... 2.1
rflds macro ............ 5.4
rfldno macro ........... 2.1
rbget functions ........ 5.1
rbput functions ........ 5.1
rcbget functions ....... 5.1
rcbput functions ....... 5.1
rcget functions ........ 5.1
rcput functions ........ 5.1
rget functions ......... 5.1
rgetfldpos function .... 5.11
rgetrec function ....... 4.1
rinit function ......... 2.3
ristxtfld macro ........ 5.4
risvalid function ...... 2.1
rnames macro ........... 2.1
rnumfld function ....... 5.10
ropen function  ........ 3.1
rput functions ......... 5.1
rrecs macro ............ 2.1, 4.3
rrecno macro ........... 2.1, 4.4
rresetrec macro ........ 4.6
rsetbegcolno function .. 3.6
rsetcxtno function ..... 3.5
rseterr function ....... 5.7
rseterrfn function ..... 2.3
rsetfldch function ..... 3.3
rsetfldpos function .... 5.11
rsetfldsiz function .... 3.4
rsetfldstr function .... 2.1
rsetrecsiz function .... 3.4
rsetrecstr function .... 4.5
rsettxtch function ..... 3.3, 5.4
rsetwarn function ...... 5.9
rsetwarnfn function .... 2.3
rskipfld macro  ........ 5.2
rskipnfld function ..... 5.3
rwarning function ...... 2.2, 5.8
rwarnmsg function ...... 2.2, 2.3
rwarnstr function ...... 2.2, 5.7
scats macro ............ 7.2
scpys macro ............ 7.2
sctrimbegs function .... 7.1
sctrimends function .... 7.1
sctrims function ....... 7.1
strimbegs macro ........ 7.1
strimends macro ........ 7.1
strims macro ........... 7.1
