                     Accessing dBase Files With C

                            Matthew Probert
                           Servile  Software



Ashton Tate dBase data files are amongst the most prolific data 
structure in use on PCs. They can often be identified by their .DBF 
suffix.

The format of a dBase file is as follows:

Ŀ  
 Signature Byte                               
Ĵ  
 Date of Last Update block (3 Bytes)          
Ĵ  
 Last Record Word                             
Ĵ  
 Offset to start of data word                 
Ĵ  
 Record size word                             
Ĵ   Header
 Unused padding block (20 bytes)              
Ĵ  
 First Field Name block (11 bytes)            
Ĵ  
 First Field Type byte                        
Ĵ  
 First Field Padding block (4 Bytes)          
Ĵ  
 First Field Length Byte                      
Ĵ   Field Definition
 First Field Decimal Positions                
Ĵ  
 First Field Extra Padding Block (14 Bytes)   
Ĵ  
 First Field Offset Word                      
Ĵ  
/                                            \   Field Definitions
\                                            /  
Ĵ  
 Last Field Name block (11 bytes)             
Ĵ  
 Last Field Type byte                         
Ĵ  
 Last Field Padding block (4 Bytes)           
Ĵ  
 Last Field Length Byte                       
Ĵ  
 Last Field Decimal Positions                 
Ĵ  
 Last Field Extra Padding Block (14 Bytes)    
Ĵ  
 Last Field Offset Word                       
Ĵ  
 First Record Of Data                         
Ĵ  
 Last Record Of Data                          
  
                                            


The following program illustrates some basic techniques for reading 
dBase files. The program takes a single command line parameter which 
is the name (including the extension) of a dBase file. The program 
then displays the contents of each record on the screen (or may be 
redirected to a file).

----------------------------Cut Here----------------------------
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <string.h>
#include <stdlib.h>

/*
    Header structure
*/
typedef struct
{
    unsigned char signature;    /* 03 = dbf, 83 dbf+dbt */
    unsigned char ymd[3];
    long last_rec;
    int data_off;
    int rec_size;
    unsigned char pad[20];
}
DBF_HEADER;

typedef struct
{
    char name[11];
    char type;
    char xtra[4];
    char left;
    char right;
    char waste[14];
    int offset;
}
DBF_STRUCT;

typedef struct
{
    DBF_HEADER head;
    DBF_STRUCT fields[255];
    char *record;
    char *memo;
    int handle;
    char fname[255];
    long recnum;
    int bof;
    int eof;
}
DBF;

typedef struct
{
    long numrecs;
    char pad[508];
}
MEMOHEAD;


/*
   Memo fields are recorded in <fname>.DBT.
   The starting position of the record
   connected with a dbf record is recorded by a 10 byte numeric string
   in the DBF file.
   This number multiplied by 512 gives the starting position of
   the memo field within the .DBT file.
   Each .DBT record is terminated by an EOF marker, 0x1A.
   Each .DBT record is a minimum of 512 bytes long.
*/



DBF *db_open(char *fname, DBF *dbstruc);
unsigned db_fcount(DBF *dbstruc);
char *db_field(DBF *dbstruc,unsigned which);
char *db_getd(DBF *, int);
long db_goto(DBF *dbstruc,long which);
long db_skip(DBF *dbstruc,long many);
void db_strncpy(char huge *, char huge *, unsigned);
int db_eof(DBF *dbstruc);
void BROWSE(void);
void DISPLAY(unsigned);

/*
    Structure to reference data file
*/
DBF demo;


/*
    Field extraction buffer
*/
char db_xcg[256];


DBF *db_open(char *fname, DBF *dbstruc)
{
    /*
        Open a DBF file, returning a DBF pointer.
    */

    int n;

    dbstruc->handle = open(fname,O_RDWR);

    if (dbstruc->handle == -1)
        return(NULL);

    /*
        Having opened the DBF file, lets get the details
    */
    strcpy(dbstruc->fname,fname);

    _read(dbstruc->handle,&dbstruc->head,32);

    n = 0;
    dbstruc->head.rec_size = 1;

    while(dbstruc->fields[n].name[0] != 0x0d)
    {
        _read(dbstruc->handle,&dbstruc->fields[n],32);
        if(dbstruc->fields[n].name[0] == 0x0d)
            break;
        dbstruc->fields[n].offset = dbstruc->head.rec_size;
        dbstruc->head.rec_size += dbstruc->fields[n].left;
        n++;
    }
    dbstruc->record = (char *)malloc(dbstruc->head.rec_size);
    if (dbstruc->record == NULL)
        return(NULL);

    lseek(dbstruc->handle,dbstruc->head.data_off,0);

    /*
        Read first record
    */
    _read(dbstruc->handle,dbstruc->record,dbstruc->head.rec_size);
    dbstruc->record[dbstruc->head.rec_size] = 0;
    if (dbstruc->head.last_rec == 0)
    {
        dbstruc->bof = 1;
        dbstruc->eof = 1;
    }
    else
    {
        dbstruc->bof = 0;
        dbstruc->eof = 0;
    }
    dbstruc->recnum = 1;
    dbstruc->memo = (char *)malloc(512);
    return(dbstruc);
}

int db_eof(DBF *dbstruc)
{
    /*
        Return eof status
    */

    return(dbstruc->eof);
}

long db_skip(DBF *dbstruc,long many)
{
    /*
        Attempt to move the DBF file pointer 'many' records
    */

    long result;
    long newrec;

    result = -1;
    newrec = dbstruc->recnum + many;
    if (newrec < 1)
    {
        /*
            Attempt to traverse back past the bottom of the file
        */
        dbstruc->bof = 1;
        dbstruc->eof = 0;
    }
    else
    if (newrec > dbstruc->head.last_rec)
    {
        /*
            Attempt to traverse past the end of the file
        */
        dbstruc->bof = 0;
        dbstruc->eof = 1;
    }
    else
        result = db_goto(dbstruc,newrec);

    return(result);
}

long db_goto(DBF *dbstruc,long which)
{
    /*
        Positions file pointer at the start of specified record
        and reads it into dbfstruc->record
    */

    long result;

    result = -1;
    if ((which > 0) && (which <= dbstruc->head.last_rec))
    {

        which--;
        result = lseek(dbstruc->handle,
                       dbstruc->head.data_off + dbstruc->head.rec_size *
                       which,0);

        if (result != -1)
        {
            _read(dbstruc->handle,dbstruc->record,dbstruc->head.rec_size);
            /*
                Restore file pointer to start of record
            */
            result = lseek(dbstruc->handle,
                           dbstruc->head.data_off + dbstruc->head.rec_size *
                           which,0);
            dbstruc->recnum = which + 1;
        }
    }
    return(result);
}

void db_strncpy(char huge *dest, char huge *src, unsigned length)
{
    /*
        Strncpy with huge pointers
    */

    while((*dest++ = *src++) && --length)
        ;
    *dest = 0;
}

unsigned db_fcount(DBF *dbstruc)
{
    /*
        Return the number of fields within the DBF structure
        pointed to by 'dbstruc'
    */

    unsigned n;

    n = 0;
    while(dbstruc->fields[n].name[0] != 0x0d)
        n++;
    return(n);
}

char *db_getd(DBF *dbstruc,int fnum)
{
    /*
        Copy the contents of field 'fnum' in the
        file pointed to by 'dbstruc' into the exchange
        buffer db_xcg
    */

    char huge *p;
    int x;
    unsigned offset;

    /*
        Initialise the exchange buffer to all zero bytes
    */
    memset(db_xcg,0,255);

    /*
        Set pointer to start of record data
    */
    p = dbstruc->record;


    /*
        calculate the offset within the record to
        the required field
        Different field types have different lengths
    */

    offset = 1;

    for (x = 0; x < fnum; x++)
    {
        if (dbstruc->fields[x].type == 'D')
            offset += 8;
        else
        if (dbstruc->fields[x].type == 'L')
            offset++;
        else
        if (dbstruc->fields[x].type == 'N')
            offset += dbstruc->fields[x].left;
        else
        if (dbstruc->fields[x].type == 'C')
            offset += dbstruc->fields[x].left;
        else
        if (dbstruc->fields[x].type == 'M')
            offset += 10;
    }

    p += offset;

    /*
        We now have a pointer 'p' to the start of the required
        field within the current record
        so we can copy the data from the record into the exchange
        buffer
    */

    if (dbstruc->fields[fnum].type == 'D')
        db_strncpy(db_xcg,p,8);
    else
    if (dbstruc->fields[fnum].type == 'L')
    {
        if (*p++ == 32)
            strcpy(db_xcg,"F");
        else
            strcpy(db_xcg,"T");
    }
    else
    if (dbstruc->fields[fnum].type == 'N')
        db_strncpy(db_xcg,p,dbstruc->fields[fnum].left);
    else
    if (dbstruc->fields[fnum].type == 'C')
        db_strncpy(db_xcg,p,dbstruc->fields[fnum].left);

    return (db_xcg);
}

void DISPLAY(unsigned numfields)
{
    unsigned x;

    /*
        Loop each field in turn displaying its contents
    */
    for (x = 0; x < numfields; x++)
        puts(db_getd(&demo,x));
}


void BROWSE()
{
    unsigned numfields;

    /*
        Get number of fields in data file
    */
    numfields = db_fcount(&demo);

    /*
        Go to start of file
    */
    db_goto(&demo,1);

    /*
        Loop until end of file detected
    */
    while(!db_eof(&demo))
    {
        DISPLAY(numfields);

        /*
            Move file pointer forward one record
        */
        db_skip(&demo,1);
    }
}

void main(int argc, char *argv[])
{
    void *p;

    if (argc == 2)
    {
        /*
            Open data file and assign the structure 'demo' to it
        */
        p = db_open(argv[1],&demo);

        if (p == NULL)
            /*
                Error occured opening data file
            */
            perror("Unable to open data file");
        else
            BROWSE();
    }
}
----------------------------Cut Here----------------------------

