Coverage Report

Created: 2026-02-14 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * Name:     mitab_tabfile.cpp
4
 * Project:  MapInfo TAB Read/Write library
5
 * Language: C++
6
 * Purpose:  Implementation of the TABFile class, the main class of the lib.
7
 *           To be used by external programs to handle reading/writing of
8
 *           features from/to TAB datasets.
9
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
10
 *
11
 **********************************************************************
12
 * Copyright (c) 1999-2003, Daniel Morissette
13
 * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
14
 *
15
 * SPDX-License-Identifier: MIT
16
 **********************************************************************/
17
18
#include "cpl_port.h"
19
#include "mitab.h"
20
21
#include <cctype>
22
#include <climits>
23
#include <cstdio>
24
#include <cstdlib>
25
#include <cstring>
26
#include <algorithm>
27
#include <memory>
28
29
#include "cpl_conv.h"
30
#include "cpl_error.h"
31
#include "cpl_minixml.h"
32
#include "cpl_string.h"
33
#include "cpl_vsi.h"
34
#include "mitab_priv.h"
35
#include "mitab_utils.h"
36
#include "ogr_core.h"
37
#include "ogr_feature.h"
38
#include "ogr_geometry.h"
39
#include "ogr_p.h"
40
#include "ogr_spatialref.h"
41
#include "ogrsf_frmts.h"
42
43
static const char UNSUPPORTED_OP_READ_ONLY[] =
44
    "%s : unsupported operation on a read-only datasource.";
45
46
constexpr const char *DESCRIPTION_KEY = "DESCRIPTION";
47
// Tab file allow to store description longer than 255 characters.
48
// But only 255 will shown in MapInfo layer list.
49
constexpr int MAX_DESCRIPTION_LEN = 254 * 2;
50
51
static char *EscapeString(const char *pszInput,
52
                          bool bEscapeDoubleQuotes = false)
53
226
{
54
226
    if (nullptr == pszInput)
55
226
    {
56
226
        return nullptr;
57
226
    }
58
59
0
    auto nLength = CPLStrnlen(pszInput, MAX_DESCRIPTION_LEN);
60
0
    char *pszOutput = static_cast<char *>(CPLMalloc(nLength * 2 + 1));
61
0
    int iOut = 0;
62
0
    int nDoubleQuotesCount = 0;
63
0
    for (int iIn = 0; iIn < static_cast<int>(nLength + 1); ++iIn)
64
0
    {
65
0
        if (pszInput[iIn] == '"')
66
0
        {
67
0
            if (bEscapeDoubleQuotes)
68
0
            {
69
0
                pszOutput[iOut++] = '"';
70
0
                pszOutput[iOut++] = '"';
71
0
            }
72
0
            else
73
0
            {
74
0
                nDoubleQuotesCount++;
75
0
                pszOutput[iOut++] = pszInput[iIn];
76
0
            }
77
0
        }
78
0
        else if (pszInput[iIn] == '\n' || pszInput[iIn] == '\r')
79
0
        {
80
0
            pszOutput[iOut++] = ' ';
81
0
        }
82
0
        else
83
0
        {
84
0
            if ((pszInput[iIn] & 0xc0) != 0x80)
85
0
            {
86
                // Stop at the start of the character just beyond the maximum
87
                // accepted
88
0
                if (iOut >= MAX_DESCRIPTION_LEN - nDoubleQuotesCount)
89
0
                {
90
0
                    break;
91
0
                }
92
0
            }
93
0
            pszOutput[iOut++] = pszInput[iIn];
94
0
        }
95
0
    }
96
97
0
    pszOutput[iOut] = '\0';
98
0
    return pszOutput;
99
226
}
100
101
static char *UnescapeString(const char *pszInput)
102
0
{
103
0
    if (nullptr == pszInput)
104
0
    {
105
0
        return nullptr;
106
0
    }
107
108
0
    auto nLength = CPLStrnlen(pszInput, MAX_DESCRIPTION_LEN);
109
0
    char *pszOutput = static_cast<char *>(CPLMalloc(nLength * 2 + 1));
110
0
    int iOut = 0;
111
0
    for (int iIn = 0; iIn < static_cast<int>(nLength + 1); ++iIn)
112
0
    {
113
0
        if (pszInput[iIn] == '"' && pszInput[iIn + 1] == '"')
114
0
        {
115
0
            ++iIn;
116
0
            pszOutput[iOut++] = pszInput[iIn];
117
0
        }
118
0
        else
119
0
        {
120
0
            if ((pszInput[iIn] & 0xc0) != 0x80)
121
0
            {
122
                // Stop at the start of the character just beyond the maximum
123
                // accepted
124
0
                if (iOut >= MAX_DESCRIPTION_LEN)
125
0
                {
126
0
                    break;
127
0
                }
128
0
            }
129
0
            pszOutput[iOut++] = pszInput[iIn];
130
0
        }
131
0
    }
132
0
    pszOutput[iOut] = '\0';
133
0
    return pszOutput;
134
0
}
135
136
static std::string GetTabDescription(const char *pszLine)
137
0
{
138
0
    CPLString osDescriptionLine(pszLine);
139
0
    auto nStart = osDescriptionLine.find_first_of('"') + 1;
140
0
    if (nStart != std::string::npos)
141
0
    {
142
0
        auto nEnd = osDescriptionLine.find_last_of('"');
143
0
        auto nLen = nEnd == std::string::npos ? nEnd : nEnd - nStart;
144
0
        return osDescriptionLine.substr(nStart, nLen);
145
0
    }
146
0
    return "";
147
0
}
148
149
/*=====================================================================
150
 *                      class TABFile
151
 *====================================================================*/
152
153
/**********************************************************************
154
 *                   TABFile::TABFile()
155
 *
156
 * Constructor.
157
 **********************************************************************/
158
TABFile::TABFile(GDALDataset *poDS)
159
2.81k
    : IMapInfoFile(poDS), m_pszFname(nullptr), m_eAccessMode(TABRead),
160
2.81k
      m_papszTABFile(nullptr), m_nVersion(300), m_panIndexNo(nullptr),
161
2.81k
      m_eTableType(TABTableNative), m_poDATFile(nullptr), m_poMAPFile(nullptr),
162
2.81k
      m_poINDFile(nullptr), m_poDefn(nullptr), m_poSpatialRef(nullptr),
163
2.81k
      bUseSpatialTraversal(FALSE), m_nLastFeatureId(0),
164
2.81k
      m_panMatchingFIDs(nullptr), m_iMatchingFID(0), m_bNeedTABRewrite(FALSE),
165
2.81k
      m_bLastOpWasRead(FALSE), m_bLastOpWasWrite(FALSE)
166
2.81k
{
167
2.81k
    m_poCurFeature = nullptr;
168
2.81k
    m_nCurFeatureId = 0;
169
2.81k
}
170
171
/**********************************************************************
172
 *                   TABFile::~TABFile()
173
 *
174
 * Destructor.
175
 **********************************************************************/
176
TABFile::~TABFile()
177
2.81k
{
178
2.81k
    TABFile::Close();
179
2.81k
}
180
181
/************************************************************************/
182
/*                          GetFeatureCount()                           */
183
/************************************************************************/
184
185
GIntBig TABFile::GetFeatureCount(int bForce)
186
0
{
187
188
0
    if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr || bForce)
189
0
        return OGRLayer::GetFeatureCount(bForce);
190
0
    else
191
0
        return m_nLastFeatureId;
192
0
}
193
194
/************************************************************************/
195
/*                            ResetReading()                            */
196
/************************************************************************/
197
void TABFile::ResetReading()
198
0
{
199
0
    CPLFree(m_panMatchingFIDs);
200
0
    m_panMatchingFIDs = nullptr;
201
0
    m_iMatchingFID = 0;
202
203
0
    m_nCurFeatureId = 0;
204
0
    if (m_poMAPFile != nullptr)
205
0
        m_poMAPFile->ResetReading();
206
207
    /* -------------------------------------------------------------------- */
208
    /*      Decide whether to operate in spatial traversal mode or not,     */
209
    /*      and ensure the current spatial filter is applied to the map     */
210
    /*      file object.                                                    */
211
    /* -------------------------------------------------------------------- */
212
0
    if (m_poMAPFile)
213
0
    {
214
0
        bUseSpatialTraversal = FALSE;
215
216
0
        m_poMAPFile->ResetCoordFilter();
217
218
0
        if (m_poFilterGeom != nullptr)
219
0
        {
220
            // TABMAPHeaderBlock *poHeader = m_poMAPFile->GetHeaderBlock();
221
222
0
            OGREnvelope sEnvelope;
223
0
            m_poFilterGeom->getEnvelope(&sEnvelope);
224
225
0
            TABVertex sMin;
226
0
            TABVertex sMax;
227
0
            m_poMAPFile->GetCoordFilter(sMin, sMax);
228
229
0
            if (sEnvelope.MinX > sMin.x || sEnvelope.MinY > sMin.y ||
230
0
                sEnvelope.MaxX < sMax.x || sEnvelope.MaxY < sMax.y)
231
0
            {
232
0
                bUseSpatialTraversal = TRUE;
233
0
                sMin.x = sEnvelope.MinX;
234
0
                sMin.y = sEnvelope.MinY;
235
0
                sMax.x = sEnvelope.MaxX;
236
0
                sMax.y = sEnvelope.MaxY;
237
0
                m_poMAPFile->SetCoordFilter(sMin, sMax);
238
0
            }
239
0
        }
240
0
    }
241
242
0
    m_bLastOpWasRead = FALSE;
243
0
    m_bLastOpWasWrite = FALSE;
244
0
}
245
246
/**********************************************************************
247
 *                   TABFile::Open()
248
 *
249
 * Open a .TAB dataset and the associated files, and initialize the
250
 * structures to be ready to read features from (or write to) it.
251
 *
252
 * Supported access modes are "r" (read-only) and "w" (create new dataset or
253
 * update).
254
 *
255
 * Set bTestOpenNoError=TRUE to silently return -1 with no error message
256
 * if the file cannot be opened.  This is intended to be used in the
257
 * context of a TestOpen() function.  The default value is FALSE which
258
 * means that an error is reported if the file cannot be opened.
259
 *
260
 * Note that dataset extents will have to be set using SetBounds() before
261
 * any feature can be written to a newly created dataset.
262
 *
263
 * In read mode, a valid dataset must have at least a .TAB and a .DAT file.
264
 * The .MAP and .ID files are optional and if they do not exist then
265
 * all features will be returned with NONE geometry.
266
 *
267
 * Returns 0 on success, -1 on error.
268
 **********************************************************************/
269
int TABFile::Open(const char *pszFname, TABAccess eAccess,
270
                  GBool bTestOpenNoError /*=FALSE*/, int nBlockSizeForCreate,
271
                  const char *pszCharset /* = NULL */)
272
2.81k
{
273
2.81k
    char *pszTmpFname = nullptr;
274
2.81k
    int nFnameLen = 0;
275
276
2.81k
    CPLErrorReset();
277
278
2.81k
    if (m_poMAPFile)
279
0
    {
280
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
281
0
                 "Open() failed: object already contains an open file");
282
283
0
        return -1;
284
0
    }
285
286
2.81k
    m_eAccessMode = eAccess;
287
288
    /*-----------------------------------------------------------------
289
     * Make sure filename has a .TAB extension...
290
     *----------------------------------------------------------------*/
291
2.81k
    m_pszFname = CPLStrdup(pszFname);
292
2.81k
    nFnameLen = static_cast<int>(strlen(m_pszFname));
293
294
2.81k
    if (nFnameLen > 4 && (strcmp(m_pszFname + nFnameLen - 4, ".TAB") == 0 ||
295
2.72k
                          strcmp(m_pszFname + nFnameLen - 4, ".MAP") == 0 ||
296
2.72k
                          strcmp(m_pszFname + nFnameLen - 4, ".DAT") == 0))
297
88
        strcpy(m_pszFname + nFnameLen - 4, ".TAB");
298
2.72k
    else if (nFnameLen > 4 && (EQUAL(m_pszFname + nFnameLen - 4, ".tab") ||
299
0
                               EQUAL(m_pszFname + nFnameLen - 4, ".map") ||
300
0
                               EQUAL(m_pszFname + nFnameLen - 4, ".dat")))
301
2.72k
        strcpy(m_pszFname + nFnameLen - 4, ".tab");
302
0
    else
303
0
    {
304
0
        if (!bTestOpenNoError)
305
0
            CPLError(CE_Failure, CPLE_FileIO,
306
0
                     "Open() failed for %s: invalid filename extension",
307
0
                     m_pszFname);
308
0
        else
309
0
            CPLErrorReset();
310
311
0
        CPLFree(m_pszFname);
312
0
        m_pszFname = nullptr;
313
0
        return -1;
314
0
    }
315
316
2.81k
    pszTmpFname = CPLStrdup(m_pszFname);
317
318
2.81k
#ifndef _WIN32
319
    /*-----------------------------------------------------------------
320
     * On Unix, make sure extension uses the right cases
321
     * We do it even for write access because if a file with the same
322
     * extension already exists we want to overwrite it.
323
     *----------------------------------------------------------------*/
324
2.81k
    TABAdjustFilenameExtension(m_pszFname);
325
2.81k
#endif
326
327
    /*-----------------------------------------------------------------
328
     * Handle .TAB file... depends on access mode.
329
     *----------------------------------------------------------------*/
330
2.81k
    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
331
2.58k
    {
332
        /*-------------------------------------------------------------
333
         * Open .TAB file... since it is a small text file, we will just load
334
         * it as a stringlist in memory.
335
         *------------------------------------------------------------*/
336
2.58k
        m_papszTABFile = TAB_CSLLoad(m_pszFname);
337
2.58k
        if (m_papszTABFile == nullptr)
338
37
        {
339
37
            if (!bTestOpenNoError)
340
0
            {
341
0
                CPLError(CE_Failure, CPLE_FileIO, "Failed opening %s.",
342
0
                         m_pszFname);
343
0
            }
344
37
            CPLFree(m_pszFname);
345
37
            m_pszFname = nullptr;
346
37
            CSLDestroy(m_papszTABFile);
347
37
            m_papszTABFile = nullptr;
348
37
            CPLFree(pszTmpFname);
349
37
            return -1;
350
37
        }
351
352
        /*-------------------------------------------------------------
353
         * Do a first pass on the TAB header to establish the type of
354
         * dataset we have (NATIVE, DBF, etc.)... and also to know if
355
         * it is a supported type.
356
         *------------------------------------------------------------*/
357
2.54k
        if (ParseTABFileFirstPass(bTestOpenNoError) != 0)
358
311
        {
359
            // No need to produce an error... it is already been done if
360
            // necessary... just cleanup and exit.
361
362
311
            CPLFree(m_pszFname);
363
311
            m_pszFname = nullptr;
364
311
            CSLDestroy(m_papszTABFile);
365
311
            m_papszTABFile = nullptr;
366
311
            CPLFree(pszTmpFname);
367
368
311
            return -1;
369
311
        }
370
2.54k
    }
371
231
    else
372
231
    {
373
        /*-------------------------------------------------------------
374
         * In Write access mode, the .TAB file will be written during the
375
         * Close() call... we will just set some defaults here.
376
         *------------------------------------------------------------*/
377
231
        m_nVersion = 300;
378
231
        if (pszCharset != nullptr)
379
231
            SetCharset(pszCharset);
380
0
        else
381
0
            SetCharset("Neutral");
382
231
        m_eTableType = TABTableNative;
383
384
        /*-------------------------------------------------------------
385
         * Do initial setup of feature definition.
386
         *------------------------------------------------------------*/
387
231
        char *pszFeatureClassName = TABGetBasename(m_pszFname);
388
231
        m_poDefn = new OGRFeatureDefn(pszFeatureClassName);
389
231
        m_poDefn->Reference();
390
231
        CPLFree(pszFeatureClassName);
391
392
231
        m_bNeedTABRewrite = TRUE;
393
231
    }
394
395
    /*-----------------------------------------------------------------
396
     * Open .DAT file (or .DBF)
397
     *----------------------------------------------------------------*/
398
2.46k
    if (nFnameLen > 4 && strcmp(pszTmpFname + nFnameLen - 4, ".TAB") == 0)
399
23
    {
400
23
        if (m_eTableType == TABTableDBF)
401
0
            strcpy(pszTmpFname + nFnameLen - 4, ".DBF");
402
23
        else  // Default is NATIVE
403
23
            strcpy(pszTmpFname + nFnameLen - 4, ".DAT");
404
23
    }
405
2.44k
    else
406
2.44k
    {
407
2.44k
        if (m_eTableType == TABTableDBF)
408
0
            strcpy(pszTmpFname + nFnameLen - 4, ".dbf");
409
2.44k
        else  // Default is NATIVE
410
2.44k
            strcpy(pszTmpFname + nFnameLen - 4, ".dat");
411
2.44k
    }
412
413
2.46k
#ifndef _WIN32
414
2.46k
    TABAdjustFilenameExtension(pszTmpFname);
415
2.46k
#endif
416
417
2.46k
    CPLString oEncoding;
418
419
2.46k
    if (eAccess == TABRead || eAccess == TABReadWrite)
420
2.23k
    {
421
2.23k
        oEncoding = CharsetToEncoding(GetCharset());
422
2.23k
    }
423
231
    else if (eAccess == TABWrite)
424
231
    {
425
231
        oEncoding = CharsetToEncoding(pszCharset);
426
231
    }
427
428
2.46k
    m_poDATFile = new TABDATFile(oEncoding);
429
430
2.46k
    if (m_poDATFile->Open(pszTmpFname, eAccess, m_eTableType) != 0)
431
160
    {
432
        // Open Failed... an error has already been reported, just return.
433
160
        CPLFree(pszTmpFname);
434
160
        Close();
435
160
        if (bTestOpenNoError)
436
160
            CPLErrorReset();
437
438
160
        return -1;
439
160
    }
440
441
2.30k
    m_nLastFeatureId = m_poDATFile->GetNumRecords();
442
443
    /*-----------------------------------------------------------------
444
     * Parse .TAB file field defs and build FeatureDefn (only in read access)
445
     *----------------------------------------------------------------*/
446
2.30k
    if ((m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite) &&
447
2.07k
        ParseTABFileFields() != 0)
448
125
    {
449
        // Failed... an error has already been reported, just return.
450
125
        CPLFree(pszTmpFname);
451
125
        Close();
452
125
        if (bTestOpenNoError)
453
125
            CPLErrorReset();
454
455
125
        return -1;
456
125
    }
457
458
    /*-----------------------------------------------------------------
459
     * Open .MAP (and .ID) file
460
     * Note that the .MAP and .ID files are optional.  Failure to open them
461
     * is not an error... it simply means that all features will be returned
462
     * with NONE geometry.
463
     *----------------------------------------------------------------*/
464
2.18k
    bool bUpperCase = false;
465
2.18k
    if (nFnameLen > 4 && strcmp(pszTmpFname + nFnameLen - 4, ".DAT") == 0)
466
0
    {
467
0
        bUpperCase = true;
468
0
        strcpy(pszTmpFname + nFnameLen - 4, ".MAP");
469
0
    }
470
2.18k
    else
471
2.18k
        strcpy(pszTmpFname + nFnameLen - 4, ".map");
472
473
2.18k
#ifndef _WIN32
474
2.18k
    TABAdjustFilenameExtension(pszTmpFname);
475
2.18k
#endif
476
477
2.18k
    m_poMAPFile = new TABMAPFile(oEncoding);
478
2.18k
    if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
479
1.95k
    {
480
        /*-------------------------------------------------------------
481
         * Read access: .MAP/.ID are optional... try to open but return
482
         * no error if files do not exist.
483
         *------------------------------------------------------------*/
484
1.95k
        if (m_poMAPFile->Open(pszTmpFname, eAccess, TRUE) < 0)
485
78
        {
486
            // File exists, but Open Failed...
487
            // we have to produce an error message
488
78
            if (!bTestOpenNoError)
489
0
                CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s",
490
0
                         pszTmpFname);
491
78
            else
492
78
                CPLErrorReset();
493
494
78
            CPLFree(pszTmpFname);
495
78
            Close();
496
78
            return -1;
497
78
        }
498
499
        /*-------------------------------------------------------------
500
         * Set geometry type if the geometry objects are uniform.
501
         *------------------------------------------------------------*/
502
1.87k
        int numPoints = 0, numRegions = 0, numTexts = 0, numLines = 0;
503
504
1.87k
        GetFeatureCountByType(numPoints, numLines, numRegions, numTexts);
505
506
1.87k
        if (numPoints >= 0 && numTexts >= 0 && numPoints < INT_MAX - numTexts)
507
1.64k
            numPoints += numTexts;
508
1.87k
        if (numPoints > 0 && numLines == 0 && numRegions == 0)
509
119
            m_poDefn->SetGeomType(wkbPoint);
510
1.75k
        else if (numPoints == 0 && numLines > 0 && numRegions == 0)
511
17
            m_poDefn->SetGeomType(wkbLineString);
512
1.73k
        else if (m_eAccessMode == TABRead && numPoints == 0 && numLines == 0 &&
513
1.03k
                 numRegions == 0)
514
            /* No geometries present; this is an aspatial dataset */
515
739
            m_poDefn->SetGeomType(wkbNone);
516
998
        else
517
998
        {
518
            /* we leave it unknown indicating a mixture */
519
998
        }
520
1.87k
    }
521
231
    else if (m_poMAPFile->Open(pszTmpFname, eAccess, FALSE,
522
231
                               nBlockSizeForCreate) != 0)
523
0
    {
524
        // Open Failed for write...
525
        // an error has already been reported, just return.
526
527
0
        m_poMAPFile->Close();
528
0
        delete m_poMAPFile;
529
0
        m_poMAPFile = nullptr;
530
531
0
        CPLFree(pszTmpFname);
532
0
        Close();
533
0
        if (bTestOpenNoError)
534
0
            CPLErrorReset();
535
536
0
        return -1;
537
0
    }
538
539
    /*-----------------------------------------------------------------
540
     * Set BOUNDS metadata
541
     *----------------------------------------------------------------*/
542
2.10k
    {
543
2.10k
        OGREnvelope e;
544
2.10k
        GetBounds(e.MinX, e.MinY, e.MaxX, e.MaxY, 1);
545
2.10k
        const char *pszBounds = CPLSPrintf("%.17g,%.17g,%.17g,%.17g", e.MinX,
546
2.10k
                                           e.MinY, e.MaxX, e.MaxY);
547
2.10k
        SetMetadataItem("BOUNDS", pszBounds);
548
2.10k
    }
549
550
    /*-----------------------------------------------------------------
551
     * Initializing the attribute index (.IND) support
552
     *----------------------------------------------------------------*/
553
2.10k
    bool bHasIndex = false;
554
555
2.10k
    CPLXMLNode *psRoot =
556
2.10k
        CPLCreateXMLNode(nullptr, CXT_Element, "OGRMILayerAttrIndex");
557
2.10k
    const OGRFeatureDefn *poLayerDefn = GetLayerDefn();
558
8.27k
    for (int iField = 0; iField < poLayerDefn->GetFieldCount(); iField++)
559
6.64k
    {
560
6.64k
        int iIndexIndex = GetFieldIndexNumber(iField);
561
6.64k
        if (iIndexIndex > 0)
562
1.35k
        {
563
1.35k
            if (!bHasIndex)
564
1.35k
            {
565
1.35k
                const std::string osIndFilename =
566
1.35k
                    CPLFormCIFilenameSafe(CPLGetPathSafe(pszFname).c_str(),
567
1.35k
                                          CPLGetBasenameSafe(pszFname).c_str(),
568
1.35k
                                          (bUpperCase) ? "IND" : "ind");
569
1.35k
                VSIStatBufL sStat;
570
1.35k
                if (VSIStatL(osIndFilename.c_str(), &sStat) == 0)
571
884
                {
572
884
                    CPLCreateXMLElementAndValue(psRoot, "MIIDFilename",
573
884
                                                osIndFilename.c_str());
574
884
                }
575
466
                else
576
466
                {
577
466
                    CPLDebug("MITAB",
578
466
                             "At least one field is supposed to be indexed, "
579
466
                             "but index file is missing");
580
466
                    break;
581
466
                }
582
1.35k
            }
583
584
884
            CPLXMLNode *psIndex =
585
884
                CPLCreateXMLNode(psRoot, CXT_Element, "OGRMIAttrIndex");
586
884
            CPLCreateXMLElementAndValue(psIndex, "FieldIndex",
587
884
                                        CPLSPrintf("%d", iField));
588
884
            CPLCreateXMLElementAndValue(
589
884
                psIndex, "FieldName",
590
884
                poLayerDefn->GetFieldDefn(iField)->GetNameRef());
591
884
            CPLCreateXMLElementAndValue(psIndex, "IndexIndex",
592
884
                                        CPLSPrintf("%d", iIndexIndex));
593
884
            bHasIndex = true;
594
884
        }
595
6.64k
    }
596
597
2.10k
    if (bHasIndex)
598
884
    {
599
884
        char *pszRawXML = CPLSerializeXMLTree(psRoot);
600
884
        InitializeIndexSupport(pszRawXML);
601
884
        CPLFree(pszRawXML);
602
884
    }
603
604
2.10k
    CPLDestroyXMLNode(psRoot);
605
606
2.10k
    CPLFree(pszTmpFname);
607
2.10k
    pszTmpFname = nullptr;
608
609
2.10k
    if (m_poDefn != nullptr && m_eAccessMode != TABWrite &&
610
1.87k
        m_poDefn->GetGeomFieldCount() != 0)
611
1.13k
        m_poDefn->GetGeomFieldDefn(0)->SetSpatialRef(GetSpatialRef());
612
613
2.10k
    if (m_poDefn)
614
2.10k
        m_poDefn->Seal(/* bSealFields = */ true);
615
616
2.10k
    return 0;
617
2.18k
}
618
619
/**********************************************************************
620
 *                   TABFile::ParseTABFileFirstPass()
621
 *
622
 * Do a first pass in the TAB header file to establish the table type, etc.
623
 * and store any useful information into class members.
624
 *
625
 * This private method should be used only during the Open() call.
626
 *
627
 * Returns 0 on success, -1 on error.
628
 **********************************************************************/
629
int TABFile::ParseTABFileFirstPass(GBool bTestOpenNoError)
630
2.54k
{
631
2.54k
    int iLine, numLines, numFields = 0;
632
2.54k
    char **papszTok = nullptr;
633
2.54k
    GBool bInsideTableDef = FALSE, bFoundTableFields = FALSE;
634
635
2.54k
    if (m_eAccessMode == TABWrite)
636
0
    {
637
0
        CPLError(CE_Failure, CPLE_NotSupported,
638
0
                 "ParseTABFile() can be used only with Read access.");
639
0
        return -1;
640
0
    }
641
642
2.54k
    numLines = CSLCount(m_papszTABFile);
643
644
1.94M
    for (iLine = 0; iLine < numLines; iLine++)
645
1.94M
    {
646
        /*-------------------------------------------------------------
647
         * Tokenize the next .TAB line, and check first keyword
648
         *------------------------------------------------------------*/
649
1.94M
        CSLDestroy(papszTok);
650
1.94M
        papszTok = CSLTokenizeStringComplex(m_papszTABFile[iLine], " \t(),;",
651
1.94M
                                            TRUE, FALSE);
652
1.94M
        if (CSLCount(papszTok) < 2)
653
1.77M
            continue;  // All interesting lines have at least 2 tokens
654
655
165k
        if (EQUAL(papszTok[0], "!version"))
656
9.45k
        {
657
9.45k
            m_nVersion = atoi(papszTok[1]);
658
9.45k
            if (m_nVersion == 100)
659
474
            {
660
                /* Version 100 files contain only the fields definition,
661
                 * so we set default values for the other params.
662
                 */
663
474
                bInsideTableDef = TRUE;
664
474
                SetCharset("Neutral");
665
474
                m_eTableType = TABTableNative;
666
474
            }
667
9.45k
        }
668
155k
        else if (EQUAL(papszTok[0], "!edit_version"))
669
0
        {
670
            /* Sometimes, V450 files have version 300 + edit_version 450
671
             * for us version and edit_version are the same
672
             */
673
0
            m_nVersion = atoi(papszTok[1]);
674
0
        }
675
155k
        else if (EQUAL(papszTok[0], "!charset"))
676
7.88k
        {
677
7.88k
            SetCharset(papszTok[1]);
678
7.88k
        }
679
147k
        else if (EQUAL(papszTok[0], "Definition") &&
680
8.59k
                 EQUAL(papszTok[1], "Table"))
681
5.18k
        {
682
5.18k
            bInsideTableDef = TRUE;
683
5.18k
        }
684
142k
        else if (bInsideTableDef && !bFoundTableFields &&
685
34.4k
                 (EQUAL(papszTok[0], "Type") || EQUAL(papszTok[0], "FORMAT:")))
686
2.76k
        {
687
2.76k
            if (EQUAL(papszTok[1], "NATIVE") || EQUAL(papszTok[1], "LINKED"))
688
2.76k
                m_eTableType = TABTableNative;
689
5
            else if (EQUAL(papszTok[1], "DBF"))
690
0
                m_eTableType = TABTableDBF;
691
5
            else
692
5
            {
693
                // Type=ACCESS, or other unsupported type... cannot open!
694
5
                if (!bTestOpenNoError)
695
0
                    CPLError(CE_Failure, CPLE_NotSupported,
696
0
                             "Unsupported table type '%s' in file %s.  "
697
0
                             "This type of .TAB file cannot be read by this "
698
0
                             "library.",
699
0
                             papszTok[1], m_pszFname);
700
5
                CSLDestroy(papszTok);
701
5
                return -1;
702
5
            }
703
2.76k
        }
704
139k
        else if (bInsideTableDef && !bFoundTableFields &&
705
31.6k
                 EQUAL(papszTok[0], "Description"))
706
0
        {
707
0
            auto osDescription = GetTabDescription(m_papszTABFile[iLine]);
708
0
            if (!osDescription.empty())
709
0
            {
710
0
                const char *pszEncoding = GetEncoding();
711
0
                if (pszEncoding == nullptr || EQUAL(pszEncoding, ""))
712
0
                {
713
0
                    std::shared_ptr<char> oUnescapedDescription(
714
0
                        UnescapeString(osDescription.c_str()), CPLFree);
715
0
                    IMapInfoFile::SetMetadataItem(DESCRIPTION_KEY,
716
0
                                                  oUnescapedDescription.get());
717
0
                }
718
0
                else
719
0
                {
720
0
                    std::shared_ptr<char> oEncodedDescription(
721
0
                        CPLRecode(osDescription.c_str(), pszEncoding,
722
0
                                  CPL_ENC_UTF8),
723
0
                        CPLFree);
724
725
0
                    std::shared_ptr<char> oUnescapedDescription(
726
0
                        UnescapeString(oEncodedDescription.get()), CPLFree);
727
0
                    IMapInfoFile::SetMetadataItem(DESCRIPTION_KEY,
728
0
                                                  oUnescapedDescription.get());
729
0
                }
730
0
            }
731
0
        }
732
139k
        else if (bInsideTableDef && !bFoundTableFields &&
733
31.6k
                 (EQUAL(papszTok[0], "Fields") ||
734
29.4k
                  EQUAL(papszTok[0], "FIELDS:")))
735
2.26k
        {
736
            /*---------------------------------------------------------
737
             * We found the list of table fields
738
             * Just remember number of fields... the field types will be
739
             * parsed inside ParseTABFileFields() later...
740
             *--------------------------------------------------------*/
741
2.26k
            bFoundTableFields = TRUE;
742
2.26k
            numFields = atoi(papszTok[1]);
743
744
2.26k
            if (numFields < 1 || numFields > 2048 ||
745
2.25k
                iLine + numFields >= numLines)
746
24
            {
747
24
                if (!bTestOpenNoError)
748
0
                    CPLError(
749
0
                        CE_Failure, CPLE_FileIO,
750
0
                        "Invalid number of fields (%s) at line %d in file %s",
751
0
                        papszTok[1], iLine + 1, m_pszFname);
752
753
24
                CSLDestroy(papszTok);
754
24
                return -1;
755
24
            }
756
757
2.23k
            bInsideTableDef = FALSE;
758
2.23k
        } /* end of fields section*/
759
137k
        else
760
137k
        {
761
            // Simply Ignore unrecognized lines
762
137k
        }
763
165k
    }
764
765
2.51k
    CSLDestroy(papszTok);
766
767
2.51k
    if (m_pszCharset == nullptr)
768
714
        SetCharset("Neutral");
769
770
2.51k
    if (numFields == 0)
771
282
    {
772
282
        if (!bTestOpenNoError)
773
0
            CPLError(CE_Failure, CPLE_NotSupported,
774
0
                     "%s contains no table field definition.  "
775
0
                     "This type of .TAB file cannot be read by this library.",
776
0
                     m_pszFname);
777
282
        return -1;
778
282
    }
779
780
2.23k
    return 0;
781
2.51k
}
782
783
/**********************************************************************
784
 *                   TABFile::ParseTABFileFields()
785
 *
786
 * Extract the field definition from the TAB header file, validate
787
 * with what we have in the previously opened .DAT or .DBF file, and
788
 * finally build the m_poDefn OGRFeatureDefn for this dataset.
789
 *
790
 * This private method should be used only during the Open() call and after
791
 * ParseTABFileFirstPass() has been called.
792
 *
793
 * Returns 0 on success, -1 on error.
794
 **********************************************************************/
795
int TABFile::ParseTABFileFields()
796
2.07k
{
797
798
2.07k
    if (m_eAccessMode == TABWrite)
799
0
    {
800
0
        CPLError(CE_Failure, CPLE_NotSupported,
801
0
                 "ParseTABFile() can be used only with Read access.");
802
0
        return -1;
803
0
    }
804
805
2.07k
    char *pszFeatureClassName = TABGetBasename(m_pszFname);
806
2.07k
    m_poDefn = new OGRFeatureDefn(pszFeatureClassName);
807
2.07k
    CPLFree(pszFeatureClassName);
808
    // Ref count defaults to 0... set it to 1
809
2.07k
    m_poDefn->Reference();
810
811
    /*-------------------------------------------------------------
812
     * Scan for fields.
813
     *------------------------------------------------------------*/
814
2.07k
    OGRFieldDefn *poFieldDefn = nullptr;
815
2.07k
    char **papszTok = nullptr;
816
817
2.07k
    const int numLines = CSLCount(m_papszTABFile);
818
60.2k
    for (int iLine = 0; iLine < numLines; iLine++)
819
60.2k
    {
820
        /*-------------------------------------------------------------
821
         * Tokenize the next .TAB line, and check first keyword
822
         *------------------------------------------------------------*/
823
60.2k
        const char *pszStr = m_papszTABFile[iLine];
824
69.4k
        while (*pszStr != '\0' && isspace(static_cast<unsigned char>(*pszStr)))
825
9.25k
            pszStr++;
826
827
60.2k
        if (STARTS_WITH_CI(pszStr, "Fields") && CPLStrnlen(pszStr, 7) >= 7)
828
2.06k
        {
829
            /*---------------------------------------------------------
830
             * We found the list of table fields
831
             *--------------------------------------------------------*/
832
2.06k
            int iField = 0;
833
2.06k
            int numFields = atoi(pszStr + 7);
834
2.06k
            if (numFields < 1 || numFields > 2048 ||
835
2.06k
                iLine + numFields >= numLines)
836
1
            {
837
1
                CPLError(CE_Failure, CPLE_FileIO,
838
1
                         "Invalid number of fields (%s) at line %d in file %s",
839
1
                         pszStr + 7, iLine + 1, m_pszFname);
840
1
                CSLDestroy(papszTok);
841
1
                return -1;
842
1
            }
843
844
            // Alloc the array to keep track of indexed fields
845
2.06k
            m_panIndexNo =
846
2.06k
                static_cast<int *>(CPLCalloc(numFields, sizeof(int)));
847
848
2.06k
            iLine++;
849
2.06k
            poFieldDefn = nullptr;
850
9.41k
            for (iField = 0; iField < numFields; iField++, iLine++)
851
7.46k
            {
852
                /*-----------------------------------------------------
853
                 * For each field definition found in the .TAB:
854
                 * Pass the info to the DAT file object.  It will validate
855
                 * the info with what is found in the .DAT header, and will
856
                 * also use this info later to interpret field values.
857
                 *
858
                 * We also create the OGRFieldDefn at the same time to
859
                 * initialize the OGRFeatureDefn
860
                 *----------------------------------------------------*/
861
7.46k
                CSLDestroy(papszTok);
862
7.46k
                papszTok = CSLTokenizeStringComplex(m_papszTABFile[iLine],
863
7.46k
                                                    " \t(),;", TRUE, FALSE);
864
7.46k
                const int numTok = CSLCount(papszTok);
865
866
7.46k
                CPLAssert(m_poDefn);
867
7.46k
                poFieldDefn = nullptr;
868
869
7.46k
                CPLString osFieldName;
870
7.46k
                if (numTok > 0)
871
7.45k
                {
872
7.45k
                    osFieldName = papszTok[0];
873
7.45k
                    if (strlen(GetEncoding()) > 0)
874
0
                        osFieldName.Recode(GetEncoding(), CPL_ENC_UTF8);
875
7.45k
                }
876
877
7.46k
                int nStatus = -1;
878
7.46k
                if (numTok >= 3 && EQUAL(papszTok[1], "char"))
879
5.03k
                {
880
                    /*-------------------------------------------------
881
                     * CHAR type
882
                     *------------------------------------------------*/
883
5.03k
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
884
5.03k
                        iField, osFieldName, TABFChar, atoi(papszTok[2]), 0);
885
5.03k
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTString);
886
5.03k
                    poFieldDefn->SetWidth(atoi(papszTok[2]));
887
5.03k
                }
888
2.43k
                else if (numTok >= 2 && EQUAL(papszTok[1], "integer"))
889
2.24k
                {
890
                    /*-------------------------------------------------
891
                     * INTEGER type
892
                     *------------------------------------------------*/
893
2.24k
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
894
2.24k
                        iField, osFieldName, TABFInteger, 0, 0);
895
2.24k
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTInteger);
896
2.24k
                    if (numTok > 2 && atoi(papszTok[2]) > 0)
897
1
                        poFieldDefn->SetWidth(atoi(papszTok[2]));
898
2.24k
                }
899
190
                else if (numTok >= 2 && EQUAL(papszTok[1], "smallint"))
900
0
                {
901
                    /*-------------------------------------------------
902
                     * SMALLINT type
903
                     *------------------------------------------------*/
904
0
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
905
0
                        iField, osFieldName, TABFSmallInt, 0, 0);
906
0
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTInteger);
907
0
                    if (numTok > 2 && atoi(papszTok[2]) > 0)
908
0
                        poFieldDefn->SetWidth(atoi(papszTok[2]));
909
0
                }
910
190
                else if (numTok >= 2 && EQUAL(papszTok[1], "largeint"))
911
0
                {
912
                    /*-------------------------------------------------
913
                     * LargeInt type
914
                     *------------------------------------------------*/
915
0
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
916
0
                        iField, osFieldName, TABFLargeInt, 0, 0);
917
0
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTInteger64);
918
0
                    if (numTok > 2 && atoi(papszTok[2]) > 0)
919
0
                        poFieldDefn->SetWidth(atoi(papszTok[2]));
920
0
                }
921
190
                else if (numTok >= 4 && EQUAL(papszTok[1], "decimal"))
922
82
                {
923
                    /*-------------------------------------------------
924
                     * DECIMAL type
925
                     *------------------------------------------------*/
926
82
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
927
82
                        iField, osFieldName, TABFDecimal, atoi(papszTok[2]),
928
82
                        atoi(papszTok[3]));
929
82
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTReal);
930
82
                    poFieldDefn->SetWidth(atoi(papszTok[2]));
931
82
                    poFieldDefn->SetPrecision(atoi(papszTok[3]));
932
82
                }
933
108
                else if (numTok >= 2 && EQUAL(papszTok[1], "float"))
934
8
                {
935
                    /*-------------------------------------------------
936
                     * FLOAT type
937
                     *------------------------------------------------*/
938
8
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
939
8
                        iField, osFieldName, TABFFloat, 0, 0);
940
8
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTReal);
941
8
                }
942
100
                else if (numTok >= 2 && EQUAL(papszTok[1], "date"))
943
32
                {
944
                    /*-------------------------------------------------
945
                     * DATE type (returned as a string: "DD/MM/YYYY")
946
                     *------------------------------------------------*/
947
32
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
948
32
                        iField, osFieldName, TABFDate, 0, 0);
949
32
                    poFieldDefn = new OGRFieldDefn(osFieldName,
950
32
#ifdef MITAB_USE_OFTDATETIME
951
32
                                                   OFTDate);
952
#else
953
                                                   OFTString);
954
#endif
955
32
                    poFieldDefn->SetWidth(10);
956
32
                }
957
68
                else if (numTok >= 2 && EQUAL(papszTok[1], "time"))
958
18
                {
959
                    /*-------------------------------------------------
960
                     * TIME type (returned as a string: "HH:MM:SS")
961
                     *------------------------------------------------*/
962
18
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
963
18
                        iField, osFieldName, TABFTime, 0, 0);
964
18
                    poFieldDefn = new OGRFieldDefn(osFieldName,
965
18
#ifdef MITAB_USE_OFTDATETIME
966
18
                                                   OFTTime);
967
#else
968
                                                   OFTString);
969
#endif
970
18
                    poFieldDefn->SetWidth(9);
971
18
                }
972
50
                else if (numTok >= 2 && EQUAL(papszTok[1], "datetime"))
973
0
                {
974
                    /*-------------------------------------------------
975
                     * DATETIME type (returned as a string: "DD/MM/YYYY
976
                     *HH:MM:SS")
977
                     *------------------------------------------------*/
978
0
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
979
0
                        iField, osFieldName, TABFDateTime, 0, 0);
980
0
                    poFieldDefn = new OGRFieldDefn(osFieldName,
981
0
#ifdef MITAB_USE_OFTDATETIME
982
0
                                                   OFTDateTime);
983
#else
984
                                                   OFTString);
985
#endif
986
0
                    poFieldDefn->SetWidth(19);
987
0
                }
988
50
                else if (numTok >= 2 && EQUAL(papszTok[1], "logical"))
989
0
                {
990
                    /*-------------------------------------------------
991
                     * LOGICAL type (value "T" or "F")
992
                     *------------------------------------------------*/
993
0
                    nStatus = m_poDATFile->ValidateFieldInfoFromTAB(
994
0
                        iField, osFieldName, TABFLogical, 0, 0);
995
0
                    poFieldDefn = new OGRFieldDefn(osFieldName, OFTInteger);
996
0
                    poFieldDefn->SetSubType(OFSTBoolean);
997
0
                    poFieldDefn->SetWidth(1);
998
0
                }
999
50
                else
1000
50
                {
1001
                    // Unrecognized field type or line corrupt
1002
50
                }
1003
1004
7.46k
                if (nStatus != 0)
1005
117
                {
1006
117
                    CPLError(CE_Failure, CPLE_FileIO,
1007
117
                             "Failed to parse field definition at line %d in "
1008
117
                             "file %s",
1009
117
                             iLine + 1, m_pszFname);
1010
117
                    CSLDestroy(papszTok);
1011
117
                    delete poFieldDefn;
1012
117
                    return -1;
1013
117
                }
1014
                /*-----------------------------------------------------
1015
                 * Keep track of index number if present
1016
                 *----------------------------------------------------*/
1017
7.34k
                if (numTok >= 4 && EQUAL(papszTok[numTok - 2], "index"))
1018
1.50k
                {
1019
1.50k
                    m_panIndexNo[iField] = atoi(papszTok[numTok - 1]);
1020
1.50k
                }
1021
5.84k
                else
1022
5.84k
                {
1023
5.84k
                    m_panIndexNo[iField] = 0;
1024
5.84k
                }
1025
1026
                /*-----------------------------------------------------
1027
                 * Add the FieldDefn to the FeatureDefn and continue with
1028
                 * the next one.
1029
                 *----------------------------------------------------*/
1030
7.34k
                m_poDefn->AddFieldDefn(poFieldDefn);
1031
7.34k
                m_oSetFields.insert(
1032
7.34k
                    CPLString(poFieldDefn->GetNameRef()).toupper());
1033
                // AddFieldDenf() takes a copy, so we delete the original
1034
7.34k
                delete poFieldDefn;
1035
7.34k
                poFieldDefn = nullptr;
1036
7.34k
            }
1037
1038
            /*---------------------------------------------------------
1039
             * OK, we're done... end the loop now.
1040
             *--------------------------------------------------------*/
1041
1.95k
            break;
1042
2.06k
        } /* end of fields section*/
1043
58.1k
        else
1044
58.1k
        {
1045
            // Simply Ignore unrecognized lines
1046
58.1k
        }
1047
60.2k
    }
1048
1049
1.95k
    CSLDestroy(papszTok);
1050
1051
1.95k
    if (m_poDefn->GetFieldCount() == 0)
1052
7
    {
1053
7
        CPLError(CE_Failure, CPLE_NotSupported,
1054
7
                 "%s contains no table field definition.  "
1055
7
                 "This type of .TAB file cannot be read by this library.",
1056
7
                 m_pszFname);
1057
7
        return -1;
1058
7
    }
1059
1060
1.95k
    return 0;
1061
1.95k
}
1062
1063
/**********************************************************************
1064
 *                   TABFile::WriteTABFile()
1065
 *
1066
 * Generate the .TAB file using mainly the attribute fields definition.
1067
 *
1068
 *
1069
 * Returns 0 on success, -1 on error.
1070
 **********************************************************************/
1071
int TABFile::WriteTABFile()
1072
457
{
1073
457
    if (!m_bNeedTABRewrite)
1074
226
    {
1075
226
        return 0;
1076
226
    }
1077
1078
231
    if (m_poMAPFile == nullptr || m_eAccessMode == TABRead)
1079
0
    {
1080
0
        CPLError(CE_Failure, CPLE_NotSupported,
1081
0
                 "WriteTABFile() can be used only with Write access.");
1082
0
        return -1;
1083
0
    }
1084
1085
    // First update file version number...
1086
231
    int nMapObjVersion = m_poMAPFile->GetMinTABFileVersion();
1087
231
    m_nVersion = std::max(m_nVersion, nMapObjVersion);
1088
1089
231
    VSILFILE *fp = VSIFOpenL(m_pszFname, "wt");
1090
231
    if (fp != nullptr)
1091
231
    {
1092
231
        VSIFPrintfL(fp, "!table\n");
1093
231
        VSIFPrintfL(fp, "!version %d\n", m_nVersion);
1094
231
        VSIFPrintfL(fp, "!charset %s\n", m_pszCharset);
1095
231
        VSIFPrintfL(fp, "\n");
1096
1097
231
        if (m_poDefn && m_poDefn->GetFieldCount() > 0)
1098
223
        {
1099
223
            VSIFPrintfL(fp, "Definition Table\n");
1100
223
            VSIFPrintfL(fp, "  Type NATIVE Charset \"%s\"\n", m_pszCharset);
1101
223
            const char *pszDescription = GetMetadataItem(DESCRIPTION_KEY);
1102
223
            if (nullptr != pszDescription)
1103
0
            {
1104
0
                std::shared_ptr<char> oEscapedDescription(
1105
0
                    EscapeString(pszDescription, true), CPLFree);
1106
0
                const char *pszEncoding = GetEncoding();
1107
0
                if (nullptr == pszEncoding || EQUAL(pszEncoding, ""))
1108
0
                {
1109
0
                    VSIFPrintfL(fp, "  Description \"%s\"\n",
1110
0
                                oEscapedDescription.get());
1111
0
                }
1112
0
                else
1113
0
                {
1114
0
                    std::shared_ptr<char> oEncodedDescription(
1115
0
                        CPLRecode(oEscapedDescription.get(), CPL_ENC_UTF8,
1116
0
                                  pszEncoding),
1117
0
                        CPLFree);
1118
0
                    VSIFPrintfL(fp, "  Description \"%s\"\n",
1119
0
                                oEncodedDescription.get());
1120
0
                }
1121
0
            }
1122
223
            VSIFPrintfL(fp, "  Fields %d\n", m_poDefn->GetFieldCount());
1123
1124
12.6k
            for (int iField = 0; iField < m_poDefn->GetFieldCount(); iField++)
1125
12.4k
            {
1126
12.4k
                const char *pszFieldType = nullptr;
1127
12.4k
                OGRFieldDefn *poFieldDefn = m_poDefn->GetFieldDefn(iField);
1128
12.4k
                switch (GetNativeFieldType(iField))
1129
12.4k
                {
1130
12.4k
                    case TABFChar:
1131
12.4k
                        pszFieldType =
1132
12.4k
                            CPLSPrintf("Char (%d)", poFieldDefn->GetWidth());
1133
12.4k
                        break;
1134
0
                    case TABFDecimal:
1135
0
                        pszFieldType = CPLSPrintf("Decimal (%d,%d)",
1136
0
                                                  poFieldDefn->GetWidth(),
1137
0
                                                  poFieldDefn->GetPrecision());
1138
0
                        break;
1139
5
                    case TABFInteger:
1140
5
                        if (poFieldDefn->GetWidth() == 0)
1141
5
                            pszFieldType = "Integer";
1142
0
                        else
1143
0
                            pszFieldType = CPLSPrintf("Integer (%d)",
1144
0
                                                      poFieldDefn->GetWidth());
1145
5
                        break;
1146
0
                    case TABFSmallInt:
1147
0
                        if (poFieldDefn->GetWidth() == 0)
1148
0
                            pszFieldType = "SmallInt";
1149
0
                        else
1150
0
                            pszFieldType = CPLSPrintf("SmallInt (%d)",
1151
0
                                                      poFieldDefn->GetWidth());
1152
0
                        break;
1153
0
                    case TABFLargeInt:
1154
0
                        if (poFieldDefn->GetWidth() == 0)
1155
0
                            pszFieldType = "LargeInt";
1156
0
                        else
1157
0
                            pszFieldType = CPLSPrintf("LargeInt (%d)",
1158
0
                                                      poFieldDefn->GetWidth());
1159
0
                        break;
1160
5
                    case TABFFloat:
1161
5
                        pszFieldType = "Float";
1162
5
                        break;
1163
0
                    case TABFLogical:
1164
0
                        pszFieldType = "Logical";
1165
0
                        break;
1166
0
                    case TABFDate:
1167
0
                        pszFieldType = "Date";
1168
0
                        break;
1169
0
                    case TABFTime:
1170
0
                        pszFieldType = "Time";
1171
0
                        break;
1172
0
                    case TABFDateTime:
1173
0
                        pszFieldType = "DateTime";
1174
0
                        break;
1175
0
                    default:
1176
                        // Unsupported field type!!!  This should never happen.
1177
0
                        CPLError(CE_Failure, CPLE_AssertionFailed,
1178
0
                                 "WriteTABFile(): Unsupported field type");
1179
0
                        VSIFCloseL(fp);
1180
0
                        return -1;
1181
12.4k
                }
1182
1183
12.4k
                CPLString osFieldName(poFieldDefn->GetNameRef());
1184
1185
12.4k
                if (strlen(GetEncoding()) > 0)
1186
0
                    osFieldName.Recode(CPL_ENC_UTF8, GetEncoding());
1187
1188
12.4k
                char *pszCleanName = TABCleanFieldName(
1189
12.4k
                    osFieldName, GetEncoding(), m_bStrictLaundering);
1190
12.4k
                osFieldName = pszCleanName;
1191
12.4k
                CPLFree(pszCleanName);
1192
1193
12.4k
                if (GetFieldIndexNumber(iField) == 0)
1194
12.4k
                {
1195
12.4k
                    VSIFPrintfL(fp, "    %s %s ;\n", osFieldName.c_str(),
1196
12.4k
                                pszFieldType);
1197
12.4k
                }
1198
0
                else
1199
0
                {
1200
0
                    VSIFPrintfL(fp, "    %s %s Index %d ;\n",
1201
0
                                osFieldName.c_str(), pszFieldType,
1202
0
                                GetFieldIndexNumber(iField));
1203
0
                }
1204
12.4k
            }
1205
223
        }
1206
8
        else
1207
8
        {
1208
8
            VSIFPrintfL(fp, "Definition Table\n");
1209
8
            VSIFPrintfL(fp, "  Type NATIVE Charset \"%s\"\n", m_pszCharset);
1210
8
            VSIFPrintfL(fp, "  Fields 1\n");
1211
8
            VSIFPrintfL(fp, "    FID Integer ;\n");
1212
8
        }
1213
1214
231
        VSIFCloseL(fp);
1215
1216
231
        m_bNeedTABRewrite = FALSE;
1217
231
    }
1218
0
    else
1219
0
    {
1220
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create file `%s'",
1221
0
                 m_pszFname);
1222
0
        return -1;
1223
0
    }
1224
1225
231
    return 0;
1226
231
}
1227
1228
/**********************************************************************
1229
 *                   TABFile::Close()
1230
 *
1231
 * Close current file, and release all memory used.
1232
 *
1233
 * Returns 0 on success, -1 on error.
1234
 **********************************************************************/
1235
int TABFile::Close()
1236
3.17k
{
1237
3.17k
    CPLErrorReset();
1238
1239
    // Commit the latest changes to the file...
1240
1241
3.17k
    if (m_poMAPFile)
1242
2.18k
    {
1243
        // In Write access, it is time to write the .TAB file.
1244
2.18k
        if (m_eAccessMode != TABRead)
1245
231
        {
1246
231
            WriteTABFile();
1247
231
        }
1248
1249
2.18k
        m_poMAPFile->Close();
1250
2.18k
        delete m_poMAPFile;
1251
2.18k
        m_poMAPFile = nullptr;
1252
2.18k
    }
1253
1254
3.17k
    if (m_poDATFile)
1255
2.46k
    {
1256
2.46k
        m_poDATFile->Close();
1257
2.46k
        delete m_poDATFile;
1258
2.46k
        m_poDATFile = nullptr;
1259
2.46k
    }
1260
1261
3.17k
    if (m_poINDFile)
1262
315
    {
1263
315
        m_poINDFile->Close();
1264
315
        delete m_poINDFile;
1265
315
        m_poINDFile = nullptr;
1266
315
    }
1267
1268
3.17k
    if (m_poCurFeature)
1269
153
    {
1270
153
        delete m_poCurFeature;
1271
153
        m_poCurFeature = nullptr;
1272
153
    }
1273
1274
3.17k
    if (m_poDefn)
1275
2.30k
        m_poDefn->Release();
1276
3.17k
    m_poDefn = nullptr;
1277
1278
3.17k
    if (m_poSpatialRef)
1279
1.15k
        m_poSpatialRef->Release();
1280
3.17k
    m_poSpatialRef = nullptr;
1281
1282
3.17k
    CSLDestroy(m_papszTABFile);
1283
3.17k
    m_papszTABFile = nullptr;
1284
1285
3.17k
    CPLFree(m_pszFname);
1286
3.17k
    m_pszFname = nullptr;
1287
1288
3.17k
    CPLFree(m_pszCharset);
1289
3.17k
    m_pszCharset = nullptr;
1290
1291
3.17k
    CPLFree(m_panIndexNo);
1292
3.17k
    m_panIndexNo = nullptr;
1293
1294
3.17k
    CPLFree(m_panMatchingFIDs);
1295
3.17k
    m_panMatchingFIDs = nullptr;
1296
1297
3.17k
    return 0;
1298
3.17k
}
1299
1300
/**********************************************************************
1301
 *                   TABFile::SetQuickSpatialIndexMode()
1302
 *
1303
 * Select "quick spatial index mode".
1304
 *
1305
 * The default behavior of MITAB is to generate an optimized spatial index,
1306
 * but this results in slower write speed.
1307
 *
1308
 * Applications that want faster write speed and do not care
1309
 * about the performance of spatial queries on the resulting file can
1310
 * use SetQuickSpatialIndexMode() to require the creation of a non-optimal
1311
 * spatial index (actually emulating the type of spatial index produced
1312
 * by MITAB before version 1.6.0). In this mode writing files can be
1313
 * about 5 times faster, but spatial queries can be up to 30 times slower.
1314
 *
1315
 * Returns 0 on success, -1 on error.
1316
 **********************************************************************/
1317
int TABFile::SetQuickSpatialIndexMode(GBool bQuickSpatialIndexMode /*=TRUE*/)
1318
0
{
1319
0
    if (m_eAccessMode != TABWrite || m_poMAPFile == nullptr)
1320
0
    {
1321
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
1322
0
                 "SetQuickSpatialIndexMode() failed: file not opened for write "
1323
0
                 "access.");
1324
0
        return -1;
1325
0
    }
1326
1327
0
    return m_poMAPFile->SetQuickSpatialIndexMode(bQuickSpatialIndexMode);
1328
0
}
1329
1330
/**********************************************************************
1331
 *                   TABFile::GetNextFeatureId()
1332
 *
1333
 * Returns feature id that follows nPrevId, or -1 if it is the
1334
 * last feature id.  Pass nPrevId=-1 to fetch the first valid feature id.
1335
 **********************************************************************/
1336
GIntBig TABFile::GetNextFeatureId(GIntBig nPrevId)
1337
28.4k
{
1338
28.4k
    if (m_bLastOpWasWrite)
1339
0
        ResetReading();
1340
28.4k
    m_bLastOpWasRead = TRUE;
1341
1342
28.4k
    if (!CPL_INT64_FITS_ON_INT32(nPrevId))
1343
0
        return -1;
1344
1345
    /*-----------------------------------------------------------------
1346
     * Are we using spatial rather than .ID based traversal?
1347
     *----------------------------------------------------------------*/
1348
28.4k
    if (bUseSpatialTraversal)
1349
0
        return m_poMAPFile->GetNextFeatureId(static_cast<int>(nPrevId));
1350
1351
    /*-----------------------------------------------------------------
1352
     * Should we use an attribute index traversal?
1353
     *----------------------------------------------------------------*/
1354
28.4k
    if (m_poAttrQuery != nullptr)
1355
0
    {
1356
0
        if (m_panMatchingFIDs == nullptr)
1357
0
        {
1358
0
            m_iMatchingFID = 0;
1359
0
            m_panMatchingFIDs =
1360
0
                m_poAttrQuery->EvaluateAgainstIndices(this, nullptr);
1361
0
        }
1362
0
        if (m_panMatchingFIDs != nullptr)
1363
0
        {
1364
0
            if (m_panMatchingFIDs[m_iMatchingFID] == OGRNullFID)
1365
0
                return OGRNullFID;
1366
1367
0
            return m_panMatchingFIDs[m_iMatchingFID++] + 1;
1368
0
        }
1369
0
    }
1370
1371
    /*-----------------------------------------------------------------
1372
     * Establish what the next logical feature ID should be
1373
     *----------------------------------------------------------------*/
1374
28.4k
    int nFeatureId = -1;
1375
1376
28.4k
    if (nPrevId <= 0 && m_nLastFeatureId > 0)
1377
339
        nFeatureId = 1;  // Feature Ids start at 1
1378
28.1k
    else if (nPrevId > 0 && nPrevId < m_nLastFeatureId)
1379
28.0k
        nFeatureId = static_cast<int>(nPrevId) + 1;
1380
18
    else
1381
18
    {
1382
        // This was the last feature
1383
18
        return OGRNullFID;
1384
18
    }
1385
1386
    /*-----------------------------------------------------------------
1387
     * Skip any feature with NONE geometry and a deleted attribute record
1388
     *----------------------------------------------------------------*/
1389
759k
    while (nFeatureId <= m_nLastFeatureId)
1390
759k
    {
1391
759k
        if (m_poMAPFile->MoveToObjId(nFeatureId) != 0 ||
1392
759k
            m_poDATFile->GetRecordBlock(nFeatureId) == nullptr)
1393
277
        {
1394
277
            CPLError(CE_Failure, CPLE_IllegalArg,
1395
277
                     "GetNextFeatureId() failed: unable to set read pointer "
1396
277
                     "to feature id %d",
1397
277
                     nFeatureId);
1398
277
            return -1;
1399
277
        }
1400
1401
        // __TODO__ Add a test here to check if object is deleted,
1402
        // i.e. 0x40 set on object_id in object block
1403
759k
        if (m_poMAPFile->GetCurObjType() != TAB_GEOM_NONE ||
1404
759k
            m_poDATFile->IsCurrentRecordDeleted() == FALSE)
1405
28.1k
        {
1406
            // This feature contains at least a geometry or some attributes...
1407
            // return its id.
1408
28.1k
            return nFeatureId;
1409
28.1k
        }
1410
1411
731k
        nFeatureId++;
1412
731k
    }
1413
1414
    // If we reached this point, then we kept skipping deleted features
1415
    // and stopped when EOF was reached.
1416
5
    return -1;
1417
28.4k
}
1418
1419
/**********************************************************************
1420
 *                   TABFile::GetNextFeatureId_Spatial()
1421
 *
1422
 * Returns feature id that follows nPrevId, or -1 if it is the
1423
 * last feature id, but by traversing the spatial tree instead of the
1424
 * direct object index.  Generally speaking the feature id's will be
1425
 * returned in an unordered fashion.
1426
 **********************************************************************/
1427
int TABFile::GetNextFeatureId_Spatial(int nPrevId)
1428
0
{
1429
0
    if (m_eAccessMode != TABRead)
1430
0
    {
1431
0
        CPLError(
1432
0
            CE_Failure, CPLE_NotSupported,
1433
0
            "GetNextFeatureId_Spatial() can be used only with Read access.");
1434
0
        return -1;
1435
0
    }
1436
1437
0
    if (m_poMAPFile == nullptr)
1438
0
    {
1439
0
        CPLError(
1440
0
            CE_Failure, CPLE_NotSupported,
1441
0
            "GetNextFeatureId_Spatial() requires availability of .MAP file.");
1442
0
        return -1;
1443
0
    }
1444
1445
0
    return m_poMAPFile->GetNextFeatureId(nPrevId);
1446
0
}
1447
1448
/**********************************************************************
1449
 *                   TABFile::GetFeatureRef()
1450
 *
1451
 * Fill and return a TABFeature object for the specified feature id.
1452
 *
1453
 * The returned pointer is a reference to an object owned and maintained
1454
 * by this TABFile object.  It should not be altered or freed by the
1455
 * caller and its contents is guaranteed to be valid only until the next
1456
 * call to GetFeatureRef() or Close().
1457
 *
1458
 * Returns NULL if the specified feature id does not exist of if an
1459
 * error happened.  In any case, CPLError() will have been called to
1460
 * report the reason of the failure.
1461
 *
1462
 * If an unsupported object type is encountered (likely from a newer version
1463
 * of MapInfo) then a valid feature will be returned with a NONE geometry,
1464
 * and a warning will be produced with code TAB_WarningFeatureTypeNotSupported
1465
 * CPLGetLastErrorNo() should be used to detect that case.
1466
 **********************************************************************/
1467
TABFeature *TABFile::GetFeatureRef(GIntBig nFeatureId)
1468
29.8k
{
1469
29.8k
    CPLErrorReset();
1470
1471
    /*-----------------------------------------------------------------
1472
     * Make sure file is opened and Validate feature id by positioning
1473
     * the read pointers for the .MAP and .DAT files to this feature id.
1474
     *----------------------------------------------------------------*/
1475
29.8k
    if (m_poMAPFile == nullptr)
1476
0
    {
1477
0
        CPLError(CE_Failure, CPLE_IllegalArg,
1478
0
                 "GetFeatureRef() failed: file is not opened!");
1479
0
        return nullptr;
1480
0
    }
1481
1482
29.8k
    if (m_bLastOpWasWrite)
1483
0
        ResetReading();
1484
29.8k
    m_bLastOpWasRead = TRUE;
1485
1486
29.8k
    if (nFeatureId <= 0 || nFeatureId > m_nLastFeatureId ||
1487
29.6k
        m_poMAPFile->MoveToObjId(static_cast<int>(nFeatureId)) != 0 ||
1488
29.6k
        m_poDATFile->GetRecordBlock(static_cast<int>(nFeatureId)) == nullptr)
1489
453
    {
1490
        //     CPLError(CE_Failure, CPLE_IllegalArg,
1491
        //    "GetFeatureRef() failed: invalid feature id %d",
1492
        //    nFeatureId);
1493
453
        return nullptr;
1494
453
    }
1495
1496
29.3k
    if (m_poDATFile->IsCurrentRecordDeleted())
1497
1.23k
    {
1498
1.23k
        if (m_poMAPFile->GetCurObjType() != TAB_GEOM_NONE)
1499
1
        {
1500
1
            CPLError(
1501
1
                CE_Failure, CPLE_AppDefined,
1502
1
                "Valid .MAP record " CPL_FRMT_GIB
1503
1
                " found, but .DAT is marked as deleted. File likely corrupt",
1504
1
                nFeatureId);
1505
1
        }
1506
1.23k
        return nullptr;
1507
1.23k
    }
1508
1509
    /*-----------------------------------------------------------------
1510
     * Flush current feature object
1511
     * __TODO__ try to reuse if it is already of the right type
1512
     *----------------------------------------------------------------*/
1513
28.1k
    if (m_poCurFeature)
1514
10.0k
    {
1515
10.0k
        delete m_poCurFeature;
1516
10.0k
        m_poCurFeature = nullptr;
1517
10.0k
    }
1518
1519
    /*-----------------------------------------------------------------
1520
     * Create new feature object of the right type
1521
     * Unsupported object types are returned as raw TABFeature (i.e. NONE
1522
     * geometry)
1523
     *----------------------------------------------------------------*/
1524
28.1k
    m_poCurFeature = TABFeature::CreateFromMapInfoType(
1525
28.1k
        m_poMAPFile->GetCurObjType(), m_poDefn);
1526
1527
    /*-----------------------------------------------------------------
1528
     * Read fields from the .DAT file
1529
     * GetRecordBlock() has already been called above...
1530
     *----------------------------------------------------------------*/
1531
28.1k
    if (m_poCurFeature->ReadRecordFromDATFile(m_poDATFile) != 0)
1532
0
    {
1533
0
        delete m_poCurFeature;
1534
0
        m_poCurFeature = nullptr;
1535
0
        return nullptr;
1536
0
    }
1537
1538
    /*-----------------------------------------------------------------
1539
     * Read geometry from the .MAP file
1540
     * MoveToObjId() has already been called above...
1541
     *----------------------------------------------------------------*/
1542
28.1k
    TABMAPObjHdr *poObjHdr = TABMAPObjHdr::NewObj(m_poMAPFile->GetCurObjType(),
1543
28.1k
                                                  m_poMAPFile->GetCurObjId());
1544
    // Note that poObjHdr==NULL is a valid case if geometry type is NONE
1545
1546
28.1k
    if ((poObjHdr && poObjHdr->ReadObj(m_poMAPFile->GetCurObjBlock()) != 0) ||
1547
28.1k
        m_poCurFeature->ReadGeometryFromMAPFile(m_poMAPFile, poObjHdr) != 0)
1548
33
    {
1549
33
        delete m_poCurFeature;
1550
33
        m_poCurFeature = nullptr;
1551
33
        if (poObjHdr)
1552
33
            delete poObjHdr;
1553
33
        return nullptr;
1554
33
    }
1555
28.1k
    if (poObjHdr)  // May be NULL if feature geometry type is NONE
1556
28.1k
        delete poObjHdr;
1557
1558
28.1k
    m_nCurFeatureId = nFeatureId;
1559
28.1k
    m_poCurFeature->SetFID(m_nCurFeatureId);
1560
1561
28.1k
    m_poCurFeature->SetRecordDeleted(m_poDATFile->IsCurrentRecordDeleted());
1562
1563
28.1k
    return m_poCurFeature;
1564
28.1k
}
1565
1566
/**********************************************************************
1567
 *                   TABFile::DeleteFeature()
1568
 *
1569
 * Standard OGR DeleteFeature implementation.
1570
 **********************************************************************/
1571
OGRErr TABFile::DeleteFeature(GIntBig nFeatureId)
1572
0
{
1573
0
    CPLErrorReset();
1574
1575
0
    if (m_eAccessMode == TABRead)
1576
0
    {
1577
0
        CPLError(CE_Failure, CPLE_NotSupported,
1578
0
                 "DeleteFeature() cannot be used in read-only access.");
1579
0
        return OGRERR_FAILURE;
1580
0
    }
1581
1582
    /*-----------------------------------------------------------------
1583
     * Make sure file is opened and establish new feature id.
1584
     *----------------------------------------------------------------*/
1585
0
    if (m_poMAPFile == nullptr)
1586
0
    {
1587
0
        CPLError(CE_Failure, CPLE_IllegalArg,
1588
0
                 "DeleteFeature() failed: file is not opened!");
1589
0
        return OGRERR_FAILURE;
1590
0
    }
1591
1592
0
    if (m_bLastOpWasWrite)
1593
0
        ResetReading();
1594
1595
0
    if (nFeatureId <= 0 || nFeatureId > m_nLastFeatureId ||
1596
0
        m_poMAPFile->MoveToObjId(static_cast<int>(nFeatureId)) != 0 ||
1597
0
        m_poDATFile->GetRecordBlock(static_cast<int>(nFeatureId)) == nullptr)
1598
0
    {
1599
        /*CPLError(CE_Failure, CPLE_IllegalArg,
1600
                 "DeleteFeature() failed: invalid feature id " CPL_FRMT_GIB,
1601
                 nFeatureId);*/
1602
0
        return OGRERR_NON_EXISTING_FEATURE;
1603
0
    }
1604
1605
0
    if (m_poDATFile->IsCurrentRecordDeleted())
1606
0
    {
1607
        /*CPLError(CE_Failure, CPLE_IllegalArg,
1608
                 "DeleteFeature() failed: record is already deleted!");*/
1609
0
        return OGRERR_NON_EXISTING_FEATURE;
1610
0
    }
1611
1612
0
    if (m_poCurFeature)
1613
0
    {
1614
0
        delete m_poCurFeature;
1615
0
        m_poCurFeature = nullptr;
1616
0
    }
1617
1618
0
    if (m_poMAPFile->MarkAsDeleted() != 0 || m_poDATFile->MarkAsDeleted() != 0)
1619
0
    {
1620
0
        return OGRERR_FAILURE;
1621
0
    }
1622
1623
0
    return OGRERR_NONE;
1624
0
}
1625
1626
/**********************************************************************
1627
 *                   TABFile::WriteFeature()
1628
 *
1629
 * Write a feature to this dataset.
1630
 *
1631
 * Returns 0 on success, or -1 if an error happened in which case,
1632
 * CPLError() will have been called to
1633
 * report the reason of the failure.
1634
 **********************************************************************/
1635
int TABFile::WriteFeature(TABFeature *poFeature)
1636
315k
{
1637
1638
315k
    m_bLastOpWasWrite = TRUE;
1639
1640
    /*-----------------------------------------------------------------
1641
     * Make sure file is opened and establish new feature id.
1642
     *----------------------------------------------------------------*/
1643
315k
    if (m_poMAPFile == nullptr)
1644
0
    {
1645
0
        CPLError(CE_Failure, CPLE_IllegalArg,
1646
0
                 "WriteFeature() failed: file is not opened!");
1647
0
        return -1;
1648
0
    }
1649
1650
315k
    int nFeatureId = 0;
1651
315k
    if (poFeature->GetFID() >= 0)
1652
0
    {
1653
0
        nFeatureId = static_cast<int>(poFeature->GetFID());
1654
0
    }
1655
315k
    else if (m_nLastFeatureId < 1)
1656
211
    {
1657
        /*-------------------------------------------------------------
1658
         * Special hack to write out at least one field if none are in
1659
         * OGRFeatureDefn.
1660
         *------------------------------------------------------------*/
1661
211
        if (m_poDATFile->GetNumFields() == 0)
1662
3
        {
1663
3
            CPLError(CE_Warning, CPLE_IllegalArg,
1664
3
                     "MapInfo tables must contain at least 1 column, adding "
1665
3
                     "dummy FID column.");
1666
3
            CPLErrorReset();
1667
3
            m_poDATFile->AddField("FID", TABFInteger, 10, 0);
1668
3
        }
1669
1670
211
        nFeatureId = 1;
1671
211
    }
1672
315k
    else
1673
315k
    {
1674
315k
        nFeatureId = m_nLastFeatureId + 1;
1675
315k
    }
1676
1677
315k
    poFeature->SetFID(nFeatureId);
1678
1679
    /*-----------------------------------------------------------------
1680
     * Write fields to the .DAT file and update .IND if necessary
1681
     *----------------------------------------------------------------*/
1682
315k
    if (m_poDATFile->GetRecordBlock(nFeatureId) == nullptr ||
1683
315k
        poFeature->WriteRecordToDATFile(m_poDATFile, m_poINDFile,
1684
315k
                                        m_panIndexNo) != 0)
1685
0
    {
1686
0
        CPLError(CE_Failure, CPLE_FileIO,
1687
0
                 "Failed writing attributes for feature id %d in %s",
1688
0
                 nFeatureId, m_pszFname);
1689
0
        return -1;
1690
0
    }
1691
1692
    /*-----------------------------------------------------------------
1693
     * Write geometry to the .MAP file
1694
     * The call to PrepareNewObj() takes care of the .ID file.
1695
     *----------------------------------------------------------------*/
1696
315k
    std::unique_ptr<TABMAPObjHdr> poObjHdr(TABMAPObjHdr::NewObj(
1697
315k
        poFeature->ValidateMapInfoType(m_poMAPFile), nFeatureId));
1698
1699
315k
    if (poObjHdr == nullptr)
1700
0
    {
1701
0
        CPLError(CE_Failure, CPLE_FileIO,
1702
0
                 "Failed writing geometry for feature id %d in %s", nFeatureId,
1703
0
                 m_pszFname);
1704
0
        return -1;
1705
0
    }
1706
1707
    /*-----------------------------------------------------------------
1708
     * ValidateMapInfoType() may have returned TAB_GEOM_NONE if feature
1709
     * contained an invalid geometry for its class. Need to catch that
1710
     * case and return the error.
1711
     *----------------------------------------------------------------*/
1712
315k
    if (poObjHdr->m_nType == TAB_GEOM_NONE &&
1713
302k
        poFeature->GetFeatureClass() != TABFCNoGeomFeature)
1714
4.26k
    {
1715
4.26k
        CPLError(CE_Failure, CPLE_FileIO,
1716
4.26k
                 "Invalid geometry for feature id %d in %s", nFeatureId,
1717
4.26k
                 m_pszFname);
1718
4.26k
        return -1;
1719
4.26k
    }
1720
1721
    /*-----------------------------------------------------------------
1722
     * The ValidateMapInfoType() call above has forced calculation of the
1723
     * feature's IntMBR. Store that value in the ObjHdr for use by
1724
     * PrepareNewObj() to search the best node to insert the feature.
1725
     *----------------------------------------------------------------*/
1726
311k
    if (poObjHdr->m_nType != TAB_GEOM_NONE)
1727
13.2k
    {
1728
13.2k
        poFeature->GetIntMBR(poObjHdr->m_nMinX, poObjHdr->m_nMinY,
1729
13.2k
                             poObjHdr->m_nMaxX, poObjHdr->m_nMaxY);
1730
13.2k
    }
1731
1732
    /*
1733
        if( m_nCurFeatureId < m_nLastFeatureId )
1734
        {
1735
            delete GetFeatureRef(m_nLastFeatureId);
1736
            m_poCurFeature = NULL;
1737
        }*/
1738
1739
311k
    if (m_poMAPFile->PrepareNewObj(poObjHdr.get()) != 0 ||
1740
311k
        poFeature->WriteGeometryToMAPFile(m_poMAPFile, poObjHdr.get()) != 0 ||
1741
310k
        m_poMAPFile->CommitNewObj(poObjHdr.get()) != 0)
1742
874
    {
1743
874
        CPLError(CE_Failure, CPLE_FileIO,
1744
874
                 "Failed writing geometry for feature id %d in %s", nFeatureId,
1745
874
                 m_pszFname);
1746
874
        return -1;
1747
874
    }
1748
1749
310k
    m_nLastFeatureId = std::max(m_nLastFeatureId, nFeatureId);
1750
310k
    m_nCurFeatureId = nFeatureId;
1751
1752
310k
    return 0;
1753
311k
}
1754
1755
int TABFile::SetCharset(const char *pszCharset)
1756
9.30k
{
1757
9.30k
    if (0 != IMapInfoFile::SetCharset(pszCharset))
1758
0
    {
1759
0
        return -1;
1760
0
    }
1761
9.30k
    if (m_poDATFile != nullptr)
1762
0
    {
1763
0
        m_poDATFile->SetEncoding(CharsetToEncoding(pszCharset));
1764
0
    }
1765
9.30k
    if (m_poMAPFile != nullptr)
1766
0
    {
1767
0
        m_poMAPFile->SetEncoding(CharsetToEncoding(pszCharset));
1768
0
    }
1769
9.30k
    if (EQUAL(pszCharset, "UTF-8"))
1770
0
    {
1771
0
        m_nVersion = std::max(m_nVersion, 1520);
1772
0
    }
1773
9.30k
    return 0;
1774
9.30k
}
1775
1776
void TABFile::SetStrictLaundering(bool bStrictLaundering)
1777
457
{
1778
457
    IMapInfoFile::SetStrictLaundering(bStrictLaundering);
1779
457
    if (!bStrictLaundering)
1780
0
    {
1781
0
        m_nVersion = std::max(m_nVersion, 1520);
1782
0
    }
1783
457
}
1784
1785
/**********************************************************************
1786
 *                   TABFile::CreateFeature()
1787
 *
1788
 * Write a new feature to this dataset. The passed in feature is updated
1789
 * with the new feature id.
1790
 *
1791
 * Returns OGRERR_NONE on success, or an appropriate OGRERR_ code if an
1792
 * error happened in which case, CPLError() will have been called to
1793
 * report the reason of the failure.
1794
 **********************************************************************/
1795
OGRErr TABFile::CreateFeature(TABFeature *poFeature)
1796
315k
{
1797
315k
    CPLErrorReset();
1798
1799
315k
    if (m_eAccessMode == TABRead)
1800
0
    {
1801
0
        CPLError(CE_Failure, CPLE_NotSupported,
1802
0
                 "CreateFeature() cannot be used in read-only access.");
1803
0
        return OGRERR_FAILURE;
1804
0
    }
1805
1806
315k
    GIntBig nFeatureId = poFeature->GetFID();
1807
315k
    if (nFeatureId != OGRNullFID)
1808
0
    {
1809
0
        if (nFeatureId <= 0 || nFeatureId > m_nLastFeatureId)
1810
0
        {
1811
0
            CPLError(CE_Failure, CPLE_IllegalArg,
1812
0
                     "CreateFeature() failed: invalid feature id " CPL_FRMT_GIB,
1813
0
                     nFeatureId);
1814
0
            return OGRERR_FAILURE;
1815
0
        }
1816
1817
0
        if (m_poDATFile->GetRecordBlock(static_cast<int>(nFeatureId)) ==
1818
0
                nullptr ||
1819
0
            !m_poDATFile->IsCurrentRecordDeleted())
1820
0
        {
1821
0
            CPLError(CE_Failure, CPLE_IllegalArg,
1822
0
                     "CreateFeature() failed: cannot re-write already existing "
1823
0
                     "feature " CPL_FRMT_GIB,
1824
0
                     nFeatureId);
1825
0
            return OGRERR_FAILURE;
1826
0
        }
1827
0
    }
1828
1829
315k
    if (WriteFeature(poFeature) < 0)
1830
5.13k
        return OGRERR_FAILURE;
1831
1832
310k
    return OGRERR_NONE;
1833
315k
}
1834
1835
/**********************************************************************
1836
 *                   TABFile::ISetFeature()
1837
 *
1838
 * Implementation of OGRLayer's SetFeature()
1839
 **********************************************************************/
1840
OGRErr TABFile::ISetFeature(OGRFeature *poFeature)
1841
1842
0
{
1843
0
    CPLErrorReset();
1844
1845
0
    if (m_eAccessMode == TABRead)
1846
0
    {
1847
0
        CPLError(CE_Failure, CPLE_NotSupported,
1848
0
                 "SetFeature() cannot be used in read-only access.");
1849
0
        return OGRERR_FAILURE;
1850
0
    }
1851
1852
    /*-----------------------------------------------------------------
1853
     * Make sure file is opened.
1854
     *----------------------------------------------------------------*/
1855
0
    if (m_poMAPFile == nullptr)
1856
0
    {
1857
0
        CPLError(CE_Failure, CPLE_IllegalArg,
1858
0
                 "SetFeature() failed: file is not opened!");
1859
0
        return OGRERR_FAILURE;
1860
0
    }
1861
1862
0
    GIntBig nFeatureId = poFeature->GetFID();
1863
0
    if (nFeatureId == OGRNullFID)
1864
0
    {
1865
0
        CPLError(CE_Failure, CPLE_NotSupported,
1866
0
                 "SetFeature() must be used on a feature with a FID.");
1867
0
        return OGRERR_FAILURE;
1868
0
    }
1869
0
    if (nFeatureId <= 0 || nFeatureId > m_nLastFeatureId)
1870
0
    {
1871
        /*CPLError(CE_Failure, CPLE_IllegalArg,
1872
                    "SetFeature() failed: invalid feature id " CPL_FRMT_GIB,
1873
                    nFeatureId);*/
1874
0
        return OGRERR_NON_EXISTING_FEATURE;
1875
0
    }
1876
1877
0
    OGRGeometry *poGeom = poFeature->GetGeometryRef();
1878
0
    if (poGeom != nullptr &&
1879
0
        ((wkbFlatten(poGeom->getGeometryType()) == wkbMultiPoint) ||
1880
0
         (wkbFlatten(poGeom->getGeometryType()) == wkbGeometryCollection)))
1881
0
    {
1882
0
        CPLError(CE_Failure, CPLE_NotSupported,
1883
0
                 "SetFeature() failed: setting MultiPoint or "
1884
0
                 "GeometryCollection not supported");
1885
0
        return OGRERR_FAILURE;
1886
0
    }
1887
1888
0
    TABFeature *poTABFeature = CreateTABFeature(poFeature);
1889
0
    if (poTABFeature == nullptr)
1890
0
        return OGRERR_FAILURE;
1891
1892
0
    if (m_bLastOpWasWrite)
1893
0
        ResetReading();
1894
1895
0
    if (m_poDATFile->GetRecordBlock(static_cast<int>(nFeatureId)) == nullptr)
1896
0
    {
1897
        /*CPLError(CE_Failure, CPLE_IllegalArg,
1898
                 "SetFeature() failed: invalid feature id " CPL_FRMT_GIB,
1899
                 nFeatureId);*/
1900
0
        delete poTABFeature;
1901
0
        return OGRERR_NON_EXISTING_FEATURE;
1902
0
    }
1903
1904
    /* If the object is not already deleted, delete it */
1905
0
    if (!(m_poDATFile->IsCurrentRecordDeleted()))
1906
0
    {
1907
0
        OGRFeature *poOldFeature = GetFeature(nFeatureId);
1908
0
        if (poOldFeature != nullptr)
1909
0
        {
1910
            /* Optimization: if old and new features are the same, do nothing */
1911
0
            if (poOldFeature->Equal(poFeature))
1912
0
            {
1913
0
                CPLDebug("MITAB", "Un-modified object " CPL_FRMT_GIB,
1914
0
                         nFeatureId);
1915
0
                delete poTABFeature;
1916
0
                delete poOldFeature;
1917
0
                return OGRERR_NONE;
1918
0
            }
1919
1920
            /* Optimization: if old and new geometries are the same, just */
1921
            /* rewrite the attributes */
1922
0
            OGRGeometry *poOldGeom = poOldFeature->GetGeometryRef();
1923
0
            OGRGeometry *poNewGeom = poFeature->GetGeometryRef();
1924
0
            if ((poOldGeom == nullptr && poNewGeom == nullptr) ||
1925
0
                (poOldGeom != nullptr && poNewGeom != nullptr &&
1926
0
                 poOldGeom->Equals(poNewGeom)))
1927
0
            {
1928
0
                const char *pszOldStyle = poOldFeature->GetStyleString();
1929
0
                const char *pszNewStyle = poFeature->GetStyleString();
1930
0
                if ((pszOldStyle == nullptr && pszNewStyle == nullptr) ||
1931
0
                    (pszOldStyle != nullptr && pszNewStyle != nullptr &&
1932
0
                     EQUAL(pszOldStyle, pszNewStyle)))
1933
0
                {
1934
0
                    CPLDebug("MITAB",
1935
0
                             "Rewrite only attributes for object " CPL_FRMT_GIB,
1936
0
                             nFeatureId);
1937
0
                    if (poTABFeature->WriteRecordToDATFile(
1938
0
                            m_poDATFile, m_poINDFile, m_panIndexNo) != 0)
1939
0
                    {
1940
0
                        CPLError(CE_Failure, CPLE_FileIO,
1941
0
                                 "Failed writing attributes for feature "
1942
0
                                 "id " CPL_FRMT_GIB " in %s",
1943
0
                                 nFeatureId, m_pszFname);
1944
0
                        delete poTABFeature;
1945
0
                        delete poOldFeature;
1946
0
                        return OGRERR_FAILURE;
1947
0
                    }
1948
1949
0
                    delete poTABFeature;
1950
0
                    delete poOldFeature;
1951
0
                    return OGRERR_NONE;
1952
0
                }
1953
0
            }
1954
1955
0
            delete poOldFeature;
1956
0
        }
1957
1958
0
        if (DeleteFeature(nFeatureId) != OGRERR_NONE)
1959
0
        {
1960
0
            delete poTABFeature;
1961
0
            return OGRERR_FAILURE;
1962
0
        }
1963
0
    }
1964
1965
0
    int nStatus = WriteFeature(poTABFeature);
1966
1967
0
    delete poTABFeature;
1968
1969
0
    if (nStatus < 0)
1970
0
        return OGRERR_FAILURE;
1971
1972
0
    return OGRERR_NONE;
1973
0
}
1974
1975
/**********************************************************************
1976
 *                   TABFile::GetLayerDefn() const
1977
 *
1978
 * Returns a reference to the OGRFeatureDefn that will be used to create
1979
 * features in this dataset.
1980
 *
1981
 * Returns a reference to an object that is maintained by this TABFile
1982
 * object (and thus should not be modified or freed by the caller) or
1983
 * NULL if the OGRFeatureDefn has not been initialized yet (i.e. no file
1984
 * opened yet)
1985
 **********************************************************************/
1986
const OGRFeatureDefn *TABFile::GetLayerDefn() const
1987
326k
{
1988
326k
    return m_poDefn;
1989
326k
}
1990
1991
/**********************************************************************
1992
 *                   TABFile::SetFeatureDefn()
1993
 *
1994
 * Pass a reference to the OGRFeatureDefn that will be used to create
1995
 * features in this dataset.  This function should be called after
1996
 * creating a new dataset, but before writing the first feature.
1997
 * All features that will be written to this dataset must share this same
1998
 * OGRFeatureDefn.
1999
 *
2000
 * A reference to the OGRFeatureDefn will be kept and will be used to
2001
 * build the .DAT file, etc.
2002
 *
2003
 * Returns 0 on success, -1 on error.
2004
 **********************************************************************/
2005
int TABFile::SetFeatureDefn(
2006
    OGRFeatureDefn *poFeatureDefn,
2007
    TABFieldType *paeMapInfoNativeFieldTypes /* =NULL */)
2008
0
{
2009
0
    if (m_eAccessMode != TABWrite)
2010
0
    {
2011
0
        CPLError(CE_Failure, CPLE_NotSupported,
2012
0
                 "SetFeatureDefn() can be used only with Write access.");
2013
0
        return -1;
2014
0
    }
2015
2016
    /*-----------------------------------------------------------------
2017
     * Keep a reference to the OGRFeatureDefn... we'll have to take the
2018
     * reference count into account when we are done with it.
2019
     *----------------------------------------------------------------*/
2020
0
    if (m_poDefn && m_poDefn->Dereference() == 0)
2021
0
        delete m_poDefn;
2022
2023
0
    m_poDefn = poFeatureDefn;
2024
0
    m_poDefn->Reference();
2025
2026
    /*-----------------------------------------------------------------
2027
     * Pass field information to the .DAT file, after making sure that
2028
     * it has been created and that it does not contain any field
2029
     * definition yet.
2030
     *----------------------------------------------------------------*/
2031
0
    if (m_poDATFile == nullptr || m_poDATFile->GetNumFields() > 0)
2032
0
    {
2033
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
2034
0
                 "SetFeatureDefn() can be called only once in a newly "
2035
0
                 "created dataset.");
2036
0
        return -1;
2037
0
    }
2038
2039
0
    const int numFields = poFeatureDefn->GetFieldCount();
2040
0
    TABFieldType eMapInfoType = TABFUnknown;
2041
0
    int nStatus = 0;
2042
0
    for (int iField = 0; nStatus == 0 && iField < numFields; iField++)
2043
0
    {
2044
0
        OGRFieldDefn *poFieldDefn = m_poDefn->GetFieldDefn(iField);
2045
2046
0
        if (paeMapInfoNativeFieldTypes)
2047
0
        {
2048
0
            eMapInfoType = paeMapInfoNativeFieldTypes[iField];
2049
0
        }
2050
0
        else
2051
0
        {
2052
            /*---------------------------------------------------------
2053
             * Map OGRFieldTypes to MapInfo native types
2054
             *--------------------------------------------------------*/
2055
0
            switch (poFieldDefn->GetType())
2056
0
            {
2057
0
                case OFTInteger:
2058
0
                    eMapInfoType = poFieldDefn->GetSubType() == OFSTBoolean
2059
0
                                       ? TABFLogical
2060
0
                                       : TABFInteger;
2061
0
                    break;
2062
0
                case OFTReal:
2063
0
                    if (poFieldDefn->GetWidth() > 0 ||
2064
0
                        poFieldDefn->GetPrecision() > 0)
2065
0
                        eMapInfoType = TABFDecimal;
2066
0
                    else
2067
0
                        eMapInfoType = TABFFloat;
2068
0
                    break;
2069
0
                case OFTDateTime:
2070
0
                    eMapInfoType = TABFDateTime;
2071
0
                    break;
2072
0
                case OFTDate:
2073
0
                    eMapInfoType = TABFDate;
2074
0
                    break;
2075
0
                case OFTTime:
2076
0
                    eMapInfoType = TABFTime;
2077
0
                    break;
2078
0
                case OFTString:
2079
0
                default:
2080
0
                    eMapInfoType = TABFChar;
2081
0
            }
2082
0
        }
2083
2084
0
        nStatus = m_poDATFile->AddField(poFieldDefn->GetNameRef(), eMapInfoType,
2085
0
                                        poFieldDefn->GetWidth(),
2086
0
                                        poFieldDefn->GetPrecision());
2087
0
    }
2088
2089
    /*-----------------------------------------------------------------
2090
     * Alloc the array to keep track of indexed fields (default=NOT indexed)
2091
     *----------------------------------------------------------------*/
2092
0
    m_panIndexNo = static_cast<int *>(CPLCalloc(numFields, sizeof(int)));
2093
2094
0
    return nStatus;
2095
0
}
2096
2097
/**********************************************************************
2098
 *                   TABFile::AddFieldNative()
2099
 *
2100
 * Create a new field using a native mapinfo data type... this is an
2101
 * alternative to defining fields through the OGR interface.
2102
 * This function should be called after creating a new dataset.
2103
 *
2104
 * This function will build/update the OGRFeatureDefn that will have to be
2105
 * used when writing features to this dataset.
2106
 *
2107
 * A reference to the OGRFeatureDefn can be obtained using GetLayerDefn().
2108
 *
2109
 * Note: The bUnique flag has no effect on TABFiles.  See the TABView class.
2110
 *
2111
 * Returns 0 on success, -1 on error.
2112
 **********************************************************************/
2113
int TABFile::AddFieldNative(const char *pszName, TABFieldType eMapInfoType,
2114
                            int nWidth /*=0*/, int nPrecision /*=0*/,
2115
                            GBool bIndexed /*=FALSE*/, GBool /*bUnique=FALSE*/,
2116
                            int /*bApproxOK*/)
2117
12.4k
{
2118
12.4k
    if (m_eAccessMode == TABRead || m_poDATFile == nullptr)
2119
0
    {
2120
0
        CPLError(CE_Failure, CPLE_NotSupported,
2121
0
                 "AddFieldNative() cannot be used only with Read access.");
2122
0
        return -1;
2123
0
    }
2124
2125
12.4k
    m_bNeedTABRewrite = TRUE;
2126
2127
    /*-----------------------------------------------------------------
2128
     * Validate field width... must be <= 254
2129
     *----------------------------------------------------------------*/
2130
12.4k
    if (nWidth > 254)
2131
0
    {
2132
0
        CPLError(CE_Warning, CPLE_IllegalArg,
2133
0
                 "Invalid size (%d) for field '%s'.  "
2134
0
                 "Size must be 254 or less.",
2135
0
                 nWidth, pszName);
2136
0
        nWidth = 254;
2137
0
    }
2138
2139
    /*-----------------------------------------------------------------
2140
     * Map fields with width=0 (variable length in OGR) to a valid default
2141
     *----------------------------------------------------------------*/
2142
12.4k
    if (eMapInfoType == TABFDecimal && nWidth == 0)
2143
0
        nWidth = 20;
2144
12.4k
    else if (nWidth == 0)
2145
0
        nWidth = 254; /* char fields */
2146
2147
12.4k
    CPLString osName(NormalizeFieldName(pszName));
2148
2149
    /*-----------------------------------------------------------------
2150
     * Map MapInfo native types to OGR types
2151
     *----------------------------------------------------------------*/
2152
12.4k
    OGRFieldDefn *poFieldDefn = nullptr;
2153
2154
12.4k
    switch (eMapInfoType)
2155
12.4k
    {
2156
12.4k
        case TABFChar:
2157
            /*-------------------------------------------------
2158
             * CHAR type
2159
             *------------------------------------------------*/
2160
12.4k
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTString);
2161
12.4k
            poFieldDefn->SetWidth(nWidth);
2162
12.4k
            break;
2163
5
        case TABFInteger:
2164
            /*-------------------------------------------------
2165
             * INTEGER type
2166
             *------------------------------------------------*/
2167
5
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTInteger);
2168
5
            if (nWidth <= 10)
2169
0
                poFieldDefn->SetWidth(nWidth);
2170
5
            break;
2171
0
        case TABFSmallInt:
2172
            /*-------------------------------------------------
2173
             * SMALLINT type
2174
             *------------------------------------------------*/
2175
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTInteger);
2176
0
            if (nWidth <= 5)
2177
0
                poFieldDefn->SetWidth(nWidth);
2178
0
            break;
2179
0
        case TABFLargeInt:
2180
            /*-------------------------------------------------
2181
             * SMALLINT type
2182
             *------------------------------------------------*/
2183
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTInteger64);
2184
0
            break;
2185
0
        case TABFDecimal:
2186
            /*-------------------------------------------------
2187
             * DECIMAL type
2188
             *------------------------------------------------*/
2189
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTReal);
2190
0
            poFieldDefn->SetWidth(nWidth);
2191
0
            poFieldDefn->SetPrecision(nPrecision);
2192
0
            break;
2193
5
        case TABFFloat:
2194
            /*-------------------------------------------------
2195
             * FLOAT type
2196
             *------------------------------------------------*/
2197
5
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTReal);
2198
5
            break;
2199
0
        case TABFDate:
2200
            /*-------------------------------------------------
2201
             * DATE type (V450, returned as a string: "DD/MM/YYYY")
2202
             *------------------------------------------------*/
2203
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(),
2204
0
#ifdef MITAB_USE_OFTDATETIME
2205
0
                                           OFTDate);
2206
#else
2207
                                           OFTString);
2208
#endif
2209
0
            poFieldDefn->SetWidth(10);
2210
0
            m_nVersion = std::max(m_nVersion, 450);
2211
0
            break;
2212
0
        case TABFTime:
2213
            /*-------------------------------------------------
2214
             * TIME type (V900, returned as a string: "HH:MM:SS")
2215
             *------------------------------------------------*/
2216
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(),
2217
0
#ifdef MITAB_USE_OFTDATETIME
2218
0
                                           OFTTime);
2219
#else
2220
                                           OFTString);
2221
#endif
2222
0
            poFieldDefn->SetWidth(8);
2223
0
            m_nVersion = std::max(m_nVersion, 900);
2224
0
            break;
2225
0
        case TABFDateTime:
2226
            /*-------------------------------------------------
2227
             * DATETIME type (V900, returned as a string: "DD/MM/YYYY HH:MM:SS")
2228
             *------------------------------------------------*/
2229
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(),
2230
0
#ifdef MITAB_USE_OFTDATETIME
2231
0
                                           OFTDateTime);
2232
#else
2233
                                           OFTString);
2234
#endif
2235
0
            poFieldDefn->SetWidth(19);
2236
0
            m_nVersion = std::max(m_nVersion, 900);
2237
0
            break;
2238
0
        case TABFLogical:
2239
            /*-------------------------------------------------
2240
             * LOGICAL type (value "T" or "F")
2241
             *------------------------------------------------*/
2242
0
            poFieldDefn = new OGRFieldDefn(osName.c_str(), OFTInteger);
2243
0
            poFieldDefn->SetSubType(OFSTBoolean);
2244
0
            poFieldDefn->SetWidth(1);
2245
0
            break;
2246
0
        default:
2247
0
            CPLError(CE_Failure, CPLE_NotSupported,
2248
0
                     "Unsupported type for field %s", osName.c_str());
2249
0
            return -1;
2250
12.4k
    }
2251
2252
    /*-----------------------------------------------------
2253
     * Add the FieldDefn to the FeatureDefn
2254
     *----------------------------------------------------*/
2255
12.4k
    whileUnsealing(m_poDefn)->AddFieldDefn(poFieldDefn);
2256
12.4k
    m_oSetFields.insert(CPLString(poFieldDefn->GetNameRef()).toupper());
2257
12.4k
    delete poFieldDefn;
2258
2259
    /*-----------------------------------------------------
2260
     * ... and pass field info to the .DAT file.
2261
     *----------------------------------------------------*/
2262
12.4k
    int nStatus =
2263
12.4k
        m_poDATFile->AddField(osName.c_str(), eMapInfoType, nWidth, nPrecision);
2264
2265
    /*-----------------------------------------------------------------
2266
     * Extend the array to keep track of indexed fields (default=NOT indexed)
2267
     *----------------------------------------------------------------*/
2268
12.4k
    m_panIndexNo = static_cast<int *>(
2269
12.4k
        CPLRealloc(m_panIndexNo, m_poDefn->GetFieldCount() * sizeof(int)));
2270
12.4k
    m_panIndexNo[m_poDefn->GetFieldCount() - 1] = 0;
2271
2272
    /*-----------------------------------------------------------------
2273
     * Index the field if requested
2274
     *----------------------------------------------------------------*/
2275
12.4k
    if (nStatus == 0 && bIndexed)
2276
0
        nStatus = SetFieldIndexed(m_poDefn->GetFieldCount() - 1);
2277
2278
12.4k
    if (nStatus == 0 && m_eAccessMode == TABReadWrite)
2279
0
        nStatus = WriteTABFile();
2280
2281
12.4k
    return nStatus;
2282
12.4k
}
2283
2284
/**********************************************************************
2285
 *                   TABFile::GetNativeFieldType()
2286
 *
2287
 * Returns the native MapInfo field type for the specified field.
2288
 *
2289
 * Returns TABFUnknown if file is not opened, or if specified field index is
2290
 * invalid.
2291
 *
2292
 * Note that field ids are positive and start at 0.
2293
 **********************************************************************/
2294
TABFieldType TABFile::GetNativeFieldType(int nFieldId)
2295
21.2k
{
2296
21.2k
    if (m_poDATFile)
2297
21.2k
    {
2298
21.2k
        return m_poDATFile->GetFieldType(nFieldId);
2299
21.2k
    }
2300
0
    return TABFUnknown;
2301
21.2k
}
2302
2303
/**********************************************************************
2304
 *                   TABFile::GetFieldIndexNumber()
2305
 *
2306
 * Returns the field's index number that was specified in the .TAB header
2307
 * or 0 if the specified field is not indexed.
2308
 *
2309
 * Note that field ids are positive and start at 0
2310
 * and valid index ids are positive and start at 1.
2311
 **********************************************************************/
2312
int TABFile::GetFieldIndexNumber(int nFieldId)
2313
19.4k
{
2314
19.4k
    if (m_panIndexNo == nullptr || nFieldId < 0 || m_poDATFile == nullptr ||
2315
19.4k
        nFieldId >= m_poDefn->GetFieldCount())
2316
28
        return 0;  // no index
2317
2318
19.4k
    return m_panIndexNo[nFieldId];
2319
19.4k
}
2320
2321
/************************************************************************
2322
 *                       TABFile::SetFieldIndexed()
2323
 *
2324
 * Request that a field be indexed.  This will create the .IND file if
2325
 * necessary, etc.
2326
 *
2327
 * Note that field ids are positive and start at 0.
2328
 *
2329
 * Returns 0 on success, -1 on error.
2330
 ************************************************************************/
2331
int TABFile::SetFieldIndexed(int nFieldId)
2332
0
{
2333
    /*-----------------------------------------------------------------
2334
     * Make sure things are OK
2335
     *----------------------------------------------------------------*/
2336
0
    if (m_pszFname == nullptr || m_eAccessMode != TABWrite ||
2337
0
        m_poDefn == nullptr)
2338
0
    {
2339
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
2340
0
                 "SetFieldIndexed() must be called after opening a new "
2341
0
                 "dataset, but before writing the first feature to it.");
2342
0
        return -1;
2343
0
    }
2344
2345
0
    if (m_panIndexNo == nullptr || nFieldId < 0 || m_poDATFile == nullptr ||
2346
0
        nFieldId >= m_poDefn->GetFieldCount())
2347
0
    {
2348
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
2349
0
                 "Invalid field number in SetFieldIndexed().");
2350
0
        return -1;
2351
0
    }
2352
2353
    /*-----------------------------------------------------------------
2354
     * If field is already indexed then just return
2355
     *----------------------------------------------------------------*/
2356
0
    if (m_panIndexNo[nFieldId] != 0)
2357
0
        return 0;  // Nothing to do
2358
2359
    /*-----------------------------------------------------------------
2360
     * Create .IND file if it is not done yet.
2361
     *
2362
     * Note: We can pass the .TAB's filename directly and the
2363
     * TABINDFile class will automagically adjust the extension.
2364
     *----------------------------------------------------------------*/
2365
0
    if (m_poINDFile == nullptr)
2366
0
    {
2367
0
        m_poINDFile = new TABINDFile;
2368
2369
0
        if (m_poINDFile->Open(m_pszFname, "w", TRUE) != 0)
2370
0
        {
2371
            // File could not be opened...
2372
0
            delete m_poINDFile;
2373
0
            m_poINDFile = nullptr;
2374
0
            return -1;
2375
0
        }
2376
0
    }
2377
2378
    /*-----------------------------------------------------------------
2379
     * Init new index.
2380
     *----------------------------------------------------------------*/
2381
0
    OGRFieldDefn *poFieldDefn = m_poDefn->GetFieldDefn(nFieldId);
2382
2383
0
    if (poFieldDefn == nullptr)
2384
0
        return -1;
2385
0
    const int nNewIndexNo = m_poINDFile->CreateIndex(
2386
0
        GetNativeFieldType(nFieldId), poFieldDefn->GetWidth());
2387
0
    if (nNewIndexNo < 1)
2388
0
    {
2389
        // Failed... an error has already been reported.
2390
0
        return -1;
2391
0
    }
2392
2393
0
    m_panIndexNo[nFieldId] = nNewIndexNo;
2394
2395
0
    return 0;
2396
0
}
2397
2398
/************************************************************************
2399
 *                       TABFile::IsFieldIndexed()
2400
 *
2401
 * Returns TRUE if field is indexed, or FALSE otherwise.
2402
 ************************************************************************/
2403
GBool TABFile::IsFieldIndexed(int nFieldId)
2404
0
{
2405
0
    return GetFieldIndexNumber(nFieldId) > 0 ? TRUE : FALSE;
2406
0
}
2407
2408
/**********************************************************************
2409
 *                   TABFile::GetINDFileRef()
2410
 *
2411
 * Opens the .IND file for this dataset and returns a reference to
2412
 * the handle.
2413
 * If the .IND file has already been opened then the same handle is
2414
 * returned directly.
2415
 * If the .IND file does not exist then the function silently returns NULL.
2416
 *
2417
 * Note that the returned TABINDFile handle is only a reference to an
2418
 * object that is owned by this class.  Callers can use it but cannot
2419
 * destroy the object.  The object will remain valid for as long as
2420
 * the TABFile will remain open.
2421
 **********************************************************************/
2422
TABINDFile *TABFile::GetINDFileRef()
2423
397
{
2424
397
    if (m_pszFname == nullptr)
2425
0
        return nullptr;
2426
2427
397
    if (m_eAccessMode == TABRead && m_poINDFile == nullptr)
2428
397
    {
2429
        /*-------------------------------------------------------------
2430
         * File is not opened yet... do it now.
2431
         *
2432
         * Note: We can pass the .TAB's filename directly and the
2433
         * TABINDFile class will automagically adjust the extension.
2434
         *------------------------------------------------------------*/
2435
397
        m_poINDFile = new TABINDFile;
2436
2437
397
        if (m_poINDFile->Open(m_pszFname, "r", TRUE) != 0)
2438
82
        {
2439
            // File could not be opened... probably does not exist
2440
82
            delete m_poINDFile;
2441
82
            m_poINDFile = nullptr;
2442
82
        }
2443
315
        else if (m_panIndexNo && m_poDATFile)
2444
315
        {
2445
            /*---------------------------------------------------------
2446
             * Pass type information for each indexed field.
2447
             *--------------------------------------------------------*/
2448
811
            for (int i = 0; i < m_poDefn->GetFieldCount(); i++)
2449
496
            {
2450
496
                if (m_panIndexNo[i] > 0)
2451
310
                {
2452
310
                    m_poINDFile->SetIndexFieldType(m_panIndexNo[i],
2453
310
                                                   GetNativeFieldType(i));
2454
310
                }
2455
496
            }
2456
315
        }
2457
397
    }
2458
2459
397
    return m_poINDFile;
2460
397
}
2461
2462
/**********************************************************************
2463
 *                   TABFile::SetBounds()
2464
 *
2465
 * Set projection coordinates bounds of the newly created dataset.
2466
 *
2467
 * This function must be called after creating a new dataset and before any
2468
 * feature can be written to it.
2469
 *
2470
 * Returns 0 on success, -1 on error.
2471
 **********************************************************************/
2472
int TABFile::SetBounds(double dXMin, double dYMin, double dXMax, double dYMax)
2473
226
{
2474
226
    if (m_eAccessMode != TABWrite)
2475
0
    {
2476
0
        CPLError(CE_Failure, CPLE_NotSupported,
2477
0
                 "SetBounds() can be used only with Write access.");
2478
0
        return -1;
2479
0
    }
2480
2481
    /*-----------------------------------------------------------------
2482
     * Check that dataset has been created but no feature set yet.
2483
     *----------------------------------------------------------------*/
2484
226
    if (m_poMAPFile && m_nLastFeatureId < 1)
2485
226
    {
2486
226
        m_poMAPFile->SetCoordsysBounds(dXMin, dYMin, dXMax, dYMax);
2487
2488
        /*-----------------------------------------------------------------
2489
         * Set BOUNDS metadata
2490
         *----------------------------------------------------------------*/
2491
226
        {
2492
226
            OGREnvelope e;
2493
226
            GetBounds(e.MinX, e.MinY, e.MaxX, e.MaxY, 1);
2494
226
            const char *pszBounds = CPLSPrintf("%.17g,%.17g,%.17g,%.17g",
2495
226
                                               e.MinX, e.MinY, e.MaxX, e.MaxY);
2496
226
            SetMetadataItem("BOUNDS", pszBounds);
2497
226
        }
2498
2499
226
        m_bBoundsSet = TRUE;
2500
226
    }
2501
0
    else
2502
0
    {
2503
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
2504
0
                 "SetBounds() can be called only after dataset has been "
2505
0
                 "created and before any feature is set.");
2506
0
        return -1;
2507
0
    }
2508
2509
226
    return 0;
2510
226
}
2511
2512
/**********************************************************************
2513
 *                   TABFile::GetBounds()
2514
 *
2515
 * Fetch projection coordinates bounds of a dataset.
2516
 *
2517
 * The bForce flag has no effect on TAB files since the bounds are
2518
 * always in the header.
2519
 *
2520
 * Returns 0 on success, -1 on error.
2521
 **********************************************************************/
2522
int TABFile::GetBounds(double &dXMin, double &dYMin, double &dXMax,
2523
                       double &dYMax, GBool /*bForce = TRUE*/)
2524
2.33k
{
2525
2.33k
    if (m_poMAPFile)
2526
2.33k
    {
2527
2.33k
        TABMAPHeaderBlock *poHeader = m_poMAPFile->GetHeaderBlock();
2528
2.33k
        if (poHeader != nullptr)
2529
2.33k
        {
2530
            /*-------------------------------------------------------------
2531
             * Projection bounds correspond to the +/- 1e9 integer coord. limits
2532
             *------------------------------------------------------------*/
2533
2.33k
            double dX0 = 0.0;
2534
2.33k
            double dX1 = 0.0;
2535
2.33k
            double dY0 = 0.0;
2536
2.33k
            double dY1 = 0.0;
2537
2.33k
            m_poMAPFile->Int2Coordsys(-1000000000, -1000000000, dX0, dY0);
2538
2.33k
            m_poMAPFile->Int2Coordsys(1000000000, 1000000000, dX1, dY1);
2539
            /*-------------------------------------------------------------
2540
             * ... and make sure that Min < Max
2541
             *------------------------------------------------------------*/
2542
2.33k
            dXMin = std::min(dX0, dX1);
2543
2.33k
            dXMax = std::max(dX0, dX1);
2544
2.33k
            dYMin = std::min(dY0, dY1);
2545
2.33k
            dYMax = std::max(dY0, dY1);
2546
2.33k
            return 0;
2547
2.33k
        }
2548
2.33k
    }
2549
2550
0
    CPLError(CE_Failure, CPLE_AppDefined,
2551
0
             "GetBounds() can be called only after dataset has been opened.");
2552
0
    return -1;
2553
2.33k
}
2554
2555
/**********************************************************************
2556
 *                   TABFile::IGetExtent()
2557
 *
2558
 * Fetch extent of the data currently stored in the dataset.
2559
 *
2560
 * The bForce flag has no effect on TAB files since that value is
2561
 * always in the header.
2562
 *
2563
 * Returns OGRERR_NONE/OGRRERR_FAILURE.
2564
 **********************************************************************/
2565
OGRErr TABFile::IGetExtent(int /*iGeomField*/, OGREnvelope *psExtent,
2566
                           bool /* bForce */)
2567
0
{
2568
0
    TABMAPHeaderBlock *poHeader = nullptr;
2569
2570
0
    if (m_poMAPFile && (poHeader = m_poMAPFile->GetHeaderBlock()) != nullptr &&
2571
0
        GetGeomType() != wkbNone)
2572
0
    {
2573
0
        double dX0 = 0.0;
2574
0
        double dX1 = 0.0;
2575
0
        double dY0 = 0.0;
2576
0
        double dY1 = 0.0;
2577
        /*-------------------------------------------------------------
2578
         * Fetch extent of the data from the .map header block
2579
         * this value is different from the projection bounds.
2580
         *------------------------------------------------------------*/
2581
0
        m_poMAPFile->Int2Coordsys(poHeader->m_nXMin, poHeader->m_nYMin, dX0,
2582
0
                                  dY0);
2583
0
        m_poMAPFile->Int2Coordsys(poHeader->m_nXMax, poHeader->m_nYMax, dX1,
2584
0
                                  dY1);
2585
2586
        /*-------------------------------------------------------------
2587
         * ... and make sure that Min < Max
2588
         *------------------------------------------------------------*/
2589
0
        psExtent->MinX = std::min(dX0, dX1);
2590
0
        psExtent->MaxX = std::max(dX0, dX1);
2591
0
        psExtent->MinY = std::min(dY0, dY1);
2592
0
        psExtent->MaxY = std::max(dY0, dY1);
2593
2594
0
        return OGRERR_NONE;
2595
0
    }
2596
2597
0
    return OGRERR_FAILURE;
2598
0
}
2599
2600
/**********************************************************************
2601
 *                   TABFile::GetFeatureCountByType()
2602
 *
2603
 * Return number of features of each type.
2604
 *
2605
 * Note that the sum of the 4 returned values may be different from
2606
 * the total number of features since features with NONE geometry
2607
 * are not taken into account here.
2608
 *
2609
 * Note: the bForce flag has nmo effect on .TAB files since the info
2610
 * is always in the header.
2611
 *
2612
 * Returns 0 on success, or silently returns -1 (with no error) if this
2613
 * information is not available.
2614
 **********************************************************************/
2615
int TABFile::GetFeatureCountByType(int &numPoints, int &numLines,
2616
                                   int &numRegions, int &numTexts,
2617
                                   GBool /* bForce = TRUE*/)
2618
1.87k
{
2619
1.87k
    TABMAPHeaderBlock *poHeader = nullptr;
2620
2621
1.87k
    if (m_poMAPFile && (poHeader = m_poMAPFile->GetHeaderBlock()) != nullptr)
2622
1.87k
    {
2623
1.87k
        numPoints = poHeader->m_numPointObjects;
2624
1.87k
        numLines = poHeader->m_numLineObjects;
2625
1.87k
        numRegions = poHeader->m_numRegionObjects;
2626
1.87k
        numTexts = poHeader->m_numTextObjects;
2627
1.87k
    }
2628
0
    else
2629
0
    {
2630
0
        numPoints = numLines = numRegions = numTexts = 0;
2631
0
        return -1;
2632
0
    }
2633
2634
1.87k
    return 0;
2635
1.87k
}
2636
2637
/**********************************************************************
2638
 *                   TABFile::SetMIFCoordSys()
2639
 *
2640
 * Set projection for a new file using a MIF coordsys string.
2641
 *
2642
 * This function must be called after creating a new dataset and before any
2643
 * feature can be written to it.
2644
 *
2645
 * Returns 0 on success, -1 on error.
2646
 **********************************************************************/
2647
int TABFile::SetMIFCoordSys(const char *pszMIFCoordSys)
2648
0
{
2649
0
    if (m_eAccessMode != TABWrite)
2650
0
    {
2651
0
        CPLError(CE_Failure, CPLE_NotSupported,
2652
0
                 "SetMIFCoordSys() can be used only with Write access.");
2653
0
        return -1;
2654
0
    }
2655
2656
    /*-----------------------------------------------------------------
2657
     * Check that dataset has been created but no feature set yet.
2658
     *----------------------------------------------------------------*/
2659
0
    if (m_poMAPFile && m_nLastFeatureId < 1)
2660
0
    {
2661
0
        OGRSpatialReference *poSpatialRef =
2662
0
            MITABCoordSys2SpatialRef(pszMIFCoordSys);
2663
2664
0
        if (poSpatialRef)
2665
0
        {
2666
0
            double dXMin = 0.0;
2667
0
            double dYMin = 0.0;
2668
0
            double dXMax = 0.0;
2669
0
            double dYMax = 0.0;
2670
0
            if (SetSpatialRef(poSpatialRef) == 0)
2671
0
            {
2672
0
                if (MITABExtractCoordSysBounds(pszMIFCoordSys, dXMin, dYMin,
2673
0
                                               dXMax, dYMax))
2674
0
                {
2675
                    // If the coordsys string contains bounds, then use them
2676
0
                    if (SetBounds(dXMin, dYMin, dXMax, dYMax) != 0)
2677
0
                    {
2678
                        // Failed Setting Bounds... an error should have
2679
                        // been already reported.
2680
0
                        return -1;
2681
0
                    }
2682
0
                }
2683
0
            }
2684
0
            else
2685
0
            {
2686
                // Failed setting poSpatialRef... and error should have
2687
                // been reported.
2688
0
                return -1;
2689
0
            }
2690
2691
            // Release our handle on poSpatialRef
2692
0
            if (poSpatialRef->Dereference() == 0)
2693
0
                delete poSpatialRef;
2694
0
        }
2695
0
    }
2696
0
    else
2697
0
    {
2698
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
2699
0
                 "SetMIFCoordSys() can be called only after dataset has been "
2700
0
                 "created and before any feature is set.");
2701
0
        return -1;
2702
0
    }
2703
2704
0
    return 0;
2705
0
}
2706
2707
/**********************************************************************
2708
 *                   TABFile::SetProjInfo()
2709
 *
2710
 * Set projection for a new file using a TABProjInfo structure.
2711
 *
2712
 * This function must be called after creating a new dataset and before any
2713
 * feature can be written to it.
2714
 *
2715
 * This call will also trigger a lookup of default bounds for the specified
2716
 * projection (except nonearth), and reset the m_bBoundsValid flag.
2717
 *
2718
 * Returns 0 on success, -1 on error.
2719
 **********************************************************************/
2720
int TABFile::SetProjInfo(TABProjInfo *poPI)
2721
16
{
2722
16
    if (m_eAccessMode != TABWrite)
2723
0
    {
2724
0
        CPLError(CE_Failure, CPLE_NotSupported,
2725
0
                 "SetProjInfo() can be used only with Write access.");
2726
0
        return -1;
2727
0
    }
2728
2729
    /*-----------------------------------------------------------------
2730
     * Lookup default bounds and reset m_bBoundsSet flag
2731
     *----------------------------------------------------------------*/
2732
16
    double dXMin;
2733
16
    double dYMin;
2734
16
    double dXMax;
2735
16
    double dYMax;
2736
2737
16
    m_bBoundsSet = FALSE;
2738
16
    if (MITABLookupCoordSysBounds(poPI, dXMin, dYMin, dXMax, dYMax))
2739
1
    {
2740
1
        SetBounds(dXMin, dYMin, dXMax, dYMax);
2741
1
    }
2742
2743
    /*-----------------------------------------------------------------
2744
     * Check that dataset has been created but no feature set yet.
2745
     *----------------------------------------------------------------*/
2746
16
    if (m_poMAPFile && m_nLastFeatureId < 1)
2747
16
    {
2748
16
        if (m_poMAPFile->GetHeaderBlock()->SetProjInfo(poPI) != 0)
2749
0
            return -1;
2750
16
    }
2751
0
    else
2752
0
    {
2753
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
2754
0
                 "SetProjInfo() can be called only after dataset has been "
2755
0
                 "created and before any feature is set.");
2756
0
        return -1;
2757
0
    }
2758
2759
16
    return 0;
2760
16
}
2761
2762
/************************************************************************/
2763
/*                            DeleteField()                             */
2764
/************************************************************************/
2765
2766
OGRErr TABFile::DeleteField(int iField)
2767
0
{
2768
0
    if (m_poDATFile == nullptr || !TestCapability(OLCDeleteField))
2769
0
    {
2770
0
        CPLError(CE_Failure, CPLE_NotSupported, UNSUPPORTED_OP_READ_ONLY,
2771
0
                 "DeleteField");
2772
0
        return OGRERR_FAILURE;
2773
0
    }
2774
2775
0
    if (iField < 0 || iField >= m_poDefn->GetFieldCount())
2776
0
    {
2777
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
2778
0
        return OGRERR_FAILURE;
2779
0
    }
2780
2781
0
    if (m_poDATFile->DeleteField(iField) == 0)
2782
0
    {
2783
0
        m_bNeedTABRewrite = TRUE;
2784
0
        m_oSetFields.erase(
2785
0
            CPLString(m_poDefn->GetFieldDefn(iField)->GetNameRef()).toupper());
2786
2787
        /* Delete from the array of indexed fields */
2788
0
        if (iField < m_poDefn->GetFieldCount() - 1)
2789
0
        {
2790
0
            memmove(m_panIndexNo + iField, m_panIndexNo + iField + 1,
2791
0
                    (m_poDefn->GetFieldCount() - 1 - iField) * sizeof(int));
2792
0
        }
2793
2794
0
        whileUnsealing(m_poDefn)->DeleteFieldDefn(iField);
2795
2796
0
        if (m_eAccessMode == TABReadWrite)
2797
0
            WriteTABFile();
2798
2799
0
        return OGRERR_NONE;
2800
0
    }
2801
0
    else
2802
0
        return OGRERR_FAILURE;
2803
0
}
2804
2805
/************************************************************************/
2806
/*                           ReorderFields()                            */
2807
/************************************************************************/
2808
2809
OGRErr TABFile::ReorderFields(int *panMap)
2810
0
{
2811
0
    if (m_poDATFile == nullptr || !TestCapability(OLCDeleteField))
2812
0
    {
2813
0
        CPLError(CE_Failure, CPLE_NotSupported, UNSUPPORTED_OP_READ_ONLY,
2814
0
                 "ReorderFields");
2815
0
        return OGRERR_FAILURE;
2816
0
    }
2817
0
    if (m_poDefn->GetFieldCount() == 0)
2818
0
        return OGRERR_NONE;
2819
2820
0
    OGRErr eErr = OGRCheckPermutation(panMap, m_poDefn->GetFieldCount());
2821
0
    if (eErr != OGRERR_NONE)
2822
0
        return eErr;
2823
2824
0
    if (m_poDATFile->ReorderFields(panMap) == 0)
2825
0
    {
2826
0
        m_bNeedTABRewrite = TRUE;
2827
2828
0
        int *panNewIndexedField = static_cast<int *>(
2829
0
            CPLMalloc(sizeof(int) * m_poDefn->GetFieldCount()));
2830
0
        for (int i = 0; i < m_poDefn->GetFieldCount(); i++)
2831
0
        {
2832
0
            panNewIndexedField[i] = m_panIndexNo[panMap[i]];
2833
0
        }
2834
0
        CPLFree(m_panIndexNo);
2835
0
        m_panIndexNo = panNewIndexedField;
2836
2837
0
        whileUnsealing(m_poDefn)->ReorderFieldDefns(panMap);
2838
2839
0
        if (m_eAccessMode == TABReadWrite)
2840
0
            WriteTABFile();
2841
2842
0
        return OGRERR_NONE;
2843
0
    }
2844
0
    else
2845
0
        return OGRERR_FAILURE;
2846
0
}
2847
2848
/************************************************************************/
2849
/*                           AlterFieldDefn()                           */
2850
/************************************************************************/
2851
2852
OGRErr TABFile::AlterFieldDefn(int iField, OGRFieldDefn *poNewFieldDefn,
2853
                               int nFlagsIn)
2854
0
{
2855
0
    if (m_poDATFile == nullptr || !TestCapability(OLCDeleteField))
2856
0
    {
2857
0
        CPLError(CE_Failure, CPLE_NotSupported, UNSUPPORTED_OP_READ_ONLY,
2858
0
                 "AlterFieldDefn");
2859
0
        return OGRERR_FAILURE;
2860
0
    }
2861
2862
0
    if (iField < 0 || iField >= m_poDefn->GetFieldCount())
2863
0
    {
2864
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
2865
0
        return OGRERR_FAILURE;
2866
0
    }
2867
2868
0
    OGRFieldDefn *poFieldDefn = m_poDefn->GetFieldDefn(iField);
2869
0
    if (m_poDATFile->AlterFieldDefn(iField, poFieldDefn, poNewFieldDefn,
2870
0
                                    nFlagsIn) == 0)
2871
0
    {
2872
0
        m_bNeedTABRewrite = TRUE;
2873
2874
0
        auto oTemporaryUnsealer(poFieldDefn->GetTemporaryUnsealer());
2875
0
        if ((nFlagsIn & ALTER_TYPE_FLAG) &&
2876
0
            poNewFieldDefn->GetType() != poFieldDefn->GetType())
2877
0
        {
2878
0
            poFieldDefn->SetType(poNewFieldDefn->GetType());
2879
0
        }
2880
0
        if (nFlagsIn & ALTER_NAME_FLAG)
2881
0
        {
2882
0
            m_oSetFields.erase(CPLString(poFieldDefn->GetNameRef()).toupper());
2883
0
            poFieldDefn->SetName(poNewFieldDefn->GetNameRef());
2884
0
            m_oSetFields.insert(
2885
0
                CPLString(poNewFieldDefn->GetNameRef()).toupper());
2886
0
        }
2887
0
        if (poFieldDefn->GetType() == OFTString)
2888
0
        {
2889
0
            poFieldDefn->SetWidth(m_poDATFile->GetFieldWidth(iField));
2890
0
        }
2891
0
        else if (nFlagsIn & ALTER_WIDTH_PRECISION_FLAG)
2892
0
        {
2893
0
            poFieldDefn->SetWidth(poNewFieldDefn->GetWidth());
2894
0
            poFieldDefn->SetPrecision(poNewFieldDefn->GetPrecision());
2895
0
        }
2896
2897
        // Take into account .dat limitations on width & precision to clamp
2898
        // what user might have specify
2899
0
        int nWidth = 0;
2900
0
        int nPrecision = 0;
2901
0
        GetTABType(poFieldDefn, nullptr, &nWidth, &nPrecision);
2902
0
        poFieldDefn->SetWidth(nWidth);
2903
0
        poFieldDefn->SetPrecision(nPrecision);
2904
2905
0
        if (m_eAccessMode == TABReadWrite)
2906
0
            WriteTABFile();
2907
2908
0
        return OGRERR_NONE;
2909
0
    }
2910
0
    else
2911
0
        return OGRERR_FAILURE;
2912
0
}
2913
2914
/************************************************************************/
2915
/*                             SyncToDisk()                             */
2916
/************************************************************************/
2917
2918
OGRErr TABFile::SyncToDisk()
2919
226
{
2920
    /* Silently return */
2921
226
    if (m_eAccessMode == TABRead)
2922
0
        return OGRERR_NONE;
2923
2924
226
    OGRErr eErr = OGRERR_NONE;
2925
2926
    // This is a hack for Windows and VSIFFlushL() issue. See
2927
    // http://trac.osgeo.org/gdal/ticket/5556
2928
226
    CPLSetConfigOption("VSI_FLUSH", "TRUE");
2929
2930
226
    if (WriteTABFile() != 0)
2931
0
        eErr = OGRERR_FAILURE;
2932
2933
226
    if (m_poMAPFile->SyncToDisk() != 0)
2934
0
        eErr = OGRERR_FAILURE;
2935
2936
226
    if (m_poDATFile->SyncToDisk() != 0)
2937
0
        eErr = OGRERR_FAILURE;
2938
2939
226
    CPLSetConfigOption("VSI_FLUSH", nullptr);
2940
2941
226
    return eErr;
2942
226
}
2943
2944
/************************************************************************/
2945
/*                           TestCapability()                           */
2946
/************************************************************************/
2947
2948
int TABFile::TestCapability(const char *pszCap) const
2949
2950
648
{
2951
648
    if (EQUAL(pszCap, OLCRandomRead))
2952
0
        return TRUE;
2953
2954
648
    else if (EQUAL(pszCap, OLCSequentialWrite))
2955
0
        return m_eAccessMode != TABRead;
2956
2957
648
    else if (EQUAL(pszCap, OLCRandomWrite))
2958
0
        return m_eAccessMode != TABRead;
2959
2960
648
    else if (EQUAL(pszCap, OLCDeleteFeature))
2961
0
        return m_eAccessMode != TABRead;
2962
2963
648
    else if (EQUAL(pszCap, OLCFastFeatureCount))
2964
0
        return m_poFilterGeom == nullptr && m_poAttrQuery == nullptr;
2965
2966
648
    else if (EQUAL(pszCap, OLCFastSpatialFilter))
2967
0
        return TRUE;
2968
2969
648
    else if (EQUAL(pszCap, OLCFastGetExtent))
2970
0
        return TRUE;
2971
2972
648
    else if (EQUAL(pszCap, OLCCreateField))
2973
0
        return m_eAccessMode != TABRead;
2974
2975
648
    else if (EQUAL(pszCap, OLCDeleteField))
2976
0
        return m_eAccessMode != TABRead;
2977
2978
648
    else if (EQUAL(pszCap, OLCReorderFields))
2979
0
        return m_eAccessMode != TABRead;
2980
2981
648
    else if (EQUAL(pszCap, OLCAlterFieldDefn))
2982
0
        return m_eAccessMode != TABRead;
2983
2984
648
    else if (EQUAL(pszCap, OLCStringsAsUTF8))
2985
0
        return TestUtf8Capability();
2986
2987
648
    else
2988
648
        return FALSE;
2989
648
}
2990
2991
/**********************************************************************
2992
 *                   TABFile::Dump()
2993
 *
2994
 * Dump block contents... available only in DEBUG mode.
2995
 **********************************************************************/
2996
#ifdef DEBUG
2997
2998
void TABFile::Dump(FILE *fpOut /*=NULL*/)
2999
{
3000
    if (fpOut == nullptr)
3001
        fpOut = stdout;
3002
3003
    fprintf(fpOut, "----- TABFile::Dump() -----\n");
3004
3005
    if (m_poMAPFile == nullptr)
3006
    {
3007
        fprintf(fpOut, "File is not opened.\n");
3008
    }
3009
    else
3010
    {
3011
        fprintf(fpOut, "File is opened: %s\n", m_pszFname);
3012
        fprintf(fpOut, "Associated TABLE file ...\n\n");
3013
        m_poDATFile->Dump(fpOut);
3014
        fprintf(fpOut, "... end of TABLE file dump.\n\n");
3015
        if (GetSpatialRef() != nullptr)
3016
        {
3017
            char *pszWKT = nullptr;
3018
3019
            GetSpatialRef()->exportToWkt(&pszWKT);
3020
            fprintf(fpOut, "SRS = %s\n", pszWKT);
3021
            CPLFree(pszWKT);
3022
        }
3023
        fprintf(fpOut, "Associated .MAP file ...\n\n");
3024
        m_poMAPFile->Dump(fpOut);
3025
        fprintf(fpOut, "... end of .MAP file dump.\n\n");
3026
    }
3027
3028
    fflush(fpOut);
3029
}
3030
3031
#endif  // DEBUG
3032
3033
/*
3034
 * SetMetadataItem()
3035
 */
3036
CPLErr TABFile::SetMetadataItem(const char *pszName, const char *pszValue,
3037
                                const char *pszDomain)
3038
2.55k
{
3039
2.55k
    if (EQUAL(DESCRIPTION_KEY, pszName) && EQUAL(pszDomain, ""))
3040
226
    {
3041
226
        if (m_eAccessMode == TABRead)
3042
0
        {
3043
0
            CPLError(CE_Warning, CPLE_AppDefined,
3044
0
                     "Description will not save in TAB file in readonly mode.");
3045
0
        }
3046
3047
226
        m_bNeedTABRewrite = TRUE;
3048
226
        std::shared_ptr<char> oEscapedString(EscapeString(pszValue), CPLFree);
3049
226
        auto result = IMapInfoFile::SetMetadataItem(DESCRIPTION_KEY,
3050
226
                                                    oEscapedString.get());
3051
226
        if (oEscapedString)
3052
0
        {
3053
0
            CPLDebug("MITAB", "Set description to '%s'", oEscapedString.get());
3054
0
        }
3055
226
        return result;
3056
226
    }
3057
2.33k
    return IMapInfoFile::SetMetadataItem(pszName, pszValue, pszDomain);
3058
2.55k
}
3059
3060
/**********************************************************************
3061
 *                   TABFile::GetSpatialRef()
3062
 *
3063
 * Returns a reference to an OGRSpatialReference for this dataset.
3064
 * If the projection parameters have not been parsed yet, then we will
3065
 * parse them before returning.
3066
 *
3067
 * The returned object is owned and maintained by this TABFile and
3068
 * should not be modified or freed by the caller.
3069
 *
3070
 * Returns NULL if the SpatialRef cannot be accessed.
3071
 **********************************************************************/
3072
const OGRSpatialReference *TABFile::GetSpatialRef() const
3073
1.97k
{
3074
1.97k
    if (m_poMAPFile == nullptr)
3075
0
    {
3076
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
3077
0
                 "GetSpatialRef() failed: file has not been opened yet.");
3078
0
        return nullptr;
3079
0
    }
3080
3081
1.97k
    if (GetGeomType() == wkbNone)
3082
215
        return nullptr;
3083
3084
    /*-----------------------------------------------------------------
3085
     * If projection params have already been processed, just use them.
3086
     *----------------------------------------------------------------*/
3087
1.75k
    if (m_poSpatialRef != nullptr)
3088
622
        return m_poSpatialRef;
3089
3090
    /*-----------------------------------------------------------------
3091
     * Fetch the parameters from the header.
3092
     *----------------------------------------------------------------*/
3093
1.13k
    TABProjInfo sTABProj;
3094
3095
1.13k
    TABMAPHeaderBlock *poHeader = nullptr;
3096
1.13k
    if ((poHeader = m_poMAPFile->GetHeaderBlock()) == nullptr ||
3097
1.13k
        poHeader->GetProjInfo(&sTABProj) != 0)
3098
0
    {
3099
0
        CPLError(CE_Failure, CPLE_FileIO,
3100
0
                 "GetSpatialRef() failed reading projection parameters.");
3101
0
        return nullptr;
3102
0
    }
3103
3104
1.13k
    m_poSpatialRef = TABFileGetSpatialRefFromTABProj(sTABProj);
3105
1.13k
    return m_poSpatialRef;
3106
1.13k
}
3107
3108
/**********************************************************************
3109
 *                   TABFile::SetSpatialRef()
3110
 *
3111
 * Set the OGRSpatialReference for this dataset.
3112
 * A reference to the OGRSpatialReference will be kept, and it will also
3113
 * be converted into a TABProjInfo to be stored in the .MAP header.
3114
 *
3115
 * Returns 0 on success, and -1 on error.
3116
 **********************************************************************/
3117
int TABFile::SetSpatialRef(OGRSpatialReference *poSpatialRef)
3118
16
{
3119
16
    if (m_eAccessMode != TABWrite)
3120
0
    {
3121
0
        CPLError(CE_Failure, CPLE_NotSupported,
3122
0
                 "SetSpatialRef() can be used only with Write access.");
3123
0
        return -1;
3124
0
    }
3125
3126
16
    if (m_poMAPFile == nullptr)
3127
0
    {
3128
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
3129
0
                 "SetSpatialRef() failed: file has not been opened yet.");
3130
0
        return -1;
3131
0
    }
3132
3133
16
    if (poSpatialRef == nullptr)
3134
0
    {
3135
0
        CPLError(CE_Failure, CPLE_AssertionFailed,
3136
0
                 "SetSpatialRef() failed: Called with NULL poSpatialRef.");
3137
0
        return -1;
3138
0
    }
3139
3140
    /*-----------------------------------------------------------------
3141
     * Keep a copy of the OGRSpatialReference...
3142
     * Note: we have to take the reference count into account...
3143
     *----------------------------------------------------------------*/
3144
16
    if (m_poSpatialRef && m_poSpatialRef->Dereference() == 0)
3145
0
        delete m_poSpatialRef;
3146
3147
16
    m_poSpatialRef = poSpatialRef->Clone();
3148
3149
16
    TABProjInfo sTABProj;
3150
16
    int nParamCount = 0;
3151
16
    TABFileGetTABProjFromSpatialRef(poSpatialRef, sTABProj, nParamCount);
3152
3153
    /*-----------------------------------------------------------------
3154
     * Set the new parameters in the .MAP header.
3155
     * This will also trigger lookup of default bounds for the projection.
3156
     *----------------------------------------------------------------*/
3157
16
    if (SetProjInfo(&sTABProj) != 0)
3158
0
    {
3159
0
        CPLError(CE_Failure, CPLE_FileIO,
3160
0
                 "SetSpatialRef() failed setting projection parameters.");
3161
0
        return -1;
3162
0
    }
3163
3164
16
    return 0;
3165
16
}