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

Generated by: LCOV version 1.14