



Tabula
___________________________________________________________________________
                                                                Version 1.0
                                                                August 1995



Tabula is a deamon that provides simple record input and output using standard
text files. It provides both reading and writing to these text files. When
writing, it supports insertion of a record somewhere in the middle of the file.

Tabula supports virtually unlimited file sizes (up to 2 GB) and very long
records (up to 64 kBytes).

To use Tabula, you also need the ``Daemon server'' package. The documentation
that comes with Daemon server contains detailed descriptions on how to compile
and link an application to use daemons.



Introduction........................................................... 1
Working with records .................................................. 2
Working with fields.................................................... 5
Interfacing with databases............................................. 5
  Microsoft Access .................................................... 6
  Raima Database Manager............................................... 6
Building databases with Tabula ........................................ 6
Unorthodox uses of Tabula ............................................. 8
Tricks, limitations and known problems................................. 9
Function reference ................................................... 10
___________________________________________________________________________
                                                            ITB CompuPhase
ii


``IBM'' is a registered trademark of International Business Machines Corpo-
ration.
``Microsoft'' and ``Microsoft Access'' are a registered trademarks of
Microsoft Corporation, and ``Windows'' is an unregistered trademark of
Microsoft Corporation.
``Raima'' and ``Raima Database Manager'' are (unregistered) trademarks of
Raima Corporation.
``CompuPhase'' and ``Win4C'' are registered trademarks of ITB CompuPhase,
and Daemon server is an unregistered trademark of ITB CompuPhase.



Copyright (c) 1995, ITB CompuPhase; Brinklaan 74-b, 1404GL Bussum, The Nether-
lands (Pays Bas); voice: (+31)-(0)35 6939 261; fax: (+31)-(0)35 6939 293;
Compuserve: 100115,2074


No part of this manual may be reproduced by any means, electronic, photocopy-
ing or otherwise, without prior written permission from ITB CompuPhase. The
software associated with this manual may be copied only for back-up purposes.


The examples and programs in this manual have been included for their instruc-
tional value. They have been tested with care, but are not guaranteed for any
particular purpose.


Requests for corrections and additions to the manual and the software can be
directed to ITB CompuPhase at the above address.


Typeset with TeX in ``Computer Modern'' 11 points. Printed at 21 Aug 1995
2:06p.m.
                                                      Introduction --- 1


Introduction


A record file is just a computer file containing data. The only thing special
with Tabula is that these files are text files where, by default, records take
one line and fields in this record are separated from each other with commas.
You can create/maintain/hack a record file with a text editor, or generate it
with some database product (any decent database will import or export comma
separated files).


There are many more tools available that process text files, than there are
for database files. In addition, text file tools are much more portable and
generic. These tools are now all available for database files built on Tabula:
Just think of:
* GREP---to find patterns (regular expressions) in a record file that exceed
  the database application's search capability.
* File differencing and (visual) comparison with tools like DIFF or VCOMP.
* Revision Control (RCS)---how come that revision control is considered
  indispensable in maintaining software development projects, yet is rarely
  thought of in maintaining data files.


A record file consists of records. For Tabula, each record takes one line in
the ASCII file; that is, a record finishes with a ``newline''. A record is a set
of related data. For example, in a ``customer'' file, each record would hold
the name and address of the customer (and probably other data).


Every record consists of one or more fields. Fields are often separated
from each other with a comma or a TAB character. In the above example, the
``customer's name'' is a field. His or her address probably consists of
several fields (``address'', ``city'', ``postal code'' and ``country''). In
database terminology, a file is a table; fields are the columns of a table and
records are the rows of a table.
__________________________________________________________________________

        [ Sorry, figures not available in the ASCII version]
__________________________________________________________________________
Figure 1: File, records and fields; terminology



Fields are typed. The basic types are number or string. There are a few more
types for more detail or diagnostics. There are no types for date, time or
money. Date and time are conveniently stored in a string and money in scaled
integers. If you need timestamps (to check when a record was last modified),
store the timestamp in a number. One common scheme is to store the time in
the ``number of seconds since 00:00:00 GMT, January 1, 1970''. This value
is returned by the standard C function time. The Internet ``Time'' protocol
(used to synchronize communicating computers) uses basically the same
technique.


Normally, every record in a particular file has the same number of fields, and
the types of the fields match from record to record. This is probably clearer
2 --- Working with records


if you view the file as a table. The columns of the table have a type (and a
meaning); they are the definition of data. The rows are instances of data.


Records are implicitly numbered (this is just a convention, the line number
is the record number). You can jump to a record by calling tab_setpos with the
record number you want. Technically, record numbering starts at 0, but many
comma separated files reserve the very first record for other purposes. Some
put in the names of the fields, some use it for a comment on what the file is
about.


Other than by using tab_setpos, you read (and write) through the record file
in a sequential way. You also handle fields sequentially, from the first to
the last. There is no equivalent of tab_setpos for fields; you cannot select
a field by number. The only option you have is to reposition to the very first
field, using tab_reset.


The layout of the file that Tabula understands is basically fixed (a comma
separated file), but you can modify some settings to adjust to slightly
different file formats.


Tabula assumes the ASCII character set. The two character sets that are used
by most people are both supersets of ASCII: IBM PC extended ASCII (also known
as ``codepage 437'') and Microsoft Windows' ANSI (a slightly modified version
of ISO Latin 1). When you select special characters for Tabula outside the
range of the standard ASCII range, you should check that these characters are
what you intended in the character set on the machines/operating systems that
you use Tabula in.



Working with records


With every function, you must pass a pointer to a record structure (REC*).
This is similar to passing a file structure pointer (FILE*) in standard C file
functions, but the record structure contains more information. One (small)
difference with the standard C functions is that for Tabula you must declare a
variable of type ``REC'', not of ``REC*'', because Tabula does not have a set
of pre-allocated REC structures. This does not make much difference in the
ease of use of the Tabula library, and it allows a virtually unlimited number
of concurrently open record files (provided that the application and the
operating system can open that many files).


Typically, the following flow of Tabula functions occur in a program:
* allocate a record buffer (statically or with malloc)
* open a record file with tab_open
* perhaps jump directly to a record with tab_setpos
* read a record with tab_read
* work on the record (get fields, change fields)
* optionally write the record back with tab_write
* read/write other records, perhaps add/insert new records
* close the record file with tab_close
                                                Working with records --- 3


The functions are heavily based on the ``current position'' of read and
write actions. This is similar to the ``file pointer'' that the standard C
functions fseek and ftell use. There is a technical problem with this file
pointer if you do both reading and writing to the file. The C manual explains
that between reading and writing, you should do an fseek so that the buffers
are flushed and the file pointer is positioned correctly. This is awkward in
Tabula. You probably spend much time in reading/modifying/writing a record;
requiring you to continuously set the file pointer between the read and write
actions is silly. Therefore Tabula has two file pointers (called ``current
positions'' in Tabula): the current record read position and the current
record write position. Some functions (e.g. tab_write) update only one of
these positions, some (e.g. tab_read) update both current positions.


Figures 2 and 3 illustrate how Tabula uses the current positions. After
reading a record, the record read position is set just behind end of the record
that was read. In other words, it points to the beginning of the next record,
ready to read it on a subsequent call to tab_read. The record write position
points to the start of the record just read; it is one record behind the record
read position.

__________________________________________________________________________

        [ Sorry, figures not available in the ASCII version]
__________________________________________________________________________
Figure 2: Current positions after reading a record



When writing a record, the record write position catches up with the record
read position. Now, both point to the start of the next record to be read.
When you select a specific record with tab_setpos, both the record read and
record write positions are set to the start of the specified record (so figure
3 applies here too).

__________________________________________________________________________

        [ Sorry, figures not available in the ASCII version]
__________________________________________________________________________
Figure 3: Current positions after writing a record or after positioning



An additional property, related to the record read and record write po-
sitions, and that is not in figures 2 and 3, is the contents of the record
buffer. After tab_read the complete record that is read is kept in an internal
buffer (actually it is not that internal, you pass this buffer in the call to
tab_open). In figure 2, the record read and record write positions are not
equal and the record buffer is valid. In figure 3, the record read position
is equal to the record write position, and (hence) the record buffer is not
valid. Field functions, which work on the record buffer, fail if the buffer is
invalid.
4 --- Working with records


Tabula does no memory management and has no fixed internal maximum sizes.
Instead it requires you to allocate a block of memory that is required for the
maximum size of a record and pass that to the tab_open call. There is no maximum
number of record structures that are supported, you must declare every REC
type variable yourself and pass it to library. This is in contrast to some C
compilers, where you must edit and re-assemble the startup code if you to open
more than 20 files.


There are several popular variations of comma separated files. Tabula is
flexible enough to handle the most popular:


a)  comma separated files


    Records are separated with newlines, fields are separated with commas,
    strings are between double quotes, special characters (such as the double
    quote and the backslash) in a string escaped with a backslash.


    For example:


    123, "a string", "C:""FILENAME", "a fish called ""Wanda""."


    This is the default configuration of Tabula.


b)  comma separated files with a different string convention


    The same as the standard comma separated file, but instead of putting a
    backslash before a double quote in a string, the double quote is doubled.


    For example:


    123, "a string", "C:"FILENAME", "a fish called ""Wanda""."


    Set tab_setchar(rec, '"n', ',', '"', '"', ' ');


    This sets all special characters to their default, except the ``escape''
    character. The escape character is set to a double quote too, so two double
    quotes is interpreted as an escaped double quote character.


c)  TAB separated files


    Records are separated with newlines, fields are separated with TABs,
    strings are not enclosed in special characters (escape characters are
    therefore not needed).


    For example:



     123 ->a string ->C:"FILENAME ->a fish called "Wanda".


    Set tab_setchar(rec, '"n', '"t', '"0', '"0', '"0');


    This sets the field separator to a TAB (indicated with ``-> ''), it cancels
    string parsing and white space skipping.
                                         Interfacing with databases --- 5


Note that you can only call tab_setchar after you opened the record file,
and that every record file starts with the default settings. The values of
tab_setchar work only on the record file that was passed as a parameter. You
may therefore use several record files at the same time, each with a different
layout.


A record file is kept open during its processing. With many programs, this
means that the file is kept open for the entire duration of the program. Now,
I know some of you are thinking ``What if the user turns the machine off in the
middle of the program---or the program crashes---and the files are still
open. Everything written to the file may be partly in the file, partly in the
DOS buffers. Data is lost and the file is corrupted.'' Well,... no. Tabula
flushes the file buffer after each record write action. This slows down the
record processing a bit, but the file remains valid at all times. (You see, I
thought of this too, if I had not, well... you would not be reading this.)



Working with fields


After reading a record (or creating a new one), you will want to retrieve or
modify the fields. The field functions work at an in-memory buffer (the buffer
you passed to the tab_open function), during the processing of the buffer
there is no disk activity.


Field functions use the same technique of a pair of ``currency'' pointers as
the record functions. There is one field read position and one ``field write''
position. Some functions set both these pointers, some set only one of them
(and one, tab_type, adjusts neither).


Fields can have one of the following types:
empty     a field that contains no information does not have a type (an empty
          field is a valid field, but since there is no data, it has no type)
long      this is a long integer (since Tabula uses ASCII files, the distinc-
          tion between long and short integers is not important; in the record
          file the same number is written)
string    a series of characters between double quotes
unknown   a series of characters that cannot form a valid number and that
          are not between double quotes; you can still read the field with
          tab_gets
endrec    a special ``type'' value that tab_type returns if there are no more
          fields in the record, or if no record has been read.



Interfacing with databases


The primary design goal of Tabula was that programs using Tabula would be able
to read and write files that a common database program would export or import.


To a certain extent, I was faced with the need to keep the format of record
files very flexible, so that Tabula would be compatible with most (or nearly
6 --- Microsoft Access


all) database generated files. On the other hand, many modern databases also
provide flexible settings for exporting to ``foreign'' files, or importing
from these files.


This chapter covers the few databases I have had my hands on. Most of the
information is generally applicable.



Microsoft Access


The delimiters of Microsoft Access can be changed, but the defaults map nicely
to the defaults of Tabula. The single exception is that Microsoft Access
always uses the string delimiter (or ``text delimiter'' as Access calls it)
for the escape character as well. Thus, the following setting is adequate for
Microsoft Access:


       tab_setchar(rec, '"n', ',', '"', '"', '.', ' ');


Also note that Microsoft Access does not put date and time fields between
string delimiters (double quotes). To set a date or time field, you should
use tab_setraw instead of tab_sets. Additionally, it always stores both the
date and the time in a field (Microsoft stores a timestamp value internally for
all date/time field representations). The trouble is, you should know which
which field is valid (or whether they are both valid). If only the ``date''
part of the date/time field is valid, the ``time'' part is garbage, and vice
versa.



Raima Database Manager


The file format for the file import and export tools of Raima are the same as
the
defaults for Tabula.



Building databases with Tabula


Tabula was built to provide a general interface to information from databases.
Database applications usually use proprietary file formats for their own data
processing, for one good reason: speed. But some databases are specified
using comma separated files exclusively. This often happens when data orig-
inating from different sources are tied together in a single application, or
when several, separately designed, programs communicate through one set of
files. The primitive (but flexible) comma separated files are a practical
solution, because everyone can handle them.


Thus, there may be good reason to build a database on top of comma separated
files, despite of its slow performance. This chapter is a very brief introduc-
tion to the relational database technique.


The relational database is based on the mathematical theory of ``sets''. The
basic principles were developed by Dr. E.F. Codd at IBM's research center
                                      Building databases with Tabula --- 7


in San Jose. The principal property of relational databases as opposed to
then popular techniques (hierarchical databases, network databases) is
that the relations between records are based on contents alone. There are no
``pointers'' or ``hard-coded relations'' in the relational model.


One record file is a ``table'' in database terminology. To have a database,
you need several tables. In Tabula, all tables are separate files. Other
database products may combine several tables in one file.


Each file in a database holds a particular kind of record. There are often re-
lationships between the records of two files. To tie these records together,
one picks out a unique field from one record definition and adds that field
to the other record definition. For example, we have a ``customer record''
for all our customers, and a ``sales'' record file that keeps a record for
every product sold. To see which ``customer'' acquired what product, we add
a unique key to each customer: the ``customer code'' (i.e. there are no two
customers with the same customer code). We also add the customer code field to
the ``sales'' record, but it does not need to be unique in the ``sales'' record
(one customer can buy several products).

__________________________________________________________________________

        [ Sorry, figures not available in the ASCII version]
__________________________________________________________________________
Figure 4: Relations between records in different files



To find all products a customer has acquired, you retrieve his/her ``customer
code'', and search through the ``sales'' record file for all products that
have a matching customer code.


A regular database product speeds up this search by keeping an ``index'' file.
An index file is nothing else than a file containing the ``search results''
of all relations between two files. An index file can always be reconstructed
from the data files, although this may take some time (1).


With Tabula, index files are not useful. Due to the flexibility of ASCII files
(the length of a record, measured in bytes, is variable) and the convenience
of physical insertion and deletion of records, the index file would need to be
rebuilt after nearly every write operation.

___________________________

  1 Also  note  that  an  index  file  contains  what  the  tables  in  a
    relational  database  may  not  contain:    hard-coded  relations---
    perhaps even pointers.   That is why an index must be purely additional
    to the tables.
8 --- Unorthodox uses of Tabula


Any good book on designing relational databases will tell you that the se-
lection of a unique key must be done with care. Too often, people choose an
existing field that they think to be unique. But such a field may turn out not
to be that if you think more carefully about it. For example, it is wrong to
use the telephone number as a unique ``customer code'' in a table that contains
information on customers. Several customers can live on the same address
and share the same telephone numbers. If you consider using the full name and
address of each customer as his/her unique key, keep in mind that a person may
have two addresses. If that person orders from a different address, you will
not recognize him or her as an existing customer (2). These errors are
expensive to correct once the database is in full production---often too
expensive, that is why these erroneous databases persist.


The point has been made, and I suggest you take it into consideration, that
unique fields (or keys as they are often called) should never be based on
existing fields, and should never be filled in by the database user. Each
record contains an explicit ``key'' field. When you add a record, the computer
concocts a value for the key field, in a way that assures its uniqueness.



Unorthodox uses of Tabula


A simple example of how Tabula may be used to do other things than what it was
primarily intended to do, is to set the field separator to a space and set the
string and escape characters to '"0'. Now, Tabula breaks lines down into
words. With this setup, it is easy to create ``search and replace'' utilities
for text files, or the ubiquitous ``word count'' program.


The record functions of Tabula work on lines of text (separated by a newline
character) from an ASCII file. In this respect, the record functions are
similar to standard C functions fgets and fputs. This offers opportunities
for any program that uses these standard C functions. To start with: Tabula
can modify (write) lines of text, whereas the standard functions allow only to
append data onto the end of the file.


Many ``text file' utilities process an input file and produce an output file,
rather than working directly on the input file. For most of these tools, this
is also the preferred option, because if something goes wrong, you will still
have your original (untouched) input file. I hate it when a program destroys
my data and a program that works on the input file is more likely to do just
that than a program that makes a copy.

___________________________

  2 These  are  not  hypothetical  problems,  even  today  databases  exist
    that  cannot  distinguish  a  father  from  his  daughter  (both  live  at
    the same address, have the same telephone number and have the same
    last name),  which leads to amusing and sometimes annoying errors.
    Likewise,  double  entries  in  databases  are  as  common  as  buzzing
    bees in july.
                              Tricks, limitations and known problems --- 9


Sometimes, however, creating a copy of the input file is troublesome. Perhaps
because the file modified by the program is needed a moment later for input
by the same program. Here, modifying the input file probably is the better
option (but make sure the program doesn't crash---at least not easily). One
particular use I have been thinking of is the maintenance of .INI files (that
some of our DOS programs also use).


You will not be using the field functions, because the format of .INI file
don't format nicely into fields. Therefore, you will need to work on the
buffer read with tab_read directly. Although you passed this buffer to Tabula
when opening the record file, it is often more convenient to ask Tabula for a
pointer to the buffer with tab_getrec. This function also checks whether the
buffer contains valid data.



Tricks, limitations and known problems


* Tabula does not support empty lines. An empty line in a file lets tab_read a
  ``single field'' record. Making this into a valid, multiple field, record
  requires to work on the buffer manually (to insert the correct number of
  field separators).


* Tabula requires DOS 3.0 or higher. DOS 2.x has too little functionality
  to maintain file integrity, or to share/protect files. Tabula does not
  require SHARE (or even check for its presence), but you may want to enforce
  SHARE if a record file might be used by two people or two applications at the
  same time.


* At the moment, there is no provision for ``column positioned'' files.
  Column positioned files are files where a field is specified to start at
  character position p(1) on a line and to stop at position p(2) (where p(1)
  and p(2) are numbers between 1 and the record length). The records in a
  column positioned file are still delimited with a newline character, like
  Tabula in its default configuration.


  Future versions of Tabula may provide a function in the style of rcompress
  that modifies a record from column positioned to TAB delimited. After that,
  you can use the field functions to extract data. A corresponding function,
  rexpand reverses the process just prior to writing the record.


* There is no function to return the number of records. Everytime I thought I
  needed such a function, I found a different way that was simpler or faster.
  So I never added an rreccount function.


  You may be inclined to inquire the number of records in the file, and then
  execute a for loop to walk over the files. Don't! Walk the file only once by
  calling tab_read until it fails.


  If you want to append a new record to a file (and you intended to do an
  tab_setpos to the current ``number of records'' in the file), it may be
10 --- Function reference


  an option to insert the record at the top of the file (or even at the current
  position). The whole idea of appending a record implies that the records in
  the file are not sorted.


  If all fails, there is a trick to get the number of records(3): do an
  tab_setpos to 0xFFFFFFFF, the absolut maximum number of records that Tabula
  will ever support (by the way, to create a record file with that many
  records, you will need a fairly large disk).



Function reference



__________________________________________________________________________
tab_close                                               close a record file


Syntax:     int tab_close(REC _far *rec);


Returns:
            0   no error
            6   invalid file handle (DOS error); perhaps you closed a record
                twice, or you closed a record that was not opened, or the REC
                structure was damaged



__________________________________________________________________________
tab_count                                        return the number of fields


Syntax:     short tab_count(REC _far *rec);


Description:Counts the number of fields in a record. Technically, every
            recording a file need not to have the same number of fields.


            This function resets the field read and field write pointers to
            the beginning of the record (like tab_reset).


Returns:    The number of fields in the current record. It returns 0 if no
            record was read.

___________________________

  3
    The actual proverb is:  ``If all fails, read the manual'', but you
    are already doing that so I had to find something else.
                                                        tab_getrec --- 11


__________________________________________________________________________
tab_delete                                        delete the current record


Syntax:     int tab_delete(REC _far *rec);


Description:Deletes the record at the record write position. This is the last
            record that was read, or the record tab_setpos'ed to (or the very
            first record immediately after opening the record file).


            Unlike some databases, this is a physical delete. There is no way
            to undo the operation.


Returns:
            0            no error
            R_NORECORD   no record at this position (end of file reached)



__________________________________________________________________________
tab_getl                                       get a numeric (integer) field


Syntax:     long tab_getl(REC _far *rec);


Description:Returns the value of the current field, or zero if the field does
            not have a numeric type. The current field read position is moved
            to the next field.


            Use tab_type to check if a field is valid, and what type it has.



__________________________________________________________________________
tab_getrec                                 return a pointer to the raw record


Syntax:     char _far *tab_getrec(REC _far *rec);


Description:Returns a pointer to the raw record. It returns NULL if the record
            buffer is not valid (nothing was read in it).


            One main use of this function is to use the powerful record func-
            tions for insertion, deletion or replacement of lines in a text
            file, without using the field functions.


            Another use is the translation of special characters through
            the complete record, before parsing it into fields. If, for
            example, the file contains accented characters using extended
            ASCII (the IBM PC character set) and you want to use that file in
            a Win4C program, you will want to pass each record through the
            ConvAnsi function. (Note: ConvAnsi is a Win4C function, not one
            of Tabula).
12 --- tab_gets


__________________________________________________________________________
tab_gets                                                 get a string field


Syntax:     char _far *tab_gets(REC _far *rec, char _far *string, short
                        maxlen);


Description:Returns the text of the current field, or an empty string the
            field does not have a string type. The current field read position
            is moved to the next field.


            Use tab_type to check if a field is valid, and what type it has.



__________________________________________________________________________
tab_new                                                 create a new record


Syntax:     void tab_new(REC _far *rec, short fields);


Description:Creates a new record, with enough commas for all fields. It is set
            to be inserted at the current record read position (so after the
            last record read, or before last record tab_setpos'ed to).



__________________________________________________________________________
tab_open                                                 open a record file


Syntax:     int tab_open(REC _far *rec, char _far *filename, int writeac-
                        cess, char _far *buffer, unsigned short length);


Description:If you only read from the file, set writeaccess to FALSE. This
            makes the file shareable (but no-one is able to write to it). If
            the file is opened with write access, only one person can use the
            file.


            Function tab_open automatically creates the record file if it
            does not exist and if you set writeaccess to non-zero.


            Parameter buffer is the buffer into which each record is read.
            This buffer should have a size that is sufficient to contain
            the largest record in the file, plus 1 for the terminating '"0'
            character. The buffer will contain a record exactly as it appears
            in the record file, except that the record separator (by default a
            '"n') is replaced by the C language string delimiter '"0'.


            Parameter length gives the size you allocated for buffer. Tabula
            will truncate any record that exceeds length - 1 (1 is subtracted
            to make space for the '"0' delimiter).


Returns:
            0   no error
            2   file not found (DOS error)
            3   path not found (DOS error)
            4   too many open files (DOS error)
            5   access denied (DOS error, you must have SHARE loaded to get
                this error)
                                                       tab_setchar --- 13


__________________________________________________________________________
tab_read                                                      read a record


Syntax:     int tab_read(REC _far *rec);


Description:Reads the next record in the buffer that you passed to tab_open.
            Although you may work on this buffer directly, it is probably more
            convenient to use the field functions.


            This function updates both the record read and the record write
            positions. Upon return of this function, the record read posi-
            tion is set to the start of the next record, so that the following
            call to tab_read returns the next record. The record write posi-
            tion is set to the start of the record just read, so that calling
            tab_write overwrites this record (with new fields).


            Thus the procedure:
            * read a record
            * change one or more fields
            * write the record
            works as expected (tab_write writes the record to the same posi-
            tion where tab_read read it from).


Returns:
            0            no error
            5            access denied (DOS error, you must have SHARE
                         loaded to get this error)
            R_NORECORD   no record read (end of file reached)



__________________________________________________________________________
tab_reset                              reset current field to the first field


Syntax:     void tab_reset(REC _far *rec);


Description:Moves the current field read and field write pointers to the start
            of the record. Thus, you can walk through the record again.



__________________________________________________________________________
tab_setchar                                         set special characters


Syntax:     void tab_setchar(REC _far *rec, char recsep, char fldsep, char
                        string, char escape, char space);


Description:Sets the special characters used when parsing fields and
            records. The values set here are only used for the specified
            record file (which must have been opened before calling this
            function.


            recsep   the record separator, by default a '"n'.
            fldsep   the field separator, by default a comma (,)
            string   the string delimiter, by default a double quote ("),
                     this character must appear on the start and the end of
                     the field
14 --- tab_setl


            escape   the escape character, by default a backslash ("), it
                     makes the next character a literal and it is used to put a
                     double quote inside a string
            space    the ``padding'' or white space character that may
                     precede a field, by default a space


            Note: the '"r' (carriage return) character is always ignored by
            Tabula when reading, and it is added automatically when writing
            if `recsep' is '"n'.


            There is only one ``white space'' character, this means that you
            cannot skip both spaces and TABs. This is important only if the
            record file has been (or may have been) carelessly edited by a
            text editor. Files generated by database programs or by Tabula
            usually have no white space at all.



__________________________________________________________________________
tab_setl                                       set a numeric (integer) field


Syntax:     int tab_setl(REC _far *rec, long value);


Description:Sets the current ``write field'' to the given value. The current
            type of the field is not important (i.e. this function sets or
            changes the current type of the field to a numeric field).


            The current field write position is moved to the next field.



__________________________________________________________________________
tab_setraw                                                  set a raw field


Syntax:     int tab_setraw(REC _far *rec, char _far *string);


Description:Sets the current ``write field'' to the given string without
            adding the string delimiters and escape characters. This func-
            tion is useful to support a field format that Tabula does not
            provide.


            For example, some databases expect date fields and time fields
            in formats like 21/06/1995 and 09:35:17. These are not valid
            numbers and, with the default settings of Tabula, not valid
            strings. This function allows you to format the string yourself
            and to store it in the record as is.


            There is no tab_getraw function; the function tab_gets is able
            to read both string fields and raw fields. (By the way, if you set
            the characters for the ``string delimiter'' and the ``escape
            character'' to '"0', tab_sets is equivalent to tab_setraw.)


            The current field write position is moved to the next field.
                                                          tab_type --- 15


__________________________________________________________________________
tab_setpos                                         move to a specific record


Syntax:     unsigned long tab_setpos(REC _far *rec, unsigned long number);


Description:Sets the record read and record write positions to the start of
            the given record.


            You cannot write a record immediately after an tab_setpos; you
            must either read the record with tab_read, or create a new (empty)
            record with tab_new.


Returns:    The new current record number. If this number is not equal to the
            number you passed, Tabula encountered and end of file before
            reaching the requested record. In that case, tab_read will also
            fail and the only options are to create a new record with tab_new
            or another tab_setpos.



__________________________________________________________________________
tab_sets                                                 set a string field


Syntax:     int tab_sets(REC _far *rec, char _far *string);


Description:Sets the current ``write field'' to the given string. The current
            type of the field is not important.



__________________________________________________________________________
tab_skip                                skip a field (move to the next field)


Syntax:     void tab_skip(REC _far *rec, short number);


Description:Skips the indicated number of fields, starting from the current
            field. Thus the next call to tab_type, tab_getl or tab_gets
            refers to the field that is number positions behind the current
            field. The field write position is set to the same value as the
            field read position.



__________________________________________________________________________
tab_type                                             get the type of a field


Syntax:     short tab_type(REC _far *rec);


Description:Determines the type and validity of the field at the current field
            read position.


Returns:
            FLD_EMPTY    A field that contains no information does not have a
                         type.
            FLD_UNKNOWN  A series of characters that cannot form a valid
                         number or a a valid string. You may still retrieve
                         the field with tab_gets and you can set ``unknown''
                         fields into the record with tab_setraw.
16 --- tab_write


            FLD_LONG     A numeric (integral) value.
            FLD_STRING   A series of characters that form a valid string.
            FLD_ENDREC   A special ``type'' value that tab_type returns
                         if there are no more fields in the record, or if no
                         record has been read.



__________________________________________________________________________
tab_write                                                    write a record


Syntax:     int tab_write(REC _far *rec);


Description:Writes a record. This record is inserted in the file if it was
            created with tab_new. If it was read with tab_read, the record
            replaces that record.


            The record file may shrink or grow through this action. When
            growing, it may encounter a ``disk full'' situation. Of course,
            tab_write could check the amount of free disk space just prior to
            expanding the file, but in a multitasking environment, the free
            disk space may change at any instant. Therefore, tab_write first
            appends the number of bytes it needs to grow to the record file. If
            it cannot append enough bytes (because there is not enough free
            disk space), it fails. Although it has not written the record,
            the record file has also not been damaged. If it can append the
            required number of bytes, it continues, with the confidence
            that it can no longer be short of disk space, even when another
            application intervenes (by a time slice).
