LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/mitab - mitab_datfile.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 697 1171 59.5 %
Date: 2025-09-10 17:48:50 Functions: 43 52 82.7 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Name:     mitab_datfile.cpp
       4             :  * Project:  MapInfo TAB Read/Write library
       5             :  * Language: C++
       6             :  * Purpose:  Implementation of the TABIDFile class used to handle
       7             :  *           reading/writing of the .DAT file
       8             :  * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
       9             :  *
      10             :  **********************************************************************
      11             :  * Copyright (c) 1999-2001, Daniel Morissette
      12             :  * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
      13             :  *
      14             :  * SPDX-License-Identifier: MIT
      15             :  **********************************************************************/
      16             : 
      17             : #include "cpl_port.h"
      18             : #include "mitab.h"
      19             : 
      20             : #include <climits>
      21             : #include <cstdio>
      22             : #include <cstdlib>
      23             : #include <cstring>
      24             : #include <algorithm>
      25             : #include <string>
      26             : 
      27             : #include "cpl_conv.h"
      28             : #include "cpl_error.h"
      29             : #include "cpl_string.h"
      30             : #include "cpl_vsi.h"
      31             : #include "mitab_priv.h"
      32             : #include "ogr_core.h"
      33             : #include "ogr_feature.h"
      34             : #include "ogr_p.h"
      35             : 
      36             : /*=====================================================================
      37             :  *                      class TABDATFile
      38             :  *
      39             :  * Note that the .DAT files are .DBF files with some exceptions:
      40             :  *
      41             :  * All fields in the DBF header are defined as 'C' type (strings),
      42             :  * even for binary integers.  So we have to look in the associated .TAB
      43             :  * file to find the real field definition.
      44             :  *
      45             :  * Even though binary integers are defined as 'C' type, they are stored
      46             :  * in binary form inside a 4 bytes string field.
      47             :  *====================================================================*/
      48             : 
      49             : /**********************************************************************
      50             :  *                   TABDATFile::TABDATFile()
      51             :  *
      52             :  * Constructor.
      53             :  **********************************************************************/
      54        1464 : TABDATFile::TABDATFile(const char *pszEncoding)
      55             :     : m_pszFname(nullptr), m_fp(nullptr), m_eAccessMode(TABRead),
      56             :       m_eTableType(TABTableNative), m_poHeaderBlock(nullptr), m_numFields(-1),
      57             :       m_pasFieldDef(nullptr), m_poRecordBlock(nullptr), m_nBlockSize(0),
      58             :       m_nRecordSize(-1), m_nCurRecordId(-1), m_bCurRecordDeletedFlag(FALSE),
      59             :       m_numRecords(-1), m_nFirstRecordPtr(0), m_bWriteHeaderInitialized(FALSE),
      60             :       m_bWriteEOF(FALSE), m_bUpdated(FALSE),
      61        1464 :       m_osEncoding(pszEncoding), m_szBuffer{}
      62             : {
      63        1464 : }
      64             : 
      65             : /**********************************************************************
      66             :  *                   TABDATFile::~TABDATFile()
      67             :  *
      68             :  * Destructor.
      69             :  **********************************************************************/
      70        1464 : TABDATFile::~TABDATFile()
      71             : {
      72        1464 :     Close();
      73        1464 : }
      74             : 
      75             : /**********************************************************************
      76             :  *                   TABDATFile::Open()
      77             :  *
      78             :  * Compatibility layer with new interface.
      79             :  * Return 0 on success, -1 in case of failure.
      80             :  **********************************************************************/
      81             : 
      82           0 : int TABDATFile::Open(const char *pszFname, const char *pszAccess,
      83             :                      TABTableType eTableType)
      84             : {
      85             :     // cppcheck-suppress nullPointer
      86           0 :     if (STARTS_WITH_CI(pszAccess, "r"))
      87             :     {
      88           0 :         return Open(pszFname, TABRead, eTableType);
      89             :     }
      90           0 :     else if (STARTS_WITH_CI(pszAccess, "w"))
      91             :     {
      92           0 :         return Open(pszFname, TABWrite, eTableType);
      93             :     }
      94             :     else
      95             :     {
      96           0 :         CPLError(CE_Failure, CPLE_FileIO,
      97             :                  "Open() failed: access mode \"%s\" not supported", pszAccess);
      98           0 :         return -1;
      99             :     }
     100             : }
     101             : 
     102             : /**********************************************************************
     103             :  *                   TABDATFile::Open()
     104             :  *
     105             :  * Open a .DAT file, and initialize the structures to be ready to read
     106             :  * records from it.
     107             :  *
     108             :  * We currently support NATIVE and DBF tables for reading, and only
     109             :  * NATIVE tables for writing.
     110             :  *
     111             :  * Returns 0 on success, -1 on error.
     112             :  **********************************************************************/
     113        1495 : int TABDATFile::Open(const char *pszFname, TABAccess eAccess,
     114             :                      TABTableType eTableType /*=TABNativeTable*/)
     115             : {
     116        1495 :     if (m_fp)
     117             :     {
     118           0 :         CPLError(CE_Failure, CPLE_FileIO,
     119             :                  "Open() failed: object already contains an open file");
     120           0 :         return -1;
     121             :     }
     122             : 
     123             :     // Validate access mode and make sure we use binary access.
     124        1495 :     const char *pszAccess = nullptr;
     125        1495 :     if (eAccess == TABRead &&
     126           1 :         (eTableType == TABTableNative || eTableType == TABTableDBF))
     127             :     {
     128         261 :         pszAccess = "rb";
     129             :     }
     130        1234 :     else if (eAccess == TABWrite && eTableType == TABTableNative)
     131             :     {
     132         154 :         pszAccess = "wb+";
     133             :     }
     134        1080 :     else if (eAccess == TABReadWrite && eTableType == TABTableNative)
     135             :     {
     136        1080 :         pszAccess = "rb+";
     137             :     }
     138             :     else
     139             :     {
     140           0 :         CPLError(CE_Failure, CPLE_FileIO,
     141             :                  "Open() failed: access mode \"%d\" "
     142             :                  "not supported with eTableType=%d",
     143             :                  eAccess, eTableType);
     144           0 :         return -1;
     145             :     }
     146        1495 :     m_eAccessMode = eAccess;
     147             : 
     148             :     // Open file for reading.
     149        1495 :     m_pszFname = CPLStrdup(pszFname);
     150        1495 :     m_fp = VSIFOpenL(m_pszFname, pszAccess);
     151        1495 :     m_eTableType = eTableType;
     152             : 
     153        1495 :     if (m_fp == nullptr)
     154             :     {
     155           0 :         CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", m_pszFname);
     156           0 :         CPLFree(m_pszFname);
     157           0 :         m_pszFname = nullptr;
     158           0 :         return -1;
     159             :     }
     160             : 
     161        1495 :     if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
     162             :     {
     163             :         // READ ACCESS:
     164             :         // Read .DAT file header (record size, num records, etc...)
     165             :         // m_poHeaderBlock will be reused later to read field definition
     166        1341 :         m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE);
     167        1341 :         CPL_IGNORE_RET_VAL(m_poHeaderBlock->ReadFromFile(m_fp, 0, 32));
     168             : 
     169        1341 :         m_poHeaderBlock->ReadByte();  // Table type ??? 0x03
     170        1341 :         m_poHeaderBlock->ReadByte();  // Last update year
     171        1341 :         m_poHeaderBlock->ReadByte();  // Last update month
     172        1341 :         m_poHeaderBlock->ReadByte();  // Last update day
     173             : 
     174        1341 :         m_numRecords = m_poHeaderBlock->ReadInt32();
     175        1341 :         m_nFirstRecordPtr = m_poHeaderBlock->ReadInt16();
     176        1341 :         m_nRecordSize = m_poHeaderBlock->ReadInt16();
     177        1341 :         if (m_nFirstRecordPtr < 32 || m_nRecordSize <= 0 || m_numRecords < 0)
     178             :         {
     179           0 :             VSIFCloseL(m_fp);
     180           0 :             m_fp = nullptr;
     181           0 :             CPLFree(m_pszFname);
     182           0 :             m_pszFname = nullptr;
     183           0 :             delete m_poHeaderBlock;
     184           0 :             m_poHeaderBlock = nullptr;
     185           0 :             return -1;
     186             :         }
     187             : 
     188             :         // Limit number of records to avoid int overflow
     189        1341 :         if (m_numRecords > INT_MAX / m_nRecordSize ||
     190        1341 :             m_nFirstRecordPtr > INT_MAX - m_numRecords * m_nRecordSize)
     191             :         {
     192           0 :             m_numRecords = (INT_MAX - m_nFirstRecordPtr) / m_nRecordSize;
     193             :         }
     194             : 
     195        1341 :         m_numFields = m_nFirstRecordPtr / 32 - 1;
     196             : 
     197             :         // Read the field definitions.
     198             :         // First 32 bytes field definition starts at byte 32 in file.
     199        1341 :         m_pasFieldDef = static_cast<TABDATFieldDef *>(
     200        1341 :             CPLCalloc(m_numFields, sizeof(TABDATFieldDef)));
     201             : 
     202        2978 :         for (int i = 0; i < m_numFields; i++)
     203             :         {
     204        1638 :             m_poHeaderBlock->GotoByteInFile((i + 1) * 32);
     205        1638 :             m_poHeaderBlock->ReadBytes(
     206        1638 :                 11, reinterpret_cast<GByte *>(m_pasFieldDef[i].szName));
     207        1638 :             constexpr char HEADER_RECORD_TERMINATOR = 0x0D;
     208        1638 :             if (m_pasFieldDef[i].szName[0] == HEADER_RECORD_TERMINATOR)
     209             :             {
     210           1 :                 m_numFields = i;
     211           1 :                 break;
     212             :             }
     213        1637 :             m_pasFieldDef[i].szName[10] = '\0';
     214        1637 :             m_pasFieldDef[i].cType =
     215        1637 :                 static_cast<char>(m_poHeaderBlock->ReadByte());
     216             : 
     217        1637 :             m_poHeaderBlock->ReadInt32();  // Skip Bytes 12-15
     218        1637 :             m_pasFieldDef[i].byLength = m_poHeaderBlock->ReadByte();
     219        1637 :             m_pasFieldDef[i].byDecimals = m_poHeaderBlock->ReadByte();
     220             : 
     221        1637 :             m_pasFieldDef[i].eTABType = TABFUnknown;
     222             :         }
     223             : 
     224             :         // Establish a good record block size to use based on record size, and
     225             :         // then create m_poRecordBlock.
     226             :         // Record block size has to be a multiple of record size.
     227        1341 :         m_nBlockSize = ((1024 / m_nRecordSize) + 1) * m_nRecordSize;
     228        1341 :         m_nBlockSize = std::min(m_nBlockSize, (m_numRecords * m_nRecordSize));
     229             : 
     230        1341 :         CPLAssert(m_poRecordBlock == nullptr);
     231        1341 :         m_poRecordBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
     232        1341 :         m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize);
     233        1341 :         m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr);
     234             : 
     235        1341 :         m_bWriteHeaderInitialized = TRUE;
     236             :     }
     237             :     else
     238             :     {
     239             :         // WRITE ACCESS:
     240             :         // Set acceptable defaults for all class members.
     241             :         // The real header initialization will be done when the first
     242             :         // record is written.
     243         154 :         m_poHeaderBlock = nullptr;
     244             : 
     245         154 :         m_numRecords = 0;
     246         154 :         m_nFirstRecordPtr = 0;
     247         154 :         m_nRecordSize = 0;
     248         154 :         m_numFields = 0;
     249         154 :         m_pasFieldDef = nullptr;
     250         154 :         m_bWriteHeaderInitialized = FALSE;
     251             :     }
     252             : 
     253        1495 :     return 0;
     254             : }
     255             : 
     256             : /**********************************************************************
     257             :  *                   TABDATFile::Close()
     258             :  *
     259             :  * Close current file, and release all memory used.
     260             :  *
     261             :  * Returns 0 on success, -1 on error.
     262             :  **********************************************************************/
     263        2959 : int TABDATFile::Close()
     264             : {
     265        2959 :     if (m_fp == nullptr)
     266        1464 :         return 0;
     267             : 
     268             :     // Write access: Update the header with number of records, etc.
     269             :     // and add a CTRL-Z char at the end of the file.
     270        1495 :     if (m_eAccessMode != TABRead)
     271             :     {
     272        1234 :         SyncToDisk();
     273             :     }
     274             : 
     275             :     // Delete all structures
     276        1495 :     if (m_poHeaderBlock)
     277             :     {
     278        1495 :         delete m_poHeaderBlock;
     279        1495 :         m_poHeaderBlock = nullptr;
     280             :     }
     281             : 
     282        1495 :     if (m_poRecordBlock)
     283             :     {
     284        1495 :         delete m_poRecordBlock;
     285        1495 :         m_poRecordBlock = nullptr;
     286             :     }
     287             : 
     288             :     // Close file
     289        1495 :     VSIFCloseL(m_fp);
     290        1495 :     m_fp = nullptr;
     291             : 
     292        1495 :     CPLFree(m_pszFname);
     293        1495 :     m_pszFname = nullptr;
     294             : 
     295        1495 :     CPLFree(m_pasFieldDef);
     296        1495 :     m_pasFieldDef = nullptr;
     297             : 
     298        1495 :     m_numFields = -1;
     299        1495 :     m_numRecords = -1;
     300        1495 :     m_nFirstRecordPtr = 0;
     301        1495 :     m_nBlockSize = 0;
     302        1495 :     m_nRecordSize = -1;
     303        1495 :     m_nCurRecordId = -1;
     304        1495 :     m_bWriteHeaderInitialized = FALSE;
     305        1495 :     m_bWriteEOF = FALSE;
     306        1495 :     m_bUpdated = FALSE;
     307             : 
     308        1495 :     return 0;
     309             : }
     310             : 
     311             : /************************************************************************/
     312             : /*                            SyncToDisk()                             */
     313             : /************************************************************************/
     314             : 
     315        1353 : int TABDATFile::SyncToDisk()
     316             : {
     317        1353 :     if (m_fp == nullptr)
     318           0 :         return 0;
     319             : 
     320        1353 :     if (m_eAccessMode == TABRead)
     321             :     {
     322           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     323             :                  "SyncToDisk() can be used only with Write access.");
     324           0 :         return -1;
     325             :     }
     326             : 
     327        1353 :     if (!m_bUpdated && m_bWriteHeaderInitialized)
     328         114 :         return 0;
     329             : 
     330             :     // No need to call. CommitRecordToFile(). It is normally called by
     331             :     // TABFeature::WriteRecordToDATFile()
     332        1239 :     if (WriteHeader() != 0)
     333           0 :         return -1;
     334             : 
     335        1239 :     m_bUpdated = FALSE;
     336        1239 :     return 0;
     337             : }
     338             : 
     339             : /**********************************************************************
     340             :  *                   TABDATFile::InitWriteHeader()
     341             :  *
     342             :  * Init the header members to be ready to write the header and data records
     343             :  * to a newly created data file.
     344             :  *
     345             :  * Returns 0 on success, -1 on error.
     346             :  **********************************************************************/
     347         154 : int TABDATFile::InitWriteHeader()
     348             : {
     349         154 :     if (m_eAccessMode == TABRead || m_bWriteHeaderInitialized)
     350           0 :         return 0;
     351             : 
     352             :     // Compute values for Record size, header size, etc.
     353         154 :     m_nFirstRecordPtr = (m_numFields + 1) * 32 + 1;
     354             : 
     355         154 :     m_nRecordSize = 1;
     356         469 :     for (int i = 0; i < m_numFields; i++)
     357             :     {
     358         315 :         m_nRecordSize += m_pasFieldDef[i].byLength;
     359             :     }
     360             : 
     361             :     // Create m_poRecordBlock the size of a data record.
     362         154 :     m_nBlockSize = m_nRecordSize;
     363             : 
     364         154 :     CPLAssert(m_poRecordBlock == nullptr);
     365         154 :     m_poRecordBlock = new TABRawBinBlock(TABReadWrite, FALSE);
     366         154 :     m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize);
     367         154 :     m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr);
     368             : 
     369             :     // Make sure this init. will be performed only once.
     370         154 :     m_bWriteHeaderInitialized = TRUE;
     371             : 
     372         154 :     return 0;
     373             : }
     374             : 
     375             : /**********************************************************************
     376             :  *                   TABDATFile::WriteHeader()
     377             :  *
     378             :  * Init the header members to be ready to write the header and data records
     379             :  * to a newly created data file.
     380             :  *
     381             :  * Returns 0 on success, -1 on error.
     382             :  **********************************************************************/
     383        1369 : int TABDATFile::WriteHeader()
     384             : {
     385        1369 :     if (m_eAccessMode == TABRead)
     386             :     {
     387           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     388             :                  "WriteHeader() can be used only with Write access.");
     389           0 :         return -1;
     390             :     }
     391             : 
     392        1369 :     if (!m_bWriteHeaderInitialized)
     393         154 :         InitWriteHeader();
     394             : 
     395             :     // Create a single block that will be used to generate the whole header.
     396        1369 :     if (m_poHeaderBlock == nullptr)
     397         154 :         m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE);
     398        1369 :     m_poHeaderBlock->InitNewBlock(m_fp, m_nFirstRecordPtr, 0);
     399             : 
     400             :     // First 32 bytes: main header block.
     401        1369 :     m_poHeaderBlock->WriteByte(0x03);  // Table type ??? 0x03
     402             : 
     403             :     // __TODO__ Write the correct update date value
     404        1369 :     m_poHeaderBlock->WriteByte(99);  // Last update year
     405        1369 :     m_poHeaderBlock->WriteByte(9);   // Last update month
     406        1369 :     m_poHeaderBlock->WriteByte(9);   // Last update day
     407             : 
     408        1369 :     m_poHeaderBlock->WriteInt32(m_numRecords);
     409        1369 :     m_poHeaderBlock->WriteInt16(static_cast<GInt16>(m_nFirstRecordPtr));
     410        1369 :     m_poHeaderBlock->WriteInt16(static_cast<GInt16>(m_nRecordSize));
     411             : 
     412        1369 :     m_poHeaderBlock->WriteZeros(20);  // Pad rest with zeros.
     413             : 
     414             :     // Field definitions follow.  Each field def is 32 bytes.
     415        3126 :     for (int i = 0; i < m_numFields; i++)
     416             :     {
     417        1757 :         m_poHeaderBlock->WriteBytes(
     418        1757 :             11, reinterpret_cast<GByte *>(m_pasFieldDef[i].szName));
     419        1757 :         m_poHeaderBlock->WriteByte(m_pasFieldDef[i].cType);
     420             : 
     421        1757 :         m_poHeaderBlock->WriteInt32(0);  // Skip Bytes 12-15
     422             : 
     423        1757 :         m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byLength);
     424        1757 :         m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byDecimals);
     425             : 
     426        1757 :         m_poHeaderBlock->WriteZeros(14);  // Pad rest with zeros
     427             :     }
     428             : 
     429             :     // Header ends with a 0x0d character.
     430        1369 :     m_poHeaderBlock->WriteByte(0x0d);
     431             : 
     432             :     // Write the block to the file and return.
     433        1369 :     return m_poHeaderBlock->CommitToFile();
     434             : }
     435             : 
     436             : /**********************************************************************
     437             :  *                   TABDATFile::GetNumFields()
     438             :  *
     439             :  * Return the number of fields in this table.
     440             :  *
     441             :  * Returns a value >= 0 on success, -1 on error.
     442             :  **********************************************************************/
     443      566404 : int TABDATFile::GetNumFields()
     444             : {
     445      566404 :     return m_numFields;
     446             : }
     447             : 
     448             : /**********************************************************************
     449             :  *                   TABDATFile::GetNumRecords()
     450             :  *
     451             :  * Return the number of records in this table.
     452             :  *
     453             :  * Returns a value >= 0 on success, -1 on error.
     454             :  **********************************************************************/
     455        1433 : int TABDATFile::GetNumRecords()
     456             : {
     457        1433 :     return m_numRecords;
     458             : }
     459             : 
     460             : /**********************************************************************
     461             :  *                   TABDATFile::GetRecordBlock()
     462             :  *
     463             :  * Return a TABRawBinBlock reference positioned at the beginning of the
     464             :  * specified record and ready to read (or write) field values from/to it.
     465             :  * In read access, the returned block is guaranteed to contain at least one
     466             :  * full record of data, and in write access, it is at least big enough to
     467             :  * hold one full record.
     468             :  *
     469             :  * Note that record ids are positive and start at 1.
     470             :  *
     471             :  * In Write access, CommitRecordToFile() MUST be called after the
     472             :  * data items have been written to the record, otherwise the record
     473             :  * will never make it to the file.
     474             :  *
     475             :  * Returns a reference to the TABRawBinBlock on success or NULL on error.
     476             :  * The returned pointer is a reference to a block object owned by this
     477             :  * TABDATFile object and should not be freed by the caller.
     478             :  **********************************************************************/
     479      720425 : TABRawBinBlock *TABDATFile::GetRecordBlock(int nRecordId)
     480             : {
     481      720425 :     if (m_fp == nullptr)
     482             :     {
     483           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     484             :                  "Operation not supported on closed table.");
     485           0 :         return nullptr;
     486             :     }
     487             : 
     488      720425 :     m_bCurRecordDeletedFlag = FALSE;
     489      720425 :     m_bWriteEOF = FALSE;
     490             : 
     491      720425 :     if (m_eAccessMode == TABRead || nRecordId <= m_numRecords)
     492             :     {
     493             :         // READ ACCESS
     494      705467 :         const int nFileOffset =
     495      705467 :             m_nFirstRecordPtr + (nRecordId - 1) * m_nRecordSize;
     496             : 
     497             :         // Move record block pointer to the right location.
     498      705467 :         if (m_poRecordBlock == nullptr || nRecordId < 1 ||
     499     2116400 :             nRecordId > m_numRecords ||
     500      705467 :             m_poRecordBlock->GotoByteInFile(nFileOffset) != 0)
     501             :         {
     502           0 :             CPLError(CE_Failure, CPLE_FileIO,
     503             :                      "Failed reading .DAT record block for record #%d in %s",
     504             :                      nRecordId, m_pszFname);
     505           0 :             return nullptr;
     506             :         }
     507             : 
     508             :         // The first char of the record is a ' ' for an active record, or
     509             :         // '*' for a deleted one.
     510             :         // In the case of a deleted record, we simply return default
     511             :         // values for each attribute... this is what MapInfo seems to do
     512             :         // when it takes a .TAB with deleted records and exports it to .MIF
     513      705467 :         if (m_poRecordBlock->ReadByte() != ' ')
     514             :         {
     515        1320 :             m_bCurRecordDeletedFlag = TRUE;
     516      705467 :         }
     517             :     }
     518       14958 :     else if (nRecordId > 0)
     519             :     {
     520             :         // WRITE ACCESS
     521             : 
     522             :         // Before writing the first record, we must generate the file
     523             :         // header.  We will also initialize class members such as record
     524             :         // size, etc. and will create m_poRecordBlock.
     525       14958 :         if (!m_bWriteHeaderInitialized)
     526             :         {
     527         130 :             WriteHeader();
     528             :         }
     529             : 
     530       14958 :         m_bUpdated = TRUE;
     531             : 
     532       14958 :         m_numRecords = std::max(nRecordId, m_numRecords);
     533       14958 :         if (nRecordId == m_numRecords)
     534       14958 :             m_bWriteEOF = TRUE;
     535             : 
     536       14958 :         const int nFileOffset =
     537       14958 :             m_nFirstRecordPtr + (nRecordId - 1) * m_nRecordSize;
     538             : 
     539       14958 :         m_poRecordBlock->InitNewBlock(m_fp, m_nRecordSize, nFileOffset);
     540             : 
     541             :         // The first char of the record is the active/deleted flag.
     542             :         // Automatically set it to ' ' (active).
     543       14958 :         m_poRecordBlock->WriteByte(' ');
     544             :     }
     545             : 
     546      720425 :     m_nCurRecordId = nRecordId;
     547             : 
     548      720425 :     return m_poRecordBlock;
     549             : }
     550             : 
     551             : /**********************************************************************
     552             :  *                   TABDATFile::CommitRecordToFile()
     553             :  *
     554             :  * Commit the data record previously initialized with GetRecordBlock()
     555             :  * to the file.  This function must be called after writing the data
     556             :  * values to a record otherwise the record will never make it to the
     557             :  * file.
     558             :  *
     559             :  * Returns 0 on success, -1 on error.
     560             :  **********************************************************************/
     561       15173 : int TABDATFile::CommitRecordToFile()
     562             : {
     563       15173 :     if (m_eAccessMode == TABRead || m_poRecordBlock == nullptr)
     564           0 :         return -1;
     565             : 
     566       15173 :     if (m_poRecordBlock->CommitToFile() != 0)
     567           0 :         return -1;
     568             : 
     569             :     // If this is the end of file, write EOF character.
     570       15173 :     if (m_bWriteEOF)
     571             :     {
     572       14957 :         m_bWriteEOF = FALSE;
     573       14957 :         char cEOF = 26;
     574       14957 :         if (VSIFSeekL(m_fp, 0L, SEEK_END) == 0)
     575       14957 :             VSIFWriteL(&cEOF, 1, 1, m_fp);
     576             :     }
     577             : 
     578       15173 :     return 0;
     579             : }
     580             : 
     581             : /**********************************************************************
     582             :  *                   TABDATFile::MarkAsDeleted()
     583             :  *
     584             :  * Returns 0 on success, -1 on error.
     585             :  **********************************************************************/
     586         715 : int TABDATFile::MarkAsDeleted()
     587             : {
     588         715 :     if (m_eAccessMode == TABRead || m_poRecordBlock == nullptr)
     589           0 :         return -1;
     590             : 
     591         715 :     const int nFileOffset =
     592         715 :         m_nFirstRecordPtr + (m_nCurRecordId - 1) * m_nRecordSize;
     593             : 
     594         715 :     if (m_poRecordBlock->GotoByteInFile(nFileOffset) != 0)
     595           0 :         return -1;
     596             : 
     597         715 :     m_poRecordBlock->WriteByte('*');
     598             : 
     599         715 :     if (m_poRecordBlock->CommitToFile() != 0)
     600           0 :         return -1;
     601             : 
     602         715 :     m_bCurRecordDeletedFlag = TRUE;
     603         715 :     m_bUpdated = TRUE;
     604             : 
     605         715 :     return 0;
     606             : }
     607             : 
     608             : /**********************************************************************
     609             :  *                   TABDATFile::MarkRecordAsExisting()
     610             :  *
     611             :  * Returns 0 on success, -1 on error.
     612             :  **********************************************************************/
     613       15087 : int TABDATFile::MarkRecordAsExisting()
     614             : {
     615       15087 :     if (m_eAccessMode == TABRead || m_poRecordBlock == nullptr)
     616           0 :         return -1;
     617             : 
     618       15087 :     const int nFileOffset =
     619       15087 :         m_nFirstRecordPtr + (m_nCurRecordId - 1) * m_nRecordSize;
     620             : 
     621       15087 :     if (m_poRecordBlock->GotoByteInFile(nFileOffset) != 0)
     622           0 :         return -1;
     623             : 
     624       15087 :     m_poRecordBlock->WriteByte(' ');
     625             : 
     626       15087 :     m_bCurRecordDeletedFlag = FALSE;
     627       15087 :     m_bUpdated = TRUE;
     628             : 
     629       15087 :     return 0;
     630             : }
     631             : 
     632             : /**********************************************************************
     633             :  *                   TABDATFile::ValidateFieldInfoFromTAB()
     634             :  *
     635             :  * Check that the value read from the .TAB file by the caller are
     636             :  * consistent with what is found in the .DAT header.
     637             :  *
     638             :  * Note that field ids are positive and start at 0.
     639             :  *
     640             :  * We have to use this function when opening a file for reading since
     641             :  * the .DAT file does not contain the full field types information...
     642             :  * a .DAT file is actually a .DBF file in which the .DBF types are
     643             :  * handled in a special way... type 'C' fields are used to store binary
     644             :  * values for most MapInfo types.
     645             :  *
     646             :  * For TABTableDBF, we actually have no validation to do since all types
     647             :  * are stored as strings internally, so we'll just convert from string.
     648             :  *
     649             :  * Returns a value >= 0 if OK, -1 on error.
     650             :  **********************************************************************/
     651        1539 : int TABDATFile::ValidateFieldInfoFromTAB(int iField, const char *pszName,
     652             :                                          TABFieldType eType, int nWidth,
     653             :                                          int nPrecision)
     654             : {
     655        1539 :     int i = iField;  // Just to make things shorter
     656             : 
     657        1539 :     if (m_pasFieldDef == nullptr || iField < 0 || iField >= m_numFields)
     658             :     {
     659          16 :         CPLError(
     660             :             CE_Failure, CPLE_FileIO,
     661             :             "Invalid field %d (%s) in .TAB header. %s contains only %d fields.",
     662          16 :             iField + 1, pszName, m_pszFname, m_pasFieldDef ? m_numFields : 0);
     663          16 :         return -1;
     664             :     }
     665             : 
     666             :     // We used to check that the .TAB field name matched the .DAT
     667             :     // name stored internally, but apparently some tools that rename table
     668             :     // field names only update the .TAB file and not the .DAT, so we won't
     669             :     // do that name validation any more... we'll just check the type.
     670             :     //
     671             :     // With TABTableNative, we have to validate the field sizes as well
     672             :     // because .DAT files use char fields to store binary values.
     673             :     // With TABTableDBF, no need to validate field type since all
     674             :     // fields are stored as strings internally.
     675             : 
     676        1523 :     if ((m_eTableType == TABTableNative &&
     677         201 :          ((eType == TABFChar && (m_pasFieldDef[i].cType != 'C' ||
     678        1521 :                                  m_pasFieldDef[i].byLength != nWidth)) ||
     679           8 :           (eType == TABFDecimal &&
     680           8 :            (m_pasFieldDef[i].cType != 'N' ||
     681           8 :             m_pasFieldDef[i].byLength != nWidth ||
     682        1521 :             m_pasFieldDef[i].byDecimals != nPrecision)) ||
     683        1233 :           (eType == TABFInteger &&
     684        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 4)) ||
     685           1 :           (eType == TABFSmallInt &&
     686        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 2)) ||
     687           2 :           (eType == TABFLargeInt &&
     688        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 8)) ||
     689          31 :           (eType == TABFFloat &&
     690        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 8)) ||
     691          21 :           (eType == TABFDate &&
     692        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 4)) ||
     693           3 :           (eType == TABFTime &&
     694        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 4)) ||
     695          19 :           (eType == TABFDateTime &&
     696        1521 :            (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 8)) ||
     697           2 :           (eType == TABFLogical &&
     698           2 :            (m_pasFieldDef[i].cType != 'L' || m_pasFieldDef[i].byLength != 1)))))
     699             :     {
     700           0 :         CPLError(CE_Failure, CPLE_FileIO,
     701             :                  "Definition of field %d (%s) from .TAB file does not match "
     702             :                  "what is found in %s (name=%s, type=%c, width=%d, prec=%d)",
     703           0 :                  iField + 1, pszName, m_pszFname, m_pasFieldDef[i].szName,
     704           0 :                  m_pasFieldDef[i].cType, m_pasFieldDef[i].byLength,
     705           0 :                  m_pasFieldDef[i].byDecimals);
     706           0 :         return -1;
     707             :     }
     708             : 
     709        1523 :     m_pasFieldDef[i].eTABType = eType;
     710             : 
     711        1523 :     return 0;
     712             : }
     713             : 
     714             : /**********************************************************************
     715             :  *                  TABDATFileSetFieldDefinition()
     716             :  *
     717             :  **********************************************************************/
     718         336 : static int TABDATFileSetFieldDefinition(TABDATFieldDef *psFieldDef,
     719             :                                         const char *pszName, TABFieldType eType,
     720             :                                         int nWidth, int nPrecision)
     721             : {
     722             :     // Validate field width.
     723         336 :     if (nWidth > 254)
     724             :     {
     725           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
     726             :                  "Invalid size (%d) for field '%s'.  "
     727             :                  "Size must be 254 or less.",
     728             :                  nWidth, pszName);
     729           0 :         return -1;
     730             :     }
     731             : 
     732             :     // Map fields with width=0 (variable length in OGR) to a valid default.
     733         336 :     if (eType == TABFDecimal && nWidth == 0)
     734           0 :         nWidth = 20;
     735         336 :     else if (nWidth == 0)
     736           0 :         nWidth = 254;  // char fields.
     737             : 
     738         336 :     snprintf(psFieldDef->szName, sizeof(psFieldDef->szName), "%s", pszName);
     739         336 :     psFieldDef->eTABType = eType;
     740         336 :     psFieldDef->byDecimals = 0;
     741             : 
     742         336 :     switch (eType)
     743             :     {
     744         189 :         case TABFChar:
     745         189 :             psFieldDef->cType = 'C';
     746         189 :             psFieldDef->byLength = static_cast<GByte>(nWidth);
     747         189 :             break;
     748           4 :         case TABFDecimal:
     749           4 :             psFieldDef->cType = 'N';
     750           4 :             psFieldDef->byLength = static_cast<GByte>(nWidth);
     751           4 :             psFieldDef->byDecimals = static_cast<GByte>(nPrecision);
     752           4 :             break;
     753          79 :         case TABFInteger:
     754          79 :             psFieldDef->cType = 'C';
     755          79 :             psFieldDef->byLength = 4;
     756          79 :             break;
     757           0 :         case TABFSmallInt:
     758           0 :             psFieldDef->cType = 'C';
     759           0 :             psFieldDef->byLength = 2;
     760           0 :             break;
     761           1 :         case TABFLargeInt:
     762           1 :             psFieldDef->cType = 'C';
     763           1 :             psFieldDef->byLength = 8;
     764           1 :             break;
     765          24 :         case TABFFloat:
     766          24 :             psFieldDef->cType = 'C';
     767          24 :             psFieldDef->byLength = 8;
     768          24 :             break;
     769          18 :         case TABFDate:
     770          18 :             psFieldDef->cType = 'C';
     771          18 :             psFieldDef->byLength = 4;
     772          18 :             break;
     773           2 :         case TABFTime:
     774           2 :             psFieldDef->cType = 'C';
     775           2 :             psFieldDef->byLength = 4;
     776           2 :             break;
     777          18 :         case TABFDateTime:
     778          18 :             psFieldDef->cType = 'C';
     779          18 :             psFieldDef->byLength = 8;
     780          18 :             break;
     781           1 :         case TABFLogical:
     782           1 :             psFieldDef->cType = 'L';
     783           1 :             psFieldDef->byLength = 1;
     784           1 :             break;
     785           0 :         default:
     786           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     787             :                      "Unsupported field type for field `%s'", pszName);
     788           0 :             return -1;
     789             :     }
     790             : 
     791         336 :     return 0;
     792             : }
     793             : 
     794             : /**********************************************************************
     795             :  *                   TABDATFile::AddField()
     796             :  *
     797             :  * Create a new field (column) in a newly created table.  This function
     798             :  * must be called after the file has been opened, but before writing the
     799             :  * first record.
     800             :  *
     801             :  * Returns the new field index (a value >= 0) if OK, -1 on error.
     802             :  **********************************************************************/
     803         329 : int TABDATFile::AddField(const char *pszName, TABFieldType eType, int nWidth,
     804             :                          int nPrecision /* =0 */)
     805             : {
     806         329 :     if (m_fp == nullptr)
     807             :     {
     808           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     809             :                  "Operation not supported on closed table.");
     810           0 :         return -1;
     811             :     }
     812         329 :     if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
     813             :     {
     814           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     815             :                  "Operation not supported on read-only files or "
     816             :                  "on non-native table.");
     817           0 :         return -1;
     818             :     }
     819             : 
     820             :     TABDATFieldDef sFieldDef;
     821         329 :     if (TABDATFileSetFieldDefinition(&sFieldDef, pszName, eType, nWidth,
     822         329 :                                      nPrecision) < 0)
     823           0 :         return -1;
     824             : 
     825         329 :     if (m_numFields < 0)
     826           0 :         m_numFields = 0;
     827             : 
     828         329 :     m_numFields++;
     829         329 :     m_pasFieldDef = static_cast<TABDATFieldDef *>(
     830         329 :         CPLRealloc(m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef)));
     831         329 :     memcpy(&m_pasFieldDef[m_numFields - 1], &sFieldDef, sizeof(sFieldDef));
     832             : 
     833             :     // If there are already records, we cannot update in place.
     834             :     // Create a temporary .dat.tmp in which we create the new structure
     835             :     // and then copy the widen records.
     836         329 :     if (m_numRecords > 0)
     837             :     {
     838          14 :         TABDATFile oTempFile(GetEncoding());
     839          14 :         CPLString osOriginalFile(m_pszFname);
     840          14 :         CPLString osTmpFile(m_pszFname);
     841          14 :         osTmpFile += ".tmp";
     842          14 :         if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
     843           0 :             return -1;
     844             : 
     845             :         // Create field structure.
     846          60 :         for (int i = 0; i < m_numFields; i++)
     847             :         {
     848          46 :             oTempFile.AddField(
     849          46 :                 m_pasFieldDef[i].szName, m_pasFieldDef[i].eTABType,
     850          46 :                 m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
     851             :         }
     852             : 
     853          14 :         GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
     854             : 
     855             :         // Copy records.
     856          44 :         for (int j = 0; j < m_numRecords; j++)
     857             :         {
     858          60 :             if (GetRecordBlock(1 + j) == nullptr ||
     859          30 :                 oTempFile.GetRecordBlock(1 + j) == nullptr)
     860             :             {
     861           0 :                 CPLFree(pabyRecord);
     862           0 :                 oTempFile.Close();
     863           0 :                 VSIUnlink(osTmpFile);
     864           0 :                 return -1;
     865             :             }
     866          30 :             if (m_bCurRecordDeletedFlag)
     867             :             {
     868           0 :                 oTempFile.MarkAsDeleted();
     869             :             }
     870             :             else
     871             :             {
     872          30 :                 if (m_poRecordBlock->ReadBytes(m_nRecordSize - 1, pabyRecord) !=
     873          30 :                         0 ||
     874          30 :                     oTempFile.m_poRecordBlock->WriteBytes(m_nRecordSize - 1,
     875          60 :                                                           pabyRecord) != 0 ||
     876          30 :                     oTempFile.m_poRecordBlock->WriteZeros(
     877          30 :                         m_pasFieldDef[m_numFields - 1].byLength) != 0)
     878             :                 {
     879           0 :                     CPLFree(pabyRecord);
     880           0 :                     oTempFile.Close();
     881           0 :                     VSIUnlink(osTmpFile);
     882           0 :                     return -1;
     883             :                 }
     884          30 :                 oTempFile.CommitRecordToFile();
     885             :             }
     886             :         }
     887             : 
     888          14 :         CPLFree(pabyRecord);
     889             : 
     890             :         // Close temporary file.
     891          14 :         oTempFile.Close();
     892             : 
     893             :         // Backup field definitions as we will need to set the TABFieldType.
     894             :         TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
     895          14 :             CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
     896          14 :         memcpy(pasFieldDefTmp, m_pasFieldDef,
     897          14 :                m_numFields * sizeof(TABDATFieldDef));
     898             : 
     899          14 :         m_numFields--;  // So that Close() doesn't see the new field.
     900          14 :         Close();
     901             : 
     902             :         // Move temporary file as main .data file and reopen it.
     903          14 :         VSIUnlink(osOriginalFile);
     904          14 :         VSIRename(osTmpFile, osOriginalFile);
     905          14 :         if (Open(osOriginalFile, TABReadWrite) < 0)
     906             :         {
     907           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
     908             :                      osOriginalFile.c_str());
     909           0 :             CPLFree(pasFieldDefTmp);
     910           0 :             return -1;
     911             :         }
     912             : 
     913             :         // Restore saved TABFieldType.
     914          60 :         for (int i = 0; i < m_numFields; i++)
     915             :         {
     916          46 :             m_pasFieldDef[i].eTABType = pasFieldDefTmp[i].eTABType;
     917             :         }
     918          14 :         CPLFree(pasFieldDefTmp);
     919             :     }
     920             : 
     921         329 :     return 0;
     922             : }
     923             : 
     924             : /************************************************************************/
     925             : /*                            DeleteField()                             */
     926             : /************************************************************************/
     927             : 
     928           2 : int TABDATFile::DeleteField(int iField)
     929             : {
     930           2 :     if (m_fp == nullptr)
     931             :     {
     932           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     933             :                  "Operation not supported on closed table.");
     934           0 :         return -1;
     935             :     }
     936           2 :     if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
     937             :     {
     938           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     939             :                  "Operation not supported on read-only files or "
     940             :                  "on non-native table.");
     941           0 :         return -1;
     942             :     }
     943             : 
     944           2 :     if (iField < 0 || iField >= m_numFields)
     945             :     {
     946           0 :         CPLError(CE_Failure, CPLE_IllegalArg, "Invalid field index: %d",
     947             :                  iField);
     948           0 :         return -1;
     949             :     }
     950             : 
     951             :     // If no records have been written, then just remove from the field
     952             :     // definition array.
     953           2 :     if (m_numRecords <= 0)
     954             :     {
     955           0 :         if (iField < m_numFields - 1)
     956             :         {
     957           0 :             memmove(m_pasFieldDef + iField, m_pasFieldDef + iField + 1,
     958           0 :                     (m_numFields - 1 - iField) * sizeof(TABDATFieldDef));
     959             :         }
     960           0 :         m_numFields--;
     961           0 :         return 0;
     962             :     }
     963             : 
     964           2 :     if (m_numFields == 1)
     965             :     {
     966           0 :         CPLError(CE_Failure, CPLE_IllegalArg,
     967             :                  "Cannot delete the single remaining field.");
     968           0 :         return -1;
     969             :     }
     970             : 
     971             :     // Otherwise we need to do a temporary file.
     972           4 :     TABDATFile oTempFile(GetEncoding());
     973           4 :     CPLString osOriginalFile(m_pszFname);
     974           4 :     CPLString osTmpFile(m_pszFname);
     975           2 :     osTmpFile += ".tmp";
     976           2 :     if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
     977           0 :         return -1;
     978             : 
     979             :     // Create field structure.
     980           2 :     int nRecordSizeBefore = 0;
     981           2 :     int nRecordSizeAfter = 0;
     982          11 :     for (int i = 0; i < m_numFields; i++)
     983             :     {
     984           9 :         if (i != iField)
     985             :         {
     986           7 :             if (i < iField)
     987           3 :                 nRecordSizeBefore += m_pasFieldDef[i].byLength;
     988             :             else /* if( i > iField ) */
     989           4 :                 nRecordSizeAfter += m_pasFieldDef[i].byLength;
     990           7 :             oTempFile.AddField(
     991           7 :                 m_pasFieldDef[i].szName, m_pasFieldDef[i].eTABType,
     992           7 :                 m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
     993             :         }
     994             :     }
     995             : 
     996           2 :     CPLAssert(nRecordSizeBefore + m_pasFieldDef[iField].byLength +
     997             :                   nRecordSizeAfter ==
     998             :               m_nRecordSize - 1);
     999             : 
    1000           2 :     GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
    1001             : 
    1002             :     // Copy records.
    1003           8 :     for (int j = 0; j < m_numRecords; j++)
    1004             :     {
    1005          12 :         if (GetRecordBlock(1 + j) == nullptr ||
    1006           6 :             oTempFile.GetRecordBlock(1 + j) == nullptr)
    1007             :         {
    1008           0 :             CPLFree(pabyRecord);
    1009           0 :             oTempFile.Close();
    1010           0 :             VSIUnlink(osTmpFile);
    1011           0 :             return -1;
    1012             :         }
    1013           6 :         if (m_bCurRecordDeletedFlag)
    1014             :         {
    1015           0 :             oTempFile.MarkAsDeleted();
    1016             :         }
    1017             :         else
    1018             :         {
    1019           6 :             if (m_poRecordBlock->ReadBytes(m_nRecordSize - 1, pabyRecord) !=
    1020           6 :                     0 ||
    1021           3 :                 (nRecordSizeBefore > 0 &&
    1022           3 :                  oTempFile.m_poRecordBlock->WriteBytes(nRecordSizeBefore,
    1023          18 :                                                        pabyRecord) != 0) ||
    1024           3 :                 (nRecordSizeAfter > 0 &&
    1025           3 :                  oTempFile.m_poRecordBlock->WriteBytes(
    1026           3 :                      nRecordSizeAfter, pabyRecord + nRecordSizeBefore +
    1027           3 :                                            m_pasFieldDef[iField].byLength) !=
    1028             :                      0))
    1029             :             {
    1030           0 :                 CPLFree(pabyRecord);
    1031           0 :                 oTempFile.Close();
    1032           0 :                 VSIUnlink(osTmpFile);
    1033           0 :                 return -1;
    1034             :             }
    1035           6 :             oTempFile.CommitRecordToFile();
    1036             :         }
    1037             :     }
    1038             : 
    1039           2 :     CPLFree(pabyRecord);
    1040             : 
    1041             :     // Close temporary file.
    1042           2 :     oTempFile.Close();
    1043             : 
    1044             :     // Backup field definitions as we will need to set the TABFieldType.
    1045             :     TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
    1046           2 :         CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
    1047           2 :     memcpy(pasFieldDefTmp, m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef));
    1048             : 
    1049           2 :     Close();
    1050             : 
    1051             :     // Move temporary file as main .data file and reopen it.
    1052           2 :     VSIUnlink(osOriginalFile);
    1053           2 :     VSIRename(osTmpFile, osOriginalFile);
    1054           2 :     if (Open(osOriginalFile, TABReadWrite) < 0)
    1055             :     {
    1056           0 :         CPLFree(pasFieldDefTmp);
    1057           0 :         return -1;
    1058             :     }
    1059             : 
    1060             :     // Restore saved TABFieldType.
    1061           9 :     for (int i = 0; i < m_numFields; i++)
    1062             :     {
    1063           7 :         if (i < iField)
    1064           3 :             m_pasFieldDef[i].eTABType = pasFieldDefTmp[i].eTABType;
    1065             :         else
    1066           4 :             m_pasFieldDef[i].eTABType = pasFieldDefTmp[i + 1].eTABType;
    1067             :     }
    1068           2 :     CPLFree(pasFieldDefTmp);
    1069             : 
    1070           2 :     return 0;
    1071             : }
    1072             : 
    1073             : /************************************************************************/
    1074             : /*                           ReorderFields()                            */
    1075             : /************************************************************************/
    1076             : 
    1077          10 : int TABDATFile::ReorderFields(int *panMap)
    1078             : {
    1079          10 :     if (m_fp == nullptr)
    1080             :     {
    1081           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1082             :                  "Operation not supported on closed table.");
    1083           0 :         return -1;
    1084             :     }
    1085          10 :     if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
    1086             :     {
    1087           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1088             :                  "Operation not supported on read-only files or "
    1089             :                  "on non-native table.");
    1090           0 :         return -1;
    1091             :     }
    1092             : 
    1093          10 :     if (m_numFields == 0)
    1094           0 :         return 0;
    1095             : 
    1096          10 :     OGRErr eErr = OGRCheckPermutation(panMap, m_numFields);
    1097          10 :     if (eErr != OGRERR_NONE)
    1098           0 :         return -1;
    1099             : 
    1100             :     // If no records have been written, then just reorder the field
    1101             :     // definition array.
    1102          10 :     if (m_numRecords <= 0)
    1103             :     {
    1104             :         TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
    1105           0 :             CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
    1106           0 :         memcpy(pasFieldDefTmp, m_pasFieldDef,
    1107           0 :                m_numFields * sizeof(TABDATFieldDef));
    1108           0 :         for (int i = 0; i < m_numFields; i++)
    1109             :         {
    1110           0 :             memcpy(m_pasFieldDef + i, pasFieldDefTmp + panMap[i],
    1111             :                    sizeof(TABDATFieldDef));
    1112             :         }
    1113           0 :         CPLFree(pasFieldDefTmp);
    1114           0 :         return 0;
    1115             :     }
    1116             : 
    1117             :     // We could theoretically update in place, but a sudden interruption
    1118             :     // would leave the file in a undefined state.
    1119             : 
    1120          20 :     TABDATFile oTempFile(GetEncoding());
    1121          20 :     CPLString osOriginalFile(m_pszFname);
    1122          20 :     CPLString osTmpFile(m_pszFname);
    1123          10 :     osTmpFile += ".tmp";
    1124          10 :     if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
    1125           0 :         return -1;
    1126             : 
    1127             :     // Create field structure.
    1128             :     int *panOldOffset =
    1129          10 :         static_cast<int *>(CPLMalloc(m_numFields * sizeof(int)));
    1130          52 :     for (int i = 0; i < m_numFields; i++)
    1131             :     {
    1132          42 :         int iBefore = panMap[i];
    1133          42 :         if (i == 0)
    1134          10 :             panOldOffset[i] = 0;
    1135             :         else
    1136          32 :             panOldOffset[i] =
    1137          32 :                 panOldOffset[i - 1] + m_pasFieldDef[i - 1].byLength;
    1138          42 :         oTempFile.AddField(
    1139          42 :             m_pasFieldDef[iBefore].szName, m_pasFieldDef[iBefore].eTABType,
    1140          42 :             m_pasFieldDef[iBefore].byLength, m_pasFieldDef[iBefore].byDecimals);
    1141             :     }
    1142             : 
    1143          10 :     GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
    1144             : 
    1145             :     // Copy records.
    1146          48 :     for (int j = 0; j < m_numRecords; j++)
    1147             :     {
    1148          76 :         if (GetRecordBlock(1 + j) == nullptr ||
    1149          38 :             oTempFile.GetRecordBlock(1 + j) == nullptr)
    1150             :         {
    1151           0 :             CPLFree(pabyRecord);
    1152           0 :             CPLFree(panOldOffset);
    1153           0 :             oTempFile.Close();
    1154           0 :             VSIUnlink(osTmpFile);
    1155           0 :             return -1;
    1156             :         }
    1157          38 :         if (m_bCurRecordDeletedFlag)
    1158             :         {
    1159           0 :             oTempFile.MarkAsDeleted();
    1160             :         }
    1161             :         else
    1162             :         {
    1163          38 :             if (m_poRecordBlock->ReadBytes(m_nRecordSize - 1, pabyRecord) != 0)
    1164             :             {
    1165           0 :                 CPLFree(pabyRecord);
    1166           0 :                 CPLFree(panOldOffset);
    1167           0 :                 oTempFile.Close();
    1168           0 :                 VSIUnlink(osTmpFile);
    1169           0 :                 return -1;
    1170             :             }
    1171         196 :             for (int i = 0; i < m_numFields; i++)
    1172             :             {
    1173         158 :                 int iBefore = panMap[i];
    1174         316 :                 if (oTempFile.m_poRecordBlock->WriteBytes(
    1175         158 :                         m_pasFieldDef[iBefore].byLength,
    1176         158 :                         pabyRecord + panOldOffset[iBefore]) != 0)
    1177             :                 {
    1178           0 :                     CPLFree(pabyRecord);
    1179           0 :                     CPLFree(panOldOffset);
    1180           0 :                     oTempFile.Close();
    1181           0 :                     VSIUnlink(osTmpFile);
    1182           0 :                     return -1;
    1183             :                 }
    1184             :             }
    1185             : 
    1186          38 :             oTempFile.CommitRecordToFile();
    1187             :         }
    1188             :     }
    1189             : 
    1190          10 :     CPLFree(pabyRecord);
    1191          10 :     CPLFree(panOldOffset);
    1192             : 
    1193          10 :     oTempFile.Close();
    1194             : 
    1195             :     // Backup field definitions as we will need to set the TABFieldType.
    1196             :     TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
    1197          10 :         CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
    1198          10 :     memcpy(pasFieldDefTmp, m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef));
    1199             : 
    1200             :     // Close ourselves.
    1201          10 :     Close();
    1202             : 
    1203             :     // Move temporary file as main .data file and reopen it.
    1204          10 :     VSIUnlink(osOriginalFile);
    1205          10 :     VSIRename(osTmpFile, osOriginalFile);
    1206          10 :     if (Open(osOriginalFile, TABReadWrite) < 0)
    1207             :     {
    1208           0 :         CPLFree(pasFieldDefTmp);
    1209           0 :         return -1;
    1210             :     }
    1211             : 
    1212             :     // Restore saved TABFieldType.
    1213          52 :     for (int i = 0; i < m_numFields; i++)
    1214             :     {
    1215          42 :         int iBefore = panMap[i];
    1216          42 :         m_pasFieldDef[i].eTABType = pasFieldDefTmp[iBefore].eTABType;
    1217             :     }
    1218          10 :     CPLFree(pasFieldDefTmp);
    1219             : 
    1220          10 :     return 0;
    1221             : }
    1222             : 
    1223             : /************************************************************************/
    1224             : /*                           AlterFieldDefn()                           */
    1225             : /************************************************************************/
    1226             : 
    1227          11 : int TABDATFile::AlterFieldDefn(int iField, const OGRFieldDefn *poSrcFieldDefn,
    1228             :                                OGRFieldDefn *poNewFieldDefn, int nFlags)
    1229             : {
    1230          11 :     if (m_fp == nullptr)
    1231             :     {
    1232           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1233             :                  "Operation not supported on closed table.");
    1234           0 :         return -1;
    1235             :     }
    1236          11 :     if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
    1237             :     {
    1238           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1239             :                  "Operation not supported on read-only files or "
    1240             :                  "on non-native table.");
    1241           0 :         return -1;
    1242             :     }
    1243             : 
    1244          11 :     if (iField < 0 || iField >= m_numFields)
    1245             :     {
    1246           0 :         CPLError(CE_Failure, CPLE_IllegalArg, "Invalid field index: %d",
    1247             :                  iField);
    1248           0 :         return -1;
    1249             :     }
    1250             : 
    1251          11 :     TABFieldType eTABType = m_pasFieldDef[iField].eTABType;
    1252          11 :     int nWidth = poSrcFieldDefn->GetWidth();
    1253          11 :     int nPrecision = poSrcFieldDefn->GetPrecision();
    1254          11 :     if (nFlags & ALTER_TYPE_FLAG)
    1255             :     {
    1256          11 :         if (IMapInfoFile::GetTABType(poNewFieldDefn, &eTABType, nullptr,
    1257          11 :                                      nullptr) < 0)
    1258           0 :             return -1;
    1259             :     }
    1260          11 :     if (nFlags & ALTER_WIDTH_PRECISION_FLAG)
    1261             :     {
    1262             :         // Instead of taking directly poNewFieldDefn->GetWidth()/GetPrecision(),
    1263             :         // use GetTABType() to take into account .dat limitations on
    1264             :         // width & precision to clamp what user might have specify
    1265          10 :         if (IMapInfoFile::GetTABType(poNewFieldDefn, nullptr, &nWidth,
    1266          10 :                                      &nPrecision) < 0)
    1267           0 :             return -1;
    1268             :     }
    1269             : 
    1270          11 :     if ((nFlags & ALTER_TYPE_FLAG) &&
    1271          11 :         eTABType != m_pasFieldDef[iField].eTABType)
    1272             :     {
    1273           6 :         if (eTABType != TABFChar && m_numRecords > 0)
    1274             :         {
    1275           1 :             CPLError(CE_Failure, CPLE_NotSupported,
    1276             :                      "Can only convert to OFTString");
    1277           1 :             return -1;
    1278             :         }
    1279           5 :         if (eTABType == TABFChar && (nFlags & ALTER_WIDTH_PRECISION_FLAG) == 0)
    1280           1 :             nWidth = 254;
    1281             :     }
    1282             : 
    1283          10 :     if (nFlags & ALTER_WIDTH_PRECISION_FLAG)
    1284             :     {
    1285          13 :         if (eTABType != TABFChar && nWidth != poSrcFieldDefn->GetWidth() &&
    1286           4 :             m_numRecords > 0)
    1287             :         {
    1288           1 :             CPLError(
    1289             :                 CE_Failure, CPLE_NotSupported,
    1290             :                 "Resizing only supported on String fields on non-empty layer");
    1291           1 :             return -1;
    1292             :         }
    1293             :     }
    1294             : 
    1295           9 :     if (nFlags & ALTER_NAME_FLAG)
    1296             :     {
    1297           9 :         strncpy(m_pasFieldDef[iField].szName, poNewFieldDefn->GetNameRef(),
    1298             :                 sizeof(m_pasFieldDef[iField].szName) - 1);
    1299           9 :         m_pasFieldDef[iField].szName[sizeof(m_pasFieldDef[iField].szName) - 1] =
    1300             :             '\0';
    1301             :         // If renaming is the only operation, then nothing more to do.
    1302           9 :         if (nFlags == ALTER_NAME_FLAG)
    1303             :         {
    1304           0 :             m_bUpdated = TRUE;
    1305           0 :             return 0;
    1306             :         }
    1307             :     }
    1308             : 
    1309           9 :     if (m_numRecords <= 0)
    1310             :     {
    1311           3 :         if ((nFlags & ALTER_TYPE_FLAG) &&
    1312           3 :             eTABType != m_pasFieldDef[iField].eTABType)
    1313             :         {
    1314             :             TABDATFieldDef sFieldDef;
    1315           2 :             TABDATFileSetFieldDefinition(&sFieldDef,
    1316           2 :                                          m_pasFieldDef[iField].szName, eTABType,
    1317           2 :                                          m_pasFieldDef[iField].byLength,
    1318           2 :                                          m_pasFieldDef[iField].byDecimals);
    1319           2 :             memcpy(&m_pasFieldDef[iField], &sFieldDef, sizeof(sFieldDef));
    1320             :         }
    1321           3 :         if (nFlags & ALTER_WIDTH_PRECISION_FLAG)
    1322             :         {
    1323           3 :             if (eTABType == TABFChar || eTABType == TABFDecimal)
    1324           2 :                 m_pasFieldDef[iField].byLength = static_cast<GByte>(nWidth);
    1325           3 :             if (eTABType == TABFDecimal)
    1326           2 :                 m_pasFieldDef[iField].byDecimals =
    1327             :                     static_cast<GByte>(nPrecision);
    1328             :         }
    1329           3 :         return 0;
    1330             :     }
    1331             : 
    1332             :     const bool bWidthPrecisionPreserved =
    1333           7 :         (nWidth == poSrcFieldDefn->GetWidth() &&
    1334           1 :          nPrecision == poSrcFieldDefn->GetPrecision());
    1335           6 :     if (eTABType == m_pasFieldDef[iField].eTABType && bWidthPrecisionPreserved)
    1336             :     {
    1337           1 :         return 0;
    1338             :     }
    1339             : 
    1340           5 :     if (eTABType != TABFChar)
    1341             :     {
    1342             :         // should hopefully not happen given all above checks
    1343           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1344             :                  "Unsupported AlterFieldDefn() operation");
    1345           0 :         return -1;
    1346             :     }
    1347             : 
    1348             :     // Otherwise we need to do a temporary file.
    1349          10 :     TABDATFile oTempFile(GetEncoding());
    1350          10 :     CPLString osOriginalFile(m_pszFname);
    1351          10 :     CPLString osTmpFile(m_pszFname);
    1352           5 :     osTmpFile += ".tmp";
    1353           5 :     if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
    1354           0 :         return -1;
    1355             : 
    1356             :     // Create field structure.
    1357           5 :     int nRecordSizeBefore = 0;
    1358           5 :     int nRecordSizeAfter = 0;
    1359             :     TABDATFieldDef sFieldDef;
    1360           5 :     sFieldDef.eTABType = TABFUnknown;
    1361           5 :     sFieldDef.byLength = 0;
    1362           5 :     sFieldDef.byDecimals = 0;
    1363           5 :     TABDATFileSetFieldDefinition(&sFieldDef, m_pasFieldDef[iField].szName,
    1364             :                                  eTABType, nWidth, nPrecision);
    1365             : 
    1366          24 :     for (int i = 0; i < m_numFields; i++)
    1367             :     {
    1368          19 :         if (i != iField)
    1369             :         {
    1370          14 :             if (i < iField)
    1371           4 :                 nRecordSizeBefore += m_pasFieldDef[i].byLength;
    1372             :             else /*if( i > iField )*/
    1373          10 :                 nRecordSizeAfter += m_pasFieldDef[i].byLength;
    1374          14 :             oTempFile.AddField(
    1375          14 :                 m_pasFieldDef[i].szName, m_pasFieldDef[i].eTABType,
    1376          14 :                 m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
    1377             :         }
    1378             :         else
    1379             :         {
    1380           5 :             oTempFile.AddField(sFieldDef.szName, sFieldDef.eTABType,
    1381           5 :                                sFieldDef.byLength, sFieldDef.byDecimals);
    1382             :         }
    1383             :     }
    1384             : 
    1385           5 :     GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
    1386           5 :     char *pabyNewField = static_cast<char *>(CPLMalloc(sFieldDef.byLength + 1));
    1387             : 
    1388             :     // Copy records.
    1389          18 :     for (int j = 0; j < m_numRecords; j++)
    1390             :     {
    1391          26 :         if (GetRecordBlock(1 + j) == nullptr ||
    1392          13 :             oTempFile.GetRecordBlock(1 + j) == nullptr)
    1393             :         {
    1394           0 :             CPLFree(pabyRecord);
    1395           0 :             CPLFree(pabyNewField);
    1396           0 :             oTempFile.Close();
    1397           0 :             VSIUnlink(osTmpFile);
    1398           0 :             return -1;
    1399             :         }
    1400          13 :         if (m_bCurRecordDeletedFlag)
    1401             :         {
    1402           0 :             oTempFile.MarkAsDeleted();
    1403             :         }
    1404             :         else
    1405             :         {
    1406          19 :             if (nRecordSizeBefore > 0 &&
    1407           6 :                 (m_poRecordBlock->ReadBytes(nRecordSizeBefore, pabyRecord) !=
    1408           6 :                      0 ||
    1409           6 :                  oTempFile.m_poRecordBlock->WriteBytes(nRecordSizeBefore,
    1410           6 :                                                        pabyRecord) != 0))
    1411             :             {
    1412           0 :                 CPLFree(pabyRecord);
    1413           0 :                 CPLFree(pabyNewField);
    1414           0 :                 oTempFile.Close();
    1415           0 :                 VSIUnlink(osTmpFile);
    1416           0 :                 return -1;
    1417             :             }
    1418             : 
    1419          13 :             memset(pabyNewField, 0, sFieldDef.byLength + 1);
    1420          13 :             if (m_pasFieldDef[iField].eTABType == TABFChar)
    1421             :             {
    1422           6 :                 strncpy(pabyNewField,
    1423           6 :                         ReadCharField(m_pasFieldDef[iField].byLength),
    1424           6 :                         sFieldDef.byLength);
    1425             :             }
    1426           7 :             else if (m_pasFieldDef[iField].eTABType == TABFInteger)
    1427             :             {
    1428           7 :                 snprintf(pabyNewField, sFieldDef.byLength, "%d",
    1429           7 :                          ReadIntegerField(m_pasFieldDef[iField].byLength));
    1430             :             }
    1431           0 :             else if (m_pasFieldDef[iField].eTABType == TABFSmallInt)
    1432             :             {
    1433           0 :                 snprintf(pabyNewField, sFieldDef.byLength, "%d",
    1434           0 :                          ReadSmallIntField(m_pasFieldDef[iField].byLength));
    1435             :             }
    1436           0 :             else if (m_pasFieldDef[iField].eTABType == TABFLargeInt)
    1437             :             {
    1438           0 :                 snprintf(pabyNewField, sFieldDef.byLength, CPL_FRMT_GIB,
    1439           0 :                          ReadLargeIntField(m_pasFieldDef[iField].byLength));
    1440             :             }
    1441           0 :             else if (m_pasFieldDef[iField].eTABType == TABFFloat)
    1442             :             {
    1443           0 :                 CPLsnprintf(pabyNewField, sFieldDef.byLength, "%.18f",
    1444           0 :                             ReadFloatField(m_pasFieldDef[iField].byLength));
    1445             :             }
    1446           0 :             else if (m_pasFieldDef[iField].eTABType == TABFDecimal)
    1447             :             {
    1448           0 :                 CPLsnprintf(pabyNewField, sFieldDef.byLength, "%.18f",
    1449           0 :                             ReadFloatField(m_pasFieldDef[iField].byLength));
    1450             :             }
    1451           0 :             else if (m_pasFieldDef[iField].eTABType == TABFLogical)
    1452             :             {
    1453           0 :                 strncpy(pabyNewField,
    1454           0 :                         ReadLogicalField(m_pasFieldDef[iField].byLength) ? "T"
    1455             :                                                                          : "F",
    1456           0 :                         sFieldDef.byLength);
    1457             :             }
    1458           0 :             else if (m_pasFieldDef[iField].eTABType == TABFDate)
    1459             :             {
    1460           0 :                 strncpy(pabyNewField,
    1461           0 :                         ReadDateField(m_pasFieldDef[iField].byLength),
    1462           0 :                         sFieldDef.byLength);
    1463             :             }
    1464           0 :             else if (m_pasFieldDef[iField].eTABType == TABFTime)
    1465             :             {
    1466           0 :                 strncpy(pabyNewField,
    1467           0 :                         ReadTimeField(m_pasFieldDef[iField].byLength),
    1468           0 :                         sFieldDef.byLength);
    1469             :             }
    1470           0 :             else if (m_pasFieldDef[iField].eTABType == TABFDateTime)
    1471             :             {
    1472           0 :                 strncpy(pabyNewField,
    1473           0 :                         ReadDateTimeField(m_pasFieldDef[iField].byLength),
    1474           0 :                         sFieldDef.byLength);
    1475             :             }
    1476             : 
    1477          39 :             if (oTempFile.m_poRecordBlock->WriteBytes(
    1478          13 :                     sFieldDef.byLength,
    1479          25 :                     reinterpret_cast<GByte *>(pabyNewField)) != 0 ||
    1480          12 :                 (nRecordSizeAfter > 0 &&
    1481          12 :                  (m_poRecordBlock->ReadBytes(nRecordSizeAfter, pabyRecord) !=
    1482          12 :                       0 ||
    1483          12 :                   oTempFile.m_poRecordBlock->WriteBytes(nRecordSizeAfter,
    1484          12 :                                                         pabyRecord) != 0)))
    1485             :             {
    1486           0 :                 CPLFree(pabyRecord);
    1487           0 :                 CPLFree(pabyNewField);
    1488           0 :                 oTempFile.Close();
    1489           0 :                 VSIUnlink(osTmpFile);
    1490           0 :                 return -1;
    1491             :             }
    1492          13 :             oTempFile.CommitRecordToFile();
    1493             :         }
    1494             :     }
    1495             : 
    1496           5 :     CPLFree(pabyRecord);
    1497           5 :     CPLFree(pabyNewField);
    1498             : 
    1499           5 :     oTempFile.Close();
    1500             : 
    1501             :     // Backup field definitions as we will need to set the TABFieldType.
    1502             :     TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
    1503           5 :         CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
    1504           5 :     memcpy(pasFieldDefTmp, m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef));
    1505             : 
    1506           5 :     Close();
    1507             : 
    1508             :     // Move temporary file as main .data file and reopen it.
    1509           5 :     VSIUnlink(osOriginalFile);
    1510           5 :     VSIRename(osTmpFile, osOriginalFile);
    1511           5 :     if (Open(osOriginalFile, TABReadWrite) < 0)
    1512             :     {
    1513           0 :         CPLFree(pasFieldDefTmp);
    1514           0 :         return -1;
    1515             :     }
    1516             : 
    1517             :     // Restore saved TABFieldType.
    1518          24 :     for (int i = 0; i < m_numFields; i++)
    1519             :     {
    1520          19 :         if (i != iField)
    1521          14 :             m_pasFieldDef[i].eTABType = pasFieldDefTmp[i].eTABType;
    1522             :         else
    1523           5 :             m_pasFieldDef[i].eTABType = eTABType;
    1524             :     }
    1525           5 :     CPLFree(pasFieldDefTmp);
    1526             : 
    1527           5 :     return 0;
    1528             : }
    1529             : 
    1530             : /**********************************************************************
    1531             :  *                   TABDATFile::GetFieldType()
    1532             :  *
    1533             :  * Returns the native field type for field # nFieldId as previously set
    1534             :  * by ValidateFieldInfoFromTAB().
    1535             :  *
    1536             :  * Note that field ids are positive and start at 0.
    1537             :  **********************************************************************/
    1538      569097 : TABFieldType TABDATFile::GetFieldType(int nFieldId)
    1539             : {
    1540      569097 :     if (m_pasFieldDef == nullptr || nFieldId < 0 || nFieldId >= m_numFields)
    1541           0 :         return TABFUnknown;
    1542             : 
    1543      569097 :     return m_pasFieldDef[nFieldId].eTABType;
    1544             : }
    1545             : 
    1546             : /**********************************************************************
    1547             :  *                   TABDATFile::GetFieldWidth()
    1548             :  *
    1549             :  * Returns the width for field # nFieldId as previously read from the
    1550             :  * .DAT header.
    1551             :  *
    1552             :  * Note that field ids are positive and start at 0.
    1553             :  **********************************************************************/
    1554      553413 : int TABDATFile::GetFieldWidth(int nFieldId)
    1555             : {
    1556      553413 :     if (m_pasFieldDef == nullptr || nFieldId < 0 || nFieldId >= m_numFields)
    1557           0 :         return 0;
    1558             : 
    1559      553413 :     return m_pasFieldDef[nFieldId].byLength;
    1560             : }
    1561             : 
    1562             : /**********************************************************************
    1563             :  *                   TABDATFile::GetFieldPrecision()
    1564             :  *
    1565             :  * Returns the precision for field # nFieldId as previously read from the
    1566             :  * .DAT header.
    1567             :  *
    1568             :  * Note that field ids are positive and start at 0.
    1569             :  **********************************************************************/
    1570           5 : int TABDATFile::GetFieldPrecision(int nFieldId)
    1571             : {
    1572           5 :     if (m_pasFieldDef == nullptr || nFieldId < 0 || nFieldId >= m_numFields)
    1573           0 :         return 0;
    1574             : 
    1575           5 :     return m_pasFieldDef[nFieldId].byDecimals;
    1576             : }
    1577             : 
    1578             : /**********************************************************************
    1579             :  *                   TABDATFile::ReadCharField()
    1580             :  *
    1581             :  * Read the character field value at the current position in the data
    1582             :  * block.
    1583             :  *
    1584             :  * Use GetRecordBlock() to position the data block to the beginning of
    1585             :  * a record before attempting to read values.
    1586             :  *
    1587             :  * nWidth is the field length, as defined in the .DAT header.
    1588             :  *
    1589             :  * Returns a reference to an internal buffer that will be valid only until
    1590             :  * the next field is read, or "" if the operation failed, in which case
    1591             :  * CPLError() will have been called.
    1592             :  **********************************************************************/
    1593        1355 : const char *TABDATFile::ReadCharField(int nWidth)
    1594             : {
    1595             :     // If current record has been deleted, then return an acceptable
    1596             :     // default value.
    1597        1355 :     if (m_bCurRecordDeletedFlag)
    1598           0 :         return "";
    1599             : 
    1600        1355 :     if (m_poRecordBlock == nullptr)
    1601             :     {
    1602           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1603             :                  "Can't read field value: file is not opened.");
    1604           0 :         return "";
    1605             :     }
    1606             : 
    1607        1355 :     if (nWidth < 1 || nWidth > 255)
    1608             :     {
    1609           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1610             :                  "Illegal width for a char field: %d", nWidth);
    1611           0 :         return "";
    1612             :     }
    1613             : 
    1614        2710 :     if (m_poRecordBlock->ReadBytes(nWidth,
    1615        1355 :                                    reinterpret_cast<GByte *>(m_szBuffer)) != 0)
    1616           0 :         return "";
    1617             : 
    1618        1355 :     m_szBuffer[nWidth] = '\0';
    1619             : 
    1620             :     // NATIVE tables are padded with '\0' chars, but DBF tables are padded
    1621             :     // with spaces... get rid of the trailing spaces.
    1622        1355 :     if (m_eTableType == TABTableDBF)
    1623             :     {
    1624           2 :         int nLen = static_cast<int>(strlen(m_szBuffer)) - 1;
    1625          79 :         while (nLen >= 0 && m_szBuffer[nLen] == ' ')
    1626          77 :             m_szBuffer[nLen--] = '\0';
    1627             :     }
    1628             : 
    1629        1355 :     return m_szBuffer;
    1630             : }
    1631             : 
    1632             : /**********************************************************************
    1633             :  *                   TABDATFile::ReadIntegerField()
    1634             :  *
    1635             :  * Read the integer field value at the current position in the data
    1636             :  * block.
    1637             :  *
    1638             :  * Note: nWidth is used only with TABTableDBF types.
    1639             :  *
    1640             :  * CPLError() will have been called if something fails.
    1641             :  **********************************************************************/
    1642      551001 : GInt32 TABDATFile::ReadIntegerField(int nWidth)
    1643             : {
    1644             :     // If current record has been deleted, then return an acceptable
    1645             :     // default value.
    1646      551001 :     if (m_bCurRecordDeletedFlag)
    1647           0 :         return 0;
    1648             : 
    1649      551001 :     if (m_poRecordBlock == nullptr)
    1650             :     {
    1651           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1652             :                  "Can't read field value: file is not opened.");
    1653           0 :         return 0;
    1654             :     }
    1655             : 
    1656      551001 :     if (m_eTableType == TABTableDBF)
    1657           1 :         return atoi(ReadCharField(nWidth));
    1658             : 
    1659      551000 :     return m_poRecordBlock->ReadInt32();
    1660             : }
    1661             : 
    1662             : /**********************************************************************
    1663             :  *                   TABDATFile::ReadSmallIntField()
    1664             :  *
    1665             :  * Read the smallint field value at the current position in the data
    1666             :  * block.
    1667             :  *
    1668             :  * Note: nWidth is used only with TABTableDBF types.
    1669             :  *
    1670             :  * CPLError() will have been called if something fails.
    1671             :  **********************************************************************/
    1672           4 : GInt16 TABDATFile::ReadSmallIntField(int nWidth)
    1673             : {
    1674             :     // If current record has been deleted, then return an acceptable
    1675             :     // default value.
    1676           4 :     if (m_bCurRecordDeletedFlag)
    1677           0 :         return 0;
    1678             : 
    1679           4 :     if (m_poRecordBlock == nullptr)
    1680             :     {
    1681           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1682             :                  "Can't read field value: file is not opened.");
    1683           0 :         return 0;
    1684             :     }
    1685             : 
    1686           4 :     if (m_eTableType == TABTableDBF)
    1687           0 :         return static_cast<GInt16>(atoi(ReadCharField(nWidth)));
    1688             : 
    1689           4 :     return m_poRecordBlock->ReadInt16();
    1690             : }
    1691             : 
    1692             : /**********************************************************************
    1693             :  *                   TABDATFile::ReadLargeIntField()
    1694             :  *
    1695             :  * Read the largeint field value at the current position in the data
    1696             :  * block.
    1697             :  *
    1698             :  * Note: nWidth is used only with TABTableDBF types.
    1699             :  *
    1700             :  * CPLError() will have been called if something fails.
    1701             :  **********************************************************************/
    1702           6 : GInt64 TABDATFile::ReadLargeIntField(int nWidth)
    1703             : {
    1704             :     // If current record has been deleted, then return an acceptable
    1705             :     // default value.
    1706           6 :     if (m_bCurRecordDeletedFlag)
    1707           0 :         return 0;
    1708             : 
    1709           6 :     if (m_poRecordBlock == nullptr)
    1710             :     {
    1711           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1712             :                  "Can't read field value: file is not opened.");
    1713           0 :         return 0;
    1714             :     }
    1715             : 
    1716           6 :     if (m_eTableType == TABTableDBF)
    1717           0 :         return static_cast<GIntBig>(CPLAtoGIntBig(ReadCharField(nWidth)));
    1718             : 
    1719           6 :     return m_poRecordBlock->ReadInt64();
    1720             : }
    1721             : 
    1722             : /**********************************************************************
    1723             :  *                   TABDATFile::ReadFloatField()
    1724             :  *
    1725             :  * Read the float field value at the current position in the data
    1726             :  * block.
    1727             :  *
    1728             :  * Note: nWidth is used only with TABTableDBF types.
    1729             :  *
    1730             :  * CPLError() will have been called if something fails.
    1731             :  **********************************************************************/
    1732         609 : double TABDATFile::ReadFloatField(int nWidth)
    1733             : {
    1734             :     // If current record has been deleted, then return an acceptable
    1735             :     // default value.
    1736         609 :     if (m_bCurRecordDeletedFlag)
    1737           0 :         return 0.0;
    1738             : 
    1739         609 :     if (m_poRecordBlock == nullptr)
    1740             :     {
    1741           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1742             :                  "Can't read field value: file is not opened.");
    1743           0 :         return 0.0;
    1744             :     }
    1745             : 
    1746         609 :     if (m_eTableType == TABTableDBF)
    1747           0 :         return CPLAtof(ReadCharField(nWidth));
    1748             : 
    1749         609 :     return m_poRecordBlock->ReadDouble();
    1750             : }
    1751             : 
    1752             : /**********************************************************************
    1753             :  *                   TABDATFile::ReadLogicalField()
    1754             :  *
    1755             :  * Read the logical field value at the current position in the data
    1756             :  * block.
    1757             :  *
    1758             :  * Note: nWidth is used only with TABTableDBF types.
    1759             :  *
    1760             :  * CPLError() will have been called if something fails.
    1761             :  **********************************************************************/
    1762           6 : bool TABDATFile::ReadLogicalField(int nWidth)
    1763             : {
    1764             :     // If current record has been deleted, then return an acceptable
    1765             :     // default value.
    1766           6 :     if (m_bCurRecordDeletedFlag)
    1767           0 :         return false;
    1768             : 
    1769           6 :     if (m_poRecordBlock == nullptr)
    1770             :     {
    1771           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1772             :                  "Can't read field value: file is not opened.");
    1773           0 :         return false;
    1774             :     }
    1775             : 
    1776           6 :     bool bValue = false;
    1777           6 :     if (m_eTableType == TABTableDBF)
    1778             :     {
    1779           0 :         const char *pszVal = ReadCharField(nWidth);
    1780           0 :         bValue = pszVal && strchr("1YyTt", pszVal[0]) != nullptr;
    1781             :     }
    1782             :     else
    1783             :     {
    1784             :         // In Native tables, we are guaranteed it is 1 byte with 0/1 value
    1785           6 :         bValue = CPL_TO_BOOL(m_poRecordBlock->ReadByte());
    1786             :     }
    1787             : 
    1788           6 :     return bValue;
    1789             : }
    1790             : 
    1791             : /**********************************************************************
    1792             :  *                   TABDATFile::ReadDateField()
    1793             :  *
    1794             :  * Read the logical field value at the current position in the data
    1795             :  * block.
    1796             :  *
    1797             :  * A date field is a 4 bytes binary value in which the first byte is
    1798             :  * the day, followed by 1 byte for the month, and 2 bytes for the year.
    1799             :  *
    1800             :  * We return an 8 chars string in the format "YYYYMMDD"
    1801             :  *
    1802             :  * Note: nWidth is used only with TABTableDBF types.
    1803             :  *
    1804             :  * Returns a reference to an internal buffer that will be valid only until
    1805             :  * the next field is read, or "" if the operation failed, in which case
    1806             :  * CPLError() will have been called.
    1807             :  **********************************************************************/
    1808           0 : const char *TABDATFile::ReadDateField(int nWidth)
    1809             : {
    1810           0 :     int nDay = 0;
    1811           0 :     int nMonth = 0;
    1812           0 :     int nYear = 0;
    1813           0 :     int status = ReadDateField(nWidth, &nYear, &nMonth, &nDay);
    1814             : 
    1815           0 :     if (status == -1)
    1816           0 :         return "";
    1817             : 
    1818           0 :     snprintf(m_szBuffer, sizeof(m_szBuffer), "%4.4d%2.2d%2.2d", nYear, nMonth,
    1819             :              nDay);
    1820             : 
    1821           0 :     return m_szBuffer;
    1822             : }
    1823             : 
    1824          29 : int TABDATFile::ReadDateField(int nWidth, int *nYear, int *nMonth, int *nDay)
    1825             : {
    1826             :     // If current record has been deleted, then return an acceptable
    1827             :     // default value.
    1828          29 :     if (m_bCurRecordDeletedFlag)
    1829           0 :         return -1;
    1830             : 
    1831          29 :     if (m_poRecordBlock == nullptr)
    1832             :     {
    1833           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1834             :                  "Can't read field value: file is not opened.");
    1835           0 :         return -1;
    1836             :     }
    1837             : 
    1838             :     // With .DBF files, the value should already be
    1839             :     // stored in YYYYMMDD format according to DBF specs.
    1840          29 :     if (m_eTableType == TABTableDBF)
    1841             :     {
    1842           0 :         strcpy(m_szBuffer, ReadCharField(nWidth));
    1843           0 :         sscanf(m_szBuffer, "%4d%2d%2d", nYear, nMonth, nDay);
    1844             :     }
    1845             :     else
    1846             :     {
    1847          29 :         *nYear = m_poRecordBlock->ReadInt16();
    1848          29 :         *nMonth = m_poRecordBlock->ReadByte();
    1849          29 :         *nDay = m_poRecordBlock->ReadByte();
    1850             :     }
    1851             : 
    1852          58 :     if (CPLGetLastErrorType() == CE_Failure ||
    1853          29 :         (*nYear == 0 && *nMonth == 0 && *nDay == 0))
    1854          17 :         return -1;
    1855             : 
    1856          12 :     return 0;
    1857             : }
    1858             : 
    1859             : /**********************************************************************
    1860             :  *                   TABDATFile::ReadTimeField()
    1861             :  *
    1862             :  * Read the Time field value at the current position in the data
    1863             :  * block.
    1864             :  *
    1865             :  * A time field is a 4 bytes binary value which represents the number
    1866             :  * of milliseconds since midnight.
    1867             :  *
    1868             :  * We return a 9 char string in the format "HHMMSSMMM"
    1869             :  *
    1870             :  * Note: nWidth is used only with TABTableDBF types.
    1871             :  *
    1872             :  * Returns a reference to an internal buffer that will be valid only until
    1873             :  * the next field is read, or "" if the operation failed, in which case
    1874             :  * CPLError() will have been called.
    1875             :  **********************************************************************/
    1876           0 : const char *TABDATFile::ReadTimeField(int nWidth)
    1877             : {
    1878           0 :     int nHour = 0;
    1879           0 :     int nMinute = 0;
    1880           0 :     int nSecond = 0;
    1881           0 :     int nMS = 0;
    1882           0 :     int status = ReadTimeField(nWidth, &nHour, &nMinute, &nSecond, &nMS);
    1883             : 
    1884           0 :     if (status == -1)
    1885           0 :         return "";
    1886             : 
    1887           0 :     snprintf(m_szBuffer, sizeof(m_szBuffer), "%2.2d%2.2d%2.2d%3.3d", nHour,
    1888             :              nMinute, nSecond, nMS);
    1889             : 
    1890           0 :     return m_szBuffer;
    1891             : }
    1892             : 
    1893           7 : int TABDATFile::ReadTimeField(int nWidth, int *nHour, int *nMinute,
    1894             :                               int *nSecond, int *nMS)
    1895             : {
    1896           7 :     GInt32 nS = 0;
    1897             :     // If current record has been deleted, then return an acceptable
    1898             :     // default value.
    1899           7 :     if (m_bCurRecordDeletedFlag)
    1900           0 :         return -1;
    1901             : 
    1902           7 :     if (m_poRecordBlock == nullptr)
    1903             :     {
    1904           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1905             :                  "Can't read field value: file is not opened.");
    1906           0 :         return -1;
    1907             :     }
    1908             : 
    1909             :     // With .DBF files, the value should already be stored in
    1910             :     // HHMMSSMMM format according to DBF specs.
    1911           7 :     if (m_eTableType == TABTableDBF)
    1912             :     {
    1913           0 :         strcpy(m_szBuffer, ReadCharField(nWidth));
    1914           0 :         sscanf(m_szBuffer, "%2d%2d%2d%3d", nHour, nMinute, nSecond, nMS);
    1915             :     }
    1916             :     else
    1917             :     {
    1918           7 :         nS = m_poRecordBlock->ReadInt32();  // Convert time from ms to sec
    1919             :     }
    1920             : 
    1921             :     // nS is set to -1 when the value is 'not set'
    1922           7 :     if (CPLGetLastErrorType() == CE_Failure || nS < 0 || (nS > 86400000))
    1923           1 :         return -1;
    1924             : 
    1925           6 :     *nHour = int(nS / 3600000);
    1926           6 :     *nMinute = int((nS / 1000 - *nHour * 3600) / 60);
    1927           6 :     *nSecond = int(nS / 1000 - *nHour * 3600 - *nMinute * 60);
    1928           6 :     *nMS = int(nS - *nHour * 3600000 - *nMinute * 60000 - *nSecond * 1000);
    1929             : 
    1930           6 :     return 0;
    1931             : }
    1932             : 
    1933             : /**********************************************************************
    1934             :  *                   TABDATFile::ReadDateTimeField()
    1935             :  *
    1936             :  * Read the DateTime field value at the current position in the data
    1937             :  * block.
    1938             :  *
    1939             :  * A datetime field is an 8 bytes binary value in which the first byte is
    1940             :  * the day, followed by 1 byte for the month, and 2 bytes for the year. After
    1941             :  * this is 4 bytes which represents the number of milliseconds since midnight.
    1942             :  *
    1943             :  * We return an 17 chars string in the format "YYYYMMDDhhmmssmmm"
    1944             :  *
    1945             :  * Note: nWidth is used only with TABTableDBF types.
    1946             :  *
    1947             :  * Returns a reference to an internal buffer that will be valid only until
    1948             :  * the next field is read, or "" if the operation failed, in which case
    1949             :  * CPLError() will have been called.
    1950             :  **********************************************************************/
    1951           0 : const char *TABDATFile::ReadDateTimeField(int nWidth)
    1952             : {
    1953           0 :     int nDay = 0;
    1954           0 :     int nMonth = 0;
    1955           0 :     int nYear = 0;
    1956           0 :     int nHour = 0;
    1957           0 :     int nMinute = 0;
    1958           0 :     int nSecond = 0;
    1959           0 :     int nMS = 0;
    1960           0 :     int status = ReadDateTimeField(nWidth, &nYear, &nMonth, &nDay, &nHour,
    1961             :                                    &nMinute, &nSecond, &nMS);
    1962             : 
    1963           0 :     if (status == -1)
    1964           0 :         return "";
    1965             : 
    1966           0 :     snprintf(m_szBuffer, sizeof(m_szBuffer),
    1967             :              "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d%3.3d", nYear, nMonth, nDay, nHour,
    1968             :              nMinute, nSecond, nMS);
    1969             : 
    1970           0 :     return m_szBuffer;
    1971             : }
    1972             : 
    1973          23 : int TABDATFile::ReadDateTimeField(int nWidth, int *nYear, int *nMonth,
    1974             :                                   int *nDay, int *nHour, int *nMinute,
    1975             :                                   int *nSecond, int *nMS)
    1976             : {
    1977          23 :     GInt32 nS = 0;
    1978             :     // If current record has been deleted, then return an acceptable
    1979             :     // default value.
    1980          23 :     if (m_bCurRecordDeletedFlag)
    1981           0 :         return -1;
    1982             : 
    1983          23 :     if (m_poRecordBlock == nullptr)
    1984             :     {
    1985           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    1986             :                  "Can't read field value: file is not opened.");
    1987           0 :         return -1;
    1988             :     }
    1989             : 
    1990             :     // With .DBF files, the value should already be stored in
    1991             :     // YYYYMMDD format according to DBF specs.
    1992          23 :     if (m_eTableType == TABTableDBF)
    1993             :     {
    1994           0 :         strcpy(m_szBuffer, ReadCharField(nWidth));
    1995           0 :         sscanf(m_szBuffer, "%4d%2d%2d%2d%2d%2d%3d", nYear, nMonth, nDay, nHour,
    1996             :                nMinute, nSecond, nMS);
    1997             :     }
    1998             :     else
    1999             :     {
    2000          23 :         *nYear = m_poRecordBlock->ReadInt16();
    2001          23 :         *nMonth = m_poRecordBlock->ReadByte();
    2002          23 :         *nDay = m_poRecordBlock->ReadByte();
    2003          23 :         nS = m_poRecordBlock->ReadInt32();
    2004             :     }
    2005             : 
    2006          23 :     if (CPLGetLastErrorType() == CE_Failure ||
    2007          23 :         (*nYear == 0 && *nMonth == 0 && *nDay == 0) || (nS > 86400000))
    2008          17 :         return -1;
    2009             : 
    2010           6 :     *nHour = int(nS / 3600000);
    2011           6 :     *nMinute = int((nS / 1000 - *nHour * 3600) / 60);
    2012           6 :     *nSecond = int(nS / 1000 - *nHour * 3600 - *nMinute * 60);
    2013           6 :     *nMS = int(nS - *nHour * 3600000 - *nMinute * 60000 - *nSecond * 1000);
    2014             : 
    2015           6 :     return 0;
    2016             : }
    2017             : 
    2018             : /**********************************************************************
    2019             :  *                   TABDATFile::ReadDecimalField()
    2020             :  *
    2021             :  * Read the decimal field value at the current position in the data
    2022             :  * block.
    2023             :  *
    2024             :  * A decimal field is a floating point value with a fixed number of digits
    2025             :  * stored as a character string.
    2026             :  *
    2027             :  * nWidth is the field length, as defined in the .DAT header.
    2028             :  *
    2029             :  * We return the value as a binary double.
    2030             :  *
    2031             :  * CPLError() will have been called if something fails.
    2032             :  **********************************************************************/
    2033          28 : double TABDATFile::ReadDecimalField(int nWidth)
    2034             : {
    2035             :     // If current record has been deleted, then return an acceptable
    2036             :     // default value.
    2037          28 :     if (m_bCurRecordDeletedFlag)
    2038           0 :         return 0.0;
    2039             : 
    2040          28 :     const char *pszVal = ReadCharField(nWidth);
    2041             : 
    2042          28 :     return CPLAtof(pszVal);
    2043             : }
    2044             : 
    2045             : /**********************************************************************
    2046             :  *                   TABDATFile::WriteCharField()
    2047             :  *
    2048             :  * Write the character field value at the current position in the data
    2049             :  * block.
    2050             :  *
    2051             :  * Use GetRecordBlock() to position the data block to the beginning of
    2052             :  * a record before attempting to write values.
    2053             :  *
    2054             :  * nWidth is the field length, as defined in the .DAT header.
    2055             :  *
    2056             :  * Returns 0 on success, or -1 if the operation failed, in which case
    2057             :  * CPLError() will have been called.
    2058             :  **********************************************************************/
    2059         377 : int TABDATFile::WriteCharField(const char *pszStr, int nWidth,
    2060             :                                TABINDFile *poINDFile, int nIndexNo)
    2061             : {
    2062         377 :     if (m_poRecordBlock == nullptr)
    2063             :     {
    2064           0 :         CPLError(
    2065             :             CE_Failure, CPLE_AssertionFailed,
    2066             :             "Can't write field value: GetRecordBlock() has not been called.");
    2067           0 :         return -1;
    2068             :     }
    2069             : 
    2070         377 :     if (nWidth < 1 || nWidth > 255)
    2071             :     {
    2072           0 :         CPLError(CE_Failure, CPLE_AssertionFailed,
    2073             :                  "Illegal width for a char field: %d", nWidth);
    2074           0 :         return -1;
    2075             :     }
    2076             : 
    2077             :     //
    2078             :     // Write the buffer after making sure that we don't try to read
    2079             :     // past the end of the source buffer.  The rest of the field will
    2080             :     // be padded with zeros if source string is shorter than specified
    2081             :     // field width.
    2082             :     //
    2083         377 :     const int nLen = std::min(static_cast<int>(strlen(pszStr)), nWidth);
    2084             : 
    2085         277 :     if ((nLen > 0 && m_poRecordBlock->WriteBytes(
    2086         754 :                          nLen, reinterpret_cast<const GByte *>(pszStr)) != 0) ||
    2087         377 :         (nWidth - nLen > 0 && m_poRecordBlock->WriteZeros(nWidth - nLen) != 0))
    2088           0 :         return -1;
    2089             : 
    2090             :     // Update Index
    2091         377 :     if (poINDFile && nIndexNo > 0)
    2092             :     {
    2093           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, pszStr);
    2094           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2095           0 :             return -1;
    2096             :     }
    2097             : 
    2098         377 :     return 0;
    2099             : }
    2100             : 
    2101             : /**********************************************************************
    2102             :  *                   TABDATFile::WriteIntegerField()
    2103             :  *
    2104             :  * Write the integer field value at the current position in the data
    2105             :  * block.
    2106             :  *
    2107             :  * CPLError() will have been called if something fails.
    2108             :  **********************************************************************/
    2109       14994 : int TABDATFile::WriteIntegerField(GInt32 nValue, TABINDFile *poINDFile,
    2110             :                                   int nIndexNo)
    2111             : {
    2112       14994 :     if (m_poRecordBlock == nullptr)
    2113             :     {
    2114           0 :         CPLError(
    2115             :             CE_Failure, CPLE_AssertionFailed,
    2116             :             "Can't write field value: GetRecordBlock() has not been called.");
    2117           0 :         return -1;
    2118             :     }
    2119             : 
    2120             :     // Update Index
    2121       14994 :     if (poINDFile && nIndexNo > 0)
    2122             :     {
    2123           2 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
    2124           2 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2125           0 :             return -1;
    2126             :     }
    2127             : 
    2128       14994 :     return m_poRecordBlock->WriteInt32(nValue);
    2129             : }
    2130             : 
    2131             : /**********************************************************************
    2132             :  *                   TABDATFile::WriteSmallIntField()
    2133             :  *
    2134             :  * Write the smallint field value at the current position in the data
    2135             :  * block.
    2136             :  *
    2137             :  * CPLError() will have been called if something fails.
    2138             :  **********************************************************************/
    2139           0 : int TABDATFile::WriteSmallIntField(GInt16 nValue, TABINDFile *poINDFile,
    2140             :                                    int nIndexNo)
    2141             : {
    2142           0 :     if (m_poRecordBlock == nullptr)
    2143             :     {
    2144           0 :         CPLError(
    2145             :             CE_Failure, CPLE_AssertionFailed,
    2146             :             "Can't write field value: GetRecordBlock() has not been called.");
    2147           0 :         return -1;
    2148             :     }
    2149             : 
    2150             :     // Update Index
    2151           0 :     if (poINDFile && nIndexNo > 0)
    2152             :     {
    2153           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
    2154           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2155           0 :             return -1;
    2156             :     }
    2157             : 
    2158           0 :     return m_poRecordBlock->WriteInt16(nValue);
    2159             : }
    2160             : 
    2161             : /**********************************************************************
    2162             :  *                   TABDATFile::WriteLargeIntField()
    2163             :  *
    2164             :  * Write the smallint field value at the current position in the data
    2165             :  * block.
    2166             :  *
    2167             :  * CPLError() will have been called if something fails.
    2168             :  **********************************************************************/
    2169           2 : int TABDATFile::WriteLargeIntField(GInt64 nValue, TABINDFile *poINDFile,
    2170             :                                    int nIndexNo)
    2171             : {
    2172           2 :     if (m_poRecordBlock == nullptr)
    2173             :     {
    2174           0 :         CPLError(
    2175             :             CE_Failure, CPLE_AssertionFailed,
    2176             :             "Can't write field value: GetRecordBlock() has not been called.");
    2177           0 :         return -1;
    2178             :     }
    2179             : 
    2180             :     // Update Index
    2181           2 :     if (poINDFile && nIndexNo > 0)
    2182             :     {
    2183           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
    2184           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2185           0 :             return -1;
    2186             :     }
    2187             : 
    2188           2 :     return m_poRecordBlock->WriteInt64(nValue);
    2189             : }
    2190             : 
    2191             : /**********************************************************************
    2192             :  *                   TABDATFile::WriteFloatField()
    2193             :  *
    2194             :  * Write the float field value at the current position in the data
    2195             :  * block.
    2196             :  *
    2197             :  * CPLError() will have been called if something fails.
    2198             :  **********************************************************************/
    2199         237 : int TABDATFile::WriteFloatField(double dValue, TABINDFile *poINDFile,
    2200             :                                 int nIndexNo)
    2201             : {
    2202         237 :     if (m_poRecordBlock == nullptr)
    2203             :     {
    2204           0 :         CPLError(
    2205             :             CE_Failure, CPLE_AssertionFailed,
    2206             :             "Can't write field value: GetRecordBlock() has not been called.");
    2207           0 :         return -1;
    2208             :     }
    2209             : 
    2210             :     // Update Index
    2211         237 :     if (poINDFile && nIndexNo > 0)
    2212             :     {
    2213           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue);
    2214           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2215           0 :             return -1;
    2216             :     }
    2217             : 
    2218         237 :     return m_poRecordBlock->WriteDouble(dValue);
    2219             : }
    2220             : 
    2221             : /**********************************************************************
    2222             :  *                   TABDATFile::WriteLogicalField()
    2223             :  *
    2224             :  * Write the logical field value at the current position in the data
    2225             :  * block.
    2226             :  *
    2227             :  * The value written to the file is either 0 or 1.
    2228             :  *
    2229             :  * CPLError() will have been called if something fails.
    2230             :  **********************************************************************/
    2231           2 : int TABDATFile::WriteLogicalField(bool bValue, TABINDFile *poINDFile,
    2232             :                                   int nIndexNo)
    2233             : {
    2234           2 :     if (m_poRecordBlock == nullptr)
    2235             :     {
    2236           0 :         CPLError(
    2237             :             CE_Failure, CPLE_AssertionFailed,
    2238             :             "Can't write field value: GetRecordBlock() has not been called.");
    2239           0 :         return -1;
    2240             :     }
    2241             : 
    2242           2 :     const GByte byValue = bValue ? 1 : 0;
    2243             : 
    2244             :     // Update Index
    2245           2 :     if (poINDFile && nIndexNo > 0)
    2246             :     {
    2247           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, static_cast<int>(byValue));
    2248           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2249           0 :             return -1;
    2250             :     }
    2251             : 
    2252           2 :     return m_poRecordBlock->WriteByte(byValue);
    2253             : }
    2254             : 
    2255             : /**********************************************************************
    2256             :  *                   TABDATFile::WriteDateField()
    2257             :  *
    2258             :  * Write the date field value at the current position in the data
    2259             :  * block.
    2260             :  *
    2261             :  * A date field is a 4 bytes binary value in which the first byte is
    2262             :  * the day, followed by 1 byte for the month, and 2 bytes for the year.
    2263             :  *
    2264             :  * The expected input is a 10 chars string in the format "YYYY/MM/DD"
    2265             :  * or "DD/MM/YYYY" or "YYYYMMDD"
    2266             :  *
    2267             :  * Returns 0 on success, or -1 if the operation failed, in which case
    2268             :  * CPLError() will have been called.
    2269             :  **********************************************************************/
    2270           0 : int TABDATFile::WriteDateField(const char *pszValue, TABINDFile *poINDFile,
    2271             :                                int nIndexNo)
    2272             : {
    2273           0 :     char **papszTok = nullptr;
    2274             : 
    2275             :     // Get rid of leading spaces.
    2276           0 :     while (*pszValue == ' ')
    2277             :     {
    2278           0 :         pszValue++;
    2279             :     }
    2280             : 
    2281             :     // Try to automagically detect date format, one of:
    2282             :     // "YYYY/MM/DD", "DD/MM/YYYY", or "YYYYMMDD"
    2283           0 :     int nDay = 0;
    2284           0 :     int nMonth = 0;
    2285           0 :     int nYear = 0;
    2286             : 
    2287           0 :     if (strlen(pszValue) == 8)
    2288             :     {
    2289             :         // "YYYYMMDD"
    2290           0 :         char szBuf[9] = {};
    2291           0 :         strcpy(szBuf, pszValue);
    2292           0 :         nDay = atoi(szBuf + 6);
    2293           0 :         szBuf[6] = '\0';
    2294           0 :         nMonth = atoi(szBuf + 4);
    2295           0 :         szBuf[4] = '\0';
    2296           0 :         nYear = atoi(szBuf);
    2297             :     }
    2298           0 :     else if (strlen(pszValue) == 10 &&
    2299           0 :              (papszTok = CSLTokenizeStringComplex(pszValue, "/", FALSE,
    2300           0 :                                                   FALSE)) != nullptr &&
    2301           0 :              CSLCount(papszTok) == 3 &&
    2302           0 :              (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4))
    2303             :     {
    2304             :         // Either "YYYY/MM/DD" or "DD/MM/YYYY"
    2305           0 :         if (strlen(papszTok[0]) == 4)
    2306             :         {
    2307           0 :             nYear = atoi(papszTok[0]);
    2308           0 :             nMonth = atoi(papszTok[1]);
    2309           0 :             nDay = atoi(papszTok[2]);
    2310             :         }
    2311             :         else
    2312             :         {
    2313           0 :             nYear = atoi(papszTok[2]);
    2314           0 :             nMonth = atoi(papszTok[1]);
    2315           0 :             nDay = atoi(papszTok[0]);
    2316             :         }
    2317             :     }
    2318           0 :     else if (strlen(pszValue) == 0)
    2319             :     {
    2320           0 :         nYear = 0;
    2321           0 :         nMonth = 0;
    2322           0 :         nDay = 0;
    2323             :     }
    2324             :     else
    2325             :     {
    2326           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2327             :                  "Invalid date field value `%s'.  Date field values must "
    2328             :                  "be in the format `YYYY/MM/DD', `MM/DD/YYYY' or `YYYYMMDD'",
    2329             :                  pszValue);
    2330           0 :         CSLDestroy(papszTok);
    2331           0 :         return -1;
    2332             :     }
    2333           0 :     CSLDestroy(papszTok);
    2334             : 
    2335           0 :     return WriteDateField(nYear, nMonth, nDay, poINDFile, nIndexNo);
    2336             : }
    2337             : 
    2338          76 : int TABDATFile::WriteDateField(int nYear, int nMonth, int nDay,
    2339             :                                TABINDFile *poINDFile, int nIndexNo)
    2340             : {
    2341          76 :     if (m_poRecordBlock == nullptr)
    2342             :     {
    2343           0 :         CPLError(
    2344             :             CE_Failure, CPLE_AssertionFailed,
    2345             :             "Can't write field value: GetRecordBlock() has not been called.");
    2346           0 :         return -1;
    2347             :     }
    2348             : 
    2349          76 :     m_poRecordBlock->WriteInt16(static_cast<GInt16>(nYear));
    2350          76 :     m_poRecordBlock->WriteByte(static_cast<GByte>(nMonth));
    2351          76 :     m_poRecordBlock->WriteByte(static_cast<GByte>(nDay));
    2352             : 
    2353          76 :     if (CPLGetLastErrorType() == CE_Failure)
    2354           0 :         return -1;
    2355             : 
    2356             :     // Update Index
    2357          76 :     if (poINDFile && nIndexNo > 0)
    2358             :     {
    2359           0 :         GByte *pKey = poINDFile->BuildKey(
    2360           0 :             nIndexNo, (nYear * 0x10000 + nMonth * 0x100 + nDay));
    2361           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2362           0 :             return -1;
    2363             :     }
    2364             : 
    2365          76 :     return 0;
    2366             : }
    2367             : 
    2368             : /**********************************************************************
    2369             :  *                   TABDATFile::WriteTimeField()
    2370             :  *
    2371             :  * Write the date field value at the current position in the data
    2372             :  * block.
    2373             :  *
    2374             :  * A time field is a 4 byte binary value which represents the number
    2375             :  * of milliseconds since midnight.
    2376             :  *
    2377             :  * The expected input is a 10 chars string in the format "HH:MM:SS"
    2378             :  * or "HHMMSSmmm"
    2379             :  *
    2380             :  * Returns 0 on success, or -1 if the operation failed, in which case
    2381             :  * CPLError() will have been called.
    2382             :  **********************************************************************/
    2383           0 : int TABDATFile::WriteTimeField(const char *pszValue, TABINDFile *poINDFile,
    2384             :                                int nIndexNo)
    2385             : {
    2386             :     // Get rid of leading spaces.
    2387           0 :     while (*pszValue == ' ')
    2388             :     {
    2389           0 :         pszValue++;
    2390             :     }
    2391             : 
    2392             :     // Try to automagically detect time format, one of:
    2393             :     // "HH:MM:SS", or "HHMMSSmmm"
    2394           0 :     int nHour = 0;
    2395           0 :     int nMin = 0;
    2396           0 :     int nSec = 0;
    2397           0 :     int nMS = 0;
    2398             : 
    2399           0 :     if (strlen(pszValue) == 8)
    2400             :     {
    2401             :         // "HH:MM:SS"
    2402           0 :         char szBuf[9] = {};
    2403           0 :         strcpy(szBuf, pszValue);
    2404           0 :         szBuf[2] = 0;
    2405           0 :         szBuf[5] = 0;
    2406           0 :         nHour = atoi(szBuf);
    2407           0 :         nMin = atoi(szBuf + 3);
    2408           0 :         nSec = atoi(szBuf + 6);
    2409           0 :         nMS = 0;
    2410             :     }
    2411           0 :     else if (strlen(pszValue) == 9)
    2412             :     {
    2413             :         // "HHMMSSmmm"
    2414           0 :         char szBuf[4] = {};
    2415           0 :         const int HHLength = 2;
    2416           0 :         strncpy(szBuf, pszValue, HHLength);
    2417           0 :         szBuf[HHLength] = 0;
    2418           0 :         nHour = atoi(szBuf);
    2419             : 
    2420           0 :         const int MMLength = 2;
    2421           0 :         strncpy(szBuf, pszValue + HHLength, MMLength);
    2422           0 :         szBuf[MMLength] = 0;
    2423           0 :         nMin = atoi(szBuf);
    2424             : 
    2425           0 :         const int SSLength = 2;
    2426           0 :         strncpy(szBuf, pszValue + HHLength + MMLength, SSLength);
    2427           0 :         szBuf[SSLength] = 0;
    2428           0 :         nSec = atoi(szBuf);
    2429             : 
    2430           0 :         const int mmmLength = 3;
    2431           0 :         strncpy(szBuf, pszValue + HHLength + MMLength + SSLength, mmmLength);
    2432           0 :         szBuf[mmmLength] = 0;
    2433           0 :         nMS = atoi(szBuf);
    2434             :     }
    2435           0 :     else if (strlen(pszValue) == 0)
    2436             :     {
    2437             :         // Write -1 to .DAT file if value is not set
    2438           0 :         nHour = -1;
    2439           0 :         nMin = -1;
    2440           0 :         nSec = -1;
    2441           0 :         nMS = -1;
    2442             :     }
    2443             :     else
    2444             :     {
    2445           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2446             :                  "Invalid time field value `%s'.  Time field values must "
    2447             :                  "be in the format `HH:MM:SS', or `HHMMSSmmm'",
    2448             :                  pszValue);
    2449           0 :         return -1;
    2450             :     }
    2451             : 
    2452           0 :     return WriteTimeField(nHour, nMin, nSec, nMS, poINDFile, nIndexNo);
    2453             : }
    2454             : 
    2455           3 : int TABDATFile::WriteTimeField(int nHour, int nMinute, int nSecond, int nMS,
    2456             :                                TABINDFile *poINDFile, int nIndexNo)
    2457             : {
    2458           3 :     GInt32 nS = -1;
    2459             : 
    2460           3 :     if (m_poRecordBlock == nullptr)
    2461             :     {
    2462           0 :         CPLError(
    2463             :             CE_Failure, CPLE_AssertionFailed,
    2464             :             "Can't write field value: GetRecordBlock() has not been called.");
    2465           0 :         return -1;
    2466             :     }
    2467             : 
    2468           3 :     nS = (nHour * 3600 + nMinute * 60 + nSecond) * 1000 + nMS;
    2469           3 :     if (nS < 0)
    2470           1 :         nS = -1;
    2471           3 :     m_poRecordBlock->WriteInt32(nS);
    2472             : 
    2473           3 :     if (CPLGetLastErrorType() == CE_Failure)
    2474           0 :         return -1;
    2475             : 
    2476             :     // Update Index
    2477           3 :     if (poINDFile && nIndexNo > 0)
    2478             :     {
    2479           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, (nS));
    2480           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2481           0 :             return -1;
    2482             :     }
    2483             : 
    2484           3 :     return 0;
    2485             : }
    2486             : 
    2487             : /**********************************************************************
    2488             :  *                   TABDATFile::WriteDateTimeField()
    2489             :  *
    2490             :  * Write the DateTime field value at the current position in the data
    2491             :  * block.
    2492             :  *
    2493             :  * A datetime field is a 8 bytes binary value in which the first byte is
    2494             :  * the day, followe
    2495             : d by 1 byte for the month, and 2 bytes for the year.
    2496             :  * After this the time value is stored as a 4 byte integer
    2497             :  * (milliseconds since midnight)
    2498             :  *
    2499             :  * The expected input is a 10 chars string in the format "YYYY/MM/DD HH:MM:SS"
    2500             :  * or "DD/MM/YYYY HH:MM:SS" or "YYYYMMDDhhmmssmmm"
    2501             :  *
    2502             :  * Returns 0 on success, or -1 if the operation failed, in which case
    2503             :  * CPLError() will have been called.
    2504             :  **********************************************************************/
    2505           0 : int TABDATFile::WriteDateTimeField(const char *pszValue, TABINDFile *poINDFile,
    2506             :                                    int nIndexNo)
    2507             : {
    2508             :     // Get rid of leading spaces.
    2509           0 :     while (*pszValue == ' ')
    2510             :     {
    2511           0 :         pszValue++;
    2512             :     }
    2513             : 
    2514             :     /*-----------------------------------------------------------------
    2515             :      * Try to automagically detect date format, one of:
    2516             :      * "YYYY/MM/DD HH:MM:SS", "DD/MM/YYYY HH:MM:SS", or "YYYYMMDDhhmmssmmm"
    2517             :      *----------------------------------------------------------------*/
    2518           0 :     int nDay = 0;
    2519           0 :     int nMonth = 0;
    2520           0 :     int nYear = 0;
    2521           0 :     int nHour = 0;
    2522           0 :     int nMin = 0;
    2523           0 :     int nSec = 0;
    2524           0 :     int nMS = 0;
    2525           0 :     char **papszTok = nullptr;
    2526             : 
    2527           0 :     if (strlen(pszValue) == 17)
    2528             :     {
    2529             :         // "YYYYMMDDhhmmssmmm"
    2530           0 :         char szBuf[18] = {};
    2531           0 :         strcpy(szBuf, pszValue);
    2532           0 :         nMS = atoi(szBuf + 14);
    2533           0 :         szBuf[14] = 0;
    2534           0 :         nSec = atoi(szBuf + 12);
    2535           0 :         szBuf[12] = 0;
    2536           0 :         nMin = atoi(szBuf + 10);
    2537           0 :         szBuf[10] = 0;
    2538           0 :         nHour = atoi(szBuf + 8);
    2539           0 :         szBuf[8] = 0;
    2540           0 :         nDay = atoi(szBuf + 6);
    2541           0 :         szBuf[6] = 0;
    2542           0 :         nMonth = atoi(szBuf + 4);
    2543           0 :         szBuf[4] = 0;
    2544           0 :         nYear = atoi(szBuf);
    2545             :     }
    2546           0 :     else if (strlen(pszValue) == 19 &&
    2547           0 :              (papszTok = CSLTokenizeStringComplex(pszValue, "/ :", FALSE,
    2548           0 :                                                   FALSE)) != nullptr &&
    2549           0 :              CSLCount(papszTok) == 6 &&
    2550           0 :              (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4))
    2551             :     {
    2552             :         // Either "YYYY/MM/DD HH:MM:SS" or "DD/MM/YYYY HH:MM:SS"
    2553           0 :         if (strlen(papszTok[0]) == 4)
    2554             :         {
    2555           0 :             nYear = atoi(papszTok[0]);
    2556           0 :             nMonth = atoi(papszTok[1]);
    2557           0 :             nDay = atoi(papszTok[2]);
    2558           0 :             nHour = atoi(papszTok[3]);
    2559           0 :             nMin = atoi(papszTok[4]);
    2560           0 :             nSec = atoi(papszTok[5]);
    2561           0 :             nMS = 0;
    2562             :         }
    2563             :         else
    2564             :         {
    2565           0 :             nYear = atoi(papszTok[2]);
    2566           0 :             nMonth = atoi(papszTok[1]);
    2567           0 :             nDay = atoi(papszTok[0]);
    2568           0 :             nHour = atoi(papszTok[3]);
    2569           0 :             nMin = atoi(papszTok[4]);
    2570           0 :             nSec = atoi(papszTok[5]);
    2571           0 :             nMS = 0;
    2572             :         }
    2573             :     }
    2574           0 :     else if (strlen(pszValue) == 0)
    2575             :     {
    2576           0 :         nYear = 0;
    2577           0 :         nMonth = 0;
    2578           0 :         nDay = 0;
    2579           0 :         nHour = 0;
    2580           0 :         nMin = 0;
    2581           0 :         nSec = 0;
    2582           0 :         nMS = 0;
    2583             :     }
    2584             :     else
    2585             :     {
    2586           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2587             :                  "Invalid date field value `%s'.  Date field values must "
    2588             :                  "be in the format `YYYY/MM/DD HH:MM:SS', "
    2589             :                  "`MM/DD/YYYY HH:MM:SS' or `YYYYMMDDhhmmssmmm'",
    2590             :                  pszValue);
    2591           0 :         CSLDestroy(papszTok);
    2592           0 :         return -1;
    2593             :     }
    2594           0 :     CSLDestroy(papszTok);
    2595             : 
    2596           0 :     return WriteDateTimeField(nYear, nMonth, nDay, nHour, nMin, nSec, nMS,
    2597           0 :                               poINDFile, nIndexNo);
    2598             : }
    2599             : 
    2600          75 : int TABDATFile::WriteDateTimeField(int nYear, int nMonth, int nDay, int nHour,
    2601             :                                    int nMinute, int nSecond, int nMS,
    2602             :                                    TABINDFile *poINDFile, int nIndexNo)
    2603             : {
    2604          75 :     GInt32 nS = (nHour * 3600 + nMinute * 60 + nSecond) * 1000 + nMS;
    2605             : 
    2606          75 :     if (m_poRecordBlock == nullptr)
    2607             :     {
    2608           0 :         CPLError(
    2609             :             CE_Failure, CPLE_AssertionFailed,
    2610             :             "Can't write field value: GetRecordBlock() has not been called.");
    2611           0 :         return -1;
    2612             :     }
    2613             : 
    2614          75 :     m_poRecordBlock->WriteInt16(static_cast<GInt16>(nYear));
    2615          75 :     m_poRecordBlock->WriteByte(static_cast<GByte>(nMonth));
    2616          75 :     m_poRecordBlock->WriteByte(static_cast<GByte>(nDay));
    2617          75 :     m_poRecordBlock->WriteInt32(nS);
    2618             : 
    2619          75 :     if (CPLGetLastErrorType() == CE_Failure)
    2620           0 :         return -1;
    2621             : 
    2622             :     // Update Index
    2623          75 :     if (poINDFile && nIndexNo > 0)
    2624             :     {
    2625             :         // __TODO__  (see bug #1844)
    2626             :         // Indexing on DateTime Fields not currently supported, that will
    2627             :         // require passing the 8 bytes datetime value to BuildKey() here...
    2628           0 :         CPLAssert(false);
    2629             :         GByte *pKey = poINDFile->BuildKey(
    2630             :             nIndexNo, (nYear * 0x10000 + nMonth * 0x100 + nDay));
    2631             :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2632             :             return -1;
    2633             :     }
    2634             : 
    2635          75 :     return 0;
    2636             : }
    2637             : 
    2638             : /**********************************************************************
    2639             :  *                   TABDATFile::WriteDecimalField()
    2640             :  *
    2641             :  * Write the decimal field value at the current position in the data
    2642             :  * block.
    2643             :  *
    2644             :  * A decimal field is a floating point value with a fixed number of digits
    2645             :  * stored as a character string.
    2646             :  *
    2647             :  * nWidth is the field length, as defined in the .DAT header.
    2648             :  *
    2649             :  * CPLError() will have been called if something fails.
    2650             :  **********************************************************************/
    2651           5 : int TABDATFile::WriteDecimalField(double dValue, int nWidth, int nPrec,
    2652             :                                   TABINDFile *poINDFile, int nIndexNo)
    2653             : {
    2654           5 :     char szFormat[10] = {};
    2655             : 
    2656           5 :     snprintf(szFormat, sizeof(szFormat), "%%%d.%df", nWidth, nPrec);
    2657           5 :     const char *pszVal = CPLSPrintf(szFormat, dValue);
    2658           5 :     if (static_cast<int>(strlen(pszVal)) > nWidth)
    2659             :     {
    2660           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2661             :                  "Cannot format %g as a %d.%d field", dValue, nWidth, nPrec);
    2662           1 :         return -1;
    2663             :     }
    2664             : 
    2665             :     // Update Index
    2666           4 :     if (poINDFile && nIndexNo > 0)
    2667             :     {
    2668           0 :         GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue);
    2669           0 :         if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
    2670           0 :             return -1;
    2671             :     }
    2672             : 
    2673           4 :     return m_poRecordBlock->WriteBytes(nWidth,
    2674           4 :                                        reinterpret_cast<const GByte *>(pszVal));
    2675             : }
    2676             : 
    2677        1868 : const CPLString &TABDATFile::GetEncoding() const
    2678             : {
    2679        1868 :     return m_osEncoding;
    2680             : }
    2681             : 
    2682           2 : void TABDATFile::SetEncoding(const CPLString &osEncoding)
    2683             : {
    2684           2 :     m_osEncoding = osEncoding;
    2685           2 : }
    2686             : 
    2687             : /**********************************************************************
    2688             :  *                   TABDATFile::Dump()
    2689             :  *
    2690             :  * Dump block contents... available only in DEBUG mode.
    2691             :  **********************************************************************/
    2692             : #ifdef DEBUG
    2693             : 
    2694           0 : void TABDATFile::Dump(FILE *fpOut /* =NULL */)
    2695             : {
    2696           0 :     if (fpOut == nullptr)
    2697           0 :         fpOut = stdout;
    2698             : 
    2699           0 :     fprintf(fpOut, "----- TABDATFile::Dump() -----\n");
    2700             : 
    2701           0 :     if (m_fp == nullptr)
    2702             :     {
    2703           0 :         fprintf(fpOut, "File is not opened.\n");
    2704             :     }
    2705             :     else
    2706             :     {
    2707           0 :         fprintf(fpOut, "File is opened: %s\n", m_pszFname);
    2708           0 :         fprintf(fpOut, "m_numFields  = %d\n", m_numFields);
    2709           0 :         fprintf(fpOut, "m_numRecords = %d\n", m_numRecords);
    2710             :     }
    2711             : 
    2712           0 :     fflush(fpOut);
    2713           0 : }
    2714             : 
    2715             : #endif  // DEBUG

Generated by: LCOV version 1.14