Coverage Report

Created: 2025-12-31 08:30

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