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

Generated by: LCOV version 1.14