Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/ers/ershdrnode.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  ERMapper .ers Driver
4
 * Purpose:  Implementation of ERSHdrNode class for parsing/accessing .ers hdr.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_conv.h"
14
#include "cpl_string.h"
15
#include "ershdrnode.h"
16
17
/************************************************************************/
18
/*                            ~ERSHdrNode()                             */
19
/************************************************************************/
20
21
ERSHdrNode::~ERSHdrNode()
22
23
6.63k
{
24
39.0k
    for (int i = 0; i < nItemCount; i++)
25
32.3k
    {
26
32.3k
        if (papoItemChild[i] != nullptr)
27
4.97k
            delete papoItemChild[i];
28
32.3k
        if (papszItemValue[i] != nullptr)
29
27.4k
            CPLFree(papszItemValue[i]);
30
32.3k
        CPLFree(papszItemName[i]);
31
32.3k
    }
32
33
6.63k
    CPLFree(papszItemName);
34
6.63k
    CPLFree(papszItemValue);
35
6.63k
    CPLFree(papoItemChild);
36
6.63k
}
37
38
/************************************************************************/
39
/*                             MakeSpace()                              */
40
/*                                                                      */
41
/*      Ensure we have room for at least one more entry in our item     */
42
/*      lists.                                                          */
43
/************************************************************************/
44
45
void ERSHdrNode::MakeSpace()
46
47
32.3k
{
48
32.3k
    if (nItemCount == nItemMax)
49
6.61k
    {
50
6.61k
        nItemMax = nItemMax + nItemMax / 3 + 10;
51
6.61k
        papszItemName = static_cast<char **>(
52
6.61k
            CPLRealloc(papszItemName, sizeof(char *) * nItemMax));
53
6.61k
        papszItemValue = static_cast<char **>(
54
6.61k
            CPLRealloc(papszItemValue, sizeof(char *) * nItemMax));
55
6.61k
        papoItemChild = static_cast<ERSHdrNode **>(
56
6.61k
            CPLRealloc(papoItemChild, sizeof(ERSHdrNode *) * nItemMax));
57
6.61k
    }
58
32.3k
}
59
60
/************************************************************************/
61
/*                              ReadLine()                              */
62
/*                                                                      */
63
/*      Read one virtual line from the input source.  Multiple lines    */
64
/*      will be appended for objects enclosed in {}.                    */
65
/************************************************************************/
66
67
int ERSHdrNode::ReadLine(VSILFILE *fp, CPLString &osLine)
68
69
1.03M
{
70
1.03M
    int nBracketLevel = 0;
71
1.03M
    bool bInQuote = false;
72
1.03M
    size_t i = 0;
73
1.03M
    bool bLastCharWasSlashInQuote = false;
74
75
1.03M
    osLine = "";
76
1.03M
    do
77
1.65M
    {
78
1.65M
        const char *pszNewLine = CPLReadLineL(fp);
79
80
1.65M
        if (pszNewLine == nullptr)
81
1.34k
            return FALSE;
82
83
1.65M
        osLine += pszNewLine;
84
85
24.8M
        for (; i < osLine.length(); i++)
86
23.2M
        {
87
23.2M
            const char ch = osLine[i];
88
23.2M
            if (bLastCharWasSlashInQuote)
89
27.2k
            {
90
27.2k
                bLastCharWasSlashInQuote = false;
91
27.2k
            }
92
23.1M
            else if (ch == '"')
93
230k
                bInQuote = !bInQuote;
94
22.9M
            else if (ch == '{' && !bInQuote)
95
46.4k
                nBracketLevel++;
96
22.9M
            else if (ch == '}' && !bInQuote)
97
52.5k
                nBracketLevel--;
98
            // We have to ignore escaped quotes and backslashes in strings.
99
22.8M
            else if (ch == '\\' && bInQuote)
100
27.2k
            {
101
27.2k
                bLastCharWasSlashInQuote = true;
102
27.2k
            }
103
            // A comment is a '#' up to the end of the line.
104
22.8M
            else if (ch == '#' && !bInQuote)
105
124k
            {
106
124k
                osLine = osLine.substr(0, i) + "\n";
107
124k
            }
108
23.2M
        }
109
1.65M
    } while (nBracketLevel > 0);
110
111
1.02M
    return TRUE;
112
1.03M
}
113
114
/************************************************************************/
115
/*                            ParseHeader()                             */
116
/*                                                                      */
117
/*      We receive the FILE * positioned at the start of the file       */
118
/*      and read all children.  This allows reading comment lines       */
119
/*      at the start of the file.                                       */
120
/************************************************************************/
121
122
int ERSHdrNode::ParseHeader(VSILFILE *fp)
123
124
1.65k
{
125
973k
    while (true)
126
973k
    {
127
        /* --------------------------------------------------------------------
128
         */
129
        /*      Read the next line */
130
        /* --------------------------------------------------------------------
131
         */
132
973k
        CPLString osLine;
133
973k
        size_t iOff;
134
135
973k
        if (!ReadLine(fp, osLine))
136
1.19k
            return FALSE;
137
138
        /* --------------------------------------------------------------------
139
         */
140
        /*      Got a DatasetHeader Begin */
141
        /* --------------------------------------------------------------------
142
         */
143
971k
        else if ((iOff = osLine.ifind(" Begin")) != std::string::npos)
144
44.0k
        {
145
44.0k
            CPLString osName = osLine.substr(0, iOff);
146
44.0k
            osName.Trim();
147
148
44.0k
            if (osName.tolower() == CPLString("DatasetHeader").tolower())
149
454
            {
150
454
                return ParseChildren(fp);
151
454
            }
152
44.0k
        }
153
973k
    }
154
1.65k
}
155
156
/************************************************************************/
157
/*                           ParseChildren()                            */
158
/*                                                                      */
159
/*      We receive the FILE * positioned after the "Object Begin"       */
160
/*      line for this object, and are responsible for reading all       */
161
/*      children.  We should return after consuming the                 */
162
/*      corresponding End line for this object.  Really the first       */
163
/*      unmatched End since we don't know what object we are.           */
164
/*                                                                      */
165
/*      This function is used recursively to read sub-objects.          */
166
/************************************************************************/
167
168
int ERSHdrNode::ParseChildren(VSILFILE *fp, int nRecLevel)
169
170
5.43k
{
171
5.43k
    if (nRecLevel == 100)  // arbitrary limit
172
7
    {
173
7
        CPLError(CE_Failure, CPLE_AppDefined,
174
7
                 "Too many recursion level while parsing .ers header");
175
7
        return FALSE;
176
7
    }
177
178
57.5k
    while (true)
179
57.5k
    {
180
        /* --------------------------------------------------------------------
181
         */
182
        /*      Read the next line (or multi-line for bracketed value). */
183
        /* --------------------------------------------------------------------
184
         */
185
57.5k
        CPLString osLine;
186
187
57.5k
        if (!ReadLine(fp, osLine))
188
143
            return FALSE;
189
190
        /* --------------------------------------------------------------------
191
         */
192
        /*      Got a Name=Value. */
193
        /* --------------------------------------------------------------------
194
         */
195
57.4k
        size_t iOff;
196
197
57.4k
        if ((iOff = osLine.find_first_of('=')) != std::string::npos)
198
27.4k
        {
199
27.4k
            CPLString osName =
200
27.4k
                iOff == 0 ? std::string() : osLine.substr(0, iOff);
201
27.4k
            osName.Trim();
202
203
27.4k
            CPLString osValue = osLine.c_str() + iOff + 1;
204
27.4k
            osValue.Trim();
205
206
27.4k
            MakeSpace();
207
27.4k
            papszItemName[nItemCount] = CPLStrdup(osName);
208
27.4k
            papszItemValue[nItemCount] = CPLStrdup(osValue);
209
27.4k
            papoItemChild[nItemCount] = nullptr;
210
211
27.4k
            nItemCount++;
212
27.4k
        }
213
214
        /* --------------------------------------------------------------------
215
         */
216
        /*      Got a Begin for an object. */
217
        /* --------------------------------------------------------------------
218
         */
219
30.0k
        else if ((iOff = osLine.ifind(" Begin")) != std::string::npos)
220
4.97k
        {
221
4.97k
            CPLString osName = osLine.substr(0, iOff);
222
4.97k
            osName.Trim();
223
224
4.97k
            MakeSpace();
225
4.97k
            papszItemName[nItemCount] = CPLStrdup(osName);
226
4.97k
            papszItemValue[nItemCount] = nullptr;
227
4.97k
            papoItemChild[nItemCount] = new ERSHdrNode();
228
229
4.97k
            nItemCount++;
230
231
4.97k
            if (!papoItemChild[nItemCount - 1]->ParseChildren(fp,
232
4.97k
                                                              nRecLevel + 1))
233
4.53k
                return FALSE;
234
4.97k
        }
235
236
        /* --------------------------------------------------------------------
237
         */
238
        /*      Got an End for our object.  Well, at least we *assume* it */
239
        /*      must be for our object. */
240
        /* --------------------------------------------------------------------
241
         */
242
25.0k
        else if (osLine.ifind(" End") != std::string::npos)
243
482
        {
244
482
            return TRUE;
245
482
        }
246
247
        /* --------------------------------------------------------------------
248
         */
249
        /*      Error? */
250
        /* --------------------------------------------------------------------
251
         */
252
24.5k
        else if (osLine.Trim().length() > 0)
253
266
        {
254
266
            CPLError(CE_Failure, CPLE_AppDefined,
255
266
                     "Unexpected line parsing .ecw:\n%s", osLine.c_str());
256
266
            return FALSE;
257
266
        }
258
57.4k
    }
259
5.42k
}
260
261
/************************************************************************/
262
/*                             WriteSelf()                              */
263
/*                                                                      */
264
/*      Recursively write self and children to file.                    */
265
/************************************************************************/
266
267
int ERSHdrNode::WriteSelf(VSILFILE *fp, int nIndent)
268
269
0
{
270
0
    CPLString oIndent;
271
272
0
    oIndent.assign(nIndent, '\t');
273
274
0
    for (int i = 0; i < nItemCount; i++)
275
0
    {
276
0
        if (papszItemValue[i] != nullptr)
277
0
        {
278
0
            if (VSIFPrintfL(fp, "%s%s\t= %s\n", oIndent.c_str(),
279
0
                            papszItemName[i], papszItemValue[i]) < 1)
280
0
                return FALSE;
281
0
        }
282
0
        else
283
0
        {
284
0
            VSIFPrintfL(fp, "%s%s Begin\n", oIndent.c_str(), papszItemName[i]);
285
0
            if (!papoItemChild[i]->WriteSelf(fp, nIndent + 1))
286
0
                return FALSE;
287
0
            if (VSIFPrintfL(fp, "%s%s End\n", oIndent.c_str(),
288
0
                            papszItemName[i]) < 1)
289
0
                return FALSE;
290
0
        }
291
0
    }
292
293
0
    return TRUE;
294
0
}
295
296
/************************************************************************/
297
/*                                Find()                                */
298
/*                                                                      */
299
/*      Find the desired entry value.  The input is a path with         */
300
/*      components separated by dots, relative to the current node.     */
301
/************************************************************************/
302
303
const char *ERSHdrNode::Find(const char *pszPath, const char *pszDefault)
304
305
191
{
306
    /* -------------------------------------------------------------------- */
307
    /*      If this is the final component of the path, search for a        */
308
    /*      matching child and return the value.                            */
309
    /* -------------------------------------------------------------------- */
310
191
    if (strchr(pszPath, '.') == nullptr)
311
94
    {
312
579
        for (int i = 0; i < nItemCount; i++)
313
550
        {
314
550
            if (EQUAL(pszPath, papszItemName[i]))
315
65
            {
316
65
                if (papszItemValue[i] != nullptr)
317
65
                {
318
65
                    if (papszItemValue[i][0] == '"')
319
2
                    {
320
                        // strip off quotes.
321
2
                        osTempReturn = papszItemValue[i];
322
2
                        if (osTempReturn.length() < 2)
323
0
                            osTempReturn.clear();
324
2
                        else
325
2
                            osTempReturn = osTempReturn.substr(
326
2
                                1, osTempReturn.length() - 2);
327
2
                        return osTempReturn;
328
2
                    }
329
63
                    else
330
63
                        return papszItemValue[i];
331
65
                }
332
0
                else
333
0
                    return pszDefault;
334
65
            }
335
550
        }
336
29
        return pszDefault;
337
94
    }
338
339
    /* -------------------------------------------------------------------- */
340
    /*      This is a dot path - extract the first element, find a match    */
341
    /*      and recurse.                                                    */
342
    /* -------------------------------------------------------------------- */
343
97
    CPLString osPathFirst, osPathRest, osPath = pszPath;
344
345
97
    size_t iDot = osPath.find_first_of('.');
346
97
    osPathFirst = osPath.substr(0, iDot);
347
97
    osPathRest = osPath.substr(iDot + 1);
348
349
537
    for (int i = 0; i < nItemCount; i++)
350
508
    {
351
508
        if (EQUAL(osPathFirst, papszItemName[i]))
352
68
        {
353
68
            if (papoItemChild[i] != nullptr)
354
68
                return papoItemChild[i]->Find(osPathRest, pszDefault);
355
356
0
            return pszDefault;
357
68
        }
358
508
    }
359
360
29
    return pszDefault;
361
97
}
362
363
/************************************************************************/
364
/*                              FindElem()                              */
365
/*                                                                      */
366
/*      Find a particular element from an array valued item.            */
367
/************************************************************************/
368
369
const char *ERSHdrNode::FindElem(const char *pszPath, int iElem,
370
                                 const char *pszDefault)
371
372
0
{
373
0
    const char *pszArray = Find(pszPath, nullptr);
374
375
0
    if (pszArray == nullptr)
376
0
        return pszDefault;
377
378
0
    bool bDefault = true;
379
0
    char **papszTokens =
380
0
        CSLTokenizeStringComplex(pszArray, "{ \t}", TRUE, FALSE);
381
0
    if (iElem >= 0 && iElem < CSLCount(papszTokens))
382
0
    {
383
0
        osTempReturn = papszTokens[iElem];
384
0
        bDefault = false;
385
0
    }
386
387
0
    CSLDestroy(papszTokens);
388
389
0
    if (bDefault)
390
0
        return pszDefault;
391
392
0
    return osTempReturn;
393
0
}
394
395
/************************************************************************/
396
/*                              FindNode()                              */
397
/*                                                                      */
398
/*      Find the desired node.                                          */
399
/************************************************************************/
400
401
ERSHdrNode *ERSHdrNode::FindNode(const char *pszPath)
402
403
34
{
404
34
    std::string osPathFirst, osPathRest;
405
34
    std::string osPath = pszPath;
406
34
    const size_t iDot = osPath.find('.');
407
34
    if (iDot == std::string::npos)
408
33
    {
409
33
        osPathFirst = std::move(osPath);
410
33
    }
411
1
    else
412
1
    {
413
1
        osPathFirst = osPath.substr(0, iDot);
414
1
        osPathRest = osPath.substr(iDot + 1);
415
1
    }
416
417
217
    for (int i = 0; i < nItemCount; i++)
418
185
    {
419
185
        if (EQUAL(osPathFirst.c_str(), papszItemName[i]))
420
2
        {
421
2
            if (papoItemChild[i] != nullptr)
422
2
            {
423
2
                if (osPathRest.length() > 0)
424
1
                    return papoItemChild[i]->FindNode(osPathRest.c_str());
425
1
                else
426
1
                    return papoItemChild[i];
427
2
            }
428
0
            else
429
0
                return nullptr;
430
2
        }
431
185
    }
432
433
32
    return nullptr;
434
34
}
435
436
/************************************************************************/
437
/*                                Set()                                 */
438
/*                                                                      */
439
/*      Set a value item.                                               */
440
/************************************************************************/
441
442
void ERSHdrNode::Set(const char *pszPath, const char *pszValue)
443
444
0
{
445
0
    CPLString osPath = pszPath;
446
0
    size_t iDot = osPath.find_first_of('.');
447
448
    /* -------------------------------------------------------------------- */
449
    /*      We have an intermediate node, find or create it and             */
450
    /*      recurse.                                                        */
451
    /* -------------------------------------------------------------------- */
452
0
    if (iDot != std::string::npos)
453
0
    {
454
0
        CPLString osPathFirst = osPath.substr(0, iDot);
455
0
        CPLString osPathRest = osPath.substr(iDot + 1);
456
0
        ERSHdrNode *poFirst = FindNode(osPathFirst);
457
458
0
        if (poFirst == nullptr)
459
0
        {
460
0
            poFirst = new ERSHdrNode();
461
462
0
            MakeSpace();
463
0
            papszItemName[nItemCount] = CPLStrdup(osPathFirst);
464
0
            papszItemValue[nItemCount] = nullptr;
465
0
            papoItemChild[nItemCount] = poFirst;
466
0
            nItemCount++;
467
0
        }
468
469
0
        poFirst->Set(osPathRest, pszValue);
470
0
        return;
471
0
    }
472
473
    /* -------------------------------------------------------------------- */
474
    /*      This is the final item name.  Find or create it.                */
475
    /* -------------------------------------------------------------------- */
476
0
    for (int i = 0; i < nItemCount; i++)
477
0
    {
478
0
        if (EQUAL(osPath, papszItemName[i]) && papszItemValue[i] != nullptr)
479
0
        {
480
0
            CPLFree(papszItemValue[i]);
481
0
            papszItemValue[i] = CPLStrdup(pszValue);
482
0
            return;
483
0
        }
484
0
    }
485
486
0
    MakeSpace();
487
0
    papszItemName[nItemCount] = CPLStrdup(osPath);
488
0
    papszItemValue[nItemCount] = CPLStrdup(pszValue);
489
0
    papoItemChild[nItemCount] = nullptr;
490
0
    nItemCount++;
491
0
}