Coverage Report

Created: 2025-11-15 08:43

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