Coverage Report

Created: 2025-12-03 08:24

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
93.5k
{
24
3.23M
    for (int i = 0; i < nItemCount; i++)
25
3.14M
    {
26
3.14M
        if (papoItemChild[i] != nullptr)
27
62.2k
            delete papoItemChild[i];
28
3.14M
        if (papszItemValue[i] != nullptr)
29
3.08M
            CPLFree(papszItemValue[i]);
30
3.14M
        CPLFree(papszItemName[i]);
31
3.14M
    }
32
33
93.5k
    CPLFree(papszItemName);
34
93.5k
    CPLFree(papszItemValue);
35
93.5k
    CPLFree(papoItemChild);
36
93.5k
}
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
3.14M
{
48
3.14M
    if (nItemCount == nItemMax)
49
91.3k
    {
50
91.3k
        nItemMax = nItemMax + nItemMax / 3 + 10;
51
91.3k
        papszItemName = static_cast<char **>(
52
91.3k
            CPLRealloc(papszItemName, sizeof(char *) * nItemMax));
53
91.3k
        papszItemValue = static_cast<char **>(
54
91.3k
            CPLRealloc(papszItemValue, sizeof(char *) * nItemMax));
55
91.3k
        papoItemChild = static_cast<ERSHdrNode **>(
56
91.3k
            CPLRealloc(papoItemChild, sizeof(ERSHdrNode *) * nItemMax));
57
91.3k
    }
58
3.14M
}
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
4.47M
{
70
4.47M
    int nBracketLevel = 0;
71
4.47M
    bool bInQuote = false;
72
4.47M
    size_t i = 0;
73
4.47M
    bool bLastCharWasSlashInQuote = false;
74
75
4.47M
    osLine = "";
76
4.47M
    do
77
5.31M
    {
78
5.31M
        const char *pszNewLine = CPLReadLineL(fp);
79
80
5.31M
        if (pszNewLine == nullptr)
81
1.97k
            return FALSE;
82
83
5.31M
        osLine += pszNewLine;
84
85
158M
        for (; i < osLine.length(); i++)
86
153M
        {
87
153M
            const char ch = osLine[i];
88
153M
            if (bLastCharWasSlashInQuote)
89
143k
            {
90
143k
                bLastCharWasSlashInQuote = false;
91
143k
            }
92
153M
            else if (ch == '"')
93
560k
                bInQuote = !bInQuote;
94
152M
            else if (ch == '{' && !bInQuote)
95
35.6k
                nBracketLevel++;
96
152M
            else if (ch == '}' && !bInQuote)
97
100k
                nBracketLevel--;
98
            // We have to ignore escaped quotes and backslashes in strings.
99
152M
            else if (ch == '\\' && bInQuote)
100
143k
            {
101
143k
                bLastCharWasSlashInQuote = true;
102
143k
            }
103
            // A comment is a '#' up to the end of the line.
104
152M
            else if (ch == '#' && !bInQuote)
105
104k
            {
106
104k
                osLine = osLine.substr(0, i) + "\n";
107
104k
            }
108
153M
        }
109
5.31M
    } while (nBracketLevel > 0);
110
111
4.47M
    return TRUE;
112
4.47M
}
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
31.2k
{
125
1.03M
    while (true)
126
1.03M
    {
127
        /* --------------------------------------------------------------------
128
         */
129
        /*      Read the next line */
130
        /* --------------------------------------------------------------------
131
         */
132
1.03M
        CPLString osLine;
133
1.03M
        size_t iOff;
134
135
1.03M
        if (!ReadLine(fp, osLine))
136
1.48k
            return FALSE;
137
138
        /* --------------------------------------------------------------------
139
         */
140
        /*      Got a DatasetHeader Begin */
141
        /* --------------------------------------------------------------------
142
         */
143
1.03M
        else if ((iOff = osLine.ifind(" Begin")) != std::string::npos)
144
69.7k
        {
145
69.7k
            CPLString osName = osLine.substr(0, iOff);
146
69.7k
            osName.Trim();
147
148
69.7k
            if (osName.tolower() == CPLString("DatasetHeader").tolower())
149
29.7k
            {
150
29.7k
                return ParseChildren(fp);
151
29.7k
            }
152
69.7k
        }
153
1.03M
    }
154
31.2k
}
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
92.0k
{
171
92.0k
    if (nRecLevel == 100)  // arbitrary limit
172
42
    {
173
42
        CPLError(CE_Failure, CPLE_AppDefined,
174
42
                 "Too many recursion level while parsing .ers header");
175
42
        return FALSE;
176
42
    }
177
178
3.43M
    while (true)
179
3.43M
    {
180
        /* --------------------------------------------------------------------
181
         */
182
        /*      Read the next line (or multi-line for bracketed value). */
183
        /* --------------------------------------------------------------------
184
         */
185
3.43M
        CPLString osLine;
186
187
3.43M
        if (!ReadLine(fp, osLine))
188
489
            return FALSE;
189
190
        /* --------------------------------------------------------------------
191
         */
192
        /*      Got a Name=Value. */
193
        /* --------------------------------------------------------------------
194
         */
195
3.43M
        size_t iOff;
196
197
3.43M
        if ((iOff = osLine.find_first_of('=')) != std::string::npos)
198
3.08M
        {
199
3.08M
            CPLString osName =
200
3.08M
                iOff == 0 ? std::string() : osLine.substr(0, iOff);
201
3.08M
            osName.Trim();
202
203
3.08M
            CPLString osValue = osLine.c_str() + iOff + 1;
204
3.08M
            osValue.Trim();
205
206
3.08M
            MakeSpace();
207
3.08M
            papszItemName[nItemCount] = CPLStrdup(osName);
208
3.08M
            papszItemValue[nItemCount] = CPLStrdup(osValue);
209
3.08M
            papoItemChild[nItemCount] = nullptr;
210
211
3.08M
            nItemCount++;
212
3.08M
        }
213
214
        /* --------------------------------------------------------------------
215
         */
216
        /*      Got a Begin for an object. */
217
        /* --------------------------------------------------------------------
218
         */
219
356k
        else if ((iOff = osLine.ifind(" Begin")) != std::string::npos)
220
62.2k
        {
221
62.2k
            CPLString osName = osLine.substr(0, iOff);
222
62.2k
            osName.Trim();
223
224
62.2k
            MakeSpace();
225
62.2k
            papszItemName[nItemCount] = CPLStrdup(osName);
226
62.2k
            papszItemValue[nItemCount] = nullptr;
227
62.2k
            papoItemChild[nItemCount] = new ERSHdrNode();
228
229
62.2k
            nItemCount++;
230
231
62.2k
            if (!papoItemChild[nItemCount - 1]->ParseChildren(fp,
232
62.2k
                                                              nRecLevel + 1))
233
9.69k
                return FALSE;
234
62.2k
        }
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
294k
        else if (osLine.ifind(" End") != std::string::npos)
243
80.5k
        {
244
80.5k
            return TRUE;
245
80.5k
        }
246
247
        /* --------------------------------------------------------------------
248
         */
249
        /*      Error? */
250
        /* --------------------------------------------------------------------
251
         */
252
213k
        else if (osLine.Trim().length() > 0)
253
1.29k
        {
254
1.29k
            CPLError(CE_Failure, CPLE_AppDefined,
255
1.29k
                     "Unexpected line parsing .ecw:\n%s", osLine.c_str());
256
1.29k
            return FALSE;
257
1.29k
        }
258
3.43M
    }
259
91.9k
}
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
2.58M
{
306
    /* -------------------------------------------------------------------- */
307
    /*      If this is the final component of the path, search for a        */
308
    /*      matching child and return the value.                            */
309
    /* -------------------------------------------------------------------- */
310
2.58M
    if (strchr(pszPath, '.') == nullptr)
311
1.28M
    {
312
13.6M
        for (int i = 0; i < nItemCount; i++)
313
12.8M
        {
314
12.8M
            if (EQUAL(pszPath, papszItemName[i]))
315
471k
            {
316
471k
                if (papszItemValue[i] != nullptr)
317
471k
                {
318
471k
                    if (papszItemValue[i][0] == '"')
319
220k
                    {
320
                        // strip off quotes.
321
220k
                        osTempReturn = papszItemValue[i];
322
220k
                        if (osTempReturn.length() < 2)
323
778
                            osTempReturn.clear();
324
219k
                        else
325
219k
                            osTempReturn = osTempReturn.substr(
326
219k
                                1, osTempReturn.length() - 2);
327
220k
                        return osTempReturn;
328
220k
                    }
329
250k
                    else
330
250k
                        return papszItemValue[i];
331
471k
                }
332
304
                else
333
304
                    return pszDefault;
334
471k
            }
335
12.8M
        }
336
817k
        return pszDefault;
337
1.28M
    }
338
339
    /* -------------------------------------------------------------------- */
340
    /*      This is a dot path - extract the first element, find a match    */
341
    /*      and recurse.                                                    */
342
    /* -------------------------------------------------------------------- */
343
1.29M
    CPLString osPathFirst, osPathRest, osPath = pszPath;
344
345
1.29M
    size_t iDot = osPath.find_first_of('.');
346
1.29M
    osPathFirst = osPath.substr(0, iDot);
347
1.29M
    osPathRest = osPath.substr(iDot + 1);
348
349
8.69M
    for (int i = 0; i < nItemCount; i++)
350
8.59M
    {
351
8.59M
        if (EQUAL(osPathFirst, papszItemName[i]))
352
1.20M
        {
353
1.20M
            if (papoItemChild[i] != nullptr)
354
1.20M
                return papoItemChild[i]->Find(osPathRest, pszDefault);
355
356
33
            return pszDefault;
357
1.20M
        }
358
8.59M
    }
359
360
97.1k
    return pszDefault;
361
1.29M
}
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
857k
{
373
857k
    const char *pszArray = Find(pszPath, nullptr);
374
375
857k
    if (pszArray == nullptr)
376
638k
        return pszDefault;
377
378
219k
    bool bDefault = true;
379
219k
    char **papszTokens =
380
219k
        CSLTokenizeStringComplex(pszArray, "{ \t}", TRUE, FALSE);
381
219k
    if (iElem >= 0 && iElem < CSLCount(papszTokens))
382
4.65k
    {
383
4.65k
        osTempReturn = papszTokens[iElem];
384
4.65k
        bDefault = false;
385
4.65k
    }
386
387
219k
    CSLDestroy(papszTokens);
388
389
219k
    if (bDefault)
390
214k
        return pszDefault;
391
392
4.65k
    return osTempReturn;
393
219k
}
394
395
/************************************************************************/
396
/*                              FindNode()                              */
397
/*                                                                      */
398
/*      Find the desired node.                                          */
399
/************************************************************************/
400
401
ERSHdrNode *ERSHdrNode::FindNode(const char *pszPath)
402
403
69.9k
{
404
69.9k
    std::string osPathFirst, osPathRest;
405
69.9k
    std::string osPath = pszPath;
406
69.9k
    const size_t iDot = osPath.find('.');
407
69.9k
    if (iDot == std::string::npos)
408
46.9k
    {
409
46.9k
        osPathFirst = std::move(osPath);
410
46.9k
    }
411
22.9k
    else
412
22.9k
    {
413
22.9k
        osPathFirst = osPath.substr(0, iDot);
414
22.9k
        osPathRest = osPath.substr(iDot + 1);
415
22.9k
    }
416
417
3.09M
    for (int i = 0; i < nItemCount; i++)
418
3.07M
    {
419
3.07M
        if (EQUAL(osPathFirst.c_str(), papszItemName[i]))
420
46.3k
        {
421
46.3k
            if (papoItemChild[i] != nullptr)
422
46.2k
            {
423
46.2k
                if (osPathRest.length() > 0)
424
22.9k
                    return papoItemChild[i]->FindNode(osPathRest.c_str());
425
23.2k
                else
426
23.2k
                    return papoItemChild[i];
427
46.2k
            }
428
19
            else
429
19
                return nullptr;
430
46.3k
        }
431
3.07M
    }
432
433
23.6k
    return nullptr;
434
69.9k
}
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
}