Coverage Report

Created: 2025-06-13 06:29

/src/gdal/gcore/gdal_mdreader.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Core
4
 * Purpose:  Read metadata (mainly the remote sensing imagery) from files of
5
 *           different providers like DigitalGlobe, GeoEye etc.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 * Author:   Dmitry Baryshnikov, polimax@mail.ru
8
 *
9
 ******************************************************************************
10
 * Copyright (c) HER MAJESTY THE QUEEN IN RIGHT OF CANADA (2008)
11
 * as represented by the Canadian Nuclear Safety Commission
12
 * Copyright (c) 2014-2015, NextGIS info@nextgis.ru
13
 *
14
 * SPDX-License-Identifier: MIT
15
 ****************************************************************************/
16
17
#include "cpl_port.h"
18
#include "gdal_mdreader.h"
19
20
#include <cctype>
21
#include <cstddef>
22
#include <cstdio>
23
#include <cstring>
24
#include <ctime>
25
#include <string>
26
27
#include "cpl_conv.h"
28
#include "cpl_error.h"
29
#include "cpl_minixml.h"
30
#include "cpl_string.h"
31
#include "cpl_time.h"
32
#include "cpl_vsi.h"
33
#include "cplkeywordparser.h"
34
#include "gdal_priv.h"
35
36
// readers
37
#include "mdreader/reader_alos.h"
38
#include "mdreader/reader_digital_globe.h"
39
#include "mdreader/reader_eros.h"
40
#include "mdreader/reader_geo_eye.h"
41
#include "mdreader/reader_kompsat.h"
42
#include "mdreader/reader_landsat.h"
43
#include "mdreader/reader_orb_view.h"
44
#include "mdreader/reader_pleiades.h"
45
#include "mdreader/reader_rapid_eye.h"
46
#include "mdreader/reader_rdk1.h"
47
#include "mdreader/reader_spot.h"
48
49
/**
50
 * The RPC parameters names
51
 */
52
53
static const char *const apszRPCTXTSingleValItems[] = {
54
    RPC_ERR_BIAS,   RPC_ERR_RAND,  RPC_LINE_OFF,   RPC_SAMP_OFF,
55
    RPC_LAT_OFF,    RPC_LONG_OFF,  RPC_HEIGHT_OFF, RPC_LINE_SCALE,
56
    RPC_SAMP_SCALE, RPC_LAT_SCALE, RPC_LONG_SCALE, RPC_HEIGHT_SCALE,
57
    nullptr};
58
59
static const char *const apszRPCTXT20ValItems[] = {
60
    RPC_LINE_NUM_COEFF, RPC_LINE_DEN_COEFF, RPC_SAMP_NUM_COEFF,
61
    RPC_SAMP_DEN_COEFF, nullptr};
62
63
/**
64
 * GDALMDReaderManager()
65
 */
66
0
GDALMDReaderManager::GDALMDReaderManager() = default;
67
68
/**
69
 * ~GDALMDReaderManager()
70
 */
71
GDALMDReaderManager::~GDALMDReaderManager()
72
0
{
73
0
    if (nullptr != m_pReader)
74
0
    {
75
0
        delete m_pReader;
76
0
    }
77
0
}
78
79
/**
80
 * GetReader()
81
 */
82
83
#define INIT_READER(reader)                                                    \
84
0
    GDALMDReaderBase *pReaderBase = new reader(pszPath, papszSiblingFiles);    \
85
0
    if (pReaderBase->HasRequiredFiles())                                       \
86
0
    {                                                                          \
87
0
        m_pReader = pReaderBase;                                               \
88
0
        return m_pReader;                                                      \
89
0
    }                                                                          \
90
0
    delete pReaderBase
91
92
GDALMDReaderBase *GDALMDReaderManager::GetReader(const char *pszPath,
93
                                                 char **papszSiblingFiles,
94
                                                 GUInt32 nType)
95
0
{
96
0
    if (!GDALCanFileAcceptSidecarFile(pszPath))
97
0
        return nullptr;
98
99
0
    if (nType & MDR_DG)
100
0
    {
101
0
        INIT_READER(GDALMDReaderDigitalGlobe);
102
0
    }
103
104
    // required filename.tif filename.pvl filename_rpc.txt
105
0
    if (nType & MDR_OV)
106
0
    {
107
0
        INIT_READER(GDALMDReaderOrbView);
108
0
    }
109
110
0
    if (nType & MDR_GE)
111
0
    {
112
0
        INIT_READER(GDALMDReaderGeoEye);
113
0
    }
114
115
0
    if (nType & MDR_LS)
116
0
    {
117
0
        INIT_READER(GDALMDReaderLandsat);
118
0
    }
119
120
0
    if (nType & MDR_PLEIADES)
121
0
    {
122
0
        INIT_READER(GDALMDReaderPleiades);
123
0
    }
124
125
0
    if (nType & MDR_SPOT)
126
0
    {
127
0
        INIT_READER(GDALMDReaderSpot);
128
0
    }
129
130
0
    if (nType & MDR_RDK1)
131
0
    {
132
0
        INIT_READER(GDALMDReaderResursDK1);
133
0
    }
134
135
0
    if (nType & MDR_RE)
136
0
    {
137
0
        INIT_READER(GDALMDReaderRapidEye);
138
0
    }
139
140
    // required filename.tif filename.rpc filename.txt
141
0
    if (nType & MDR_KOMPSAT)
142
0
    {
143
0
        INIT_READER(GDALMDReaderKompsat);
144
0
    }
145
146
0
    if (nType & MDR_EROS)
147
0
    {
148
0
        INIT_READER(GDALMDReaderEROS);
149
0
    }
150
151
0
    if (nType & MDR_ALOS)
152
0
    {
153
0
        INIT_READER(GDALMDReaderALOS);
154
0
    }
155
156
0
    return nullptr;
157
0
}
158
159
/**
160
 * GDALMDReaderBase()
161
 */
162
GDALMDReaderBase::GDALMDReaderBase(const char * /* pszPath */,
163
                                   char ** /* papszSiblingFiles */)
164
0
{
165
0
}
166
167
/**
168
 * ~GDALMDReaderBase()
169
 */
170
GDALMDReaderBase::~GDALMDReaderBase()
171
0
{
172
0
    CSLDestroy(m_papszIMDMD);
173
0
    CSLDestroy(m_papszRPCMD);
174
0
    CSLDestroy(m_papszIMAGERYMD);
175
0
    CSLDestroy(m_papszDEFAULTMD);
176
0
}
177
178
/**
179
 * GetMetadataItem()
180
 */
181
char **GDALMDReaderBase::GetMetadataDomain(const char *pszDomain)
182
0
{
183
0
    LoadMetadata();
184
0
    if (EQUAL(pszDomain, MD_DOMAIN_DEFAULT))
185
0
        return m_papszDEFAULTMD;
186
0
    else if (EQUAL(pszDomain, MD_DOMAIN_IMD))
187
0
        return m_papszIMDMD;
188
0
    else if (EQUAL(pszDomain, MD_DOMAIN_RPC))
189
0
        return m_papszRPCMD;
190
0
    else if (EQUAL(pszDomain, MD_DOMAIN_IMAGERY))
191
0
        return m_papszIMAGERYMD;
192
0
    return nullptr;
193
0
}
194
195
/**
196
 * LoadMetadata()
197
 */
198
void GDALMDReaderBase::LoadMetadata()
199
0
{
200
0
    if (m_bIsMetadataLoad)
201
0
        return;
202
0
    m_bIsMetadataLoad = true;
203
0
}
204
205
/**
206
 * GetAcqisitionTimeFromString()
207
 */
208
GIntBig GDALMDReaderBase::GetAcquisitionTimeFromString(const char *pszDateTime)
209
0
{
210
0
    if (nullptr == pszDateTime)
211
0
        return 0;
212
213
0
    int iYear = 0;
214
0
    int iMonth = 0;
215
0
    int iDay = 0;
216
0
    int iHours = 0;
217
0
    int iMin = 0;
218
0
    int iSec = 0;
219
220
0
    const int r = sscanf(pszDateTime, "%d-%d-%dT%d:%d:%d.%*dZ", &iYear, &iMonth,
221
0
                         &iDay, &iHours, &iMin, &iSec);
222
223
0
    if (r != 6)
224
0
        return 0;
225
226
0
    struct tm tmDateTime;
227
0
    tmDateTime.tm_sec = iSec;
228
0
    tmDateTime.tm_min = iMin;
229
0
    tmDateTime.tm_hour = iHours;
230
0
    tmDateTime.tm_mday = iDay;
231
0
    tmDateTime.tm_mon = iMonth - 1;
232
0
    tmDateTime.tm_year = iYear - 1900;
233
0
    tmDateTime.tm_isdst = -1;
234
235
0
    return CPLYMDHMSToUnixTime(&tmDateTime);
236
0
}
237
238
/**
239
 * FillMetadata()
240
 */
241
242
#define SETMETADATA(mdmd, md, domain)                                          \
243
0
    if (nullptr != md)                                                         \
244
0
    {                                                                          \
245
0
        char **papszCurrentMd = CSLDuplicate(mdmd->GetMetadata(domain));       \
246
0
        papszCurrentMd = CSLMerge(papszCurrentMd, md);                         \
247
0
        mdmd->SetMetadata(papszCurrentMd, domain);                             \
248
0
        CSLDestroy(papszCurrentMd);                                            \
249
0
    }
250
251
bool GDALMDReaderBase::FillMetadata(GDALMultiDomainMetadata *poMDMD)
252
0
{
253
0
    if (nullptr == poMDMD)
254
0
        return false;
255
256
0
    LoadMetadata();
257
258
0
    SETMETADATA(poMDMD, m_papszIMDMD, MD_DOMAIN_IMD);
259
0
    SETMETADATA(poMDMD, m_papszRPCMD, MD_DOMAIN_RPC);
260
0
    SETMETADATA(poMDMD, m_papszIMAGERYMD, MD_DOMAIN_IMAGERY);
261
0
    SETMETADATA(poMDMD, m_papszDEFAULTMD, MD_DOMAIN_DEFAULT);
262
263
0
    return true;
264
0
}
265
266
/**
267
 * AddXMLNameValueToList()
268
 */
269
char **GDALMDReaderBase::AddXMLNameValueToList(char **papszList,
270
                                               const char *pszName,
271
                                               const char *pszValue)
272
0
{
273
0
    return CSLAddNameValue(papszList, pszName, pszValue);
274
0
}
275
276
/**
277
 * ReadXMLToListFirstPass()
278
 */
279
bool GDALMDReaderBase::ReadXMLToListFirstPass(
280
    const CPLXMLNode *psNode, std::map<std::string, int> &oMapCountKeysFull,
281
    const std::string &osPrefixFull, int nDepth)
282
0
{
283
0
    if (nDepth == 10)
284
0
    {
285
0
        CPLError(CE_Failure, CPLE_AppDefined, "Too much nested XML");
286
0
        return false;
287
0
    }
288
0
    if (nullptr == psNode)
289
0
        return true;
290
0
    while (true)
291
0
    {
292
0
        if (psNode->eType == CXT_Element)
293
0
        {
294
0
            std::string osNewPrefixFull;
295
0
            for (const CPLXMLNode *psChildNode = psNode->psChild;
296
0
                 nullptr != psChildNode; psChildNode = psChildNode->psNext)
297
0
            {
298
0
                if (psChildNode->eType == CXT_Element)
299
0
                {
300
0
                    osNewPrefixFull = !osPrefixFull.empty()
301
0
                                          ? osPrefixFull
302
0
                                          : std::string(psNode->pszValue);
303
0
                    osNewPrefixFull += '.';
304
0
                    osNewPrefixFull += psChildNode->pszValue;
305
0
                    osNewPrefixFull +=
306
0
                        CPLSPrintf("_%d", ++oMapCountKeysFull[osNewPrefixFull]);
307
308
0
                    if (!ReadXMLToListFirstPass(psChildNode, oMapCountKeysFull,
309
0
                                                osNewPrefixFull, nDepth + 1))
310
0
                        return false;
311
0
                }
312
0
            }
313
0
        }
314
315
        // proceed next only on top level
316
317
0
        if (nullptr != psNode->psNext && osPrefixFull.empty())
318
0
        {
319
0
            psNode = psNode->psNext;
320
0
        }
321
0
        else
322
0
        {
323
0
            break;
324
0
        }
325
0
    }
326
0
    return true;
327
0
}
328
329
/**
330
 * ReadXMLToList()
331
 */
332
char **GDALMDReaderBase::ReadXMLToList(
333
    const CPLXMLNode *psNode, char **papszList,
334
    const std::map<std::string, int> &oMapCountKeysFullRef,
335
    std::map<std::string, int> &oMapCountKeysFull,
336
    std::map<std::string, int> &oMapCountKeys, const std::string &osPrefix,
337
    const std::string &osPrefixFull)
338
0
{
339
0
    if (nullptr == psNode)
340
0
        return papszList;
341
342
0
    while (true)
343
0
    {
344
0
        if (psNode->eType == CXT_Text)
345
0
        {
346
0
            papszList = AddXMLNameValueToList(papszList, osPrefix.c_str(),
347
0
                                              psNode->pszValue);
348
0
        }
349
350
0
        if (psNode->eType == CXT_Element)
351
0
        {
352
0
            std::string osNewPrefix;
353
0
            std::string osNewPrefixFull;
354
0
            for (const CPLXMLNode *psChildNode = psNode->psChild;
355
0
                 nullptr != psChildNode; psChildNode = psChildNode->psNext)
356
0
            {
357
0
                if (psChildNode->eType == CXT_Element)
358
0
                {
359
0
                    osNewPrefixFull = !osPrefixFull.empty()
360
0
                                          ? osPrefixFull
361
0
                                          : std::string(psNode->pszValue);
362
0
                    osNewPrefixFull += '.';
363
0
                    osNewPrefixFull += psChildNode->pszValue;
364
365
0
                    const auto oIter =
366
0
                        oMapCountKeysFullRef.find(osNewPrefixFull);
367
0
                    CPLAssert(oIter != oMapCountKeysFullRef.end());
368
0
                    osNewPrefixFull +=
369
0
                        CPLSPrintf("_%d", ++oMapCountKeysFull[osNewPrefixFull]);
370
371
0
                    osNewPrefix = !osPrefix.empty()
372
0
                                      ? osPrefix
373
0
                                      : std::string(psNode->pszValue);
374
0
                    osNewPrefix += '.';
375
0
                    osNewPrefix += psChildNode->pszValue;
376
0
                    const int nIndex = ++oMapCountKeys[osNewPrefix];
377
0
                    const bool bMultipleInstances = oIter->second >= 2;
378
0
                    if (bMultipleInstances)
379
0
                    {
380
0
                        osNewPrefix += CPLSPrintf("_%d", nIndex);
381
0
                    }
382
0
                    papszList = ReadXMLToList(psChildNode, papszList,
383
0
                                              oMapCountKeysFullRef,
384
0
                                              oMapCountKeysFull, oMapCountKeys,
385
0
                                              osNewPrefix, osNewPrefixFull);
386
0
                }
387
0
                else if (psChildNode->eType == CXT_Attribute)
388
0
                {
389
0
                    papszList = AddXMLNameValueToList(
390
0
                        papszList,
391
0
                        CPLSPrintf("%s.%s", osPrefix.c_str(),
392
0
                                   psChildNode->pszValue),
393
0
                        psChildNode->psChild->pszValue);
394
0
                }
395
0
                else
396
0
                {
397
                    // Text nodes should always have name
398
0
                    if (osPrefix.empty())
399
0
                    {
400
0
                        papszList = ReadXMLToList(
401
0
                            psChildNode, papszList, oMapCountKeysFullRef,
402
0
                            oMapCountKeysFull, oMapCountKeys, psNode->pszValue,
403
0
                            psNode->pszValue);
404
0
                    }
405
0
                    else
406
0
                    {
407
0
                        papszList = ReadXMLToList(
408
0
                            psChildNode, papszList, oMapCountKeysFullRef,
409
0
                            oMapCountKeysFull, oMapCountKeys, osPrefix.c_str(),
410
0
                            osNewPrefixFull.c_str());
411
0
                    }
412
0
                }
413
0
            }
414
0
        }
415
416
        // proceed next only on top level
417
418
0
        if (nullptr != psNode->psNext && osPrefix.empty())
419
0
        {
420
0
            psNode = psNode->psNext;
421
0
        }
422
0
        else
423
0
        {
424
0
            break;
425
0
        }
426
0
    }
427
428
0
    return papszList;
429
0
}
430
431
/**
432
 * ReadXMLToList()
433
 */
434
char **GDALMDReaderBase::ReadXMLToList(CPLXMLNode *psNode, char **papszList,
435
                                       const char *pszName)
436
0
{
437
0
    std::map<std::string, int> oMapCountKeysFullRef;
438
0
    if (!ReadXMLToListFirstPass(psNode, oMapCountKeysFullRef, pszName, 0))
439
0
        return papszList;
440
0
    std::map<std::string, int> oMapCountKeysFull;
441
0
    std::map<std::string, int> oMapCountKeys;
442
0
    return ReadXMLToList(psNode, papszList, oMapCountKeysFullRef,
443
0
                         oMapCountKeysFull, oMapCountKeys, pszName, pszName);
444
0
}
445
446
//------------------------------------------------------------------------------
447
// Miscellaneous functions
448
//------------------------------------------------------------------------------
449
450
/**
451
 * GDALCheckFileHeader()
452
 */
453
bool GDALCheckFileHeader(const CPLString &soFilePath, const char *pszTestString,
454
                         int nBufferSize)
455
0
{
456
0
    VSILFILE *fpL = VSIFOpenL(soFilePath, "r");
457
0
    if (fpL == nullptr)
458
0
        return false;
459
0
    char *pBuffer = new char[nBufferSize + 1];
460
0
    const int nReadBytes =
461
0
        static_cast<int>(VSIFReadL(pBuffer, 1, nBufferSize, fpL));
462
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
463
0
    if (nReadBytes == 0)
464
0
    {
465
0
        delete[] pBuffer;
466
0
        return false;
467
0
    }
468
0
    pBuffer[nReadBytes] = '\0';
469
470
0
    const bool bResult = strstr(pBuffer, pszTestString) != nullptr;
471
0
    delete[] pBuffer;
472
473
0
    return bResult;
474
0
}
475
476
/**
477
 * CPLStrip()
478
 */
479
CPLString CPLStrip(const CPLString &sString, const char cChar)
480
0
{
481
0
    if (sString.empty())
482
0
        return sString;
483
484
0
    size_t dCopyFrom = 0;
485
0
    size_t dCopyCount = sString.size();
486
487
0
    if (sString[0] == cChar)
488
0
    {
489
0
        dCopyFrom++;
490
0
        dCopyCount--;
491
0
    }
492
493
0
    if (sString.back() == cChar)
494
0
        dCopyCount--;
495
496
0
    if (dCopyCount == 0)
497
0
        return CPLString();
498
499
0
    return sString.substr(dCopyFrom, dCopyCount);
500
0
}
501
502
/**
503
 * CPLStripQuotes()
504
 */
505
CPLString CPLStripQuotes(const CPLString &sString)
506
0
{
507
0
    return CPLStrip(CPLStrip(sString, '"'), '\'');
508
0
}
509
510
/************************************************************************/
511
/*                          GDALLoadRPBFile()                           */
512
/************************************************************************/
513
514
static const char *const apszRPBMap[] = {apszRPCTXTSingleValItems[0],
515
                                         "IMAGE.errBias",
516
                                         apszRPCTXTSingleValItems[1],
517
                                         "IMAGE.errRand",
518
                                         apszRPCTXTSingleValItems[2],
519
                                         "IMAGE.lineOffset",
520
                                         apszRPCTXTSingleValItems[3],
521
                                         "IMAGE.sampOffset",
522
                                         apszRPCTXTSingleValItems[4],
523
                                         "IMAGE.latOffset",
524
                                         apszRPCTXTSingleValItems[5],
525
                                         "IMAGE.longOffset",
526
                                         apszRPCTXTSingleValItems[6],
527
                                         "IMAGE.heightOffset",
528
                                         apszRPCTXTSingleValItems[7],
529
                                         "IMAGE.lineScale",
530
                                         apszRPCTXTSingleValItems[8],
531
                                         "IMAGE.sampScale",
532
                                         apszRPCTXTSingleValItems[9],
533
                                         "IMAGE.latScale",
534
                                         apszRPCTXTSingleValItems[10],
535
                                         "IMAGE.longScale",
536
                                         apszRPCTXTSingleValItems[11],
537
                                         "IMAGE.heightScale",
538
                                         apszRPCTXT20ValItems[0],
539
                                         "IMAGE.lineNumCoef",
540
                                         apszRPCTXT20ValItems[1],
541
                                         "IMAGE.lineDenCoef",
542
                                         apszRPCTXT20ValItems[2],
543
                                         "IMAGE.sampNumCoef",
544
                                         apszRPCTXT20ValItems[3],
545
                                         "IMAGE.sampDenCoef",
546
                                         nullptr,
547
                                         nullptr};
548
549
char **GDALLoadRPBFile(const CPLString &soFilePath)
550
0
{
551
0
    if (soFilePath.empty())
552
0
        return nullptr;
553
554
    /* -------------------------------------------------------------------- */
555
    /*      Read file and parse.                                            */
556
    /* -------------------------------------------------------------------- */
557
0
    VSILFILE *fp = VSIFOpenL(soFilePath, "r");
558
559
0
    if (fp == nullptr)
560
0
        return nullptr;
561
562
0
    CPLKeywordParser oParser;
563
0
    if (!oParser.Ingest(fp))
564
0
    {
565
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
566
0
        return nullptr;
567
0
    }
568
569
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
570
571
    /* -------------------------------------------------------------------- */
572
    /*      Extract RPC information, in a GDAL "standard" metadata format.  */
573
    /* -------------------------------------------------------------------- */
574
0
    char **papszMD = nullptr;
575
0
    for (int i = 0; apszRPBMap[i] != nullptr; i += 2)
576
0
    {
577
0
        const char *pszRPBVal = oParser.GetKeyword(apszRPBMap[i + 1]);
578
0
        CPLString osAdjVal;
579
580
0
        if (pszRPBVal == nullptr)
581
0
        {
582
0
            if (strcmp(apszRPBMap[i], RPC_ERR_RAND) == 0 ||
583
0
                strcmp(apszRPBMap[i], RPC_ERR_BIAS) == 0)
584
0
            {
585
0
                continue;
586
0
            }
587
0
            CPLError(
588
0
                CE_Failure, CPLE_AppDefined,
589
0
                "%s file found, but missing %s field (and possibly others).",
590
0
                soFilePath.c_str(), apszRPBMap[i + 1]);
591
0
            CSLDestroy(papszMD);
592
0
            return nullptr;
593
0
        }
594
595
0
        if (strchr(pszRPBVal, ',') == nullptr)
596
0
            osAdjVal = pszRPBVal;
597
0
        else
598
0
        {
599
            // strip out commas and turn newlines into spaces.
600
0
            for (int j = 0; pszRPBVal[j] != '\0'; j++)
601
0
            {
602
0
                switch (pszRPBVal[j])
603
0
                {
604
0
                    case ',':
605
0
                    case '\n':
606
0
                    case '\r':
607
0
                        osAdjVal += ' ';
608
0
                        break;
609
610
0
                    case '(':
611
0
                    case ')':
612
0
                        break;
613
614
0
                    default:
615
0
                        osAdjVal += pszRPBVal[j];
616
0
                }
617
0
            }
618
0
        }
619
620
0
        papszMD = CSLSetNameValue(papszMD, apszRPBMap[i], osAdjVal);
621
0
    }
622
623
0
    return papszMD;
624
0
}
625
626
/************************************************************************/
627
/*                          GDALLoadRPCFile()                           */
628
/************************************************************************/
629
630
/* Load a GeoEye _rpc.txt file. See ticket
631
 * http://trac.osgeo.org/gdal/ticket/3639 */
632
633
char **GDALLoadRPCFile(const CPLString &soFilePath)
634
0
{
635
0
    if (soFilePath.empty())
636
0
        return nullptr;
637
638
    /* -------------------------------------------------------------------- */
639
    /*      Read file and parse.                                            */
640
    /* -------------------------------------------------------------------- */
641
    // 100 lines would be enough, but some .rpc files have CR CR LF end of
642
    // lines, which result in a blank line to be recognized, so accept up
643
    // to 200 lines (#6341)
644
0
    char **papszLines = CSLLoad2(soFilePath, 200, 100, nullptr);
645
0
    if (!papszLines)
646
0
        return nullptr;
647
648
0
    char **papszMD = nullptr;
649
650
    /* From LINE_OFF to HEIGHT_SCALE */
651
0
    for (size_t i = 0; i < 23; i += 2)
652
0
    {
653
0
        const char *pszRPBVal = CSLFetchNameValue(papszLines, apszRPBMap[i]);
654
655
0
        if (pszRPBVal == nullptr)
656
0
        {
657
0
            if (strcmp(apszRPBMap[i], RPC_ERR_RAND) == 0 ||
658
0
                strcmp(apszRPBMap[i], RPC_ERR_BIAS) == 0)
659
0
            {
660
0
                continue;
661
0
            }
662
0
            CPLError(
663
0
                CE_Failure, CPLE_AppDefined,
664
0
                "%s file found, but missing %s field (and possibly others).",
665
0
                soFilePath.c_str(), apszRPBMap[i]);
666
0
            CSLDestroy(papszMD);
667
0
            CSLDestroy(papszLines);
668
0
            return nullptr;
669
0
        }
670
0
        else
671
0
        {
672
0
            while (*pszRPBVal == ' ' || *pszRPBVal == '\t')
673
0
                pszRPBVal++;
674
0
            papszMD = CSLSetNameValue(papszMD, apszRPBMap[i], pszRPBVal);
675
0
        }
676
0
    }
677
678
    /* For LINE_NUM_COEFF, LINE_DEN_COEFF, SAMP_NUM_COEFF, SAMP_DEN_COEFF */
679
    /* parameters that have 20 values each */
680
0
    for (size_t i = 24; apszRPBMap[i] != nullptr; i += 2)
681
0
    {
682
0
        CPLString soVal;
683
0
        for (int j = 1; j <= 20; j++)
684
0
        {
685
0
            CPLString soRPBMapItem;
686
0
            soRPBMapItem.Printf("%s_%d", apszRPBMap[i], j);
687
0
            const char *pszRPBVal =
688
0
                CSLFetchNameValue(papszLines, soRPBMapItem.c_str());
689
0
            if (pszRPBVal == nullptr)
690
0
            {
691
0
                CPLError(CE_Failure, CPLE_AppDefined,
692
0
                         "%s file found, but missing %s field (and possibly "
693
0
                         "others).",
694
0
                         soFilePath.c_str(), soRPBMapItem.c_str());
695
0
                CSLDestroy(papszMD);
696
0
                CSLDestroy(papszLines);
697
0
                return nullptr;
698
0
            }
699
0
            else
700
0
            {
701
0
                while (*pszRPBVal == ' ' || *pszRPBVal == '\t')
702
0
                    pszRPBVal++;
703
0
                soVal += pszRPBVal;
704
0
                soVal += " ";
705
0
            }
706
0
        }
707
0
        papszMD = CSLSetNameValue(papszMD, apszRPBMap[i], soVal.c_str());
708
0
    }
709
710
0
    CSLDestroy(papszLines);
711
0
    return papszMD;
712
0
}
713
714
/************************************************************************/
715
/*                         GDALWriteRPCTXTFile()                        */
716
/************************************************************************/
717
718
CPLErr GDALWriteRPCTXTFile(const char *pszFilename, char **papszMD)
719
720
0
{
721
0
    CPLString osRPCFilename = pszFilename;
722
0
    CPLString soPt(".");
723
0
    size_t found = osRPCFilename.rfind(soPt);
724
0
    if (found == CPLString::npos)
725
0
        return CE_Failure;
726
0
    osRPCFilename.replace(found, osRPCFilename.size() - found, "_RPC.TXT");
727
0
    if (papszMD == nullptr)
728
0
    {
729
0
        VSIUnlink(osRPCFilename);
730
0
        return CE_None;
731
0
    }
732
733
    /* -------------------------------------------------------------------- */
734
    /*      Read file and parse.                                            */
735
    /* -------------------------------------------------------------------- */
736
0
    VSILFILE *fp = VSIFOpenL(osRPCFilename, "w");
737
738
0
    if (fp == nullptr)
739
0
    {
740
0
        CPLError(CE_Failure, CPLE_OpenFailed,
741
0
                 "Unable to create %s for writing.\n%s", osRPCFilename.c_str(),
742
0
                 CPLGetLastErrorMsg());
743
0
        return CE_Failure;
744
0
    }
745
746
    /* -------------------------------------------------------------------- */
747
    /*      Write RPC values from our RPC metadata.                         */
748
    /* -------------------------------------------------------------------- */
749
0
    bool bOK = true;
750
0
    for (int i = 0; apszRPCTXTSingleValItems[i] != nullptr; i++)
751
0
    {
752
0
        const char *pszRPCVal =
753
0
            CSLFetchNameValue(papszMD, apszRPCTXTSingleValItems[i]);
754
0
        if (pszRPCVal == nullptr)
755
0
        {
756
0
            if (strcmp(apszRPCTXTSingleValItems[i], RPC_ERR_BIAS) == 0 ||
757
0
                strcmp(apszRPCTXTSingleValItems[i], RPC_ERR_RAND) == 0)
758
0
            {
759
0
                continue;
760
0
            }
761
0
            CPLError(CE_Failure, CPLE_AppDefined,
762
0
                     "%s field missing in metadata, %s file not written.",
763
0
                     apszRPCTXTSingleValItems[i], osRPCFilename.c_str());
764
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
765
0
            VSIUnlink(osRPCFilename);
766
0
            return CE_Failure;
767
0
        }
768
769
0
        bOK &= VSIFPrintfL(fp, "%s: %s\n", apszRPCTXTSingleValItems[i],
770
0
                           pszRPCVal) > 0;
771
0
    }
772
773
0
    for (int i = 0; apszRPCTXT20ValItems[i] != nullptr; i++)
774
0
    {
775
0
        const char *pszRPCVal =
776
0
            CSLFetchNameValue(papszMD, apszRPCTXT20ValItems[i]);
777
0
        if (pszRPCVal == nullptr)
778
0
        {
779
0
            CPLError(CE_Failure, CPLE_AppDefined,
780
0
                     "%s field missing in metadata, %s file not written.",
781
0
                     apszRPCTXTSingleValItems[i], osRPCFilename.c_str());
782
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
783
0
            VSIUnlink(osRPCFilename);
784
0
            return CE_Failure;
785
0
        }
786
787
0
        char **papszItems =
788
0
            CSLTokenizeStringComplex(pszRPCVal, " ,", FALSE, FALSE);
789
790
0
        if (CSLCount(papszItems) != 20)
791
0
        {
792
0
            CPLError(CE_Failure, CPLE_AppDefined,
793
0
                     "%s field is corrupt (not 20 values), %s file not "
794
0
                     "written.\n%s = %s",
795
0
                     apszRPCTXT20ValItems[i], osRPCFilename.c_str(),
796
0
                     apszRPCTXT20ValItems[i], pszRPCVal);
797
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
798
0
            VSIUnlink(osRPCFilename);
799
0
            CSLDestroy(papszItems);
800
0
            return CE_Failure;
801
0
        }
802
803
0
        for (int j = 0; j < 20; j++)
804
0
        {
805
0
            bOK &= VSIFPrintfL(fp, "%s_%d: %s\n", apszRPCTXT20ValItems[i],
806
0
                               j + 1, papszItems[j]) > 0;
807
0
        }
808
0
        CSLDestroy(papszItems);
809
0
    }
810
811
0
    if (VSIFCloseL(fp) != 0)
812
0
        bOK = false;
813
814
0
    return bOK ? CE_None : CE_Failure;
815
0
}
816
817
/************************************************************************/
818
/*                          GDALWriteRPBFile()                          */
819
/************************************************************************/
820
821
CPLErr GDALWriteRPBFile(const char *pszFilename, char **papszMD)
822
823
0
{
824
0
    const CPLString osRPBFilename = CPLResetExtensionSafe(pszFilename, "RPB");
825
0
    if (papszMD == nullptr)
826
0
    {
827
0
        VSIUnlink(osRPBFilename);
828
0
        return CE_None;
829
0
    }
830
831
    /* -------------------------------------------------------------------- */
832
    /*      Read file and parse.                                            */
833
    /* -------------------------------------------------------------------- */
834
0
    VSILFILE *fp = VSIFOpenL(osRPBFilename, "w");
835
836
0
    if (fp == nullptr)
837
0
    {
838
0
        CPLError(CE_Failure, CPLE_OpenFailed,
839
0
                 "Unable to create %s for writing.\n%s", osRPBFilename.c_str(),
840
0
                 CPLGetLastErrorMsg());
841
0
        return CE_Failure;
842
0
    }
843
844
    /* -------------------------------------------------------------------- */
845
    /*      Write the prefix information.                                   */
846
    /* -------------------------------------------------------------------- */
847
0
    bool bOK = VSIFPrintfL(fp, "%s", "satId = \"QB02\";\n") > 0;
848
0
    bOK &= VSIFPrintfL(fp, "%s", "bandId = \"P\";\n") > 0;
849
0
    bOK &= VSIFPrintfL(fp, "%s", "SpecId = \"RPC00B\";\n") > 0;
850
0
    bOK &= VSIFPrintfL(fp, "%s", "BEGIN_GROUP = IMAGE\n") > 0;
851
852
    /* -------------------------------------------------------------------- */
853
    /*      Write RPC values from our RPC metadata.                         */
854
    /* -------------------------------------------------------------------- */
855
0
    for (int i = 0; apszRPBMap[i] != nullptr; i += 2)
856
0
    {
857
0
        const char *pszRPBVal = CSLFetchNameValue(papszMD, apszRPBMap[i]);
858
0
        const char *pszRPBTag;
859
860
0
        if (pszRPBVal == nullptr)
861
0
        {
862
0
            if (strcmp(apszRPBMap[i], RPC_ERR_BIAS) == 0)
863
0
            {
864
0
                bOK &= VSIFPrintfL(fp, "%s", "\terrBias = 0.0;\n") > 0;
865
0
                continue;
866
0
            }
867
0
            else if (strcmp(apszRPBMap[i], RPC_ERR_RAND) == 0)
868
0
            {
869
0
                bOK &= VSIFPrintfL(fp, "%s", "\terrRand = 0.0;\n") > 0;
870
0
                continue;
871
0
            }
872
0
            CPLError(CE_Failure, CPLE_AppDefined,
873
0
                     "%s field missing in metadata, %s file not written.",
874
0
                     apszRPBMap[i], osRPBFilename.c_str());
875
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
876
0
            VSIUnlink(osRPBFilename);
877
0
            return CE_Failure;
878
0
        }
879
880
0
        pszRPBTag = apszRPBMap[i + 1];
881
0
        if (STARTS_WITH_CI(pszRPBTag, "IMAGE."))
882
0
            pszRPBTag += 6;
883
884
0
        if (strstr(apszRPBMap[i], "COEF") == nullptr)
885
0
        {
886
0
            bOK &= VSIFPrintfL(fp, "\t%s = %s;\n", pszRPBTag, pszRPBVal) > 0;
887
0
        }
888
0
        else
889
0
        {
890
            // Reformat in brackets with commas over multiple lines.
891
892
0
            bOK &= VSIFPrintfL(fp, "\t%s = (\n", pszRPBTag) > 0;
893
894
0
            char **papszItems =
895
0
                CSLTokenizeStringComplex(pszRPBVal, " ,", FALSE, FALSE);
896
897
0
            if (CSLCount(papszItems) != 20)
898
0
            {
899
0
                CPLError(CE_Failure, CPLE_AppDefined,
900
0
                         "%s field is corrupt (not 20 values), %s file not "
901
0
                         "written.\n%s = %s",
902
0
                         apszRPBMap[i], osRPBFilename.c_str(), apszRPBMap[i],
903
0
                         pszRPBVal);
904
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
905
0
                VSIUnlink(osRPBFilename);
906
0
                CSLDestroy(papszItems);
907
0
                return CE_Failure;
908
0
            }
909
910
0
            for (int j = 0; j < 20; j++)
911
0
            {
912
0
                if (j < 19)
913
0
                    bOK &= VSIFPrintfL(fp, "\t\t\t%s,\n", papszItems[j]) > 0;
914
0
                else
915
0
                    bOK &= VSIFPrintfL(fp, "\t\t\t%s);\n", papszItems[j]) > 0;
916
0
            }
917
0
            CSLDestroy(papszItems);
918
0
        }
919
0
    }
920
921
    /* -------------------------------------------------------------------- */
922
    /*      Write end part                                                  */
923
    /* -------------------------------------------------------------------- */
924
0
    bOK &= VSIFPrintfL(fp, "%s", "END_GROUP = IMAGE\n") > 0;
925
0
    bOK &= VSIFPrintfL(fp, "END;\n") > 0;
926
0
    if (VSIFCloseL(fp) != 0)
927
0
        bOK = false;
928
929
0
    return bOK ? CE_None : CE_Failure;
930
0
}
931
932
/************************************************************************/
933
/*                           GDAL_IMD_AA2R()                            */
934
/*                                                                      */
935
/*      Translate AA version IMD file to R version.                     */
936
/************************************************************************/
937
938
static bool GDAL_IMD_AA2R(char ***ppapszIMD)
939
940
0
{
941
0
    char **papszIMD = *ppapszIMD;
942
943
    /* -------------------------------------------------------------------- */
944
    /*      Verify that we have a new format file.                          */
945
    /* -------------------------------------------------------------------- */
946
0
    const char *pszValue = CSLFetchNameValue(papszIMD, "version");
947
948
0
    if (pszValue == nullptr)
949
0
        return false;
950
951
0
    if (EQUAL(pszValue, "\"R\""))
952
0
        return true;
953
954
0
    if (!EQUAL(pszValue, "\"AA\""))
955
0
    {
956
0
        CPLDebug("IMD",
957
0
                 "The file is not the expected 'version = \"AA\"' format.\n"
958
0
                 "Proceeding, but file may be corrupted.");
959
0
    }
960
961
    /* -------------------------------------------------------------------- */
962
    /*      Fix the version line.                                           */
963
    /* -------------------------------------------------------------------- */
964
0
    papszIMD = CSLSetNameValue(papszIMD, "version", "\"R\"");
965
966
    /* -------------------------------------------------------------------- */
967
    /*      remove a bunch of fields.                                       */
968
    /* -------------------------------------------------------------------- */
969
0
    static const char *const apszToRemove[] = {
970
0
        "productCatalogId",   "childCatalogId",
971
0
        "productType",        "numberOfLooks",
972
0
        "effectiveBandwidth", "mode",
973
0
        "scanDirection",      "cloudCover",
974
0
        "productGSD",         nullptr};
975
976
0
    for (int iKey = 0; apszToRemove[iKey] != nullptr; iKey++)
977
0
    {
978
0
        int iTarget = CSLFindName(papszIMD, apszToRemove[iKey]);
979
0
        if (iTarget != -1)
980
0
            papszIMD = CSLRemoveStrings(papszIMD, iTarget, 1, nullptr);
981
0
    }
982
983
    /* -------------------------------------------------------------------- */
984
    /*      Replace various min/mean/max with just the mean.                */
985
    /* -------------------------------------------------------------------- */
986
0
    static const char *const keylist[] = {"CollectedRowGSD",
987
0
                                          "CollectedColGSD",
988
0
                                          "SunAz",
989
0
                                          "SunEl",
990
0
                                          "SatAz",
991
0
                                          "SatEl",
992
0
                                          "InTrackViewAngle",
993
0
                                          "CrossTrackViewAngle",
994
0
                                          "OffNadirViewAngle",
995
0
                                          nullptr};
996
997
0
    for (int iKey = 0; keylist[iKey] != nullptr; iKey++)
998
0
    {
999
0
        CPLString osTarget;
1000
0
        osTarget.Printf("IMAGE_1.min%s", keylist[iKey]);
1001
0
        int iTarget = CSLFindName(papszIMD, osTarget);
1002
0
        if (iTarget != -1)
1003
0
            papszIMD = CSLRemoveStrings(papszIMD, iTarget, 1, nullptr);
1004
1005
0
        osTarget.Printf("IMAGE_1.max%s", keylist[iKey]);
1006
0
        iTarget = CSLFindName(papszIMD, osTarget);
1007
0
        if (iTarget != -1)
1008
0
            papszIMD = CSLRemoveStrings(papszIMD, iTarget, 1, nullptr);
1009
1010
0
        osTarget.Printf("IMAGE_1.mean%s", keylist[iKey]);
1011
0
        iTarget = CSLFindName(papszIMD, osTarget);
1012
0
        if (iTarget != -1)
1013
0
        {
1014
0
            CPLString osValue = CSLFetchNameValue(papszIMD, osTarget);
1015
0
            CPLString osLine;
1016
0
            osTarget.Printf(
1017
0
                "IMAGE_1.%c%s",
1018
0
                CPLTolower(static_cast<unsigned char>(keylist[iKey][0])),
1019
0
                keylist[iKey] + 1);
1020
1021
0
            osLine = osTarget + "=" + osValue;
1022
1023
0
            CPLFree(papszIMD[iTarget]);
1024
0
            papszIMD[iTarget] = CPLStrdup(osLine);
1025
0
        }
1026
0
    }
1027
1028
0
    *ppapszIMD = papszIMD;
1029
0
    return true;
1030
0
}
1031
1032
/************************************************************************/
1033
/*                          GDALLoadIMDFile()                           */
1034
/************************************************************************/
1035
1036
char **GDALLoadIMDFile(const CPLString &osFilePath)
1037
0
{
1038
0
    if (osFilePath.empty())
1039
0
        return nullptr;
1040
1041
    /* -------------------------------------------------------------------- */
1042
    /*      Read file and parse.                                            */
1043
    /* -------------------------------------------------------------------- */
1044
0
    CPLKeywordParser oParser;
1045
1046
0
    VSILFILE *fp = VSIFOpenL(osFilePath, "r");
1047
1048
0
    if (fp == nullptr)
1049
0
        return nullptr;
1050
1051
0
    if (!oParser.Ingest(fp))
1052
0
    {
1053
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1054
0
        return nullptr;
1055
0
    }
1056
1057
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
1058
1059
    /* -------------------------------------------------------------------- */
1060
    /*      Consider version changing.                                      */
1061
    /* -------------------------------------------------------------------- */
1062
0
    char **papszIMD = CSLDuplicate(oParser.GetAllKeywords());
1063
0
    const char *pszVersion = CSLFetchNameValue(papszIMD, "version");
1064
1065
0
    if (pszVersion == nullptr)
1066
0
    {
1067
0
        /* ? */;
1068
0
    }
1069
0
    else if (EQUAL(pszVersion, "\"AA\""))
1070
0
    {
1071
0
        GDAL_IMD_AA2R(&papszIMD);
1072
0
    }
1073
1074
0
    return papszIMD;
1075
0
}
1076
1077
/************************************************************************/
1078
/*                       GDALWriteIMDMultiLine()                        */
1079
/*                                                                      */
1080
/*      Write a value that is split over multiple lines.                */
1081
/************************************************************************/
1082
1083
static void GDALWriteIMDMultiLine(VSILFILE *fp, const char *pszValue)
1084
1085
0
{
1086
0
    char **papszItems =
1087
0
        CSLTokenizeStringComplex(pszValue, "(,) ", FALSE, FALSE);
1088
0
    const int nItemCount = CSLCount(papszItems);
1089
1090
0
    CPL_IGNORE_RET_VAL(VSIFPrintfL(fp, "(\n"));
1091
1092
0
    for (int i = 0; i < nItemCount; i++)
1093
0
    {
1094
0
        if (i == nItemCount - 1)
1095
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fp, "\t%s );\n", papszItems[i]));
1096
0
        else
1097
0
            CPL_IGNORE_RET_VAL(VSIFPrintfL(fp, "\t%s,\n", papszItems[i]));
1098
0
    }
1099
0
    CSLDestroy(papszItems);
1100
0
}
1101
1102
/************************************************************************/
1103
/*                          GDALWriteIMDFile()                          */
1104
/************************************************************************/
1105
1106
CPLErr GDALWriteIMDFile(const char *pszFilename, char **papszMD)
1107
1108
0
{
1109
0
    const CPLString osRPBFilename = CPLResetExtensionSafe(pszFilename, "IMD");
1110
1111
    /* -------------------------------------------------------------------- */
1112
    /*      Read file and parse.                                            */
1113
    /* -------------------------------------------------------------------- */
1114
0
    VSILFILE *fp = VSIFOpenL(osRPBFilename, "w");
1115
1116
0
    if (fp == nullptr)
1117
0
    {
1118
0
        CPLError(CE_Failure, CPLE_OpenFailed,
1119
0
                 "Unable to create %s for writing.\n%s", osRPBFilename.c_str(),
1120
0
                 CPLGetLastErrorMsg());
1121
0
        return CE_Failure;
1122
0
    }
1123
1124
    /* ==================================================================== */
1125
    /* -------------------------------------------------------------------- */
1126
    /*      Loop through all values writing.                                */
1127
    /* -------------------------------------------------------------------- */
1128
    /* ==================================================================== */
1129
0
    CPLString osCurSection;
1130
0
    bool bOK = true;
1131
1132
0
    for (int iKey = 0; papszMD[iKey] != nullptr; iKey++)
1133
0
    {
1134
0
        char *pszRawKey = nullptr;
1135
0
        const char *pszValue = CPLParseNameValue(papszMD[iKey], &pszRawKey);
1136
0
        if (pszRawKey == nullptr)
1137
0
            continue;
1138
0
        CPLString osKeySection;
1139
0
        CPLString osKeyItem;
1140
0
        char *pszDot = strchr(pszRawKey, '.');
1141
1142
        /* --------------------------------------------------------------------
1143
         */
1144
        /*      Split stuff like BAND_P.ULLon into section and item. */
1145
        /* --------------------------------------------------------------------
1146
         */
1147
0
        if (pszDot == nullptr)
1148
0
        {
1149
0
            osKeyItem = pszRawKey;
1150
0
        }
1151
0
        else
1152
0
        {
1153
0
            osKeyItem = pszDot + 1;
1154
0
            *pszDot = '\0';
1155
0
            osKeySection = pszRawKey;
1156
0
        }
1157
0
        CPLFree(pszRawKey);
1158
1159
        /* --------------------------------------------------------------------
1160
         */
1161
        /*      Close and/or start sections as needed. */
1162
        /* --------------------------------------------------------------------
1163
         */
1164
0
        if (!osCurSection.empty() && !EQUAL(osCurSection, osKeySection))
1165
0
            bOK &=
1166
0
                VSIFPrintfL(fp, "END_GROUP = %s\n", osCurSection.c_str()) > 0;
1167
1168
0
        if (!osKeySection.empty() && !EQUAL(osCurSection, osKeySection))
1169
0
            bOK &=
1170
0
                VSIFPrintfL(fp, "BEGIN_GROUP = %s\n", osKeySection.c_str()) > 0;
1171
1172
0
        osCurSection = std::move(osKeySection);
1173
1174
        /* --------------------------------------------------------------------
1175
         */
1176
        /*      Print out simple item. */
1177
        /* --------------------------------------------------------------------
1178
         */
1179
0
        if (!osCurSection.empty())
1180
0
            bOK &= VSIFPrintfL(fp, "\t%s = ", osKeyItem.c_str()) > 0;
1181
0
        else
1182
0
            bOK &= VSIFPrintfL(fp, "%s = ", osKeyItem.c_str()) > 0;
1183
1184
0
        if (pszValue[0] != '(')
1185
0
        {
1186
0
            const bool bHasSingleQuote = strchr(pszValue, '\'') != nullptr;
1187
0
            const bool bHasDoubleQuote = strchr(pszValue, '"') != nullptr;
1188
0
            if (strchr(pszValue, ' ') != nullptr ||
1189
0
                strchr(pszValue, ';') != nullptr ||
1190
0
                strchr(pszValue, '\t') != nullptr || bHasSingleQuote ||
1191
0
                (bHasDoubleQuote && !(pszValue[0] == '"' &&
1192
0
                                      pszValue[strlen(pszValue) - 1] == '"')))
1193
0
            {
1194
0
                if (!bHasDoubleQuote)
1195
0
                    bOK &= VSIFPrintfL(fp, "\"%s\";\n", pszValue) > 0;
1196
0
                else if (!bHasSingleQuote)
1197
0
                    bOK &= VSIFPrintfL(fp, "'%s';\n", pszValue) > 0;
1198
0
                else
1199
0
                {
1200
                    // There does not seem to have a way to escape double-quotes
1201
                    // in a double-quoted string (or single-quote in a
1202
                    // single-quoted string), so we are going to convert
1203
                    // double-quotes as two single-quotes...
1204
0
                    bOK &=
1205
0
                        VSIFPrintfL(
1206
0
                            fp, "\"%s\";\n",
1207
0
                            CPLString(pszValue).replaceAll('"', "''").c_str()) >
1208
0
                        0;
1209
0
                }
1210
0
            }
1211
0
            else
1212
0
            {
1213
0
                bOK &= VSIFPrintfL(fp, "%s;\n", pszValue) > 0;
1214
0
            }
1215
0
        }
1216
0
        else
1217
0
            GDALWriteIMDMultiLine(fp, pszValue);
1218
0
    }
1219
1220
    /* -------------------------------------------------------------------- */
1221
    /*      Close off.                                                      */
1222
    /* -------------------------------------------------------------------- */
1223
0
    if (!osCurSection.empty())
1224
0
        bOK &= VSIFPrintfL(fp, "END_GROUP = %s\n", osCurSection.c_str()) > 0;
1225
1226
0
    bOK &= VSIFPrintfL(fp, "END;\n") > 0;
1227
1228
0
    if (VSIFCloseL(fp) != 0)
1229
0
        bOK = false;
1230
1231
0
    return bOK ? CE_None : CE_Failure;
1232
0
}