www.pudn.com > mitab-1.5.1.zip > mitab_datfile.cpp


/********************************************************************** 
 * $Id: mitab_datfile.cpp,v 1.17 2004/06/30 20:29:03 dmorissette Exp $ 
 * 
 * Name:     mitab_datfile.cpp 
 * Project:  MapInfo TAB Read/Write library 
 * Language: C++ 
 * Purpose:  Implementation of the TABIDFile class used to handle 
 *           reading/writing of the .DAT file 
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca 
 * 
 ********************************************************************** 
 * Copyright (c) 1999-2001, Daniel Morissette 
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions: 
 *  
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software. 
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER  
 * DEALINGS IN THE SOFTWARE. 
 ********************************************************************** 
 * 
 * $Log: mitab_datfile.cpp,v $ 
 * Revision 1.17  2004/06/30 20:29:03  dmorissette 
 * Fixed refs to old address danmo@videotron.ca 
 * 
 * Revision 1.16  2001/05/01 12:32:03  daniel 
 * Get rid of leading spaces in WriteDateField(). 
 * 
 * Revision 1.15  2000/04/27 15:42:03  daniel 
 * Map variable field length (width=0) coming from OGR to acceptable default 
 * 
 * Revision 1.14  2000/02/28 16:52:52  daniel 
 * Added support for writing indexes, removed validation on field name in 
 * NATIVE tables, and remove trailing spaces in DBF char field values 
 * 
 * Revision 1.13  2000/01/28 07:31:49  daniel 
 * Validate char field width (must be <= 254 chars) 
 * 
 * Revision 1.12  2000/01/16 19:08:48  daniel 
 * Added support for reading 'Table Type DBF' tables 
 * 
 * Revision 1.11  2000/01/15 22:30:43  daniel 
 * Switch to MIT/X-Consortium OpenSource license 
 * 
 * Revision 1.10  1999/12/20 18:59:20  daniel 
 * Dates again... now returned as "YYYYMMDD" 
 * 
 * Revision 1.9  1999/12/16 17:11:45  daniel 
 * Date fields: return as "YYYY/MM/DD", and accept 3 diff. formats as input 
 * 
 * Revision 1.8  1999/12/14 03:58:29  daniel 
 * Fixed date read/write (bytes were reversed) 
 * 
 * Revision 1.7  1999/11/09 07:34:35  daniel 
 * Return default values when deleted attribute records are encountered 
 * 
 * Revision 1.6  1999/10/19 06:09:25  daniel 
 * Removed obsolete GetFieldDef() method 
 * 
 * Revision 1.5  1999/10/01 03:56:28  daniel 
 * Avoid multiple InitWriteHeader() calls (caused a leak) and added a fix 
 * in WriteCharField() to prevent reading bytes past end of string buffer 
 * 
 * Revision 1.4  1999/10/01 02:02:36  warmerda 
 * Added assertions to try and track TABRawBinBlock leak. 
 * 
 * Revision 1.3  1999/09/26 14:59:36  daniel 
 * Implemented write support 
 * 
 * Revision 1.2  1999/09/20 18:43:20  daniel 
 * Use binary access to open file. 
 * 
 * Revision 1.1  1999/07/12 04:18:23  daniel 
 * Initial checkin 
 * 
 **********************************************************************/ 
 
#include "mitab.h" 
 
/*===================================================================== 
 *                      class TABDATFile 
 * 
 * Note that the .DAT files are .DBF files with some exceptions: 
 * 
 * All fields in the DBF header are defined as 'C' type (strings), 
 * even for binary integers.  So we have to look in the associated .TAB 
 * file to find the real field definition. 
 * 
 * Even though binary integers are defined as 'C' type, they are stored 
 * in binary form inside a 4 bytes string field. 
 *====================================================================*/ 
 
 
/********************************************************************** 
 *                   TABDATFile::TABDATFile() 
 * 
 * Constructor. 
 **********************************************************************/ 
TABDATFile::TABDATFile() 
{ 
    m_fp = NULL; 
    m_pszFname = NULL; 
    m_eTableType = TABTableNative; 
 
    m_poHeaderBlock = NULL; 
    m_poRecordBlock = NULL; 
    m_pasFieldDef = NULL; 
 
    m_numFields = -1; 
    m_numRecords = -1; 
    m_nFirstRecordPtr = 0; 
    m_nBlockSize = 0; 
    m_nRecordSize = -1; 
    m_nCurRecordId = -1; 
    m_bCurRecordDeletedFlag = FALSE; 
    m_bWriteHeaderInitialized = FALSE; 
} 
 
/********************************************************************** 
 *                   TABDATFile::~TABDATFile() 
 * 
 * Destructor. 
 **********************************************************************/ 
TABDATFile::~TABDATFile() 
{ 
    Close(); 
} 
 
/********************************************************************** 
 *                   TABDATFile::Open() 
 * 
 * Open a .DAT file, and initialize the structures to be ready to read 
 * records from it. 
 * 
 * We currently support NATIVE and DBF tables for reading, and only 
 * NATIVE tables for writing. 
 * 
 * Returns 0 on success, -1 on error. 
 **********************************************************************/ 
int TABDATFile::Open(const char *pszFname, const char *pszAccess, 
                     TABTableType eTableType /*=TABNativeTable*/) 
{ 
    int i; 
 
    if (m_fp) 
    { 
        CPLError(CE_Failure, CPLE_FileIO, 
                 "Open() failed: object already contains an open file"); 
        return -1; 
    } 
 
    /*----------------------------------------------------------------- 
     * Validate access mode and make sure we use binary access. 
     *----------------------------------------------------------------*/ 
    if (EQUALN(pszAccess, "r", 1) && (eTableType==TABTableNative || 
                                      eTableType==TABTableDBF)  ) 
    { 
        m_eAccessMode = TABRead; 
        pszAccess = "rb"; 
    } 
    else if (EQUALN(pszAccess, "w", 1) && eTableType==TABTableNative) 
    { 
        m_eAccessMode = TABWrite; 
        pszAccess = "wb"; 
    } 
    else 
    { 
        CPLError(CE_Failure, CPLE_FileIO, 
                 "Open() failed: access mode \"%s\" not supported", pszAccess); 
        return -1; 
    } 
 
    /*----------------------------------------------------------------- 
     * Open file for reading 
     *----------------------------------------------------------------*/ 
    m_pszFname = CPLStrdup(pszFname); 
    m_fp = VSIFOpen(m_pszFname, pszAccess); 
    m_eTableType = eTableType; 
 
    if (m_fp == NULL) 
    { 
        CPLError(CE_Failure, CPLE_FileIO, 
                 "Open() failed for %s", m_pszFname); 
        CPLFree(m_pszFname); 
        m_pszFname = NULL; 
        return -1; 
    } 
 
    if (m_eAccessMode == TABRead) 
    { 
        /*------------------------------------------------------------ 
         * READ ACCESS: 
         * Read .DAT file header (record size, num records, etc...) 
         * m_poHeaderBlock will be reused later to read field definition 
         *-----------------------------------------------------------*/ 
        m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE); 
        m_poHeaderBlock->ReadFromFile(m_fp, 0, 32); 
 
        m_poHeaderBlock->ReadByte();       // Table type ??? 0x03 
        m_poHeaderBlock->ReadByte();       // Last update year 
        m_poHeaderBlock->ReadByte();       // Last update month 
        m_poHeaderBlock->ReadByte();       // Last update day 
 
        m_numRecords      = m_poHeaderBlock->ReadInt32(); 
        m_nFirstRecordPtr = m_poHeaderBlock->ReadInt16(); 
        m_nRecordSize     = m_poHeaderBlock->ReadInt16(); 
 
        m_numFields = m_nFirstRecordPtr/32 - 1; 
 
        /*------------------------------------------------------------- 
         * Read the field definitions 
         * First 32 bytes field definition starts at byte 32 in file 
         *------------------------------------------------------------*/ 
        m_pasFieldDef = (TABDATFieldDef*)CPLCalloc(m_numFields,  
                                                   sizeof(TABDATFieldDef)); 
 
        for(i=0; iGotoByteInFile((i+1)*32); 
            m_poHeaderBlock->ReadBytes(11, (GByte*)m_pasFieldDef[i].szName); 
            m_pasFieldDef[i].szName[10] = '\0'; 
            m_pasFieldDef[i].cType = (char)m_poHeaderBlock->ReadByte(); 
 
            m_poHeaderBlock->ReadInt32();       // Skip Bytes 12-15 
            m_pasFieldDef[i].byLength = m_poHeaderBlock->ReadByte(); 
            m_pasFieldDef[i].byDecimals = m_poHeaderBlock->ReadByte(); 
 
            m_pasFieldDef[i].eTABType = TABFUnknown; 
        } 
 
        /*------------------------------------------------------------- 
         * Establish a good record block size to use based on record size, and  
         * then create m_poRecordBlock 
         * Record block size has to be a multiple of record size. 
         *------------------------------------------------------------*/ 
        m_nBlockSize = ((1024/m_nRecordSize)+1)*m_nRecordSize; 
        m_nBlockSize = MIN(m_nBlockSize, (m_numRecords*m_nRecordSize)); 
 
        CPLAssert( m_poRecordBlock == NULL ); 
        m_poRecordBlock = new TABRawBinBlock(m_eAccessMode, FALSE); 
        m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize); 
        m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr); 
    } 
    else 
    { 
        /*------------------------------------------------------------ 
         * WRITE ACCESS: 
         * Set acceptable defaults for all class members. 
         * The real header initialization will be done when the first 
         * record is written 
         *-----------------------------------------------------------*/ 
        m_poHeaderBlock = NULL; 
 
        m_numRecords      = 0; 
        m_nFirstRecordPtr = 0; 
        m_nRecordSize     = 0; 
        m_numFields = 0; 
        m_pasFieldDef = NULL; 
        m_bWriteHeaderInitialized = FALSE; 
    } 
 
    return 0; 
} 
 
/********************************************************************** 
 *                   TABDATFile::Close() 
 * 
 * Close current file, and release all memory used. 
 * 
 * Returns 0 on success, -1 on error. 
 **********************************************************************/ 
int TABDATFile::Close() 
{ 
    if (m_fp == NULL) 
        return 0; 
 
    /*---------------------------------------------------------------- 
     * Write access: Update the header with number of records, etc. 
     * and add a CTRL-Z char at the end of the file. 
     *---------------------------------------------------------------*/ 
    if (m_eAccessMode == TABWrite) 
    { 
        WriteHeader(); 
 
        char cEOF = 26; 
        if (VSIFSeek(m_fp, 0L, SEEK_END) == 0) 
            VSIFWrite(&cEOF, 1, 1, m_fp); 
    } 
     
    // Delete all structures  
    if (m_poHeaderBlock) 
    { 
        delete m_poHeaderBlock; 
        m_poHeaderBlock = NULL; 
    } 
 
    if (m_poRecordBlock) 
    { 
        delete m_poRecordBlock; 
        m_poRecordBlock = NULL; 
    } 
 
    // Close file 
    VSIFClose(m_fp); 
    m_fp = NULL; 
 
    CPLFree(m_pszFname); 
    m_pszFname = NULL; 
 
    CPLFree(m_pasFieldDef); 
    m_pasFieldDef = NULL; 
 
    m_numFields = -1; 
    m_numRecords = -1; 
    m_nFirstRecordPtr = 0; 
    m_nBlockSize = 0; 
    m_nRecordSize = -1; 
    m_nCurRecordId = -1; 
    m_bWriteHeaderInitialized = FALSE; 
 
    return 0; 
} 
 
 
/********************************************************************** 
 *                   TABDATFile::InitWriteHeader() 
 * 
 * Init the header members to be ready to write the header and data records 
 * to a newly created data file. 
 * 
 * Returns 0 on success, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::InitWriteHeader() 
{ 
    int i; 
 
    if (m_eAccessMode != TABWrite || m_bWriteHeaderInitialized) 
        return 0; 
 
    /*------------------------------------------------------------ 
     * Compute values for Record size, header size, etc. 
     *-----------------------------------------------------------*/ 
    m_nFirstRecordPtr = (m_numFields+1)*32 + 1; 
 
    m_nRecordSize = 1; 
    for(i=0; iInitNewBlock(m_fp, m_nBlockSize); 
    m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr); 
 
    /*------------------------------------------------------------- 
     * Make sure this init. will be performed only once 
     *------------------------------------------------------------*/ 
    m_bWriteHeaderInitialized = TRUE; 
 
    return 0; 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteHeader() 
 * 
 * Init the header members to be ready to write the header and data records 
 * to a newly created data file. 
 * 
 * Returns 0 on success, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::WriteHeader() 
{ 
    int i; 
 
    if (m_eAccessMode != TABWrite) 
    { 
        CPLError(CE_Failure, CPLE_NotSupported, 
                 "WriteHeader() can be used only with Write access."); 
        return -1; 
    } 
 
    if (!m_bWriteHeaderInitialized) 
        InitWriteHeader(); 
 
    /*------------------------------------------------------------ 
     * Create a single block that will be used to generate the whole header. 
     *-----------------------------------------------------------*/ 
    if (m_poHeaderBlock == NULL) 
        m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE); 
    m_poHeaderBlock->InitNewBlock(m_fp, m_nFirstRecordPtr, 0); 
 
    /*------------------------------------------------------------ 
     * First 32 bytes: main header block 
     *-----------------------------------------------------------*/ 
    m_poHeaderBlock->WriteByte(0x03);  // Table type ??? 0x03 
 
    // __TODO__ Write the correct update date value 
    m_poHeaderBlock->WriteByte(99);    // Last update year 
    m_poHeaderBlock->WriteByte(9);     // Last update month 
    m_poHeaderBlock->WriteByte(9);     // Last update day 
 
    m_poHeaderBlock->WriteInt32(m_numRecords); 
    m_poHeaderBlock->WriteInt16(m_nFirstRecordPtr); 
    m_poHeaderBlock->WriteInt16(m_nRecordSize); 
 
    m_poHeaderBlock->WriteZeros(20);    // Pad rest with zeros 
 
    /*------------------------------------------------------------- 
     * Field definitions follow.  Each field def is 32 bytes. 
     *------------------------------------------------------------*/ 
    for(i=0; iWriteBytes(11, (GByte*)m_pasFieldDef[i].szName); 
        m_poHeaderBlock->WriteByte(m_pasFieldDef[i].cType); 
 
        m_poHeaderBlock->WriteInt32(0);       // Skip Bytes 12-15 
 
        m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byLength); 
        m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byDecimals); 
 
        m_poHeaderBlock->WriteZeros(14);    // Pad rest with zeros 
    } 
 
    /*------------------------------------------------------------- 
     * Header ends with a 0x0d character. 
     *------------------------------------------------------------*/ 
    m_poHeaderBlock->WriteByte(0x0d); 
 
    /*------------------------------------------------------------- 
     * Write the block to the file and return. 
     *------------------------------------------------------------*/ 
    return m_poHeaderBlock->CommitToFile(); 
} 
 
 
 
/********************************************************************** 
 *                   TABDATFile::GetNumFields() 
 * 
 * Return the number of fields in this table. 
 * 
 * Returns a value >= 0 on success, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::GetNumFields() 
{ 
    return m_numFields; 
} 
 
/********************************************************************** 
 *                   TABDATFile::GetNumRecords() 
 * 
 * Return the number of records in this table. 
 * 
 * Returns a value >= 0 on success, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::GetNumRecords() 
{ 
    return m_numRecords; 
} 
 
/********************************************************************** 
 *                   TABDATFile::GetRecordBlock() 
 * 
 * Return a TABRawBinBlock reference positioned at the beginning of the 
 * specified record and ready to read (or write) field values from/to it. 
 * In read access, the returned block is guaranteed to contain at least one 
 * full record of data, and in write access, it is at least big enough to 
 * hold one full record. 
 *  
 * Note that record ids are positive and start at 1. 
 * 
 * In Write access, CommitRecordToFile() MUST be called after the 
 * data items have been written to the record, otherwise the record  
 * will never make it to the file. 
 * 
 * Returns a reference to the TABRawBinBlock on success or NULL on error. 
 * The returned pointer is a reference to a block object owned by this  
 * TABDATFile object and should not be freed by the caller. 
 **********************************************************************/ 
TABRawBinBlock *TABDATFile::GetRecordBlock(int nRecordId) 
{ 
    m_bCurRecordDeletedFlag = FALSE; 
 
    if (m_eAccessMode == TABRead) 
    { 
        /*------------------------------------------------------------- 
         * READ ACCESS 
         *------------------------------------------------------------*/ 
        int nFileOffset; 
 
        nFileOffset = m_nFirstRecordPtr+(nRecordId-1)*m_nRecordSize; 
 
        /*------------------------------------------------------------- 
         * Move record block pointer to the right location 
         *------------------------------------------------------------*/ 
        if ( m_poRecordBlock == NULL ||  
             nRecordId < 1 || nRecordId > m_numRecords || 
             m_poRecordBlock->GotoByteInFile(nFileOffset) != 0 ) 
        { 
            CPLError(CE_Failure, CPLE_FileIO, 
                     "Failed reading .DAT record block for record #%d in %s", 
                     nRecordId, m_pszFname); 
            return NULL; 
        } 
 
        /*------------------------------------------------------------- 
         * The first char of the record is a ' ' for an active record, or 
         * '*' for a deleted one. 
         * In the case of a deleted record, we simply return default 
         * values for each attribute... this is what MapInfo seems to do 
         * when it takes a .TAB with deleted records and exports it to .MIF 
         *------------------------------------------------------------*/ 
        if (m_poRecordBlock->ReadByte() != ' ') 
        { 
            m_bCurRecordDeletedFlag = TRUE; 
        } 
    } 
    else if (m_eAccessMode == TABWrite && nRecordId > 0) 
    { 
        /*------------------------------------------------------------- 
         * WRITE ACCESS 
         *------------------------------------------------------------*/ 
        int nFileOffset; 
 
        /*------------------------------------------------------------- 
         * Before writing the first record, we must generate the file  
         * header.  We will also initialize class members such as record 
         * size, etc. and will create m_poRecordBlock. 
         *------------------------------------------------------------*/ 
        if (!m_bWriteHeaderInitialized) 
        { 
            WriteHeader(); 
        } 
 
        m_numRecords = MAX(nRecordId, m_numRecords); 
 
        nFileOffset = m_nFirstRecordPtr+(nRecordId-1)*m_nRecordSize; 
 
        m_poRecordBlock->InitNewBlock(m_fp, m_nRecordSize, nFileOffset); 
 
        /*------------------------------------------------------------- 
         * The first char of the record is the active/deleted flag. 
         * Automatically set it to ' ' (active). 
         *------------------------------------------------------------*/ 
        m_poRecordBlock->WriteByte(' '); 
 
    } 
 
    m_nCurRecordId = nRecordId; 
 
    return m_poRecordBlock; 
} 
 
/********************************************************************** 
 *                   TABDATFile::CommitRecordToFile() 
 * 
 * Commit the data record previously initialized with GetRecordBlock() 
 * to the file.  This function must be called after writing the data 
 * values to a record otherwise the record will never make it to the 
 * file. 
 * 
 * Returns 0 on success, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::CommitRecordToFile() 
{ 
    if (m_eAccessMode != TABWrite || m_poRecordBlock == NULL) 
        return -1; 
 
    return m_poRecordBlock->CommitToFile(); 
} 
 
 
/********************************************************************** 
 *                   TABDATFile::ValidateFieldInfoFromTAB() 
 * 
 * Check that the value read from the .TAB file by the caller are  
 * consistent with what is found in the .DAT header. 
 * 
 * Note that field ids are positive and start at 0. 
 * 
 * We have to use this function when opening a file for reading since  
 * the .DAT file does not contain the full field types information... 
 * a .DAT file is actually a .DBF file in which the .DBF types are 
 * handled in a special way... type 'C' fields are used to store binary  
 * values for most MapInfo types. 
 * 
 * For TABTableDBF, we actually have no validation to do since all types 
 * are stored as strings internally, so we'll just convert from string. 
 * 
 * Returns a value >= 0 if OK, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::ValidateFieldInfoFromTAB(int iField, const char *pszName, 
                                          TABFieldType eType, 
                                          int nWidth, int nPrecision) 
{ 
    int i = iField;  // Just to make things shorter 
 
    CPLAssert(m_pasFieldDef); 
 
    if (m_pasFieldDef == NULL || iField < 0 || iField >= m_numFields) 
    { 
        CPLError(CE_Failure, CPLE_FileIO, 
          "Invalid field %d (%s) in .TAB header. %s contains only %d fields.", 
                 iField+1, pszName, m_pszFname, m_pasFieldDef? m_numFields:0); 
        return -1; 
    } 
 
    /*----------------------------------------------------------------- 
     * We used to check that the .TAB field name matched the .DAT 
     * name stored internally, but apparently some tools that rename table 
     * field names only update the .TAB file and not the .DAT, so we won't 
     * do that name validation any more... we'll just check the type. 
     * 
     * With TABTableNative, we have to validate the field sizes as well 
     * because .DAT files use char fields to store binary values. 
     * With TABTableDBF, no need to validate field type since all 
     * fields are stored as strings internally. 
     *----------------------------------------------------------------*/ 
    if ((m_eTableType == TABTableNative &&  
         ((eType == TABFChar && (m_pasFieldDef[i].cType != 'C' || 
                                m_pasFieldDef[i].byLength != nWidth )) || 
          (eType == TABFDecimal && (m_pasFieldDef[i].cType != 'N' || 
                                  m_pasFieldDef[i].byLength != nWidth|| 
                                   m_pasFieldDef[i].byDecimals!=nPrecision)) || 
          (eType == TABFInteger && (m_pasFieldDef[i].cType != 'C' || 
                                   m_pasFieldDef[i].byLength != 4  )) || 
          (eType == TABFSmallInt && (m_pasFieldDef[i].cType != 'C' || 
                                    m_pasFieldDef[i].byLength != 2 )) || 
          (eType == TABFFloat && (m_pasFieldDef[i].cType != 'C' || 
                                 m_pasFieldDef[i].byLength != 8    )) || 
          (eType == TABFDate && (m_pasFieldDef[i].cType != 'C' || 
                                m_pasFieldDef[i].byLength != 4     )) || 
          (eType == TABFLogical && (m_pasFieldDef[i].cType != 'L' || 
                                   m_pasFieldDef[i].byLength != 1  ))   ) )) 
    { 
        CPLError(CE_Failure, CPLE_FileIO, 
                 "Definition of field %d (%s) from .TAB file does not match " 
                 "what is found in %s (name=%s, type=%c, width=%d, prec=%d)", 
                 iField+1, pszName, m_pszFname, 
                 m_pasFieldDef[i].szName, m_pasFieldDef[i].cType,  
                 m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals); 
        return -1; 
    } 
 
    m_pasFieldDef[i].eTABType = eType; 
 
    return 0; 
} 
 
/********************************************************************** 
 *                   TABDATFile::AddField() 
 * 
 * Create a new field (column) in a newly created table.  This function 
 * must be called after the file has been opened, but before writing the 
 * first record. 
 * 
 * Returns the new field index (a value >= 0) if OK, -1 on error. 
 **********************************************************************/ 
int  TABDATFile::AddField(const char *pszName, TABFieldType eType, 
                          int nWidth, int nPrecision /*=0*/) 
{ 
    if (m_eAccessMode != TABWrite || m_bWriteHeaderInitialized || 
        m_eTableType != TABTableNative) 
    { 
        CPLError(CE_Failure, CPLE_NotSupported, 
                 "Addition of new table fields is not supported after the " 
                 "first data item has been written."); 
        return -1; 
    } 
 
    /*----------------------------------------------------------------- 
     * Validate field width... must be <= 254 
     *----------------------------------------------------------------*/ 
    if (nWidth > 254) 
    { 
        CPLError(CE_Failure, CPLE_IllegalArg, 
                 "Invalid size (%d) for field '%s'.  " 
                 "Size must be 254 or less.", nWidth, pszName); 
        return -1; 
    } 
 
    /*----------------------------------------------------------------- 
     * Map fields with width=0 (variable length in OGR) to a valid default 
     *----------------------------------------------------------------*/ 
    if (eType == TABFDecimal && nWidth == 0) 
        nWidth=20; 
    else if (nWidth == 0) 
        nWidth=254; /* char fields */ 
 
    if (m_numFields < 0) 
        m_numFields = 0; 
 
    m_numFields++; 
    m_pasFieldDef = (TABDATFieldDef*)CPLRealloc(m_pasFieldDef,  
                                          m_numFields*sizeof(TABDATFieldDef)); 
 
    strncpy(m_pasFieldDef[m_numFields-1].szName, pszName, 10); 
    m_pasFieldDef[m_numFields-1].szName[10] = '\0'; 
    m_pasFieldDef[m_numFields-1].eTABType = eType; 
    m_pasFieldDef[m_numFields-1].byLength = (GByte)nWidth; 
    m_pasFieldDef[m_numFields-1].byDecimals = (GByte)nPrecision; 
 
    switch(eType) 
    { 
      case TABFChar: 
        m_pasFieldDef[m_numFields-1].cType = 'C'; 
        break; 
      case TABFDecimal: 
        m_pasFieldDef[m_numFields-1].cType = 'N'; 
        break; 
      case TABFInteger: 
        m_pasFieldDef[m_numFields-1].cType = 'C'; 
        m_pasFieldDef[m_numFields-1].byLength = 4; 
        break; 
      case TABFSmallInt: 
        m_pasFieldDef[m_numFields-1].cType = 'C'; 
        m_pasFieldDef[m_numFields-1].byLength = 2; 
        break; 
      case TABFFloat: 
        m_pasFieldDef[m_numFields-1].cType = 'C'; 
        m_pasFieldDef[m_numFields-1].byLength = 8; 
        break; 
      case TABFDate: 
        m_pasFieldDef[m_numFields-1].cType = 'C'; 
        m_pasFieldDef[m_numFields-1].byLength = 4; 
        break; 
      case TABFLogical: 
        m_pasFieldDef[m_numFields-1].cType = 'L'; 
        m_pasFieldDef[m_numFields-1].byLength = 1; 
        break; 
      default: 
        CPLError(CE_Failure, CPLE_NotSupported, 
                 "Unsupported field type for field `%s'", pszName); 
        return -1; 
    } 
 
    return 0; 
} 
 
/********************************************************************** 
 *                   TABDATFile::GetFieldType() 
 * 
 * Returns the native field type for field # nFieldId as previously set 
 * by ValidateFieldInfoFromTAB(). 
 * 
 * Note that field ids are positive and start at 0. 
 **********************************************************************/ 
TABFieldType TABDATFile::GetFieldType(int nFieldId) 
{ 
    if (m_pasFieldDef == NULL || nFieldId < 0 || nFieldId >= m_numFields) 
        return TABFUnknown; 
 
    return m_pasFieldDef[nFieldId].eTABType; 
} 
 
/********************************************************************** 
 *                   TABDATFile::GetFieldWidth() 
 * 
 * Returns the width for field # nFieldId as previously read from the 
 * .DAT header. 
 * 
 * Note that field ids are positive and start at 0. 
 **********************************************************************/ 
int   TABDATFile::GetFieldWidth(int nFieldId) 
{ 
    if (m_pasFieldDef == NULL || nFieldId < 0 || nFieldId >= m_numFields) 
        return 0; 
 
    return m_pasFieldDef[nFieldId].byLength; 
} 
 
/********************************************************************** 
 *                   TABDATFile::GetFieldPrecision() 
 * 
 * Returns the precision for field # nFieldId as previously read from the 
 * .DAT header. 
 * 
 * Note that field ids are positive and start at 0. 
 **********************************************************************/ 
int   TABDATFile::GetFieldPrecision(int nFieldId) 
{ 
    if (m_pasFieldDef == NULL || nFieldId < 0 || nFieldId >= m_numFields) 
        return 0; 
 
    return m_pasFieldDef[nFieldId].byDecimals; 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadCharField() 
 * 
 * Read the character field value at the current position in the data  
 * block. 
 *  
 * Use GetRecordBlock() to position the data block to the beginning of 
 * a record before attempting to read values. 
 * 
 * nWidth is the field length, as defined in the .DAT header. 
 * 
 * Returns a reference to an internal buffer that will be valid only until 
 * the next field is read, or "" if the operation failed, in which case 
 * CPLError() will have been called. 
 **********************************************************************/ 
const char *TABDATFile::ReadCharField(int nWidth) 
{ 
    // We know that character strings are limited to 254 chars in MapInfo 
    static char szBuf[256]; 
 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return ""; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Can't read field value: file is not opened."); 
        return ""; 
    } 
 
    if (nWidth < 1 || nWidth > 255) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Illegal width for a char field: %d", nWidth); 
        return ""; 
    } 
 
    if (m_poRecordBlock->ReadBytes(nWidth, (GByte*)szBuf) != 0) 
        return ""; 
 
    szBuf[nWidth] = '\0'; 
 
    // NATIVE tables are padded with '\0' chars, but DBF tables are padded 
    // with spaces... get rid of the trailing spaces. 
    if (m_eTableType == TABTableDBF) 
    { 
        int nLen = strlen(szBuf)-1; 
        while(nLen>=0 && szBuf[nLen] == ' ') 
            szBuf[nLen--] = '\0'; 
    } 
 
    return szBuf; 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadIntegerField() 
 * 
 * Read the integer field value at the current position in the data  
 * block. 
 *  
 * Note: nWidth is used only with TABTableDBF types. 
 * 
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
GInt32 TABDATFile::ReadIntegerField(int nWidth) 
{ 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return 0; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Can't read field value: file is not opened."); 
        return 0; 
    } 
 
    if (m_eTableType == TABTableDBF) 
        return atoi(ReadCharField(nWidth)); 
 
    return m_poRecordBlock->ReadInt32(); 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadSmallIntField() 
 * 
 * Read the smallint field value at the current position in the data  
 * block. 
 *  
 * Note: nWidth is used only with TABTableDBF types. 
 * 
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
GInt16 TABDATFile::ReadSmallIntField(int nWidth) 
{ 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return 0; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Can't read field value: file is not opened."); 
        return 0; 
    } 
 
    if (m_eTableType == TABTableDBF) 
        return atoi(ReadCharField(nWidth)); 
 
    return m_poRecordBlock->ReadInt16(); 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadFloatField() 
 * 
 * Read the float field value at the current position in the data  
 * block. 
 *  
 * Note: nWidth is used only with TABTableDBF types. 
 * 
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
double TABDATFile::ReadFloatField(int nWidth) 
{ 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return 0.0; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Can't read field value: file is not opened."); 
        return 0.0; 
    } 
 
    if (m_eTableType == TABTableDBF) 
        return atof(ReadCharField(nWidth)); 
 
    return m_poRecordBlock->ReadDouble(); 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadLogicalField() 
 * 
 * Read the logical field value at the current position in the data  
 * block. 
 * 
 * The file contains either 0 or 1, and we return a string with  
 * "F" (false) or "T" (true) 
 *  
 * Note: nWidth is used only with TABTableDBF types. 
 * 
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
const char *TABDATFile::ReadLogicalField(int nWidth) 
{ 
    GByte bValue; 
 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return "F"; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Can't read field value: file is not opened."); 
        return ""; 
    } 
 
    if (m_eTableType == TABTableDBF) 
    { 
        const char *pszVal = ReadCharField(nWidth); 
        bValue = (pszVal && strchr("1YyTt", pszVal[0]) != NULL); 
    } 
    else 
    { 
        // In Native tables, we are guaranteed it is 1 byte with 0/1 value 
        bValue =  m_poRecordBlock->ReadByte(); 
    } 
 
    return bValue? "T":"F"; 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadDateField() 
 * 
 * Read the logical field value at the current position in the data  
 * block. 
 * 
 * A date field is a 4 bytes binary value in which the first byte is 
 * the day, followed by 1 byte for the month, and 2 bytes for the year. 
 * 
 * We return an 8 chars string in the format "YYYYMMDD" 
 *  
 * Note: nWidth is used only with TABTableDBF types. 
 * 
 * Returns a reference to an internal buffer that will be valid only until 
 * the next field is read, or "" if the operation failed, in which case 
 * CPLError() will have been called. 
 **********************************************************************/ 
const char *TABDATFile::ReadDateField(int nWidth) 
{ 
    int nDay, nMonth, nYear; 
    static char szBuf[20]; 
 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return ""; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Can't read field value: file is not opened."); 
        return ""; 
    } 
 
    // With .DBF files, there is nothing to do... the value should already 
    // be stored in YYYYMMDD format according to DBF specs. 
    if (m_eTableType == TABTableDBF) 
        return ReadCharField(nWidth); 
 
 
    nYear  = m_poRecordBlock->ReadInt16(); 
    nMonth = m_poRecordBlock->ReadByte(); 
    nDay   = m_poRecordBlock->ReadByte(); 
 
    if (CPLGetLastErrorNo() != 0 || (nYear==0 && nMonth==0 && nDay==0)) 
        return ""; 
 
    sprintf(szBuf, "%4.4d%2.2d%2.2d", nYear, nMonth, nDay); 
 
    return szBuf; 
} 
 
/********************************************************************** 
 *                   TABDATFile::ReadDecimalField() 
 * 
 * Read the decimal field value at the current position in the data  
 * block. 
 * 
 * A decimal field is a floating point value with a fixed number of digits 
 * stored as a character string. 
 * 
 * nWidth is the field length, as defined in the .DAT header. 
 * 
 * We return the value as a binary double. 
 *  
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
double TABDATFile::ReadDecimalField(int nWidth) 
{ 
    const char *pszVal; 
 
    // If current record has been deleted, then return an acceptable  
    // default value. 
    if (m_bCurRecordDeletedFlag) 
        return 0.0; 
 
    pszVal = ReadCharField(nWidth); 
 
    return atof(pszVal); 
} 
 
 
/********************************************************************** 
 *                   TABDATFile::WriteCharField() 
 * 
 * Write the character field value at the current position in the data  
 * block. 
 *  
 * Use GetRecordBlock() to position the data block to the beginning of 
 * a record before attempting to write values. 
 * 
 * nWidth is the field length, as defined in the .DAT header. 
 * 
 * Returns 0 on success, or -1 if the operation failed, in which case 
 * CPLError() will have been called. 
 **********************************************************************/ 
int TABDATFile::WriteCharField(const char *pszStr, int nWidth, 
                               TABINDFile *poINDFile, int nIndexNo) 
{ 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
            "Can't write field value: GetRecordBlock() has not been called."); 
        return -1; 
    } 
 
    if (nWidth < 1 || nWidth > 255) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
                 "Illegal width for a char field: %d", nWidth); 
        return -1; 
    } 
     
    // 
    // Write the buffer after making sure that we don't try to read 
    // past the end of the source buffer.  The rest of the field will 
    // be padded with zeros if source string is shorter than specified 
    // field width. 
    // 
    int nLen = strlen(pszStr); 
    nLen = MIN(nLen, nWidth); 
 
    if ((nLen>0 && m_poRecordBlock->WriteBytes(nLen, (GByte*)pszStr) != 0) || 
        (nWidth-nLen > 0 && m_poRecordBlock->WriteZeros(nWidth-nLen)!=0) ) 
        return -1; 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, pszStr); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return 0; 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteIntegerField() 
 * 
 * Write the integer field value at the current position in the data  
 * block. 
 *  
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
int TABDATFile::WriteIntegerField(GInt32 nValue, 
                                  TABINDFile *poINDFile, int nIndexNo) 
{ 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
            "Can't write field value: GetRecordBlock() has not been called."); 
        return -1; 
    } 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return m_poRecordBlock->WriteInt32(nValue); 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteSmallIntField() 
 * 
 * Write the smallint field value at the current position in the data  
 * block. 
 *  
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
int TABDATFile::WriteSmallIntField(GInt16 nValue, 
                                   TABINDFile *poINDFile, int nIndexNo) 
{ 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
            "Can't write field value: GetRecordBlock() has not been called."); 
        return -1; 
    } 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return m_poRecordBlock->WriteInt16(nValue); 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteFloatField() 
 * 
 * Write the float field value at the current position in the data  
 * block. 
 *  
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
int TABDATFile::WriteFloatField(double dValue, 
                                TABINDFile *poINDFile, int nIndexNo) 
{ 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
            "Can't write field value: GetRecordBlock() has not been called."); 
        return -1; 
    } 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return m_poRecordBlock->WriteDouble(dValue); 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteLogicalField() 
 * 
 * Write the logical field value at the current position in the data  
 * block. 
 * 
 * The value written to the file is either 0 or 1, but this function 
 * takes as input a string with "F" (false) or "T" (true) 
 *  
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
int TABDATFile::WriteLogicalField(const char *pszValue, 
                                  TABINDFile *poINDFile, int nIndexNo) 
{ 
    GByte bValue; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
            "Can't write field value: GetRecordBlock() has not been called."); 
        return -1; 
    } 
 
    if (EQUALN(pszValue, "T", 1)) 
        bValue = 1; 
    else 
        bValue = 0; 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, (int)bValue); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return m_poRecordBlock->WriteByte(bValue); 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteDateField() 
 * 
 * Write the date field value at the current position in the data  
 * block. 
 * 
 * A date field is a 4 bytes binary value in which the first byte is 
 * the day, followed by 1 byte for the month, and 2 bytes for the year. 
 * 
 * The expected input is a 10 chars string in the format "YYYY/MM/DD" 
 * or "DD/MM/YYYY" or "YYYYMMDD" 
 *  
 * Returns 0 on success, or -1 if the operation failed, in which case 
 * CPLError() will have been called. 
 **********************************************************************/ 
int TABDATFile::WriteDateField(const char *pszValue, 
                               TABINDFile *poINDFile, int nIndexNo) 
{ 
    int nDay, nMonth, nYear; 
    char **papszTok = NULL; 
 
    if (m_poRecordBlock == NULL) 
    { 
        CPLError(CE_Failure, CPLE_AssertionFailed, 
            "Can't write field value: GetRecordBlock() has not been called."); 
        return -1; 
    } 
 
    /*----------------------------------------------------------------- 
     * Get rid of leading spaces. 
     *----------------------------------------------------------------*/ 
    while ( *pszValue == ' ' ) { pszValue++; } 
 
    /*----------------------------------------------------------------- 
     * Try to automagically detect date format, one of: 
     * "YYYY/MM/DD", "DD/MM/YYYY", or "YYYYMMDD" 
     *----------------------------------------------------------------*/ 
     
    if (strlen(pszValue) == 8) 
    { 
        /*------------------------------------------------------------- 
         * "YYYYMMDD" 
         *------------------------------------------------------------*/ 
        char szBuf[9]; 
        strcpy(szBuf, pszValue); 
        nDay = atoi(szBuf+6); 
        szBuf[6] = '\0'; 
        nMonth = atoi(szBuf+4); 
        szBuf[4] = '\0'; 
        nYear = atoi(szBuf); 
    } 
    else if (strlen(pszValue) == 10 && 
             (papszTok = CSLTokenizeStringComplex(pszValue, "/",  
                                                  FALSE, FALSE)) != NULL && 
             CSLCount(papszTok) == 3 && 
             (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4) ) 
    { 
        /*------------------------------------------------------------- 
         * Either "YYYY/MM/DD" or "DD/MM/YYYY" 
         *------------------------------------------------------------*/ 
        if (strlen(papszTok[0]) == 4) 
        { 
            nYear = atoi(papszTok[0]); 
            nMonth = atoi(papszTok[1]); 
            nDay = atoi(papszTok[2]); 
        } 
        else 
        { 
            nYear = atoi(papszTok[2]); 
            nMonth = atoi(papszTok[1]); 
            nDay = atoi(papszTok[0]); 
        } 
    } 
    else if (strlen(pszValue) == 0) 
    { 
        nYear = nMonth = nDay = 0; 
    } 
    else 
    { 
        CPLError(CE_Failure, CPLE_AppDefined, 
                 "Invalid date field value `%s'.  Date field values must " 
                 "be in the format `YYYY/MM/DD', `MM/DD/YYYY' or `YYYYMMDD'", 
                 pszValue); 
        CSLDestroy(papszTok); 
        return -1; 
    } 
    CSLDestroy(papszTok); 
 
    m_poRecordBlock->WriteInt16(nYear); 
    m_poRecordBlock->WriteByte(nMonth); 
    m_poRecordBlock->WriteByte(nDay); 
 
    if (CPLGetLastErrorNo() != 0) 
        return -1; 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, (nYear*0x10000 + 
                                                     nMonth * 0x100 + nDay)); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return 0; 
} 
 
/********************************************************************** 
 *                   TABDATFile::WriteDecimalField() 
 * 
 * Write the decimal field value at the current position in the data  
 * block. 
 * 
 * A decimal field is a floating point value with a fixed number of digits 
 * stored as a character string. 
 * 
 * nWidth is the field length, as defined in the .DAT header. 
 * 
 * CPLError() will have been called if something fails. 
 **********************************************************************/ 
int TABDATFile::WriteDecimalField(double dValue, int nWidth, int nPrec, 
                                  TABINDFile *poINDFile, int nIndexNo) 
{ 
    const char *pszVal; 
 
    pszVal = CPLSPrintf("%*.*f", nWidth, nPrec, dValue); 
    if ((int)strlen(pszVal) > nWidth) 
        pszVal += strlen(pszVal) - nWidth; 
 
    // Update Index 
    if (poINDFile && nIndexNo > 0) 
    { 
        GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue); 
        if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0) 
            return -1; 
    } 
 
    return m_poRecordBlock->WriteBytes(nWidth, (GByte*)pszVal); 
} 
 
 
 
/********************************************************************** 
 *                   TABDATFile::Dump() 
 * 
 * Dump block contents... available only in DEBUG mode. 
 **********************************************************************/ 
#ifdef DEBUG 
 
void TABDATFile::Dump(FILE *fpOut /*=NULL*/) 
{ 
    if (fpOut == NULL) 
        fpOut = stdout; 
 
    fprintf(fpOut, "----- TABDATFile::Dump() -----\n"); 
 
    if (m_fp == NULL) 
    { 
        fprintf(fpOut, "File is not opened.\n"); 
    } 
    else 
    { 
        fprintf(fpOut, "File is opened: %s\n", m_pszFname); 
        fprintf(fpOut, "m_numFields  = %d\n", m_numFields); 
        fprintf(fpOut, "m_numRecords = %d\n", m_numRecords); 
    } 
 
    fflush(fpOut); 
} 
 
#endif // DEBUG