































                                ____________________
                                C++ Database Library
                                ____________________
                                C++ Database Library
                                ____________________
                                C++ Database Library


                             Version 2.1, 4th March 1993





       Package Contents
       ================

       The package contains the following files:

           SOURCE.EXE  The source files for the library and the project 
                       files which build the library.
                       This file contains the following files:

               DATABASE.PRJ    Project file to build database library
               DATABASE.CPP    Source file
               DATAPRIV.HPP    Private header file
               DATAEXE1.CPP    Source file
               DATAEXE2.CPP    Source file
               RECORD.CPP      Source file
               FIELD.CPP       Source file
               UTIL.CPP        Source file
               INDEX.CPP       Source file
               INDEX2.CPP      Source file
               INDCLUS.CPP     Source file
               DATABASW.PRJ    Project file to build Windows database
               library
               DATABASE.DSK    Borland desktop file
               EXOP.HPP        Private header file
               DATABASW.DSK    Borland desktop file

           DATABASE.EXE    The library files, header file, and example 
                           programs.

               DATABASE.LIB    DOS version of the library
               DATABASE.HPP    Header file for all versions of the library
               DATABASE.TXT    User manual for library.
               DATABASW.LIB    Windows version of the library
               DBRPT.CPP       Example program
               DBRPT.PRJ       Project file for example program
               FAMILYS.DBF     Example database
               FAMILYS.PRJ     Project file for example program
               FAMILYS.CPP     Example program
               FAMILYS.NDX     Index file for example database
               PACKDB.PRJ      Project file for example program
               PACKDB.CPP      Example program
               README.TXT      File list and conditions





       Library, Conditions for use:
       Library, Conditions for use:
       Library, Conditions for use:
       ============================
       ============================
       ============================

       This library of routines is provided as shareware. You may try the
       functions defined in the library within in your own programs, however
       if you decide to incorporate them permanently then you are obliged to
       register your copy of the software. Registration costs #30.00 or
       $40.00 (by cheque) to:

           Robin Abbott
           37 Plantation Drive,
           Christchurch,
           Dorset
           ENGLAND

           BH23 5SG

       Registration brings you a printed manual, the next library upgrade
       free of charge and technical support.

       Compiled programs which incorporate the code within these libraries
       may be distributed with no further royalty.

       The library may be freely distributed provided that all files are
       included.





       Introduction
       Introduction
       Introduction
       ============
       ============
       ============

       This library is intended to allow Borland C++ programs to create,
       write, interrogate and manipulate databases and indexes which are in
       dBase III format. These files usually have the extension .dbf. The
       library is based on a group of objects which represent databases,
       records and fields.

       The library of functions will be of use in building database
       applications and report generators, both standalone, and for use with
       other database applications.

       Using the Library in Building Programs
       Using the Library in Building Programs
       Using the Library in Building Programs
       ======================================
       ======================================
       ======================================

       The package incorporates two library files - database.lib and
       databasw.lib. These incorporate all the functions described in this
       document using the large model. Copy these files to your library
       directory (possibly c:\borland\lib), and copy the header file
       database.hpp to your include directory (possibly c:\borland\include).

       Programs which use the library must include the header file
       database.hpp, must be compiled using the large model, and must be
       linked with the library file. Use the database.lib library for DOS
       programs and the databasw.lib library for Windows programs.

       The project files database.prj and databasw.prj are used to build the
       libraries and are incorporated in the source.zip file. These project
       files may be modified to build different versions of the model (e.g.
       huge). However I was unable to build a DLL from this package as the
       linker (DOS, Project Workbench, and Turbo C++) crashed.

       Using the Package
       Using the Package
       Using the Package
       =================
       =================
       =================

       The package is intended to be as easy as possible to use. A database
       object should be opened with the name of the database in which the
       user is interested together with the first index file if required.
       Additional index files may be added as required. A record object (or
       many records) may now be opened on this database. The record object
       may be positioned at any record in the file and the fields in the
       record may be examined or modified. It is possible to select records
       on a variety of criteria including dBase expressions.

       The following example shows a program which opens the file "CLUB.DBF"
       and swaps the month and day for each record in the file. This was
       written to correct an error in importing a file in American date
       format. The file has a number of fields, the only one considered here
       is the DOB field - the date of birth which was incorrectly imported.

       /*
          Short Program to exchange Day & Month for every record in a
                   database
       */





       #include <string.h>
       #include <database.hpp>
       #include <stdio.h>
       #include <stdlib.h>

       void main()
       {
        char ws[128];              // Convenient Workspace

        database db("CLUB");       // Open database, no index

        record rec(db);            // Record open on database

        int fn=db.getfield("DOB")->getnumber();           // Get number of
                   DOB field

        int sel=rec.select(FIRST);     // Select first record
        while(!sel)                    // For every record in the file
        {
         int d,m,y;                    // day, month and year

         gnums(rec.getfield(fn),d,m,y);            // Get date of birth to
                   d,m and y
         sprintf(ws,"%04d%02d%02d",y,d,m);     // Print in db form, swap d &
                   m
         rec.setfield(fn,ws);                  // Update field
         rec.write();                          // Write back to disk

         sel=rec.select(NEXT);                 // Select next record
        }
       }

       Indexes
       Indexes
       Indexes
       =======
       =======
       =======

       The package is capable of handling dBase style NDX files (indexes).
       These are attached using the database::addindex() function. Indexes
       are referred to by the file name with no path and no extension. Thus
       the index "c:\c\name.ndx" is referred to using the string "name".

       Indexes are usually attached when the database is opened, or when
       they have been created. New records will be included in the index
       only if the index is attached to the database. Should an out of date
       index be attached to a database then there is a risk that the records
       will be out of order, to check a database the database::verifyindex()
       function may be used to check the index. However this function checks
       every record in the database and can be expected to be very slow on
       large databases.

       Big Files
       =========





       Large databases (greater than about 2000 records) need handling
       carefully to avoid speed problems. Indexes should be built on these
       databases as little as possible, being maintained by attaching them
       to the database. Selecting records should be done by using the index
       key (the record::selkey() function), which will be considerably
       faster than using any of the record::select() functions - these
       functions search the whole database from beginning to end. The
       database::verifyindex() function should also be avoided as this
       searches the entire database.

       Errors
       Errors
       Errors
       ======
       ======
       ======

       The database will report fatal errors (by printing the errors on
       screen) which may cause a crash such as opening a record on an
       invalid database. Some more trivial errors which may be trapped by
       the calling program will also be printed. The global variable eroff
       should be set to 1 if it is required to turn off printing of these
       non-fatal errors.

       Objects
       Objects
       Objects
       =======
       =======
       =======

       There are three types of object in the database library. Database
       objects, Field Objects and Record Objects. A database object holds
       the description of a database, field objects associated with the
       database hold the definitions of the fields in the database, and
       record objects hold all the information on a record.

       Assignment of Objects (Use of the = Operator)
       Assignment of Objects (Use of the = Operator)
       Assignment of Objects (Use of the = Operator)
       =============================================
       =============================================
       =============================================

       The = operator may be used within the database library only to copy
       one record to another (within the same database), this allows the
       user to save the position and contents of a record within the
       database, the = operator is implemented to ensure that a complete
       copy of all structures and variables associated with the record is
       made. An example of the use of this is shown above in the subtotals
       program.

       The = operator must not be used with database objects or field
       objects, or between records on different databases. See the example
       program DBPACK below to see how a record may be copied between
       different databases.

       DATABASE Objects
       DATABASE Objects
       DATABASE Objects
       ================
       ================
       ================

       A database object is constructed with the name of the database to be
       opened, and an optional index file. The file is opened and the user
       then has the capability to determine the state of the file and the
       fields in it. Constructing a database object automatically creates a
       linked list of the fields in the database.





       To create a new database a database object is opened without a file
       name, and then fields of any type can be added to the database. When
       all the database fields have been written the database can be
       established by saving it with a filename after which records may be
       created within it.

       The database object has the following public interface:





       class database
       {
        public :

        char ers[81];       // Reports the error in an expression

        database(char *name,char *index=0);  // Construct with file+index
        database(void);                      // Construct a new database
        ~database(void);

        int   addfield(field *newfield);     // Copy a field to a new
       database
        int   addfield(char *nm,int l=1,int rdp=0);   // Add a field to a
       new database
        int   addindex(char *fname);         // Add an index to the database
        int   buildindex(char *expr,char *fn); // Build an index
        field *getfield(int n)
              {return(fielda[n-1]);}               // Return field n,
       1<=n<=nfield
        field *getfield(char *name);         // Get a pointer to a field by
       name
        char  *getindkey(char *name);        // Get index key
        int   getindtype(char *name);        // Get index type, OPINT or
       OPSTR
        long  getnrec(void) {return(nrec);}        // Number of records
        int   getreclen(void) {return(reclen);}    // Total record length
        int   getnfield(void) {return(nfield);}    // Number of fields
        int   isvalid(void) {return(valid);} // Return valid flag
        int   verifyindex(char *iname);      // Verify an index
        int   subindex(char *iname);         // Subtract an index
       }





       FIELD Objects
       FIELD Objects
       FIELD Objects
       =============
       =============
       =============

       A field object is created to enable the functions associated with the
       database to determine the format of the records. It also allows the
       user to determine the type, name and parameters of any field. The
       field type has the following public interface:

       class field
       {

        public:

        field(int number,char *name,char type,int length,int rdp,
              int recpos);
        ~field(void);
                                                 // Functions to get
       privates
        int getnumber(void) {return(number);}    // Get no. of field in
       record
        char *getname(void) {strcpy(namecop,name);
                             return(namecop);}     // Get name of field
        char gettype(void) {return(type);}       // Get type of field
        int getlen(void) {return(len);}          // Get length of field
        int getrdp(void) {return(rdp);}          // Get right of dp of field
       };

       RECORD Objects
       RECORD Objects
       RECORD Objects
       ==============
       ==============
       ==============

       There may be any number of record objects associated with each
       database object. Each record object holds one record in the file, and
       it is possible to load a record object with the first, last, next,
       previous, or any numbered record in the database. The record object
       has the following public interface:






       class record
       {
        public:

        record(class database &db,char *indname=0);
        ~record(void);
        void operator=(record &s);                 // Copy record entirely

        int eval(char *expr,void *result,int &rtype); // Evaluate expression
        int eval(void *result,int &rtype);     // Evaluate prev. expression
        int indchk(char *exp,int &rtype);      // check an index expression
        char *indname();                       // Return index name
        int seldbf(long n);                    // Select record in dbf
        int select(long n,int type=NOTDEL,int df=0);  // Select rec number
        int select(int field,int val,long n,
                                    int type=NOTDEL);     // Select when
       fn==val
        int select(int field,char *s,long n,
                                     int type=NOTDEL);    // Select when
       field==s
        int select(int field,void *s,
                           int (*comp)(void *,void *),
                           long n,int type=NOTDEL);   // User def sel->
       comp()==0
        int select(char *expr,long n,
                                    int type=NOTDEL); // Select when expr is
       true
        char *getfield(int n,int tf=1);        // Get field by number
        char *getfield(char *name,int tf=1);   // Get field by name
        long getrecnum() {return(rn);}         // Current record number
        int  getdelstate() {return(delstate);}     // Delete state of record
        void setdelstate(int sval);            // Set delete state of record
        int  selkey(char *value,int type=NOTDEL,int num=OPSTR);   // Select
       by key
        int  selkey(double value,int type=NOTDEL);
        int  selkey();                         // Find other records with
       key
        int  setfield(char *name,char *value); // Set new value for field by
       name
        int  setfield(char *name,float value);
        int  setfield(int number,char *value); // Set new value for field by
       #
        int  setfield(int number,float value);
        int  write(int type=OVER);             // Write rec to dbf
       };





       Representation of Field Types
       Representation of Field Types
       Representation of Field Types
       =============================
       =============================
       =============================

       Field types in dBase are Numeric (N), Character (C), Date (D),
       Logical (L) or Memo (M). They are held internally in the follwoing
       forms:

       Numeric
       Numeric
       Numeric

       Numeric fields are held in double (8 byte) form. When values are
       returned from the database they are in ASCII form (e.g. 1.235). Thus
       the C atoi() or atof() functions should be used to convert to integer
       or floating form as required.

       Character
       Character
       Character

       Character fields are held internally as normal C format 0 terminated
       strings.

       Logical
       Logical
       Logical

       Logical fields are held as a single character string (0 terminated)
       where the value is "T","t","Y", or "y"  for true values and
       "F","f","N", or "n" for false values.

       Date
       Date
       Date

       Date fields are held as character strings (0 terminated) of length 8.
       These are in the form YYYYMMDD, thus 5th November 1962 is held as
       "19621105".

       Memo
       Memo
       Memo

       Memo fields are held as character strings which are 0 terminated,
       they may be any length up to 64000 bytes provided there is sufficient
       memory to hold them. Internally memos read from a database are held
       in a buffer which starts at 512 bytes, and is expanded when necessary
       to read a larger memo.





       Expression Evaluator
       ====================

       The expression evaluator in the database library provides a complete
       dBase compatible expression evaluation function which may be used to
       select records, may be used for calculations on records, or is used
       to create index expressions. The expression evaluator is associated
       with the database object, and there is a public string (ers in the
       database object) which holds the ASCII reperesentation of the last
       database error which occurred, see the record::eval function for an
       example.

       It provides the following operators which are shown in operator
       precedence (those at the top of the list are executed first), note
       that the dBase =< and => (which in dBase are equivalent to >= and <=)
       are not implemented in the library (use the <= and >= operators
       instead).

       ()      Parenthesis bracket operations and change the order of
               evaluation.
       +   -   Unary + and - operate on a single number.
       **  ^   Exponentiation (to the power of)
       *   /   Multiplication, Division
       +   -   Add and Subtract, Numbers, Dates or Strings
               Addition of two strings concatenates them. Subtraction on
               strings takes the spaces from the end of the first string,
               concatenates the second string to the first string, and then
               puts the spaces back at the end of the new string.
               A date may have a number added or subtracted from it which
               provides a date which is that many days in the future or
               past. A date may be subtracted from another date to give the
               number of days between the two dates.
       <
       >
       =
       <> or #
       <=
       >=      These are relational operators and act on strings, dates, or
               numbers to indicate if the first item is less than (<), less
               than or equal to (<=), equal to (=), greater than (>),
               greater than or equal (>=) or not equal (<>, or #) to the
               second item. The first item must be of the same type as the
               second item.
       $       This is a string operator used in the form A$B, it returns
               true if A is identical to B, or is contained within it.
       .AND.   Logical AND.
       .OR.    Logical OR.
       .NOT.   Logical NOT, note the lower priority than .AND. and .OR.
               which can be confusing to those used to conventional operator
               precedence.





       Field names be used wherever a number, string or date may be used.

       e.g.

         database db("Names");
         record rec(db);

         rec.select("upper(name)=\"B\"",FIRST);               // Select 1st
         record where 1st character of name field is B
         rec.select("ageyears+agemonths/12<12.5",NEXT);           // Select
         next record where age is less than 12.5
         rec.select("dob=ctod(\"17/4/78\")",FIRST);               // Select
         1st record where dob field is 17th April 1978.





       Expression Evaluator functions
       Expression Evaluator functions
       Expression Evaluator functions
       ==============================
       ==============================
       ==============================

       The following functions are available:

       ABS(numeric expr)       Gives the absolute value of the supplied 
                               expression.
       ASC(string expr)        Returns decimal ASCII value of the first 
                               character of string
       AT(char s1,char s2);    If string s1 is to be found in string s2 then
                               this returrns the position that it is to be
                               found at (starting at 1). it returns 0 if s2
                               is not to be found in s1.
       CDOW(date expr)         Returns a string which names the day of the
                               week supplied by date expr.
       CHR(numeric expr)       Gives a string of length 1 holding the
                               character which has the ASCII code supplied
                               by the numeric expression.
       CMONTH                  Returns a string which names the month
                               supplied by date expr.
       CTOD(char expr)         Returns a date matching the character string
                               supplied as input.
       DATE()                  Returns the current operating system date.
       DAY(date expr)          Returns the number of the day from the
                               supplied date expression.
       DOW(date expr)          Returns a number representing the day of the
                               week from the supplied date expression.
       DTOC(date expr)         Converts the date expression to a charcter
                               string in system date format.
       DTOS(date expr)         Gives the dBase date string form of date expr
                               (YYYYMMDD). Note this function is dBase IV,
                               not dBase III.
       EXP(numeric expr)       Gives the result of e to the power numeric
                               expr.
       IIF(condition,expr1,expr2)  If condition is true returns the result
                               of evaluating expr1, otherwise it returns the
                               result of evaluating expr2.
       INT(numeric expr)       Gives the integer value of the supplied
                               expression.
       ISALPHA(char expr)      Evaluates to TRUE if the 1st character of the
                               supplied expression is a letter of the
                               alphabet, FALSE if not.
       ISDIGIT(char expr)      Evaluates to TRUE if the 1st character of the
                               supplied expression is a number, FALSE if
                               not.
       ISLOWER(char expr)      Evaluates to TRUE if the 1st character of the
                               supplied expression is a lower case letter of
                               the alphabet, FALSE if not.
       ISUPPER(char expr)      Evaluates to TRUE if the 1st character of the
                               supplied expression is an upper case letter
                               of the alphabet, FALSE if not.
       LEFT(char expr,numeric expr)Returns the numeric expr characters from
                               the left of the supplied string.
       LEN(char expr)          Returns the length of the supplied character
                               expression.





       LOWER(char expr)        Returns a string where all letters of the
                               supplied expression have been converted to
                               lower case.
       LTRIM(char expr)        Returns a string which is the supplied string
                               with all leading spaces removed.
       MAX(expr,expr)          Returns the greater of the two supplied
                               numbers or dates.
       MIN(expr,expr)          Returns the lesser of the two supplied
                               numbers or dates.
       MOD(numeric expr,numeric expr)  Gives the remainder when the first 
                                       expression is divided by the second.
       MONTH(date expr)        Returns the number of the month represented
                               by the supplied date expression.
       RECCOUNT()              Gives the number of records in the database.
       RECNO()                 Gives the record number (in the dbf file)of
                               the record on which the function is
                               evaluated.
       RECSIZE()               Gives the length of the records in the
                               database.
       REPLICATE(char expr,num expr)   Returns a string made by num expr 
                                       repeats of the supplied character 
                                       expression.
       RIGHT(char expr, num expr)  Returns the numeric expr characters from
                               the left of the supplied string.
       ROUND(num expr,num dec) Gives the result of rounding off the supplied
                               numeric expression to dec decimal places.
       RTRIM(char expr)        (Same as TRIM).
       SOUNDEX(char expr)      Gives the string of lenght 4 which results
                               from running the SOUNDEX algorithm on the
                               supplied character expression. Note that this
                               is a dBase IV function and is not supported
                               in dBase III.
       SPACE(num expr)         Returns a string consisting of num expr space
                               characters.
       STR(number, len, decimal)   Returns the string which represents the
                               number supplied. len is the total length of
                               the resulting string, decimal is the number
                               of decimal places.
       STUFF(str 1,start,len,str 2)Gives a sting which is created by taking
                               str 1, removing the substring which is
                               defined by start and length, and inserting
                               str2 where the substring was removed.
       SUBSTR(string,start,length) Gives the string which is a substring of
                               the supplied string starting at position
                               start and going on for length characters. If
                               length is not supplied then it returns the
                               rest of the string. The string is assumed to
                               start at position 1.





       SWAPDATA(string)        This is a PCF function which takes the
                               supplied string and takes the first ~
                               character in it exchanging the information
                               after the ~ for the information before it,
                               and replacing the ~ by a space. Thus
                               "SMITH~JOHN" becomes "JOHN SMITH". This is
                               used in PCF for alphabetical sorting by
                               surname but printing by proper name. This
                               function is NOT dBase III compatible.
       TIME()                  Gives the character representation of the
                               current time.
       TRIM(string expr)       Gives the string which results from removing
                               all trailing spaces from the supplied string.
       TYPE(expr)              Gives a single character string which
                               represents the result type of expr. (C, N, L,
                               or D). Note that unlike dBase date
                               expressions are allowed, and that memo fields
                               return C, not M like they would in dBase.
       UPPER(string expr)      Returns a string where all letters of the
                               supplied expression have been converted to
                               upper case.
       VAL(char expr)          Gives the numeric value of the expression
                               contained in char expr. Note that the
                               expression is a simple number thus "4.5"
                               returns the number 4.5, but "9/5" returns the
                               number 9.
       YEAR(date expr)         Gives a four digit number which is the year
                               of the date expression.





       Examples
       Examples
       Examples
       ========
       ========
       ========

       PACKDB
       PACKDB
       PACKDB
       ======
       ======
       ======

       The following example shows a program which packs a database. That is
       it takes an input database and creates an identical output database
       which has deleted records removed. The program is invoked using the
       command line:

           PACKDB INFILE OUTFILE [INDEX]

       INFILE is the name of the input database OUTFILE is the output
       database which will be created. INDEX is the name of the optional
       index file which will be used to order the input file so that the
       records are written in this order to the output file.

       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <database.hpp>
       #include <math.h>

       main(int argc, char *argv[])
       {
        char index[128];   // Holds index name if supplied
        long i;

        if (argc<3)        // Error need at least two arguments
        {
         printf("\nUsage : packdb infile outfile [index]\n");
         printf("\n\nDuplicates infile database to outfile database");
         printf("\nremoving deleted records.\n\n");
         printf("\nindex if supplied will operate on infile to define\n");
         printf("\nthe order records are written to outfile\n\n");
         exit(1);
        }
        if (argc==4) strcpy(index,argv[3]); else *index=0;    // Copy index

        database *dbin=new database(argv[1],index);   // Open input database
        database *dbout=new database();               // Create new output
                   database

        if (dbin->isvalid())       // Check if input database is valid
        {
         delete dbin; delete dbout;
         printf("\nUnable to open input database, error %d\n\n",dbin-
                   >isvalid());
         exit(1);
        }

        for(i=1; i<=dbin->getnfield(); i++)    // Copy all fields
         dbout->addfield(dbin->getfield(i));

        if (dbout->write(argv[2])) // Check database successfully written





        {
         delete dbin; delete dbout;
         printf("\nUnable to open output database\n\n");
         exit(2);
        }

        record *reci=new record(*dbin);        // Record on old database
        record *reco=new record(*dbout);   // Record on new database

        long recn=1;
        printf("\n");





        int rv=reci->select(FIRST);        // set to 1st record (default
                   undeleted)
        while(!rv)
        {
         for(i=1; i<=dbin->getnfield(); i++)       // Copy all fields
         {
          if (dbin->getfield(i)->gettype()=='N')   // Numeric fields
          {
           reco->setfield(i,atof(reci->getfield(i)));
          }
          else reco->setfield(i,reci->getfield(i));   // All other fields
         }
         reco->write(NEW);             // Write new record
         rv=reci->select(NEXT);            // Next input record
         printf("\r%ld",recn++);
        }

        delete reci;
        delete reco;
        delete dbin;
        delete dbout;
        printf("\n\nOperation complete\n");
       }





       FAMILYS
       FAMILYS
       FAMILYS
       =======
       =======
       =======

       The second example below shows the use of sub totals. This example is
       on a database of families which has been indexed on the field name
       SURNAME. The program finds all records which have the SURNAME field
       set to the same value in the database and works out the average of
       the field called AGE (age in years) and AGEM (age in months) within
       the family. It then returns and for every member of the family
       updates the avgage field which is the difference between the AGE and
       AGEM fields in the record and the average age worked out for the
       family.

       /*
          Demonstration of Subtotals,

          Short program to update average age for each family in familys.dbf
          The average age is a field in each record which will indicate how
                   far
          away each family member is in age from the average age in that
                   family

          Index expression in familys.ndx is :

            UPPER(SURNAME)

          Thus all family members will be grouped together
       */

       #include <string.h>
       #include <database.hpp>
       #include <stdio.h>
       #include <stdlib.h>
       #include <math.h>

       void main()
       {
        char ws[128];           // Workspace

        database db("familys","familys");      // Open database
        record rec(db);                    // Current record
        record first(db);                             // Used to save
                   position
        int fn=db.getfield("avgage")->getnumber();        // Field no.
                   avgage field

        int sel=rec.select(FIRST);     // Select first record
        do
        {
         char surname[128];        // Holds current surname
         double avgage=0;          // Holds average age
         first=rec;                // Save position
         int ssel=sel;         // save sel at current position
         int trec=0;               // Total no. of records in patrol





         strcpy(surname,rec.getfield("surname"));          // Copy in patrol
                   name

         while(!sel && !strcmp(surname,rec.getfield("surname"))) // Until
                   new name
         {
          avgage+=atof(rec.getfield("age"))+atof(rec.getfield("agem"))/12;
          trec++;
          sel=rec.select(NEXT);            // On to next record
         }
         avgage/=trec;

         while(!ssel && !strcmp(surname,first.getfield("surname")))  //
                   Round again
         {
          double age;

          age=atof(first.getfield("age"))+atof(first.getfield("agem"))/12;
          first.setfield(fn,int(age-avgage));
          first.write();           // Write the new record
          ssel=first.select(NEXT);
         }
         printf("\nAverage age of %s is %f",surname,avgage);
        }
        while(!sel);
       }

       DBRPT
       DBRPT
       DBRPT
       =====
       =====
       =====

       The following program takes a database and prints a report on it,
       printing number and length of records, and type and length of each
       file. With the -c option it will print an include file for use in a
       C++ program which contains "#define" statements for each field
       number.

       It takes one argument, the file name with an optional -c to print the
       the include file.


       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <database.hpp>

       main(int argc, char *argv[])
       {
        char *wsp,ws[128]; // Useful work space
        int inc=0;         // Flags results are to be printed in #include
       form

        if (argc<2)            // Must have at least one parameter
        {
         printf("Usage : dbrpt filename [-c]\n");
         printf("        -c option prints in #include form for header
       files\n");





         exit(1);
        }
        if (argc>2) if (!strcmpl(argv[2],"-c")) inc=1;

        strcpy(ws,argv[1]); strupr(ws);
        if (wsp=strchr(ws,'.')) *wsp=0; if (wsp=strchr(ws,'\\')) *wsp=0;

        // Open database

        database *db=new database(argv[1]);
        if (db->isvalid())             // Check if failed to open
        {
         delete db;
         printf("\nError on opening database !"); exit(1);
        }

        // Dump database stats.

        if (inc) printf("// Database %s, include file\n\n// ",ws);
        else printf("\n");
        printf("Database : %s has %ld records of length %d with %d
       fields\n",
           ws,db->getnrec(),db->getreclen(),db->getnfield());

        // Dump field stats for each field

        for(int i=1; i<=db->getnfield(); i++)
        {
         field *fld;

         fld=db->getfield(i);
         if (inc) printf("\n#define %-10s %3d   // ",fld->getname(),i);
         else printf("\nField %3d - %-10s ",i,fld->getname());
         printf("Type %c, Length %3d, Rdp %d",
             fld->gettype(),fld->getlen(),fld->getrdp());
        }

        printf("\n\n");
        delete db;
        exit(0);
       }





       Global Utility Functions
       Global Utility Functions
       Global Utility Functions
       ========================
       ========================
       ========================

       The following are global functions available under the database
       library, they may be of use in any database application.

       char *getdate(time_t time);
       char *getdate(time_t time);
       char *getdate(time_t time);

       This function obtains a time in the dbf form from an ANSI C time, the
       returned string is static and is overwritten by each call to getdate.
       Thus the current date can be obtained by:

               #include <time.h>
               #include <database.hpp>

               time_t ntime;
               time(&ntime);
               char *dp=getdate(ntime);

       time_t gettime(char *string);
       time_t gettime(char *string);
       time_t gettime(char *string);

       This function obtains a time in the form used by the ANSI time
       functions (e.g. asctime() etc). The string should be in dBase form,
       YYYYMMDD.

       void gnums(char *date,int &d,int &m,int &y);
       void gnums(char *date,int &d,int &m,int &y);
       void gnums(char *date,int &d,int &m,int &y);

       This function takes the supplied date and returns the day, month and
       year functions in the variables d,m and y. See the first example
       shown above.

       char *ltrim(char *string);
       char *ltrim(char *string);
       char *ltrim(char *string);

       This function returns a pointer to the first character in string
       which is not a space.

       char *soundex(char *dest,char *src);
       char *soundex(char *dest,char *src);
       char *soundex(char *dest,char *src);

       This function runs the soundex algorithm on the string src and places
       the 4 character result in dest which is returned by the function.

       int strcmpdb(char *s1,char *s2,int len=-1);
       int strcmpdb(char *s1,char *s2,int len=-1);
       int strcmpdb(char *s1,char *s2,int len=-1);

       Compare strings using the dBase compare format. (i.e. "Bancroft"="B"
       returns TRUE, "B"="Bancroft") returns false. len is the maximum
       length to be compared, if -1 is supplied then the whole of s1 is
       examined.

       char *swapdata(char *dest,int schar='~');
       char *swapdata(char *dest,int schar='~');
       char *swapdata(char *dest,int schar='~');

       This is a PCF function which takes the supplied string and takes the
       first ~ (or whichever character is defined by schar) character in it
       exchanging the information after the ~ for the information before it,
       and replacing the ~ by a space. Thus "SMITH~JOHN" becomes "JOHN
       SMITH". This is used in PCF for alphabetical sorting by surname but
       printing by proper name. The function returns a pointer to dest.





       char *trim(char *string)
       char *trim(char *string)
       char *trim(char *string)
       char *rtrim(char *string)
       char *rtrim(char *string)
       char *rtrim(char *string)

       These functions which are identical trim all trailing spaces from the
       supplied string and return a pointer to it.






       Classes
       Classes
       Classes
       =======
       =======
       =======


       The following are the classes which make up the library , and their
       associated member functions and variables.

       database::database
       database::database
       database::database


               void database::database();
               void database::database();
               void database::database();



       +-scription :
       +-scription :
       +-scription :   This constructor creates a database object with no
                   fields and no records. The valid flag is set to -1 to
                   indicate that the database has not yet been written to
                   disk. Fields may be added to the database using the
                   database::addfield() function. Before records may be
                   written or read to or from the database then the
                   database::write() function is used to establish it on
                   disk.


       E.G.
       E.G.
       E.G.        // Create a database : "CLUB" with three fields, name
                   // Create a database : "CLUB" with three fields, name
                   // Create a database : "CLUB" with three fields, name
                   (character)
                   (character)
                   (character)
                   // dob (date), and phone (character)
                   // dob (date), and phone (character)
                   // dob (date), and phone (character)

                   #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   database db();
                   database db();
                   database db();

                   db->addfield("NAME",'C',15);
                   db->addfield("NAME",'C',15);
                   db->addfield("NAME",'C',15);           // Name field
                                                          // Name field
                                                          // Name field
                   db->addfield("DOB",'D');
                   db->addfield("DOB",'D');
                   db->addfield("DOB",'D');           // DOB field
                                                      // DOB field
                                                      // DOB field
                   db->addfield("PHONE",'C',11);
                   db->addfield("PHONE",'C',11);
                   db->addfield("PHONE",'C',11);      // Phone Field
                                                      // Phone Field
                                                      // Phone Field

                   db->write("CLUB");
                   db->write("CLUB");
                   db->write("CLUB");






       database::database
       database::database
       database::database


               void database::database(char *name,char *index=0);
               void database::database(char *name,char *index=0);
               void database::database(char *name,char *index=0);



       Description :
       Description :
       Description :   This constructor opens an existing database which may
                   have 0 or more records.

                   The database constructor requires a file name for the
                   database, and an optional file name for the dBase
                   compatible index. If the index file name is supplied as
                   an empty string, or if the index file cannot be opened,
                   then each record follows the last in the database file.
                   If an index file name is supplied then each record
                   follows the last according to the index file sequence.
                   After the constructor is called the database::isvalid()
                   function may be called to determine if the database was
                   successfully opened. Additional indexes may be added
                   using the database::addindex() function

                   The database filename is assumed to have a ".DBF"
                   extension if none is supplied, and the index filename a
                   ".NDX" extension. If the database has an associated memo
                   file (.DBT extension) then this file is also opened.

                   The constructor automatically creates and initialises a
                   linked list of field objects which describes the fields
                   of the database.

                   Should the constructor fail then the database::isvalid()
                   function will report the error, returing 0 on success.




       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   database *db;
                   database *db;
                   database *db;

                   db=new database("MEMBERS","NAME");
                   db=new database("MEMBERS","NAME");
                   db=new database("MEMBERS","NAME");
                   cout << *db;
                   cout << *db;
                   cout << *db;






       database::Accessor Functions
       database::Accessor Functions
       database::Accessor Functions


               long database::getnrec(void)
               long database::getnrec(void)
               long database::getnrec(void)


               unsigned int database::getreclen(void);
               unsigned int database::getreclen(void);
               unsigned int database::getreclen(void);


               char *getindkey(name);
               char *getindkey(name);
               char *getindkey(name);


               int getindkey(name);
               int getindkey(name);
               int getindkey(name);


               int database::getnfield(void);
               int database::getnfield(void);
               int database::getnfield(void);


               field *database::getfield(int n);
               field *database::getfield(int n);
               field *database::getfield(int n);


               field *database::(char *name);
               field *database::(char *name);
               field *database::(char *name);


               int database::isvalid(void);
               int database::isvalid(void);
               int database::isvalid(void);



       Description : 
       Description : 
       Description : These are the accessor functions for the database, and
                   return various parameters about it.


               char *database::getindkey(char *name)
               char *database::getindkey(char *name)
               char *database::getindkey(char *name)

               This function returns the key on the index which has the
               supplied file name. name should not have a .NDX extension.
               The key should be copied if any modification is to be made to
               it. It returns 0 if no index of that name exists.

               int database::getindtype(char *name)
               int database::getindtype(char *name)
               int database::getindtype(char *name)

               This function returns the type of index for the index which
               has the supplied file name. name should not have a .NDX
               extension. The returned value is either OPINT or OPSTR
               depending on whether it is an integer or string index
               expression. It returns 0 if no index of the supplied name
               exists.





               int database::getnfield(void);
               int database::getnfield(void);
               int database::getnfield(void);

               This function returns the number of fields in the database.

               long database::getnrec(void)
               long database::getnrec(void)
               long database::getnrec(void)

               This function returns the number of records in the database.

               unsigned int database::getreclen(void);
               unsigned int database::getreclen(void);
               unsigned int database::getreclen(void);

               This function returns the record length of records in the
               database.

               int database::isvalid(void);
               int database::isvalid(void);
               int database::isvalid(void);

               This function returns a code which represents the state of
               the database object. The code takes on the following values:

               -2              Creating Database with a memo field, not yet
               written to      disk
               -1              Creating Database, not yet written to disk
               0               Database file opened and initialised
               successfully.
               NOFILE          Database file could not be opened.
               NOINDEX         Index file could not be opened.
               NOMEM           Memory allocation error on database creation.

       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>



                   database db("CLUB","")
                   database db("CLUB","")
                   database db("CLUB","")

                   printf("Database has :\n");
                   printf("Database has :\n");
                   printf("Database has :\n");
                   printf("%d Records\n",db.getnrec());
                   printf("%d Records\n",db.getnrec());
                   printf("%d Records\n",db.getnrec());
                   printf("%d Fields\n",db.getnfields());
                   printf("%d Fields\n",db.getnfields());
                   printf("%d Fields\n",db.getnfields());






       database::addfield
       database::addfield
       database::addfield


               int database::addfield(char *name,int type,int length=1,int
               int database::addfield(char *name,int type,int length=1,int
               int database::addfield(char *name,int type,int length=1,int
               rdp=0);
               rdp=0);
               rdp=0);

               int database::addfield(field *fp);
               int database::addfield(field *fp);
               int database::addfield(field *fp);



       Description :
       Description :
       Description :   This function adds a field to a database which is
                   being created.

                   The first form of the function adds a defined field. name
                   is the name of the field, type is the field type -
                   'C','D','M','L' , or 'N'. Length is defined for character
                   and numeric fields and is the length of these fields in
                   the database. The other field types are set automatically
                   and the length parameter is ignored. The rdp parameter is
                   only used by numeric fields to set the number of decimal
                   places in the field.

                   The second form of the function makes a new field from a
                   pointer to an existing field from an existing (but
                   different) database.

                   The function returns 0 if successful, DUPFIELD if the
                   name of this field exists in the database already, and
                   INVFIELD if a different error occurs.


       E.G.
       E.G.
       E.G.        See the database::database() function.
                   See the database::database() function.
                   See the database::database() function.






       database::addindex
       database::addindex
       database::addindex


               field *database::addindex(char *filename);
               field *database::addindex(char *filename);
               field *database::addindex(char *filename);



       Description :
       Description :
       Description :   This function adds an index to the list of indexes
                   which is maintained for each database. When an index is
                   added it is available for use by records which may select
                   any index, which then defines the order in which records
                   are read from the database. Any index attached to a
                   database is automatically updated as records are modified
                   or added within the database. The file name is assumed to
                   have a .NDX extension.

                   Functions such as the record constructor which require to
                   identify an index on the database use the name defined in
                   the database constructor or the database::addindex()
                   function. Thus the index "NAME.NDX" is referred to as
                   "NAME" in functions which need to identify an index.

                   The function returns 0 if the index is successfully
                   attached to the database, and the error NOINDEX if the
                   file could not be opened. It returns the error INVIND if
                   there is an error in the index file or if it is already
                   attached. Index file checking is limited and will not
                   spot all types of index file corruption.


       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   database *db;
                   database *db;
                   database *db;

                   db=new database("MEMBERS","AGE");
                   db=new database("MEMBERS","AGE");
                   db=new database("MEMBERS","AGE");
                   db.addindex("AGE");
                   db.addindex("AGE");
                   db.addindex("AGE");
                   db.addindex("FUNC");
                   db.addindex("FUNC");
                   db.addindex("FUNC");
                   db.addindex("PHDIG");
                   db.addindex("PHDIG");
                   db.addindex("PHDIG");

                   record rec(db,"FUNC");
                   record rec(db,"FUNC");
                   record rec(db,"FUNC");      // rec uses the FUNC index
                                               // rec uses the FUNC index
                                               // rec uses the FUNC index






       database::buildindex
       database::buildindex
       database::buildindex


               void database::buildindex(char *expr,char *filename);
               void database::buildindex(char *expr,char *filename);
               void database::buildindex(char *expr,char *filename);



       Description :
       Description :
       Description :   This function constructs a new index on the database.
                   expr is the expression that is to be used for the index
                   file, it must be a valid index expression. filename is
                   the name of the index file to be written, if a file
                   exists it will be overwritten. Index file creation may
                   take some considerable time for a large database. After a
                   successful creation the index can be attached to the
                   database using the database::addindex() function if it is
                   desired to use it immediately.

                   The function returns 0 on success, or the error EXPRERR
                   if an error occurred in expression evaluation on any
                   record, or the error NOINDEX if the file could not be
                   opened.

                   The filename must not be already attached to the owning
                   or any other database or the program will almost
                   certainly crash.


       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   database db=database("MEMBERS");
                   database db=database("MEMBERS");
                   database db=database("MEMBERS");
                   db.buildindex("UPPER(NAME)","NAME");
                   db.buildindex("UPPER(NAME)","NAME");
                   db.buildindex("UPPER(NAME)","NAME");
                   db.addindex("NAME");
                   db.addindex("NAME");
                   db.addindex("NAME");






       database::getfield
       database::getfield
       database::getfield


               field *database::getfield(int n);
               field *database::getfield(int n);
               field *database::getfield(int n);


               field *database::getfield(char *name);
               field *database::getfield(char *name);
               field *database::getfield(char *name);



       Description :
       Description :
       Description :   These functions return a pointer to the field class
                   which is defined either by number n, or by its name. If
                   the field cannot be found a zero pointer is returned.


       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   field *fpt;
                   field *fpt;
                   field *fpt;
                   database db("LIST","NAME");
                   database db("LIST","NAME");
                   database db("LIST","NAME");

                   fpt=db.getfield("NAME");
                   fpt=db.getfield("NAME");
                   fpt=db.getfield("NAME");
                   printf("\nName field is number : %d",fpt->getnumber());
                   printf("\nName field is number : %d",fpt->getnumber());
                   printf("\nName field is number : %d",fpt->getnumber());
                   printf("\nName field is of length : %d",fpt->getlen());
                   printf("\nName field is of length : %d",fpt->getlen());
                   printf("\nName field is of length : %d",fpt->getlen());






       database::subindex
       database::subindex
       database::subindex


               int database::subindex(char *name);
               int database::subindex(char *name);
               int database::subindex(char *name);



       Description :
       Description :
       Description :   This function removes an index from the list of
                   indexes which is maintained for each database. The name
                   is the filename which was used to add the index (from the
                   database::database() or database::addindex() functions).

                   The function returns 0 on success, or the error NOINDEX
                   if the index could not be found. It will return INDINUSE
                   if a record is using the index and the index will not be
                   removed.






       database::write
       database::write
       database::write


               int database::write(char *filename);
               int database::write(char *filename);
               int database::write(char *filename);



       Description :
       Description :
       Description :   This function will write a database which is newly
                   created to disk. filename is the name of the database
                   (without the .dbf extension) on disk. After this function
                   has been executed successfully the database will be valid
                   (the valid flag will be 0), and records may be written
                   (and then read) on the database.

                   It returns 0 on success, or NOFILE if the database is not
                   newly created or cannot be written to disk.


       E.G.
       E.G.
       E.G.        See the database::database() constructor function.
                   See the database::database() constructor function.
                   See the database::database() constructor function.






       database::verifyindex
       database::verifyindex
       database::verifyindex


               int database::verifyindex(char *indexname);
               int database::verifyindex(char *indexname);
               int database::verifyindex(char *indexname);



       Description :
       Description :
       Description :   This function will run through the entire database
                   using the supplied index name which should already be
                   attached to the database, and check that all records are
                   in the index, and that they are in the correct order.

                   It will return 0 if the index is OK, INCOMP if the index
                   appears to end before all records have been read from the
                   database, OORD if the records are out of order, and
                   INVIND if the index is not attached to the database, or
                   there is an error in the index expression.

                   On large databases the function may take some time to
                   return.


       E.G.
       E.G.
       E.G.        database db("CLUB","NAME");
                   database db("CLUB","NAME");
                   database db("CLUB","NAME");

                   if (db->verifyindex("NAME))
                   if (db->verifyindex("NAME))
                   if (db->verifyindex("NAME))     // On error rebuild index
                                                   // On error rebuild index
                                                   // On error rebuild index
                   {
                   {
                   {
                    db->subindex("name");
                    db->subindex("name");
                    db->subindex("name");
                    db->buildindex("upper(name)","name");
                    db->buildindex("upper(name)","name");
                    db->buildindex("upper(name)","name");
                    db->addindex("name");
                    db->addindex("name");
                    db->addindex("name");
                   }
                   }
                   }







       field::Accessor Functions
       field::Accessor Functions
       field::Accessor Functions


               int field::getlen();
               int field::getlen();
               int field::getlen();


               char *field::getname();
               char *field::getname();
               char *field::getname();


               int field::getnumber();
               int field::getnumber();
               int field::getnumber();


               int field::getrdp();
               int field::getrdp();
               int field::getrdp();


               char field::gettype();
               char field::gettype();
               char field::gettype();



       Description :   These functions return various information about the
                   field object to which they are applied.


               int field::getlen();
               int field::getlen();
               int field::getlen();


               This returns the length of the field in characters.


               char *field::getname();
               char *field::getname();
               char *field::getname();


               This returns a pointer to the name of the field.


               int field::getnumber();
               int field::getnumber();
               int field::getnumber();


               This returns the number of the field.


               int field::getrdp();
               int field::getrdp();
               int field::getrdp();


               This returns the number of characters to the right of the
               decimal place in a numeric field.





               char field::gettype();
               char field::gettype();
               char field::gettype();


               This returns the single character field type which will be
               one of  C,N,D,L, or M. These have the following meanings :


               C          Character field
               N          Numeric field
               D          Date field
               L          Logical field
               M          Memo field


       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>



                   field *fpt;
                   field *fpt;
                   field *fpt;
                   database db("DATA","");
                   database db("DATA","");
                   database db("DATA","");

                   fpt=db.getfield("DATE");
                   fpt=db.getfield("DATE");
                   fpt=db.getfield("DATE");
                   cout << "Date field is number :" << fpt->getnumber();
                   cout << "Date field is number :" << fpt->getnumber();
                   cout << "Date field is number :" << fpt->getnumber();
                   cout << " and of type " << fpt->gettype();
                   cout << " and of type " << fpt->gettype();
                   cout << " and of type " << fpt->gettype();



       See Also :
       See Also :
       See Also :







       record::record
       record::record
       record::record


               void record::record(class database &db, char *index=0);
               void record::record(class database &db, char *index=0);
               void record::record(class database &db, char *index=0);



       Description :
       Description :
       Description :   The record constructor is called with a database to
                   which the record object will belong. The object may then
                   hold any record from that database, and the extraction
                   functions may be used to obtain any of the fields from
                   the record. The selection functions are used to read a
                   required record into the object.

                   Note that the constructor reads no information into the
                   record, the record contents will be invalid until the
                   select functions are used.

                   index is the index to be used with this record. If no
                   indexes are in use, or if NOIND is supplied as the index
                   parameter then records are read from the database in the
                   order that they were written (this is fastest). If the
                   index is supplied as 0 then the first index attached to
                   the database is used for this record, this will be
                   attached either by the database constructor, or by the
                   database::addindex() function. If the index is a name
                   then this is the one used.

                   The index name is the filename of the .NDX file without
                   the .NDX extension so the index file "name.ndx" is
                   referred to as "name".

                   If the index cannot be opened then no index is associated
                   with the record.


       E.G.
       E.G.
       E.G.    #include <database.hpp>
               #include <database.hpp>
               #include <database.hpp>

               database db("EQUIP","TITLE");
               database db("EQUIP","TITLE");
               database db("EQUIP","TITLE");       // Database constructor
                                                   // Database constructor
                                                   // Database constructor
               db.addindex("AGE");
               db.addindex("AGE");
               db.addindex("AGE");         // Age index is attached
                                           // Age index is attached
                                           // Age index is attached
               db.addindex("NAME");
               db.addindex("NAME");
               db.addindex("NAME");        // Name index is attached
                                           // Name index is attached
                                           // Name index is attached

               record rec(db);
               record rec(db);
               record rec(db);            // Record uses "TITLE" index
                                          // Record uses "TITLE" index
                                          // Record uses "TITLE" index
               record rec1(db,NOIND);
               record rec1(db,NOIND);
               record rec1(db,NOIND);     // Record with no index
                                          // Record with no index
                                          // Record with no index
               record rec2(db,"AGE");
               record rec2(db,"AGE");
               record rec2(db,"AGE");     // Record uses "AGE" index
                                          // Record uses "AGE" index
                                          // Record uses "AGE" index

       See Also : record::select(), database::addindex(),
       See Also : record::select(), database::addindex(),
       See Also : record::select(), database::addindex(),
       database::database()
       database::database()
       database::database()






       record::Accessor Functions
       record::Accessor Functions
       record::Accessor Functions


               int record::getdelstate();
               int record::getdelstate();
               int record::getdelstate();

               long record::getrecnum();
               long record::getrecnum();
               long record::getrecnum();

               char *record::indname();
               char *record::indname();
               char *record::indname();

               int record::setdelstate(int state);
               int record::setdelstate(int state);
               int record::setdelstate(int state);



       Description :
       Description :
       Description :   These functions return information about the record
                   object.


               int record::getdelstate();
               int record::getdelstate();
               int record::getdelstate();


               This function returns the delete state of the record either,
               either DEL or NOTDEL.


               long record::getrecnum();
               long record::getrecnum();
               long record::getrecnum();


               This function returns the record number of the record object
               in the database.


               char *record::indname();
               char *record::indname();
               char *record::indname();


               This function returns the index name used on this record, or
               0 if none.


               void record::setdelstate(int state);
               void record::setdelstate(int state);
               void record::setdelstate(int state);


               This setes the delete state of the record, to deleted
               (state==DEL), or not deleted (state==NOTDEL). Note that the
               record in the dbf file is not updated until a record::write()
               function is executed.






       record::eval
       record::eval
       record::eval


               void record::eval(char *expr,void *result,int &rtype);
               void record::eval(char *expr,void *result,int &rtype);
               void record::eval(char *expr,void *result,int &rtype);


               void record::eval(void *result,int &rtype);
               void record::eval(void *result,int &rtype);
               void record::eval(void *result,int &rtype);



       Description :
       Description :
       Description :           These functions evaluate an expression which
                   is provided in ASCII (e.g. "age<12") and return a result.
                   The expression is evaluated on the current record and
                   values for fields in the expression are taken from the
                   current record.

                   When the function is called, the expression is tokenised
                   and stored internally. If the same expression is to be
                   evaluated more than once, then for the second and
                   subsequent calls the second form of the expression may be
                   used. This will result in faster evaluation, however the
                   tokenised form of the expression is overwritten by any
                   call to eval by any record, by the record::indchk()
                   function, by the index building functions (which are
                   called by database::addindex(), database::buildindex(),
                   and by adding a record to an indexed database), and by
                   the record select by expression function.

                   The function returns 0 if there was an error in the
                   evaluation in which case the owning database string ers
                   may be checked for an ASCII representation of the error.
                   On success the function returns 1.

                   result points to an area of memory into which is written
                   the result of the evaluation. rtype represents the type
                   of the result:

                   rtype       Result Type     result points to :

                   1 (OPINT)   Numeric         A double
                   2 (OPSTR)   String          A string
                   4 (OPDATE)  Date            An 8 character string in
                   dBase data format
                   8 (OPLOG)   Logical         Single character string - F
                   or T.

                   Memo fields may be used freely in expression, however the
                   expression evaluator maintains its own memory area which
                   may overflow if results of string expressions are too
                   long. Memo fields are stored in a buffer which may be
                   modified by the evaluator, see the record::getfield
                   function for a further discussion of this.





       E.G.
       E.G.
       E.G.        // Print the age in years from the dob field
                   // Print the age in years from the dob field
                   // Print the age in years from the dob field
                   // in the database (date of birth) and the current date.
                   // in the database (date of birth) and the current date.
                   // in the database (date of birth) and the current date.
                   // Print for all those whose name begins with "A"
                   // Print for all those whose name begins with "A"
                   // Print for all those whose name begins with "A"

                   database db("CLUB");
                   database db("CLUB");
                   database db("CLUB");
                   record rec(db);
                   record rec(db);
                   record rec(db);

                   int sel=rec.select("name\"A\"",FIRST);
                   int sel=rec.select("name\"A\"",FIRST);
                   int sel=rec.select("name\"A\"",FIRST);

                   while(!sel)
                   while(!sel)
                   while(!sel) // For all records
                               // For all records
                               // For all records
                   {
                   {
                   {
                    char result[128];
                    char result[128];
                    char result[128];      // Holds result
                                           // Holds result
                                           // Holds result
                    int dum;
                    int dum;
                    int dum;               // Dummy variable
                                           // Dummy variable
                                           // Dummy variable

                    cout << "\n" << (char *)rec.getfield("name") << "is ";
                    cout << "\n" << (char *)rec.getfield("name") << "is ";
                    cout << "\n" << (char *)rec.getfield("name") << "is ";
                    int rv=rec.eval("int((date()-dob/365.25)",result,dum);
                    int rv=rec.eval("int((date()-dob/365.25)",result,dum);
                    int rv=rec.eval("int((date()-dob/365.25)",result,dum);
                    if (!rv)
                    if (!rv)
                    if (!rv)
                    {
                    {
                    {
                     cout << "Couldn't calculate result because :";
                     cout << "Couldn't calculate result because :";
                     cout << "Couldn't calculate result because :";
                     cout << db.ers;
                     cout << db.ers;
                     cout << db.ers;
                    }
                    }
                    }
                    else cout << *(double *)result << " years old";
                    else cout << *(double *)result << " years old";
                    else cout << *(double *)result << " years old";

                    sel=rec.select("name=\"A\"",NEXT);
                    sel=rec.select("name=\"A\"",NEXT);
                    sel=rec.select("name=\"A\"",NEXT);
                   }
                   }
                   }






       record::getfield
       record::getfield
       record::getfield


               void record::getfield(int n,int trimflag=1);
               void record::getfield(int n,int trimflag=1);
               void record::getfield(int n,int trimflag=1);


               void record::getfield(char *name,int trimflag=1);
               void record::getfield(char *name,int trimflag=1);
               void record::getfield(char *name,int trimflag=1);



       Description :
       Description :
       Description :               These functions return a pointer to (a
                   copy of) the specified field. n is the field number (the
                   first field is number 1), or the field can be specified
                   by name. It should be noted that it is much faster to
                   obtain a field by number than by name as the latter
                   requres a search of all field names in the current data
                   base.

                   Note that the string returned is not guaranteed to be
                   maintained through further calls to the database library
                   functions, so a copy should be made if it is required to
                   maintain the field value through other functions. The
                   function returns a string of spaces if the field cannot
                   be found, or if a record has not been read with one of
                   the select functions.

                   If trimflag is 1 then results are trimmed, that is
                   trailing spaces are removed, if it is 0 then the field is
                   returned as is.

                   The return values of the function are the internal form
                   held in dbf files, thus the string will have the
                   following types:

                   FIELD TYPE      FORMAT

                   Character       Zero terminated string form of field.
                   Numeric         ASCII representation of number, use
                   atoi() or atof() to obtain
                                   number in integer or floating point form.
                   Date            String form of date  : YYYYMMDD, thus
                   19/11/1965 is held as
                                   19651119. Use the gettime() function to
                   return
                                   an ANSI time in seconds which may be used
                   with ANSI C time
                                   functions.
                   Logical         The return value is a single character
                   which holds T,t,Y,y for TRUE    and F, f, N, or n for
                   FALSE.
                   Memo            Returns a pointer to a buffer which holds
                   the memo in standard    string format, see below.





       Memos:      Memo fields are held in a buffer which is initially 512
                   bytes long, but is increased in length as required as
                   bigger memos are found. Not that there is only one memo
                   buffer per database, and it is overwritten every time
                   that a memo field is accessed from any record. Expression
                   which act on memo fields may also modify this buffer.
                   Thus it is important that if it is desired to keep the
                   memo field intact through other database function calls
                   then the buffer should be copied. The buffer (and
                   therefore any memo field) has a maximum length of 64k.
                   Empty memos return a null string.


       E.G.
       E.G.
       E.G.        see record::select
                   see record::select
                   see record::select






       record::indchk
       record::indchk
       record::indchk


               int record::indchk(char *expr,int &rtype);
               int record::indchk(char *expr,int &rtype);
               int record::indchk(char *expr,int &rtype);



       Description:    This function checks the supplied expression and
                   returns the length of the result in an index expression
                   (e.g. trim(name) has a length which is the same as the
                   length of the name field). If the expression is not valid
                   for an index (e.g. it's result length exceeds 100
                   characters, the result is logical, or there is an error
                   in the expression etc.) then the function returns 0, and
                   the ers string in the database is set to the error
                   report. rtype is set to the type of the expression which
                   takes the same values as the rtype parameter in the
                   record::eval() function.


       E.G.
       E.G.
       E.G.        database db("Names","CNAME");
                   database db("Names","CNAME");
                   database db("Names","CNAME");
                   record rec(db);
                   record rec(db);
                   record rec(db);

                   int rt;
                   int rt;
                   int rt;
                   int indval=rec.indchk(exprt,rt);
                   int indval=rec.indchk(exprt,rt);
                   int indval=rec.indchk(exprt,rt);

                   if (!indval)
                   if (!indval)
                   if (!indval)
                    printf("\n %s is not a valid index because
                    printf("\n %s is not a valid index because
                    printf("\n %s is not a valid index because
                   %s",exprt,rec.db.ers);
                   %s",exprt,rec.db.ers);
                   %s",exprt,rec.db.ers);






       record::seldbf
       record::seldbf
       record::seldbf


               int record::seldbf(long n);
               int record::seldbf(long n);
               int record::seldbf(long n);



       Description:    This function sets the record to record number n in
                   the dbf file regardless of any index file in current use,
                   or the state of the record (deleted or not). Note that
                   the first record is record 1.

                   It returns 0 on success, or CANTSEL if n is out of range.

                   After this function the record::select() functions may
                   give unpredictable results unless the
                   record::select(..FIRST) function is used.


       See Also:
       See Also:
       See Also:   record::select()
                   record::select()
                   record::select()






       record::select
       record::select
       record::select


               int record::select(long n,int type=NOTDEL);
               int record::select(long n,int type=NOTDEL);
               int record::select(long n,int type=NOTDEL);


               int record::select(int fieldnum,int value,long n,int
               int record::select(int fieldnum,int value,long n,int
               int record::select(int fieldnum,int value,long n,int
               type=NOTDEL);
               type=NOTDEL);
               type=NOTDEL);


               int record::select(int fieldnum,char *value,long n,int
               int record::select(int fieldnum,char *value,long n,int
               int record::select(int fieldnum,char *value,long n,int
               type=NOTDEL);
               type=NOTDEL);
               type=NOTDEL);


               int record::select(int fieldnum,void *value,int (*cmp)(void
               int record::select(int fieldnum,void *value,int (*cmp)(void
               int record::select(int fieldnum,void *value,int (*cmp)(void
               *,void *),long n,int type=NOTDEL);
               *,void *),long n,int type=NOTDEL);
               *,void *),long n,int type=NOTDEL);


               int record::select(char *expr,long n,int type=NOTDEL);
               int record::select(char *expr,long n,int type=NOTDEL);
               int record::select(char *expr,long n,int type=NOTDEL);



       Description :
       Description :
       Description :           These functions update the record structure
                   to contain the selected record from the database. The
                   first function record::select(n) simply selects a record
                   without regard to the contents of the record, the others
                   select record by some criteria. The type parameter may be
                   supplied as NOTDEL, DEL or ALL, this defines whether
                   deleted, not deleted, or all records which match the
                   criteria are retrieved.




                   The second form selects records where the specified field
                   number which is assumed to be numeric, matches the
                   supplied integer value. The third form selects record
                   where the specified field number matches the supplied
                   string. The fourth form allows the user to supply a
                   function which searches for records. The function takes
                   two pointers, the first will be supplied as a string from
                   the record (this is the value of the specified field),
                   the second pointer is to the value supplied to the
                   function.The function should return 0 if it has found a
                   valid record.

                   The final form takes a dBase expression which should
                   return a logical value (e.g. "age<12") and returns all
                   records for which the expression returns a True value. If
                   the expression is supplied as "" then the last expression
                   which was evaluated is used. (Note that any library
                   function which uses the expression evaluator will modify
                   the last expression). If an error occurs in evaluation or
                   a non-logical value is returned, then this function
                   returns CANTSEL.





                   The functions read a record from the database according
                   to the value supplied in n, and then allows the user to
                   access the fields in that record using the record
                   accessor functions. The function returns 0 is it was
                   successful, and the error CANTSEL if it failed.
                   If an index is in use then the record order specified in
                   the index file is used by the select functions.

                   If n is greater than 0 then it is the record number to be
                   loaded, note that the first record is record number 1.
                   The record number n is the nth record which matches the
                   selection criteria, not the record number in the dbf
                   file, use the record::seldbf() function is select the nth
                   record in the dbf file.



                   If n is less than 0 then it takes the values FIRST, LAST,
                   NEXT or PREVIOUS. These values have the following
                   meanings:

                   FIRST       The first record which matches the criteria
                   is loaded.
                   LAST        The last record is loaded.
                   NEXT        The next record in sequence is loaded.
                   PREVIOUS    The previous record in sequence is loaded.





       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   int compfirst(void *,void *);
                   int compfirst(void *,void *);
                   int compfirst(void *,void *);

                   /*******************************/
                   /*******************************/
                   /*******************************/
                   /* Print all names in database */
                   /* Print all names in database */
                   /* Print all names in database */

                   void main()
                   void main()
                   void main()
                   {
                   {
                   {
                    database db("MEMBERS","SERIAL");
                    database db("MEMBERS","SERIAL");
                    database db("MEMBERS","SERIAL");

                    record rec(db);
                    record rec(db);
                    record rec(db);

                    int sel;
                    int sel;
                    int sel;

                    sel=rec.select(FIRST);
                    sel=rec.select(FIRST);
                    sel=rec.select(FIRST);
                    while (!sel)
                    while (!sel)
                    while (!sel)
                    {
                    {
                    {
                     printf("%s\n",rec.getfield("NAME"));
                     printf("%s\n",rec.getfield("NAME"));
                     printf("%s\n",rec.getfield("NAME"));
                     sel=rec.select(NEXT);
                     sel=rec.select(NEXT);
                     sel=rec.select(NEXT);
                    }
                    }
                    }

                    /*********************************************/
                    /*********************************************/
                    /*********************************************/
                    /* Find all members whose name begins with C */
                    /* Find all members whose name begins with C */
                    /* Find all members whose name begins with C */

                    fieldnum=(db.getfield("NAME"))->number;
                    fieldnum=(db.getfield("NAME"))->number;
                    fieldnum=(db.getfield("NAME"))->number;

                    int rv=rec.select(fieldnum,"C",compfirst,FIRST);
                    int rv=rec.select(fieldnum,"C",compfirst,FIRST);
                    int rv=rec.select(fieldnum,"C",compfirst,FIRST);
                    while(!rv)
                    while(!rv)
                    while(!rv)
                    {
                    {
                    {
                     printf("%s\n",rec.getfield(fieldnum));
                     printf("%s\n",rec.getfield(fieldnum));
                     printf("%s\n",rec.getfield(fieldnum));
                     rv=rec.select(fieldnum,"C",compfirst,NEXT);
                     rv=rec.select(fieldnum,"C",compfirst,NEXT);
                     rv=rec.select(fieldnum,"C",compfirst,NEXT);
                    }
                    }
                    }
                   }
                   }
                   }

                   /***************************************************/
                   /***************************************************/
                   /***************************************************/
                   /* This function compares two strings and returns  */
                   /* This function compares two strings and returns  */
                   /* This function compares two strings and returns  */
                   /* zero if the first characters are the same       */
                   /* zero if the first characters are the same       */
                   /* zero if the first characters are the same       */

                   int compfirst(void *name,void *value)
                   int compfirst(void *name,void *value)
                   int compfirst(void *name,void *value)
                   {
                   {
                   {
                    return(*(char *)name!=*(char *)value);
                    return(*(char *)name!=*(char *)value);
                    return(*(char *)name!=*(char *)value);
                   }
                   }
                   }

       See Also : record::getfield()
       See Also : record::getfield()
       See Also : record::getfield()






       record::selkey
       record::selkey
       record::selkey


               int record::selkey(char *value,int type=NOTDEL);
               int record::selkey(char *value,int type=NOTDEL);
               int record::selkey(char *value,int type=NOTDEL);

               int record::selkey(double value,int type=NOTDEL);
               int record::selkey(double value,int type=NOTDEL);
               int record::selkey(double value,int type=NOTDEL);

               int record::selkey();
               int record::selkey();
               int record::selkey();



       Description :   This function selects the record by the index key
                   attached to the record. value is the key value to find
                   and is the result of the index expression on the record
                   which will be matched to the record to find it in the
                   database. type is either DEL, NOTDEL or ALL and has the
                   same meanings as in the record::select() functions.

                   The final form of the function (record::selkey()) selects
                   records with the same key as supplied previously.

                   Selecting by key (when known) is much faster than using
                   the record::select() functions, although the right index
                   should be used.

                   It returns with 0 on success, NOKEY if record key could
                   not be found and in this case will select the record with
                   the next highest key, if there is no higher key then it
                   returns NOKEYHIGHER, and selects the last record by
                   index.

                   If there is no index it returns CANTSEL.

                   The record::select() functions may be used with NEXT and
                   PREVIOUS parameters after this function is used if
                   required.


       E.G.
       E.G.
       E.G.        // Find all records where name starts with B
                   // Find all records where name starts with B
                   // Find all records where name starts with B

                   record rec(*db,"Name");
                   record rec(*db,"Name");
                   record rec(*db,"Name");            // Use name index
                                                      // Use name index
                                                      // Use name index

                   int sel=rec.selkey("B");
                   int sel=rec.selkey("B");
                   int sel=rec.selkey("B");

                   while(!sel)
                   while(!sel)
                   while(!sel)
                   {
                   {
                   {
                    printf("\n%s",rec.getfield("name"));
                    printf("\n%s",rec.getfield("name"));
                    printf("\n%s",rec.getfield("name"));
                    sel=rec.selkey();
                    sel=rec.selkey();
                    sel=rec.selkey();
                   }
                   }
                   }






       record::setfield
       record::setfield
       record::setfield


               int record::setfield(char *name,char *value);
               int record::setfield(char *name,char *value);
               int record::setfield(char *name,char *value);

               int record::setfield(char *name,double value);
               int record::setfield(char *name,double value);
               int record::setfield(char *name,double value);

               int record::setfield(int fieldnum,char *value);
               int record::setfield(int fieldnum,char *value);
               int record::setfield(int fieldnum,char *value);

               int record::setfield(int fieldnum,double value);
               int record::setfield(int fieldnum,double value);
               int record::setfield(int fieldnum,double value);



       Description :   Update a field in the record. The first two forms use
                   the field name to set the field, the second two forms use
                   fieldnum as the number of the field. The various forms of
                   the function convert the argument types to a string and
                   write this to the field. Date, Logical, Memo and
                   Character fields should use the char * (as the 2nd
                   parameter) form of the function, Numeric fields will use
                   the double form. Date and Logical field values should be
                   in dBase format (the getdate() function, described in the
                   Functions part of this document, is useful here).

                   Note that the record class is updated, but the database
                   is not updated until a record::write() function is
                   executed.

                   The function returns 0 if the field is set to the value
                   successfully, and RECNOTSET if an error occurred.


       Memo Fields:    Memo fields must provide a pointer to a buffer which
                   should remain valid until the record is written. The old
                   buffer in the memo file is overwritten if the new memo
                   field will fit in the same space as the old one.





       E.G.
       E.G.
       E.G.        #include <database.hpp>
                   #include <database.hpp>
                   #include <database.hpp>

                   // Undelete all deleted records, and
                   // Undelete all deleted records, and
                   // Undelete all deleted records, and
                   // update a field which indicates the number of
                   // update a field which indicates the number of
                   // update a field which indicates the number of
                   modifications
                   modifications
                   modifications
                   // to the record, this is field number 17
                   // to the record, this is field number 17
                   // to the record, this is field number 17

                   database db("Names");
                   database db("Names");
                   database db("Names");

                   int rv=rec.select(FIRST,DEL);
                   int rv=rec.select(FIRST,DEL);
                   int rv=rec.select(FIRST,DEL);      // Select 1st deleted
                                                      // Select 1st deleted
                                                      // Select 1st deleted
                   record
                   record
                   record
                   while(!rv)
                   while(!rv)
                   while(!rv)
                   {
                   {
                   {
                    rec.setdelstate(NOTDEL);
                    rec.setdelstate(NOTDEL);
                    rec.setdelstate(NOTDEL);
                    int nwrites=atoi(rec.getfield(17));
                    int nwrites=atoi(rec.getfield(17));
                    int nwrites=atoi(rec.getfield(17));
                    rec.setfield(17,nwrites+1);
                    rec.setfield(17,nwrites+1);
                    rec.setfield(17,nwrites+1);
                    rec.write();
                    rec.write();
                    rec.write();
                    rv=rec.select(NEXT,DEL);
                    rv=rec.select(NEXT,DEL);
                    rv=rec.select(NEXT,DEL);
                   }
                   }
                   }






       record::write
       record::write
       record::write


               int record::write(int type=OVER);
               int record::write(int type=OVER);
               int record::write(int type=OVER);



       Description :   Write an updated record back to the dbf file. type is
                   either OVER or NEW. If type is OVER then the record is
                   written back on top of the record which was originally
                   read, this form is used for updating records. If type is
                   NEW then the record is added at the end of the dbf file.

                   The function returns 0 if it was successful, and NOWRUNR
                   if the application has attempted to overwrite an unread
                   record, and WRFAIL if the write failed for some other
                   reason.

                   The function also updates any indexes attached to the
                   current database, and it is not until this function is
                   executed that these indexes are updated.

                   The function checks to see if any other record classes
                   attached to the database have the same record selected,
                   if so the information is copied to these records updating
                   them.


       E.G.
       E.G.
       E.G.        See record::setfield()
                   See record::setfield()
                   See record::setfield()







       Appendix 1 - Format of database files
       Appendix 1 - Format of database files
       Appendix 1 - Format of database files


                                ____________________
                                DBase 3, File Format
                                ____________________
                                DBase 3, File Format
                                ____________________
                                DBase 3, File Format


       DBF File
       DBF File
       DBF File

       The DBF file holds all the records in the database together with
       global information on fields and records. It consists of 3 parts :

           1)  The file header which is 32 bytes long.

           2)  The field definitions each of which is 32 bytes long, the
               last field is followed by the byte 0DH.

           3)  The records each of which follows the last.

       File Header
       File Header
       File Header

       This is 32 bytes long, the contents are as follows :

           Byte 0      Bits 0-2 Version of dBase, Bit 7 flags Memo file for
           this database if set
           Bytes 1-3   Year, Month and Day (in hex) of last modification to
           database.
           Bytes 4-7   Number of records.
           Bytes 7-8   The offset of the first record from the start of the
           file.
           Bytes 10-11 The record length including the start byte.

       Field Definitions
       Field Definitions
       Field Definitions

       Each definition is 32 bytes long, the contents of each field are
       defined as follows:

           Bytes 0-10  Zero terminated, upper case field name, max 10
           characters.
           Byte   11   Type of the field, C, N, L, D, or M.
           Byte   16   The length of the field in characters as seen on
           screen.
           Byte   17   The number of digits right of decimal point for
           numeric fields, 0 for all others.

       Records
       Records
       Records

       Each record is stored in order of the field contents. The first byte
       of each record is either a space ' ' for an undeleted record, or a
       '*' for a deleted record. Formats for all data stored in the record
       is ASCII except for date and memo fields :

           Date        These fields are stored in 8 digit ascii, year then
                       month then day to enable simple sorting. eg 27th
                       September 1991 is stored as '19910927'.





           Memo        These fields are stored as 10 ascii digits which show
                       the cluster number of the memo in the dbt file. Thus
                       a value of '0000000023' is at location 23*512 in the
                       memo file. This field is all 0's for no memo on this
                       record.





       NDX, Index Files
       NDX, Index Files
       NDX, Index Files

       These files store the indexes for the database. They consist of a
       header cluster, and a complex hierarchical index structure which is
       based on 512 byte clusters.

       Header
       Header
       Header

       The header consists of the first cluster of the index file (512
       bytes). It contains information about the index file.

           Bytes 0-3   Cluster number of the first cluster of the index file
                       which is at the top of the tree.

           Bytes 4-7   Number of clusters in the file.

           Byte 9      Type of result, D,C or N, Logical index expressions
                       are invalid.

           Bytes 12-13 The size of the result of the dBase index expression
                       used for this file, in bytes.

           Bytes 14-15 The maximum number of index records per cluster.

           Bytes 16-17 The result type, 0=character, 1=numeric.

           Bytes 18-19 The total length of the index expression plus its
                       pointers. ie the value in bytes 12-13 + 8+n, where n
                       is chosen to round up to the nearest 4 bytes.

           Byte 24+    The dBase index expression used on this file, which
                       is zero terminated.

       Index Cluster Format
       Index Cluster Format
       Index Cluster Format

       Each cluster consists of a number of records. Each record starts with
       two 4 byte numbers, the block number and the record number. Following
       this is the record itself which is the result of the dBase index
       expression. The cluster starts with the number of records in it, or
       the number of records-1 for block clusters, this is also a 4 byte
       number..

       There are two types of cluster :

       A record cluster contains index records where the block number is set
       to 0, and the record number is set to the record number in the dbf
       file for this index. Following the last record is a 00000000 byte
       sequence.

       The second type is a block cluster which pointes in a tree like
       fashion to another block cluster, or to a record cluster. This type
       has as its records the value of the index record of the last item in
       the block to which it points. A block cluster has the record number
       set to 0, and the block number set to the cluster number of the block
       to which it refers.





       Memo FIles
       Memo FIles
       Memo FIles

       Memo files are organised into 512 byte clusters. Each memo takes up
       an integral number of clusters, and the text ends in the double byte
       1A, 1A. The first 4 bytes of the file hold the number of the next
       free block, and the rest of block 0 is free for user supplied
       comments. Block 1 is the first block available for use as a memo.
