• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16038479760

03 Jul 2025 12:12AM UTC coverage: 71.106% (-0.004%) from 71.11%
16038479760

Pull #12692

github

web-flow
Merge efeee3602 into b5d2a80d4
Pull Request #12692: C/C++/Python band algebra: add gdal.abs(), sqrt(), log(), log10() and pow()

80 of 87 new or added lines in 3 files covered. (91.95%)

8848 existing lines in 54 files now uncovered.

574863 of 808463 relevant lines covered (71.11%)

255001.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.74
/port/cpl_minixml.cpp
1
/**********************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implementation of MiniXML Parser and handling.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 **********************************************************************
8
 * Copyright (c) 2001, Frank Warmerdam
9
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 **********************************************************************
13
 *
14
 * Independent Security Audit 2003/04/05 Andrey Kiselev:
15
 *   Completed audit of this module. Any documents may be parsed without
16
 *   buffer overflows and stack corruptions.
17
 *
18
 * Security Audit 2003/03/28 warmerda:
19
 *   Completed security audit.  I believe that this module may be safely used
20
 *   to parse, and serialize arbitrary documents provided by a potentially
21
 *   hostile source.
22
 *
23
 */
24

25
#include "cpl_minixml.h"
26

27
#include <cctype>
28
#include <climits>
29
#include <cstddef>
30
#include <cstdio>
31
#include <cstring>
32

33
#include <algorithm>
34

35
#include "cpl_conv.h"
36
#include "cpl_error.h"
37
#include "cpl_string.h"
38
#include "cpl_vsi.h"
39

40
typedef enum
41
{
42
    TNone,
43
    TString,
44
    TOpen,
45
    TClose,
46
    TEqual,
47
    TToken,
48
    TSlashClose,
49
    TQuestionClose,
50
    TComment,
51
    TLiteral
52
} XMLTokenType;
53

54
typedef struct
55
{
56
    CPLXMLNode *psFirstNode;
57
    CPLXMLNode *psLastChild;
58
} StackContext;
59

60
typedef struct
61
{
62
    const char *pszInput;
63
    int nInputOffset;
64
    int nInputLine;
65
    bool bInElement;
66
    XMLTokenType eTokenType;
67
    char *pszToken;
68
    size_t nTokenMaxSize;
69
    size_t nTokenSize;
70

71
    int nStackMaxSize;
72
    int nStackSize;
73
    StackContext *papsStack;
74

75
    CPLXMLNode *psFirstNode;
76
    CPLXMLNode *psLastNode;
77
} ParseContext;
78

79
static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
80
                                     const char *pszText);
81

82
/************************************************************************/
83
/*                              ReadChar()                              */
84
/************************************************************************/
85

86
static CPL_INLINE char ReadChar(ParseContext *psContext)
443,622,000✔
87

88
{
89
    const char chReturn = psContext->pszInput[psContext->nInputOffset++];
443,622,000✔
90

91
    if (chReturn == '\0')
443,622,000✔
92
        psContext->nInputOffset--;
247,416✔
93
    else if (chReturn == 10)
443,375,000✔
94
        psContext->nInputLine++;
4,499,700✔
95

96
    return chReturn;
443,622,000✔
97
}
98

99
/************************************************************************/
100
/*                             UnreadChar()                             */
101
/************************************************************************/
102

103
static CPL_INLINE void UnreadChar(ParseContext *psContext, char chToUnread)
17,804,100✔
104

105
{
106
    if (chToUnread == '\0')
17,804,100✔
107
        return;
34✔
108

109
    CPLAssert(chToUnread == psContext->pszInput[psContext->nInputOffset - 1]);
17,804,100✔
110

111
    psContext->nInputOffset--;
17,804,100✔
112

113
    if (chToUnread == 10)
17,804,100✔
114
        psContext->nInputLine--;
305✔
115
}
116

117
/************************************************************************/
118
/*                           ReallocToken()                             */
119
/************************************************************************/
120

121
static bool ReallocToken(ParseContext *psContext)
1,044,060✔
122
{
123
    if (psContext->nTokenMaxSize > INT_MAX / 2)
1,044,060✔
124
    {
125
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
126
                 "Out of memory allocating %d*2 bytes",
127
                 static_cast<int>(psContext->nTokenMaxSize));
×
128
        VSIFree(psContext->pszToken);
×
129
        psContext->pszToken = nullptr;
×
130
        return false;
×
131
    }
132

133
    psContext->nTokenMaxSize *= 2;
1,044,060✔
134
    char *pszToken = static_cast<char *>(
135
        VSIRealloc(psContext->pszToken, psContext->nTokenMaxSize));
1,044,060✔
136
    if (pszToken == nullptr)
1,044,060✔
137
    {
138
        CPLError(CE_Failure, CPLE_OutOfMemory,
2✔
139
                 "Out of memory allocating %d bytes",
140
                 static_cast<int>(psContext->nTokenMaxSize));
2✔
141
        VSIFree(psContext->pszToken);
×
142
        psContext->pszToken = nullptr;
×
143
        return false;
×
144
    }
145
    psContext->pszToken = pszToken;
1,044,060✔
146
    return true;
1,044,060✔
147
}
148

149
/************************************************************************/
150
/*                             AddToToken()                             */
151
/************************************************************************/
152

153
static CPL_INLINE bool _AddToToken(ParseContext *psContext, char chNewChar)
326,592,000✔
154

155
{
156
    if (psContext->nTokenSize >= psContext->nTokenMaxSize - 2)
326,592,000✔
157
    {
158
        if (!ReallocToken(psContext))
1,044,060✔
159
            return false;
×
160
    }
161

162
    psContext->pszToken[psContext->nTokenSize++] = chNewChar;
326,592,000✔
163
    psContext->pszToken[psContext->nTokenSize] = '\0';
326,592,000✔
164
    return true;
326,592,000✔
165
}
166

167
// TODO(schwehr): Remove the goto.
168
#define AddToToken(psContext, chNewChar)                                       \
169
    if (!_AddToToken(psContext, chNewChar))                                    \
170
        goto fail;
171

172
/************************************************************************/
173
/*                             ReadToken()                              */
174
/************************************************************************/
175

176
static XMLTokenType ReadToken(ParseContext *psContext, CPLErr &eLastErrorType)
51,006,400✔
177

178
{
179
    psContext->nTokenSize = 0;
51,006,400✔
180
    psContext->pszToken[0] = '\0';
51,006,400✔
181

182
    char chNext = ReadChar(psContext);
51,006,400✔
183
    while (isspace(static_cast<unsigned char>(chNext)))
105,038,000✔
184
        chNext = ReadChar(psContext);
54,023,700✔
185

186
    /* -------------------------------------------------------------------- */
187
    /*      Handle comments.                                                */
188
    /* -------------------------------------------------------------------- */
189
    if (chNext == '<' &&
51,014,000✔
190
        STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset, "!--"))
7,166,140✔
191
    {
192
        psContext->eTokenType = TComment;
71,081✔
193

194
        // Skip "!--" characters.
195
        ReadChar(psContext);
71,081✔
196
        ReadChar(psContext);
71,081✔
197
        ReadChar(psContext);
71,081✔
198

199
        while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
4,090,780✔
200
                               "-->") &&
8,252,630✔
201
               (chNext = ReadChar(psContext)) != '\0')
4,090,780✔
202
            AddToToken(psContext, chNext);
4,090,780✔
203

204
        // Skip "-->" characters.
205
        ReadChar(psContext);
71,081✔
206
        ReadChar(psContext);
71,081✔
207
        ReadChar(psContext);
71,081✔
208
    }
209
    /* -------------------------------------------------------------------- */
210
    /*      Handle DOCTYPE.                                                 */
211
    /* -------------------------------------------------------------------- */
212
    else if (chNext == '<' &&
50,942,900✔
213
             STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
7,095,070✔
214
                            "!DOCTYPE"))
215
    {
216
        bool bInQuotes = false;
18✔
217
        psContext->eTokenType = TLiteral;
18✔
218

219
        AddToToken(psContext, '<');
18✔
220
        do
221
        {
222
            chNext = ReadChar(psContext);
1,153✔
223
            if (chNext == '\0')
1,153✔
224
            {
225
                eLastErrorType = CE_Failure;
×
226
                CPLError(eLastErrorType, CPLE_AppDefined,
×
227
                         "Parse error in DOCTYPE on or before line %d, "
228
                         "reached end of file without '>'.",
229
                         psContext->nInputLine);
230

231
                break;
×
232
            }
233

234
            /* The markup declaration block within a DOCTYPE tag consists of:
235
             * - a left square bracket [
236
             * - a list of declarations
237
             * - a right square bracket ]
238
             * Example:
239
             * <!DOCTYPE RootElement [ ...declarations... ]>
240
             */
241
            if (chNext == '[')
1,153✔
242
            {
243
                AddToToken(psContext, chNext);
1✔
244

245
                do
98✔
246
                {
247
                    chNext = ReadChar(psContext);
99✔
248
                    if (chNext == ']')
99✔
249
                        break;
×
250
                    AddToToken(psContext, chNext);
99✔
251
                } while (chNext != '\0' &&
99✔
252
                         !STARTS_WITH_CI(psContext->pszInput +
99✔
253
                                             psContext->nInputOffset,
254
                                         "]>"));
255

256
                if (chNext == '\0')
1✔
257
                {
258
                    eLastErrorType = CE_Failure;
×
259
                    CPLError(eLastErrorType, CPLE_AppDefined,
×
260
                             "Parse error in DOCTYPE on or before line %d, "
261
                             "reached end of file without ']'.",
262
                             psContext->nInputLine);
263
                    break;
×
264
                }
265

266
                if (chNext != ']')
1✔
267
                {
268
                    chNext = ReadChar(psContext);
1✔
269
                    AddToToken(psContext, chNext);
1✔
270

271
                    // Skip ">" character, will be consumed below.
272
                    chNext = ReadChar(psContext);
1✔
273
                }
274
            }
275

276
            if (chNext == '\"')
1,153✔
277
                bInQuotes = !bInQuotes;
46✔
278

279
            if (chNext == '>' && !bInQuotes)
1,153✔
280
            {
281
                AddToToken(psContext, '>');
18✔
282
                break;
18✔
283
            }
284

285
            AddToToken(psContext, chNext);
1,135✔
286
        } while (true);
18✔
287
    }
288
    /* -------------------------------------------------------------------- */
289
    /*      Handle CDATA.                                                   */
290
    /* -------------------------------------------------------------------- */
291
    else if (chNext == '<' &&
50,942,900✔
292
             STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
7,095,050✔
293
                            "![CDATA["))
294
    {
295
        psContext->eTokenType = TString;
175✔
296

297
        // Skip !CDATA[
298
        ReadChar(psContext);
175✔
299
        ReadChar(psContext);
175✔
300
        ReadChar(psContext);
175✔
301
        ReadChar(psContext);
175✔
302
        ReadChar(psContext);
175✔
303
        ReadChar(psContext);
175✔
304
        ReadChar(psContext);
175✔
305
        ReadChar(psContext);
175✔
306

307
        while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
91,846✔
308
                               "]]>") &&
183,868✔
309
               (chNext = ReadChar(psContext)) != '\0')
91,847✔
310
            AddToToken(psContext, chNext);
91,846✔
311

312
        // Skip "]]>" characters.
313
        ReadChar(psContext);
175✔
314
        ReadChar(psContext);
175✔
315
        ReadChar(psContext);
175✔
316
    }
317
    /* -------------------------------------------------------------------- */
318
    /*      Simple single tokens of interest.                               */
319
    /* -------------------------------------------------------------------- */
320
    else if (chNext == '<' && !psContext->bInElement)
50,942,700✔
321
    {
322
        psContext->eTokenType = TOpen;
7,094,870✔
323
        psContext->bInElement = true;
7,094,870✔
324
    }
325
    else if (chNext == '>' && psContext->bInElement)
43,847,800✔
326
    {
327
        psContext->eTokenType = TClose;
4,873,300✔
328
        psContext->bInElement = false;
4,873,300✔
329
    }
330
    else if (chNext == '=' && psContext->bInElement)
38,974,500✔
331
    {
332
        psContext->eTokenType = TEqual;
9,350,900✔
333
    }
334
    else if (chNext == '\0')
29,623,600✔
335
    {
336
        psContext->eTokenType = TNone;
247,378✔
337
    }
338
    /* -------------------------------------------------------------------- */
339
    /*      Handle the /> token terminator.                                 */
340
    /* -------------------------------------------------------------------- */
341
    else if (chNext == '/' && psContext->bInElement &&
29,376,200✔
342
             psContext->pszInput[psContext->nInputOffset] == '>')
4,644,860✔
343
    {
344
        chNext = ReadChar(psContext);
2,213,230✔
345
        (void)chNext;
346
        CPLAssert(chNext == '>');
2,213,230✔
347

348
        psContext->eTokenType = TSlashClose;
2,213,230✔
349
        psContext->bInElement = false;
2,213,230✔
350
    }
351
    /* -------------------------------------------------------------------- */
352
    /*      Handle the ?> token terminator.                                 */
353
    /* -------------------------------------------------------------------- */
354
    else if (chNext == '?' && psContext->bInElement &&
27,163,000✔
355
             psContext->pszInput[psContext->nInputOffset] == '>')
16,595✔
356
    {
357
        chNext = ReadChar(psContext);
8,296✔
358
        (void)chNext;
359
        CPLAssert(chNext == '>');
8,296✔
360

361
        psContext->eTokenType = TQuestionClose;
8,296✔
362
        psContext->bInElement = false;
8,296✔
363
    }
364
    /* -------------------------------------------------------------------- */
365
    /*      Collect a quoted string.                                        */
366
    /* -------------------------------------------------------------------- */
367
    else if (psContext->bInElement && chNext == '"')
27,154,700✔
368
    {
369
        psContext->eTokenType = TString;
7,266,060✔
370

371
        while ((chNext = ReadChar(psContext)) != '"' && chNext != '\0')
63,312,000✔
372
            AddToToken(psContext, chNext);
56,046,000✔
373

374
        if (chNext != '"')
7,265,510✔
375
        {
376
            psContext->eTokenType = TNone;
×
377
            eLastErrorType = CE_Failure;
×
378
            CPLError(
×
379
                eLastErrorType, CPLE_AppDefined,
380
                "Parse error on line %d, reached EOF before closing quote.",
381
                psContext->nInputLine);
382
        }
383

384
        // Do we need to unescape it?
385
        if (strchr(psContext->pszToken, '&') != nullptr)
7,266,050✔
386
        {
387
            int nLength = 0;
224✔
388
            char *pszUnescaped =
389
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
224✔
390
            strcpy(psContext->pszToken, pszUnescaped);
224✔
391
            CPLFree(pszUnescaped);
224✔
392
            psContext->nTokenSize = strlen(psContext->pszToken);
224✔
393
        }
7,266,050✔
394
    }
395
    else if (psContext->bInElement && chNext == '\'')
19,888,700✔
396
    {
397
        psContext->eTokenType = TString;
2,084,840✔
398

399
        while ((chNext = ReadChar(psContext)) != '\'' && chNext != '\0')
54,069,500✔
400
            AddToToken(psContext, chNext);
51,984,700✔
401

402
        if (chNext != '\'')
2,084,770✔
403
        {
404
            psContext->eTokenType = TNone;
1✔
405
            eLastErrorType = CE_Failure;
1✔
406
            CPLError(
1✔
407
                eLastErrorType, CPLE_AppDefined,
408
                "Parse error on line %d, reached EOF before closing quote.",
409
                psContext->nInputLine);
410
        }
411

412
        // Do we need to unescape it?
413
        if (strchr(psContext->pszToken, '&') != nullptr)
2,084,840✔
414
        {
415
            int nLength = 0;
1,342✔
416
            char *pszUnescaped =
417
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
1,342✔
418
            strcpy(psContext->pszToken, pszUnescaped);
1,342✔
419
            CPLFree(pszUnescaped);
1,342✔
420
            psContext->nTokenSize = strlen(psContext->pszToken);
1,342✔
421
        }
2,084,840✔
422
    }
423
    /* -------------------------------------------------------------------- */
424
    /*      Collect an unquoted string, terminated by a open angle          */
425
    /*      bracket.                                                        */
426
    /* -------------------------------------------------------------------- */
427
    else if (!psContext->bInElement)
17,803,800✔
428
    {
429
        psContext->eTokenType = TString;
1,358,440✔
430

431
        AddToToken(psContext, chNext);
1,358,440✔
432
        while ((chNext = ReadChar(psContext)) != '<' && chNext != '\0')
101,332,000✔
433
            AddToToken(psContext, chNext);
99,973,300✔
434
        UnreadChar(psContext, chNext);
1,358,440✔
435

436
        // Do we need to unescape it?
437
        if (strchr(psContext->pszToken, '&') != nullptr)
1,358,440✔
438
        {
439
            int nLength = 0;
20,338✔
440
            char *pszUnescaped =
441
                CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
20,338✔
442
            strcpy(psContext->pszToken, pszUnescaped);
20,338✔
443
            CPLFree(pszUnescaped);
20,338✔
444
            psContext->nTokenSize = strlen(psContext->pszToken);
20,338✔
445
        }
446
    }
447

448
    /* -------------------------------------------------------------------- */
449
    /*      Collect a regular token terminated by white space, or           */
450
    /*      special character(s) like an equal sign.                        */
451
    /* -------------------------------------------------------------------- */
452
    else
453
    {
454
        psContext->eTokenType = TToken;
16,445,400✔
455

456
        // Add the first character to the token regardless of what it is.
457
        AddToToken(psContext, chNext);
16,445,400✔
458

459
        for (chNext = ReadChar(psContext);
113,048,000✔
460
             (chNext >= 'A' && chNext <= 'Z') ||
113,048,000✔
461
             (chNext >= 'a' && chNext <= 'z') || chNext == '-' ||
108,304,000✔
462
             chNext == '_' || chNext == '.' || chNext == ':' ||
131,376,000✔
463
             (chNext >= '0' && chNext <= '9');
13,354,200✔
464
             chNext = ReadChar(psContext))
96,600,700✔
465
        {
466
            AddToToken(psContext, chNext);
96,602,400✔
467
        }
468

469
        UnreadChar(psContext, chNext);
16,445,700✔
470
    }
471

472
    return psContext->eTokenType;
51,014,100✔
473

474
fail:
×
475
    psContext->eTokenType = TNone;
×
476
    return TNone;
×
477
}
478

479
/************************************************************************/
480
/*                              PushNode()                              */
481
/************************************************************************/
482

483
static bool PushNode(ParseContext *psContext, CPLXMLNode *psNode,
4,663,240✔
484
                     CPLErr &eLastErrorType)
485

486
{
487
    if (psContext->nStackMaxSize <= psContext->nStackSize)
4,663,240✔
488
    {
489
        // Somewhat arbitrary number.
490
        if (psContext->nStackMaxSize >= 10000)
249,323✔
491
        {
492
            eLastErrorType = CE_Failure;
1✔
493
            CPLError(CE_Failure, CPLE_NotSupported,
1✔
494
                     "XML element depth beyond 10000. Giving up");
495
            VSIFree(psContext->papsStack);
1✔
496
            psContext->papsStack = nullptr;
1✔
497
            return false;
1✔
498
        }
499
        psContext->nStackMaxSize += 10;
249,322✔
500

501
        StackContext *papsStack = static_cast<StackContext *>(
502
            VSIRealloc(psContext->papsStack,
498,646✔
503
                       sizeof(StackContext) * psContext->nStackMaxSize));
249,322✔
504
        if (papsStack == nullptr)
249,324✔
505
        {
506
            eLastErrorType = CE_Failure;
1✔
507
            CPLError(CE_Failure, CPLE_OutOfMemory,
1✔
508
                     "Out of memory allocating %d bytes",
509
                     static_cast<int>(sizeof(StackContext)) *
510
                         psContext->nStackMaxSize);
1✔
511
            VSIFree(psContext->papsStack);
×
512
            psContext->papsStack = nullptr;
×
513
            return false;
×
514
        }
515
        psContext->papsStack = papsStack;
249,323✔
516
    }
517
#ifdef DEBUG
518
    // To make Coverity happy, but cannot happen.
519
    if (psContext->papsStack == nullptr)
4,663,240✔
520
        return false;
×
521
#endif
522

523
    psContext->papsStack[psContext->nStackSize].psFirstNode = psNode;
4,663,240✔
524
    psContext->papsStack[psContext->nStackSize].psLastChild = nullptr;
4,663,240✔
525
    psContext->nStackSize++;
4,663,240✔
526

527
    return true;
4,663,240✔
528
}
529

530
/************************************************************************/
531
/*                             AttachNode()                             */
532
/*                                                                      */
533
/*      Attach the passed node as a child of the current node.          */
534
/*      Special handling exists for adding siblings to psFirst if       */
535
/*      there is nothing on the stack.                                  */
536
/************************************************************************/
537

538
static void AttachNode(ParseContext *psContext, CPLXMLNode *psNode)
15,443,900✔
539

540
{
541
    if (psContext->psFirstNode == nullptr)
15,443,900✔
542
    {
543
        psContext->psFirstNode = psNode;
247,375✔
544
        psContext->psLastNode = psNode;
247,375✔
545
    }
546
    else if (psContext->nStackSize == 0)
15,196,500✔
547
    {
548
        psContext->psLastNode->psNext = psNode;
10,504✔
549
        psContext->psLastNode = psNode;
10,504✔
550
    }
551
    else
552
    {
553
        if (psContext->papsStack[psContext->nStackSize - 1]
15,186,000✔
554
                .psFirstNode->psChild == nullptr)
15,186,000✔
555
        {
556
            psContext->papsStack[psContext->nStackSize - 1]
4,640,370✔
557
                .psFirstNode->psChild = psNode;
4,640,370✔
558
        }
559
        else
560
        {
561
            psContext->papsStack[psContext->nStackSize - 1]
10,545,600✔
562
                .psLastChild->psNext = psNode;
10,545,600✔
563
        }
564
        psContext->papsStack[psContext->nStackSize - 1].psLastChild = psNode;
15,186,000✔
565
    }
566
}
15,443,900✔
567

568
/************************************************************************/
569
/*                         CPLParseXMLString()                          */
570
/************************************************************************/
571

572
/**
573
 * \brief Parse an XML string into tree form.
574
 *
575
 * The passed document is parsed into a CPLXMLNode tree representation.
576
 * If the document is not well formed XML then NULL is returned, and errors
577
 * are reported via CPLError().  No validation beyond wellformedness is
578
 * done.  The CPLParseXMLFile() convenience function can be used to parse
579
 * from a file.
580
 *
581
 * The returned document tree is owned by the caller and should be freed
582
 * with CPLDestroyXMLNode() when no longer needed.
583
 *
584
 * If the document has more than one "root level" element then those after the
585
 * first will be attached to the first as siblings (via the psNext pointers)
586
 * even though there is no common parent.  A document with no XML structure
587
 * (no angle brackets for instance) would be considered well formed, and
588
 * returned as a single CXT_Text node.
589
 *
590
 * @param pszString the document to parse.
591
 *
592
 * @return parsed tree or NULL on error.
593
 */
594

595
CPLXMLNode *CPLParseXMLString(const char *pszString)
247,401✔
596

597
{
598
    if (pszString == nullptr)
247,401✔
599
    {
600
        CPLError(CE_Failure, CPLE_AppDefined,
×
601
                 "CPLParseXMLString() called with NULL pointer.");
602
        return nullptr;
×
603
    }
604

605
    // Save back error context.
606
    const CPLErr eErrClass = CPLGetLastErrorType();
247,401✔
607
    const CPLErrorNum nErrNum = CPLGetLastErrorNo();
247,399✔
608
    const CPLString osErrMsg = CPLGetLastErrorMsg();
494,800✔
609

610
    // Reset it now.
611
    CPLErrorSetState(CE_None, CPLE_AppDefined, "");
247,399✔
612

613
    /* -------------------------------------------------------------------- */
614
    /*      Check for a UTF-8 BOM and skip if found                         */
615
    /*                                                                      */
616
    /*      TODO: BOM is variable-length parameter and depends on encoding. */
617
    /*            Add BOM detection for other encodings.                    */
618
    /* -------------------------------------------------------------------- */
619

620
    // Used to skip to actual beginning of XML data.
621
    if ((static_cast<unsigned char>(pszString[0]) == 0xEF) &&
247,399✔
622
        (static_cast<unsigned char>(pszString[1]) == 0xBB) &&
4✔
623
        (static_cast<unsigned char>(pszString[2]) == 0xBF))
4✔
624
    {
625
        pszString += 3;
4✔
626
    }
627

628
    /* -------------------------------------------------------------------- */
629
    /*      Initialize parse context.                                       */
630
    /* -------------------------------------------------------------------- */
631
    ParseContext sContext;
632
    sContext.pszInput = pszString;
247,399✔
633
    sContext.nInputOffset = 0;
247,399✔
634
    sContext.nInputLine = 0;
247,399✔
635
    sContext.bInElement = false;
247,399✔
636
    sContext.nTokenMaxSize = 10;
247,399✔
637
    sContext.pszToken = static_cast<char *>(VSIMalloc(sContext.nTokenMaxSize));
247,399✔
638
    if (sContext.pszToken == nullptr)
247,400✔
639
        return nullptr;
×
640
    sContext.nTokenSize = 0;
247,400✔
641
    sContext.eTokenType = TNone;
247,400✔
642
    sContext.nStackMaxSize = 0;
247,400✔
643
    sContext.nStackSize = 0;
247,400✔
644
    sContext.papsStack = nullptr;
247,400✔
645
    sContext.psFirstNode = nullptr;
247,400✔
646
    sContext.psLastNode = nullptr;
247,400✔
647

648
#ifdef DEBUG
649
    bool bRecoverableError = true;
247,400✔
650
#endif
651
    CPLErr eLastErrorType = CE_None;
247,400✔
652

653
    /* ==================================================================== */
654
    /*      Loop reading tokens.                                            */
655
    /* ==================================================================== */
656
    while (ReadToken(&sContext, eLastErrorType) != TNone)
22,786,100✔
657
    {
658
    loop_beginning:
22,538,700✔
659
        /* --------------------------------------------------------------------
660
         */
661
        /*      Create a new element. */
662
        /* --------------------------------------------------------------------
663
         */
664
        if (sContext.eTokenType == TOpen)
22,538,700✔
665
        {
666
            if (ReadToken(&sContext, eLastErrorType) != TToken)
7,094,870✔
667
            {
668
                eLastErrorType = CE_Failure;
2✔
669
                CPLError(eLastErrorType, CPLE_AppDefined,
2✔
670
                         "Line %d: Didn't find element token after "
671
                         "open angle bracket.",
672
                         sContext.nInputLine);
673
                break;
2✔
674
            }
675

676
            CPLXMLNode *psElement = nullptr;
7,094,860✔
677
            if (sContext.pszToken[0] != '/')
7,094,860✔
678
            {
679
                psElement =
680
                    _CPLCreateXMLNode(nullptr, CXT_Element, sContext.pszToken);
4,663,230✔
681
                if (!psElement)
4,663,240✔
682
                    break;
×
683
                AttachNode(&sContext, psElement);
4,663,240✔
684
                if (!PushNode(&sContext, psElement, eLastErrorType))
4,663,240✔
685
                    break;
1✔
686
            }
687
            else
688
            {
689
                if (sContext.nStackSize == 0 ||
2,431,630✔
690
                    !EQUAL(sContext.pszToken + 1,
2,431,630✔
691
                           sContext.papsStack[sContext.nStackSize - 1]
692
                               .psFirstNode->pszValue))
693
                {
694
#ifdef DEBUG
695
                    // Makes life of fuzzers easier if we accept somewhat
696
                    // corrupted XML like <foo> ... </not_foo>.
697
                    if (CPLTestBool(
15✔
698
                            CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
699
                    {
700
                        eLastErrorType = CE_Warning;
×
701
                        CPLError(
×
702
                            eLastErrorType, CPLE_AppDefined,
703
                            "Line %d: <%.500s> doesn't have matching <%.500s>.",
704
                            sContext.nInputLine, sContext.pszToken,
705
                            sContext.pszToken + 1);
×
706
                        if (sContext.nStackSize == 0)
×
707
                            break;
×
708
                        goto end_processing_close;
×
709
                    }
710
                    else
711
#endif
712
                    {
713
                        eLastErrorType = CE_Failure;
14✔
714
                        CPLError(
14✔
715
                            eLastErrorType, CPLE_AppDefined,
716
                            "Line %d: <%.500s> doesn't have matching <%.500s>.",
717
                            sContext.nInputLine, sContext.pszToken,
718
                            sContext.pszToken + 1);
14✔
719
                        break;
14✔
720
                    }
721
                }
722
                else
723
                {
724
                    if (strcmp(sContext.pszToken + 1,
2,431,610✔
725
                               sContext.papsStack[sContext.nStackSize - 1]
2,431,610✔
726
                                   .psFirstNode->pszValue) != 0)
2,431,610✔
727
                    {
728
                        // TODO: At some point we could just error out like any
729
                        // other sane XML parser would do.
730
                        eLastErrorType = CE_Warning;
1✔
731
                        CPLError(
1✔
732
                            eLastErrorType, CPLE_AppDefined,
733
                            "Line %d: <%.500s> matches <%.500s>, but the case "
734
                            "isn't the same.  Going on, but this is invalid "
735
                            "XML that might be rejected in future versions.",
736
                            sContext.nInputLine,
737
                            sContext.papsStack[sContext.nStackSize - 1]
1✔
738
                                .psFirstNode->pszValue,
1✔
739
                            sContext.pszToken);
740
                    }
741
#ifdef DEBUG
742
                end_processing_close:
2,431,610✔
743
#endif
744
                    if (ReadToken(&sContext, eLastErrorType) != TClose)
2,431,610✔
745
                    {
746
                        eLastErrorType = CE_Failure;
3✔
747
                        CPLError(eLastErrorType, CPLE_AppDefined,
3✔
748
                                 "Line %d: Missing close angle bracket "
749
                                 "after <%.500s.",
750
                                 sContext.nInputLine, sContext.pszToken);
751
                        break;
3✔
752
                    }
753

754
                    // Pop element off stack
755
                    sContext.nStackSize--;
2,431,610✔
756
                }
757
            }
758
        }
759

760
        /* --------------------------------------------------------------------
761
         */
762
        /*      Add an attribute to a token. */
763
        /* --------------------------------------------------------------------
764
         */
765
        else if (sContext.eTokenType == TToken)
15,443,800✔
766
        {
767
            CPLXMLNode *psAttr =
768
                _CPLCreateXMLNode(nullptr, CXT_Attribute, sContext.pszToken);
9,350,890✔
769
            if (!psAttr)
9,350,910✔
770
                break;
×
771
            AttachNode(&sContext, psAttr);
9,350,910✔
772

773
            XMLTokenType nextToken = ReadToken(&sContext, eLastErrorType);
9,350,900✔
774
            if (nextToken != TEqual)
9,350,900✔
775
            {
776
                // Parse stuff like <?valbuddy_schematron
777
                // ../wmtsSimpleGetCapabilities.sch?>
778
                if (sContext.nStackSize > 0 &&
5✔
779
                    sContext.papsStack[sContext.nStackSize - 1]
5✔
780
                            .psFirstNode->pszValue[0] == '?')
5✔
781
                {
782
                    psAttr->eType = CXT_Text;
3✔
783
                    if (nextToken == TNone)
3✔
784
                        break;
×
785
                    goto loop_beginning;
10✔
786
                }
787

788
                eLastErrorType = CE_Failure;
2✔
789
                CPLError(eLastErrorType, CPLE_AppDefined,
2✔
790
                         "Line %d: Didn't find expected '=' for value of "
791
                         "attribute '%.500s'.",
792
                         sContext.nInputLine, psAttr->pszValue);
793
#ifdef DEBUG
794
                // Accepting an attribute without child text
795
                // would break too much assumptions in driver code
796
                bRecoverableError = false;
2✔
797
#endif
798
                break;
2✔
799
            }
800

801
            if (ReadToken(&sContext, eLastErrorType) == TToken)
9,350,900✔
802
            {
803
                /* TODO: at some point we could just error out like any other */
804
                /* sane XML parser would do */
805
                eLastErrorType = CE_Warning;
2✔
806
                CPLError(eLastErrorType, CPLE_AppDefined,
2✔
807
                         "Line %d: Attribute value should be single or double "
808
                         "quoted.  Going on, but this is invalid XML that "
809
                         "might be rejected in future versions.",
810
                         sContext.nInputLine);
811
            }
812
            else if (sContext.eTokenType != TString)
9,350,880✔
813
            {
814
                eLastErrorType = CE_Failure;
1✔
815
                CPLError(eLastErrorType, CPLE_AppDefined,
1✔
816
                         "Line %d: Didn't find expected attribute value.",
817
                         sContext.nInputLine);
818
#ifdef DEBUG
819
                // Accepting an attribute without child text
820
                // would break too much assumptions in driver code
821
                bRecoverableError = false;
1✔
822
#endif
823
                break;
1✔
824
            }
825

826
            if (!_CPLCreateXMLNode(psAttr, CXT_Text, sContext.pszToken))
9,350,880✔
827
                break;
×
828
        }
829

830
        /* --------------------------------------------------------------------
831
         */
832
        /*      Close the start section of an element. */
833
        /* --------------------------------------------------------------------
834
         */
835
        else if (sContext.eTokenType == TClose)
6,092,920✔
836
        {
837
            if (sContext.nStackSize == 0)
2,441,680✔
838
            {
839
                eLastErrorType = CE_Failure;
×
840
                CPLError(eLastErrorType, CPLE_AppDefined,
×
841
                         "Line %d: Found unbalanced '>'.", sContext.nInputLine);
842
                break;
×
843
            }
844
        }
845

846
        /* --------------------------------------------------------------------
847
         */
848
        /*      Close the start section of an element, and pop it */
849
        /*      immediately. */
850
        /* --------------------------------------------------------------------
851
         */
852
        else if (sContext.eTokenType == TSlashClose)
3,651,230✔
853
        {
854
            if (sContext.nStackSize == 0)
2,212,920✔
855
            {
856
                eLastErrorType = CE_Failure;
×
857
                CPLError(eLastErrorType, CPLE_AppDefined,
×
858
                         "Line %d: Found unbalanced '/>'.",
859
                         sContext.nInputLine);
860
                break;
×
861
            }
862

863
            sContext.nStackSize--;
2,212,920✔
864
        }
865
        /* --------------------------------------------------------------------
866
         */
867
        /*      Close the start section of a <?...?> element, and pop it */
868
        /*      immediately. */
869
        /* --------------------------------------------------------------------
870
         */
871
        else if (sContext.eTokenType == TQuestionClose)
1,438,310✔
872
        {
873
            if (sContext.nStackSize == 0)
8,296✔
874
            {
875
                eLastErrorType = CE_Failure;
×
876
                CPLError(eLastErrorType, CPLE_AppDefined,
×
877
                         "Line %d: Found unbalanced '?>'.",
878
                         sContext.nInputLine);
879
                break;
×
880
            }
881
            else if (sContext.papsStack[sContext.nStackSize - 1]
8,296✔
882
                         .psFirstNode->pszValue[0] != '?')
8,296✔
883
            {
884
                eLastErrorType = CE_Failure;
1✔
885
                CPLError(eLastErrorType, CPLE_AppDefined,
1✔
886
                         "Line %d: Found '?>' without matching '<?'.",
887
                         sContext.nInputLine);
888
                break;
1✔
889
            }
890

891
            sContext.nStackSize--;
8,295✔
892
        }
893
        /* --------------------------------------------------------------------
894
         */
895
        /*      Handle comments.  They are returned as a whole token with the */
896
        /*      prefix and postfix omitted.  No processing of white space */
897
        /*      will be done. */
898
        /* --------------------------------------------------------------------
899
         */
900
        else if (sContext.eTokenType == TComment)
1,430,020✔
901
        {
902
            CPLXMLNode *psValue =
903
                _CPLCreateXMLNode(nullptr, CXT_Comment, sContext.pszToken);
71,081✔
904
            if (!psValue)
71,081✔
905
                break;
×
906
            AttachNode(&sContext, psValue);
71,081✔
907
        }
908
        /* --------------------------------------------------------------------
909
         */
910
        /*      Handle literals.  They are returned without processing. */
911
        /* --------------------------------------------------------------------
912
         */
913
        else if (sContext.eTokenType == TLiteral)
1,358,940✔
914
        {
915
            CPLXMLNode *psValue =
916
                _CPLCreateXMLNode(nullptr, CXT_Literal, sContext.pszToken);
18✔
917
            if (!psValue)
18✔
918
                break;
×
919
            AttachNode(&sContext, psValue);
18✔
920
        }
921
        /* --------------------------------------------------------------------
922
         */
923
        /*      Add a text value node as a child of the current element. */
924
        /* --------------------------------------------------------------------
925
         */
926
        else if (sContext.eTokenType == TString && !sContext.bInElement)
1,358,920✔
927
        {
928
            CPLXMLNode *psValue =
929
                _CPLCreateXMLNode(nullptr, CXT_Text, sContext.pszToken);
1,358,610✔
930
            if (!psValue)
1,358,610✔
931
                break;
×
932
            AttachNode(&sContext, psValue);
1,358,610✔
933
        }
934
        /* --------------------------------------------------------------------
935
         */
936
        /*      Anything else is an error. */
937
        /* --------------------------------------------------------------------
938
         */
939
        else
940
        {
941
            eLastErrorType = CE_Failure;
307✔
942
            CPLError(eLastErrorType, CPLE_AppDefined,
307✔
943
                     "Parse error at line %d, unexpected token:%.500s",
944
                     sContext.nInputLine, sContext.pszToken);
945
            break;
1✔
946
        }
947
    }
948

949
    /* -------------------------------------------------------------------- */
950
    /*      Did we pop all the way out of our stack?                        */
951
    /* -------------------------------------------------------------------- */
952
    if (CPLGetLastErrorType() != CE_Failure && sContext.nStackSize > 0 &&
247,444✔
953
        sContext.papsStack != nullptr)
50✔
954
    {
955
#ifdef DEBUG
956
        // Makes life of fuzzers easier if we accept somewhat corrupted XML
957
        // like <x> ...
958
        if (bRecoverableError &&
100✔
959
            CPLTestBool(CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
50✔
960
        {
961
            eLastErrorType = CE_Warning;
×
962
        }
963
        else
964
#endif
965
        {
966
            eLastErrorType = CE_Failure;
50✔
967
        }
968
        CPLError(
50✔
969
            eLastErrorType, CPLE_AppDefined,
970
            "Parse error at EOF, not all elements have been closed, "
971
            "starting with %.500s",
972
            sContext.papsStack[sContext.nStackSize - 1].psFirstNode->pszValue);
50✔
973
    }
974

975
    /* -------------------------------------------------------------------- */
976
    /*      Cleanup                                                         */
977
    /* -------------------------------------------------------------------- */
978
    CPLFree(sContext.pszToken);
247,401✔
979
    if (sContext.papsStack != nullptr)
247,401✔
980
        CPLFree(sContext.papsStack);
247,365✔
981

982
    // We do not trust CPLGetLastErrorType() as if CPLTurnFailureIntoWarning()
983
    // has been set we would never get failures
984
    if (eLastErrorType == CE_Failure)
247,401✔
985
    {
986
        CPLDestroyXMLNode(sContext.psFirstNode);
76✔
987
        sContext.psFirstNode = nullptr;
76✔
988
        sContext.psLastNode = nullptr;
76✔
989
    }
990

991
    if (eLastErrorType == CE_None)
247,401✔
992
    {
993
        // Restore initial error state.
994
        CPLErrorSetState(eErrClass, nErrNum, osErrMsg);
247,323✔
995
    }
996

997
    return sContext.psFirstNode;
247,400✔
998
}
999

1000
/************************************************************************/
1001
/*                            _GrowBuffer()                             */
1002
/************************************************************************/
1003

1004
static bool _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
2,883,400✔
1005

1006
{
1007
    if (nNeeded + 1 >= *pnMaxLength)
2,883,400✔
1008
    {
1009
        *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
28,803✔
1010
        char *pszTextNew =
1011
            static_cast<char *>(VSIRealloc(*ppszText, *pnMaxLength));
28,803✔
1012
        if (pszTextNew == nullptr)
28,803✔
1013
            return false;
×
1014
        *ppszText = pszTextNew;
28,803✔
1015
    }
1016
    return true;
2,883,400✔
1017
}
1018

1019
/************************************************************************/
1020
/*                        CPLSerializeXMLNode()                         */
1021
/************************************************************************/
1022

1023
// TODO(schwehr): Rewrite this whole thing using C++ string.
1024
// CPLSerializeXMLNode has buffer overflows.
1025
static bool CPLSerializeXMLNode(const CPLXMLNode *psNode, int nIndent,
1,140,950✔
1026
                                char **ppszText, size_t *pnLength,
1027
                                size_t *pnMaxLength)
1028

1029
{
1030
    if (psNode == nullptr)
1,140,950✔
1031
        return true;
×
1032

1033
    /* -------------------------------------------------------------------- */
1034
    /*      Ensure the buffer is plenty large to hold this additional       */
1035
    /*      string.                                                         */
1036
    /* -------------------------------------------------------------------- */
1037
    *pnLength += strlen(*ppszText + *pnLength);
1,140,950✔
1038
    if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 + nIndent,
1,140,950✔
1039
                     ppszText, pnMaxLength))
1040
        return false;
×
1041

1042
    /* -------------------------------------------------------------------- */
1043
    /*      Text is just directly emitted.                                  */
1044
    /* -------------------------------------------------------------------- */
1045
    if (psNode->eType == CXT_Text)
1,140,950✔
1046
    {
1047
        char *pszEscaped =
1048
            CPLEscapeString(psNode->pszValue, -1, CPLES_XML_BUT_QUOTES);
194,875✔
1049

1050
        CPLAssert(psNode->psChild == nullptr);
194,875✔
1051

1052
        // Escaped text might be bigger than expected.
1053
        if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
194,875✔
1054
        {
1055
            CPLFree(pszEscaped);
×
1056
            return false;
×
1057
        }
1058
        strcat(*ppszText + *pnLength, pszEscaped);
194,875✔
1059

1060
        CPLFree(pszEscaped);
194,875✔
1061
    }
1062

1063
    /* -------------------------------------------------------------------- */
1064
    /*      Attributes require a little formatting.                         */
1065
    /* -------------------------------------------------------------------- */
1066
    else if (psNode->eType == CXT_Attribute)
946,077✔
1067
    {
1068
        CPLAssert(psNode->psChild != nullptr &&
486,222✔
1069
                  psNode->psChild->eType == CXT_Text);
1070

1071
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, " %s=\"",
486,222✔
1072
                 psNode->pszValue);
486,222✔
1073
        *pnLength += strlen(*ppszText + *pnLength);
486,222✔
1074

1075
        char *pszEscaped =
1076
            CPLEscapeString(psNode->psChild->pszValue, -1, CPLES_XML);
486,222✔
1077

1078
        if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
486,222✔
1079
        {
1080
            CPLFree(pszEscaped);
×
1081
            return false;
×
1082
        }
1083
        strcat(*ppszText + *pnLength, pszEscaped);
486,222✔
1084

1085
        CPLFree(pszEscaped);
486,222✔
1086

1087
        *pnLength += strlen(*ppszText + *pnLength);
486,222✔
1088
        if (!_GrowBuffer(3 + *pnLength, ppszText, pnMaxLength))
486,222✔
1089
            return false;
×
1090
        strcat(*ppszText + *pnLength, "\"");
486,222✔
1091
    }
1092

1093
    /* -------------------------------------------------------------------- */
1094
    /*      Handle comment output.                                          */
1095
    /* -------------------------------------------------------------------- */
1096
    else if (psNode->eType == CXT_Comment)
459,855✔
1097
    {
1098
        CPLAssert(psNode->psChild == nullptr);
12,553✔
1099

1100
        for (int i = 0; i < nIndent; i++)
82,411✔
1101
            (*ppszText)[(*pnLength)++] = ' ';
69,858✔
1102

1103
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<!--%s-->\n",
12,553✔
1104
                 psNode->pszValue);
12,553✔
1105
    }
1106

1107
    /* -------------------------------------------------------------------- */
1108
    /*      Handle literal output (like <!DOCTYPE...>)                      */
1109
    /* -------------------------------------------------------------------- */
1110
    else if (psNode->eType == CXT_Literal)
447,302✔
1111
    {
1112
        CPLAssert(psNode->psChild == nullptr);
4✔
1113

1114
        for (int i = 0; i < nIndent; i++)
28✔
1115
            (*ppszText)[(*pnLength)++] = ' ';
24✔
1116

1117
        strcpy(*ppszText + *pnLength, psNode->pszValue);
4✔
1118
        strcat(*ppszText + *pnLength, "\n");
4✔
1119
    }
1120

1121
    /* -------------------------------------------------------------------- */
1122
    /*      Elements actually have to deal with general children, and       */
1123
    /*      various formatting issues.                                      */
1124
    /* -------------------------------------------------------------------- */
1125
    else if (psNode->eType == CXT_Element)
447,298✔
1126
    {
1127
        if (nIndent)
447,298✔
1128
            memset(*ppszText + *pnLength, ' ', nIndent);
436,887✔
1129
        *pnLength += nIndent;
447,298✔
1130
        (*ppszText)[*pnLength] = '\0';
447,298✔
1131

1132
        snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<%s",
447,298✔
1133
                 psNode->pszValue);
447,298✔
1134

1135
        if (psNode->pszValue[0] == '?')
447,298✔
1136
        {
1137
            for (const CPLXMLNode *psChild = psNode->psChild;
3,098✔
1138
                 psChild != nullptr; psChild = psChild->psNext)
9,307✔
1139
            {
1140
                if (psChild->eType == CXT_Text)
6,209✔
1141
                {
1142
                    *pnLength += strlen(*ppszText + *pnLength);
3✔
1143
                    if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
3✔
1144
                        return false;
×
1145
                    strcat(*ppszText + *pnLength, " ");
3✔
1146
                }
1147

1148
                if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
6,209✔
1149
                                         pnMaxLength))
1150
                {
1151
                    return false;
×
1152
                }
1153
            }
1154
            if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
3,098✔
1155
                return false;
×
1156

1157
            strcat(*ppszText + *pnLength, "?>\n");
3,098✔
1158
        }
1159
        else
1160
        {
1161
            bool bHasNonAttributeChildren = false;
444,200✔
1162
            // Serialize *all* the attribute children, regardless of order
1163
            for (const CPLXMLNode *psChild = psNode->psChild;
444,200✔
1164
                 psChild != nullptr; psChild = psChild->psNext)
1,568,020✔
1165
            {
1166
                if (psChild->eType == CXT_Attribute)
1,123,820✔
1167
                {
1168
                    if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
480,016✔
1169
                                             pnMaxLength))
1170
                        return false;
×
1171
                }
1172
                else
1173
                    bHasNonAttributeChildren = true;
643,808✔
1174
            }
1175

1176
            if (!bHasNonAttributeChildren)
444,200✔
1177
            {
1178
                if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
121,538✔
1179
                    return false;
×
1180

1181
                strcat(*ppszText + *pnLength, " />\n");
121,538✔
1182
            }
1183
            else
1184
            {
1185
                bool bJustText = true;
322,662✔
1186

1187
                strcat(*ppszText + *pnLength, ">");
322,662✔
1188

1189
                for (const CPLXMLNode *psChild = psNode->psChild;
322,662✔
1190
                     psChild != nullptr; psChild = psChild->psNext)
1,117,540✔
1191
                {
1192
                    if (psChild->eType == CXT_Attribute)
794,880✔
1193
                        continue;
151,072✔
1194

1195
                    if (psChild->eType != CXT_Text && bJustText)
643,808✔
1196
                    {
1197
                        bJustText = false;
127,823✔
1198
                        *pnLength += strlen(*ppszText + *pnLength);
127,823✔
1199
                        if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
127,823✔
1200
                            return false;
×
1201
                        strcat(*ppszText + *pnLength, "\n");
127,823✔
1202
                    }
1203

1204
                    if (!CPLSerializeXMLNode(psChild, nIndent + 2, ppszText,
643,808✔
1205
                                             pnLength, pnMaxLength))
1206
                        return false;
×
1207
                }
1208

1209
                *pnLength += strlen(*ppszText + *pnLength);
322,662✔
1210
                if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 +
322,662✔
1211
                                     nIndent,
322,662✔
1212
                                 ppszText, pnMaxLength))
1213
                    return false;
×
1214

1215
                if (!bJustText)
322,662✔
1216
                {
1217
                    if (nIndent)
127,823✔
1218
                        memset(*ppszText + *pnLength, ' ', nIndent);
120,626✔
1219
                    *pnLength += nIndent;
127,823✔
1220
                    (*ppszText)[*pnLength] = '\0';
127,823✔
1221
                }
1222

1223
                *pnLength += strlen(*ppszText + *pnLength);
322,662✔
1224
                snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
322,662✔
1225
                         "</%s>\n", psNode->pszValue);
322,662✔
1226
            }
1227
        }
1228
    }
1229

1230
    return true;
1,140,950✔
1231
}
1232

1233
/************************************************************************/
1234
/*                        CPLSerializeXMLTree()                         */
1235
/************************************************************************/
1236

1237
/**
1238
 * \brief Convert tree into string document.
1239
 *
1240
 * This function converts a CPLXMLNode tree representation of a document
1241
 * into a flat string representation.  White space indentation is used
1242
 * visually preserve the tree structure of the document.  The returned
1243
 * document becomes owned by the caller and should be freed with CPLFree()
1244
 * when no longer needed.
1245
 *
1246
 * @param psNode the node to serialize.
1247
 *
1248
 * @return the document on success or NULL on failure.
1249
 */
1250

1251
char *CPLSerializeXMLTree(const CPLXMLNode *psNode)
7,321✔
1252

1253
{
1254
    size_t nMaxLength = 100;
7,321✔
1255
    char *pszText = static_cast<char *>(CPLCalloc(nMaxLength, sizeof(char)));
7,321✔
1256
    if (pszText == nullptr)
7,321✔
1257
        return nullptr;
×
1258

1259
    size_t nLength = 0;
7,321✔
1260
    for (const CPLXMLNode *psThis = psNode; psThis != nullptr;
18,240✔
1261
         psThis = psThis->psNext)
10,919✔
1262
    {
1263
        if (!CPLSerializeXMLNode(psThis, 0, &pszText, &nLength, &nMaxLength))
10,919✔
1264
        {
1265
            VSIFree(pszText);
×
1266
            return nullptr;
×
1267
        }
1268
    }
1269

1270
    return pszText;
7,321✔
1271
}
1272

1273
/************************************************************************/
1274
/*                          CPLCreateXMLNode()                          */
1275
/************************************************************************/
1276

1277
#ifdef DEBUG
1278
static CPLXMLNode *psDummyStaticNode;
1279
#endif
1280

1281
/**
1282
 * \brief Create an document tree item.
1283
 *
1284
 * Create a single CPLXMLNode object with the desired value and type, and
1285
 * attach it as a child of the indicated parent.
1286
 *
1287
 * @param poParent the parent to which this node should be attached as a
1288
 * child.  May be NULL to keep as free standing.
1289
 * @param eType the type of the newly created node
1290
 * @param pszText the value of the newly created node
1291
 *
1292
 * @return the newly created node, now owned by the caller (or parent node).
1293
 */
1294

1295
CPLXMLNode *CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
948,632✔
1296
                             const char *pszText)
1297

1298
{
1299
    auto ret = _CPLCreateXMLNode(poParent, eType, pszText);
948,632✔
1300
    if (!ret)
948,632✔
1301
    {
1302
        CPLError(CE_Fatal, CPLE_OutOfMemory, "CPLCreateXMLNode() failed");
×
1303
    }
1304
    return ret;
948,632✔
1305
}
1306

1307
/************************************************************************/
1308
/*                         _CPLCreateXMLNode()                          */
1309
/************************************************************************/
1310

1311
/* Same as CPLCreateXMLNode() but can return NULL in case of out-of-memory */
1312
/* situation */
1313

1314
static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
25,743,300✔
1315
                                     const char *pszText)
1316

1317
{
1318

1319
    /* -------------------------------------------------------------------- */
1320
    /*      Create new node.                                                */
1321
    /* -------------------------------------------------------------------- */
1322
    CPLXMLNode *psNode =
1323
        static_cast<CPLXMLNode *>(VSICalloc(sizeof(CPLXMLNode), 1));
25,743,300✔
1324
    if (psNode == nullptr)
25,743,400✔
1325
    {
UNCOV
1326
        CPLError(CE_Failure, CPLE_OutOfMemory, "Cannot allocate CPLXMLNode");
×
1327
        return nullptr;
×
1328
    }
1329

1330
    psNode->eType = eType;
25,743,400✔
1331
    psNode->pszValue = VSIStrdup(pszText ? pszText : "");
25,743,400✔
1332
    if (psNode->pszValue == nullptr)
25,743,400✔
1333
    {
UNCOV
1334
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
1335
                 "Cannot allocate psNode->pszValue");
1336
        VSIFree(psNode);
×
1337
        return nullptr;
×
1338
    }
1339

1340
    /* -------------------------------------------------------------------- */
1341
    /*      Attach to parent, if provided.                                  */
1342
    /* -------------------------------------------------------------------- */
1343
    if (poParent != nullptr)
25,743,400✔
1344
    {
1345
        if (poParent->psChild == nullptr)
10,135,900✔
1346
            poParent->psChild = psNode;
9,814,540✔
1347
        else
1348
        {
1349
            CPLXMLNode *psLink = poParent->psChild;
321,361✔
1350
            if (psLink->psNext == nullptr && eType == CXT_Attribute &&
321,361✔
1351
                psLink->eType == CXT_Text)
60,251✔
1352
            {
1353
                psNode->psNext = psLink;
14,394✔
1354
                poParent->psChild = psNode;
14,394✔
1355
            }
1356
            else
1357
            {
1358
                while (psLink->psNext != nullptr)
1,257,560✔
1359
                {
1360
                    if (eType == CXT_Attribute &&
954,814✔
1361
                        psLink->psNext->eType == CXT_Text)
275,170✔
1362
                    {
1363
                        psNode->psNext = psLink->psNext;
4,224✔
1364
                        break;
4,224✔
1365
                    }
1366

1367
                    psLink = psLink->psNext;
950,590✔
1368
                }
1369

1370
                psLink->psNext = psNode;
306,967✔
1371
            }
1372
        }
1373
    }
1374
#ifdef DEBUG
1375
    else
1376
    {
1377
        // Coverity sometimes doesn't realize that this function is passed
1378
        // with a non NULL parent and thinks that this branch is taken, leading
1379
        // to creating object being leak by caller. This ugly hack hopefully
1380
        // makes it believe that someone will reference it.
1381
        psDummyStaticNode = psNode;
15,607,500✔
1382
    }
1383
#endif
1384

1385
    return psNode;
25,743,400✔
1386
}
1387

1388
/************************************************************************/
1389
/*                         CPLDestroyXMLNode()                          */
1390
/************************************************************************/
1391

1392
/**
1393
 * \brief Destroy a tree.
1394
 *
1395
 * This function frees resources associated with a CPLXMLNode and all its
1396
 * children nodes.
1397
 *
1398
 * @param psNode the tree to free.
1399
 */
1400

1401
void CPLDestroyXMLNode(CPLXMLNode *psNode)
26,032,300✔
1402

1403
{
1404
    while (psNode != nullptr)
26,032,300✔
1405
    {
1406
        if (psNode->pszValue != nullptr)
25,769,700✔
1407
            CPLFree(psNode->pszValue);
25,769,700✔
1408

1409
        if (psNode->psChild != nullptr)
25,768,700✔
1410
        {
1411
            CPLXMLNode *psNext = psNode->psNext;
14,529,500✔
1412
            psNode->psNext = psNode->psChild;
14,529,500✔
1413
            // Move the child and its siblings as the next
1414
            // siblings of the current node.
1415
            if (psNext != nullptr)
14,529,500✔
1416
            {
1417
                CPLXMLNode *psIter = psNode->psChild;
13,923,000✔
1418
                while (psIter->psNext != nullptr)
23,271,400✔
1419
                    psIter = psIter->psNext;
9,348,320✔
1420
                psIter->psNext = psNext;
13,923,000✔
1421
            }
1422
        }
1423

1424
        CPLXMLNode *psNext = psNode->psNext;
25,768,700✔
1425

1426
        CPLFree(psNode);
25,768,700✔
1427

1428
        psNode = psNext;
25,769,700✔
1429
    }
1430
}
262,572✔
1431

1432
/************************************************************************/
1433
/*                           CPLSearchXMLNode()                         */
1434
/************************************************************************/
1435

1436
/**
1437
 * \brief Search for a node in document.
1438
 *
1439
 * Searches the children (and potentially siblings) of the documented
1440
 * passed in for the named element or attribute.  To search following
1441
 * siblings as well as children, prefix the pszElement name with an equal
1442
 * sign.  This function does an in-order traversal of the document tree.
1443
 * So it will first match against the current node, then its first child,
1444
 * that child's first child, and so on.
1445
 *
1446
 * Use CPLGetXMLNode() to find a specific child, or along a specific
1447
 * node path.
1448
 *
1449
 * @param psRoot the subtree to search.  This should be a node of type
1450
 * CXT_Element.  NULL is safe.
1451
 *
1452
 * @param pszElement the name of the element or attribute to search for.
1453
 *
1454
 * @return The matching node or NULL on failure.
1455
 */
1456

1457
CPLXMLNode *CPLSearchXMLNode(CPLXMLNode *psRoot, const char *pszElement)
94,865✔
1458

1459
{
1460
    if (psRoot == nullptr || pszElement == nullptr)
94,865✔
1461
        return nullptr;
×
1462

1463
    bool bSideSearch = false;
94,865✔
1464

1465
    if (*pszElement == '=')
94,865✔
1466
    {
1467
        bSideSearch = true;
5,391✔
1468
        pszElement++;
5,391✔
1469
    }
1470

1471
    /* -------------------------------------------------------------------- */
1472
    /*      Does this node match?                                           */
1473
    /* -------------------------------------------------------------------- */
1474
    if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
94,865✔
1475
        EQUAL(pszElement, psRoot->pszValue))
94,694✔
1476
        return psRoot;
4,098✔
1477

1478
    /* -------------------------------------------------------------------- */
1479
    /*      Search children.                                                */
1480
    /* -------------------------------------------------------------------- */
1481
    CPLXMLNode *psChild = nullptr;
90,767✔
1482
    for (psChild = psRoot->psChild; psChild != nullptr;
242,250✔
1483
         psChild = psChild->psNext)
151,483✔
1484
    {
1485
        if ((psChild->eType == CXT_Element ||
152,753✔
1486
             psChild->eType == CXT_Attribute) &&
91,414✔
1487
            EQUAL(pszElement, psChild->pszValue))
88,512✔
1488
            return psChild;
426✔
1489

1490
        if (psChild->psChild != nullptr)
152,327✔
1491
        {
1492
            CPLXMLNode *psResult = CPLSearchXMLNode(psChild, pszElement);
84,352✔
1493
            if (psResult != nullptr)
84,352✔
1494
                return psResult;
844✔
1495
        }
1496
    }
1497

1498
    /* -------------------------------------------------------------------- */
1499
    /*      Search siblings if we are in side search mode.                  */
1500
    /* -------------------------------------------------------------------- */
1501
    if (bSideSearch)
89,497✔
1502
    {
1503
        for (psRoot = psRoot->psNext; psRoot != nullptr;
5,784✔
1504
             psRoot = psRoot->psNext)
843✔
1505
        {
1506
            CPLXMLNode *psResult = CPLSearchXMLNode(psRoot, pszElement);
4,866✔
1507
            if (psResult != nullptr)
4,866✔
1508
                return psResult;
4,023✔
1509
        }
1510
    }
1511

1512
    return nullptr;
85,474✔
1513
}
1514

1515
/************************************************************************/
1516
/*                           CPLGetXMLNode()                            */
1517
/************************************************************************/
1518

1519
/**
1520
 * \brief Find node by path.
1521
 *
1522
 * Searches the document or subdocument indicated by psRoot for an element
1523
 * (or attribute) with the given path.  The path should consist of a set of
1524
 * element names separated by dots, not including the name of the root
1525
 * element (psRoot).  If the requested element is not found NULL is returned.
1526
 *
1527
 * Attribute names may only appear as the last item in the path.
1528
 *
1529
 * The search is done from the root nodes children, but all intermediate
1530
 * nodes in the path must be specified.  Searching for "name" would only find
1531
 * a name element or attribute if it is a direct child of the root, not at any
1532
 * level in the subdocument.
1533
 *
1534
 * If the pszPath is prefixed by "=" then the search will begin with the
1535
 * root node, and its siblings, instead of the root nodes children.  This
1536
 * is particularly useful when searching within a whole document which is
1537
 * often prefixed by one or more "junk" nodes like the <?xml> declaration.
1538
 *
1539
 * @param psRoot the subtree in which to search.  This should be a node of
1540
 * type CXT_Element.  NULL is safe.
1541
 *
1542
 * @param pszPath the list of element names in the path (dot separated).
1543
 *
1544
 * @return the requested element node, or NULL if not found.
1545
 */
1546

1547
CPLXMLNode *CPLGetXMLNode(CPLXMLNode *psRoot, const char *pszPath)
3,358,060✔
1548

1549
{
1550
    if (psRoot == nullptr || pszPath == nullptr)
3,358,060✔
1551
        return nullptr;
1,080✔
1552

1553
    bool bSideSearch = false;
3,356,980✔
1554

1555
    if (*pszPath == '=')
3,356,980✔
1556
    {
1557
        bSideSearch = true;
224,622✔
1558
        pszPath++;
224,622✔
1559
    }
1560

1561
    const char *const apszTokens[2] = {pszPath, nullptr};
3,356,980✔
1562

1563
    // Slight optimization: avoid using CSLTokenizeStringComplex that
1564
    // does memory allocations when it is not really necessary.
1565
    bool bFreeTokens = false;
3,356,980✔
1566
    char **papszTokensToFree = nullptr;
3,356,980✔
1567
    const char *const *papszTokens;
1568
    if (strchr(pszPath, '.'))
3,356,980✔
1569
    {
1570
        papszTokensToFree =
1571
            CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
235,844✔
1572
        papszTokens = papszTokensToFree;
235,844✔
1573
        bFreeTokens = true;
235,844✔
1574
    }
1575
    else
1576
    {
1577
        papszTokens = apszTokens;
3,121,140✔
1578
    }
1579

1580
    int iToken = 0;
3,356,980✔
1581
    while (papszTokens[iToken] != nullptr && psRoot != nullptr)
5,141,890✔
1582
    {
1583
        CPLXMLNode *psChild = nullptr;
3,597,650✔
1584

1585
        if (bSideSearch)
3,597,650✔
1586
        {
1587
            psChild = psRoot;
224,622✔
1588
            bSideSearch = false;
224,622✔
1589
        }
1590
        else
1591
            psChild = psRoot->psChild;
3,373,030✔
1592

1593
        for (; psChild != nullptr; psChild = psChild->psNext)
14,497,700✔
1594
        {
1595
            if (psChild->eType != CXT_Text &&
12,685,000✔
1596
                EQUAL(papszTokens[iToken], psChild->pszValue))
12,432,200✔
1597
                break;
1,784,920✔
1598
        }
1599

1600
        if (psChild == nullptr)
3,597,650✔
1601
        {
1602
            psRoot = nullptr;
1,812,740✔
1603
            break;
1,812,740✔
1604
        }
1605

1606
        psRoot = psChild;
1,784,910✔
1607
        iToken++;
1,784,910✔
1608
    }
1609

1610
    if (bFreeTokens)
3,356,980✔
1611
        CSLDestroy(papszTokensToFree);
235,844✔
1612
    return psRoot;
3,356,980✔
1613
}
1614

1615
/************************************************************************/
1616
/*                           CPLGetXMLValue()                           */
1617
/************************************************************************/
1618

1619
/**
1620
 * \brief Fetch element/attribute value.
1621
 *
1622
 * Searches the document for the element/attribute value associated with
1623
 * the path.  The corresponding node is internally found with CPLGetXMLNode()
1624
 * (see there for details on path handling).  Once found, the value is
1625
 * considered to be the first CXT_Text child of the node.
1626
 *
1627
 * If the attribute/element search fails, or if the found node has no
1628
 * value then the passed default value is returned.
1629
 *
1630
 * The returned value points to memory within the document tree, and should
1631
 * not be altered or freed.
1632
 *
1633
 * @param psRoot the subtree in which to search.  This should be a node of
1634
 * type CXT_Element.  NULL is safe.
1635
 *
1636
 * @param pszPath the list of element names in the path (dot separated).  An
1637
 * empty path means get the value of the psRoot node.
1638
 *
1639
 * @param pszDefault the value to return if a corresponding value is not
1640
 * found, may be NULL.
1641
 *
1642
 * @return the requested value or pszDefault if not found.
1643
 */
1644

1645
const char *CPLGetXMLValue(const CPLXMLNode *psRoot, const char *pszPath,
2,900,370✔
1646
                           const char *pszDefault)
1647

1648
{
1649
    const CPLXMLNode *psTarget = nullptr;
2,900,370✔
1650

1651
    if (pszPath == nullptr || *pszPath == '\0')
2,900,370✔
1652
        psTarget = psRoot;
192,542✔
1653
    else
1654
        psTarget = CPLGetXMLNode(psRoot, pszPath);
2,707,830✔
1655

1656
    if (psTarget == nullptr)
2,900,380✔
1657
        return pszDefault;
1,344,950✔
1658

1659
    if (psTarget->eType == CXT_Attribute)
1,555,430✔
1660
    {
1661
        CPLAssert(psTarget->psChild != nullptr &&
928,814✔
1662
                  psTarget->psChild->eType == CXT_Text);
1663

1664
        return psTarget->psChild->pszValue;
928,811✔
1665
    }
1666

1667
    if (psTarget->eType == CXT_Element)
626,619✔
1668
    {
1669
        // Find first non-attribute child, and verify it is a single text
1670
        // with no siblings.
1671

1672
        psTarget = psTarget->psChild;
626,596✔
1673

1674
        while (psTarget != nullptr && psTarget->eType == CXT_Attribute)
681,126✔
1675
            psTarget = psTarget->psNext;
54,530✔
1676

1677
        if (psTarget != nullptr && psTarget->eType == CXT_Text &&
626,596✔
1678
            psTarget->psNext == nullptr)
624,056✔
1679
            return psTarget->pszValue;
624,055✔
1680
    }
1681

1682
    return pszDefault;
2,564✔
1683
}
1684

1685
/************************************************************************/
1686
/*                           CPLAddXMLChild()                           */
1687
/************************************************************************/
1688

1689
/**
1690
 * \brief Add child node to parent.
1691
 *
1692
 * The passed child is added to the list of children of the indicated
1693
 * parent.  Normally the child is added at the end of the parents child
1694
 * list, but attributes (CXT_Attribute) will be inserted after any other
1695
 * attributes but before any other element type.  Ownership of the child
1696
 * node is effectively assumed by the parent node.   If the child has
1697
 * siblings (its psNext is not NULL) they will be trimmed, but if the child
1698
 * has children they are carried with it.
1699
 *
1700
 * @param psParent the node to attach the child to.  May not be NULL.
1701
 *
1702
 * @param psChild the child to add to the parent.  May not be NULL.  Should
1703
 * not be a child of any other parent.
1704
 */
1705

1706
void CPLAddXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
11,336✔
1707

1708
{
1709
    if (psParent->psChild == nullptr)
11,336✔
1710
    {
1711
        psParent->psChild = psChild;
2,449✔
1712
        return;
2,449✔
1713
    }
1714

1715
    // Insert at head of list if first child is not attribute.
1716
    if (psChild->eType == CXT_Attribute &&
8,887✔
1717
        psParent->psChild->eType != CXT_Attribute)
21✔
1718
    {
1719
        psChild->psNext = psParent->psChild;
×
1720
        psParent->psChild = psChild;
×
1721
        return;
×
1722
    }
1723

1724
    // Search for end of list.
1725
    CPLXMLNode *psSib = nullptr;
8,887✔
1726
    for (psSib = psParent->psChild; psSib->psNext != nullptr;
63,971✔
1727
         psSib = psSib->psNext)
55,084✔
1728
    {
1729
        // Insert attributes if the next node is not an attribute.
1730
        if (psChild->eType == CXT_Attribute && psSib->psNext != nullptr &&
55,085✔
1731
            psSib->psNext->eType != CXT_Attribute)
5✔
1732
        {
1733
            psChild->psNext = psSib->psNext;
1✔
1734
            psSib->psNext = psChild;
1✔
1735
            return;
1✔
1736
        }
1737
    }
1738

1739
    psSib->psNext = psChild;
8,886✔
1740
}
1741

1742
/************************************************************************/
1743
/*                        CPLRemoveXMLChild()                           */
1744
/************************************************************************/
1745

1746
/**
1747
 * \brief Remove child node from parent.
1748
 *
1749
 * The passed child is removed from the child list of the passed parent,
1750
 * but the child is not destroyed.  The child retains ownership of its
1751
 * own children, but is cleanly removed from the child list of the parent.
1752
 *
1753
 * @param psParent the node to the child is attached to.
1754
 *
1755
 * @param psChild the child to remove.
1756
 *
1757
 * @return TRUE on success or FALSE if the child was not found.
1758
 */
1759

1760
int CPLRemoveXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
2,597✔
1761

1762
{
1763
    if (psParent == nullptr)
2,597✔
1764
        return FALSE;
×
1765

1766
    CPLXMLNode *psLast = nullptr;
2,597✔
1767
    CPLXMLNode *psThis = nullptr;
2,597✔
1768
    for (psThis = psParent->psChild; psThis != nullptr; psThis = psThis->psNext)
5,691✔
1769
    {
1770
        if (psThis == psChild)
4,457✔
1771
        {
1772
            if (psLast == nullptr)
1,363✔
1773
                psParent->psChild = psThis->psNext;
876✔
1774
            else
1775
                psLast->psNext = psThis->psNext;
487✔
1776

1777
            psThis->psNext = nullptr;
1,363✔
1778
            return TRUE;
1,363✔
1779
        }
1780
        psLast = psThis;
3,094✔
1781
    }
1782

1783
    return FALSE;
1,234✔
1784
}
1785

1786
/************************************************************************/
1787
/*                          CPLAddXMLSibling()                          */
1788
/************************************************************************/
1789

1790
/**
1791
 * \brief Add new sibling.
1792
 *
1793
 * The passed psNewSibling is added to the end of siblings of the
1794
 * psOlderSibling node.  That is, it is added to the end of the psNext
1795
 * chain.  There is no special handling if psNewSibling is an attribute.
1796
 * If this is required, use CPLAddXMLChild().
1797
 *
1798
 * @param psOlderSibling the node to attach the sibling after.
1799
 *
1800
 * @param psNewSibling the node to add at the end of psOlderSiblings psNext
1801
 * chain.
1802
 */
1803

1804
void CPLAddXMLSibling(CPLXMLNode *psOlderSibling, CPLXMLNode *psNewSibling)
4,731✔
1805

1806
{
1807
    if (psOlderSibling == nullptr)
4,731✔
1808
        return;
×
1809

1810
    while (psOlderSibling->psNext != nullptr)
4,847✔
1811
        psOlderSibling = psOlderSibling->psNext;
116✔
1812

1813
    psOlderSibling->psNext = psNewSibling;
4,731✔
1814
}
1815

1816
/************************************************************************/
1817
/*                    CPLCreateXMLElementAndValue()                     */
1818
/************************************************************************/
1819

1820
/**
1821
 * \brief Create an element and text value.
1822
 *
1823
 * This is function is a convenient short form for:
1824
 *
1825
 * \code
1826
 *     CPLXMLNode *psTextNode;
1827
 *     CPLXMLNode *psElementNode;
1828
 *
1829
 *     psElementNode = CPLCreateXMLNode( psParent, CXT_Element, pszName );
1830
 *     psTextNode = CPLCreateXMLNode( psElementNode, CXT_Text, pszValue );
1831
 *
1832
 *     return psElementNode;
1833
 * \endcode
1834
 *
1835
 * It creates a CXT_Element node, with a CXT_Text child, and
1836
 * attaches the element to the passed parent.
1837
 *
1838
 * @param psParent the parent node to which the resulting node should
1839
 * be attached.  May be NULL to keep as freestanding.
1840
 *
1841
 * @param pszName the element name to create.
1842
 * @param pszValue the text to attach to the element. Must not be NULL.
1843
 *
1844
 * @return the pointer to the new element node.
1845
 */
1846

1847
CPLXMLNode *CPLCreateXMLElementAndValue(CPLXMLNode *psParent,
93,157✔
1848
                                        const char *pszName,
1849
                                        const char *pszValue)
1850

1851
{
1852
    CPLXMLNode *psElementNode =
1853
        CPLCreateXMLNode(psParent, CXT_Element, pszName);
93,157✔
1854
    CPLCreateXMLNode(psElementNode, CXT_Text, pszValue);
93,157✔
1855

1856
    return psElementNode;
93,157✔
1857
}
1858

1859
/************************************************************************/
1860
/*                    CPLCreateXMLElementAndValue()                     */
1861
/************************************************************************/
1862

1863
/**
1864
 * \brief Create an attribute and text value.
1865
 *
1866
 * This is function is a convenient short form for:
1867
 *
1868
 * \code
1869
 *   CPLXMLNode *psAttributeNode;
1870
 *
1871
 *   psAttributeNode = CPLCreateXMLNode( psParent, CXT_Attribute, pszName );
1872
 *   CPLCreateXMLNode( psAttributeNode, CXT_Text, pszValue );
1873
 * \endcode
1874
 *
1875
 * It creates a CXT_Attribute node, with a CXT_Text child, and
1876
 * attaches the element to the passed parent.
1877
 *
1878
 * @param psParent the parent node to which the resulting node should
1879
 * be attached.  Must not be NULL.
1880
 * @param pszName the attribute name to create.
1881
 * @param pszValue the text to attach to the attribute. Must not be NULL.
1882
 *
1883
 * @since GDAL 2.0
1884
 */
1885

1886
void CPLAddXMLAttributeAndValue(CPLXMLNode *psParent, const char *pszName,
52,137✔
1887
                                const char *pszValue)
1888
{
1889
    CPLAssert(psParent != nullptr);
52,137✔
1890
    CPLXMLNode *psAttributeNode =
1891
        CPLCreateXMLNode(psParent, CXT_Attribute, pszName);
52,137✔
1892
    CPLCreateXMLNode(psAttributeNode, CXT_Text, pszValue);
52,137✔
1893
}
52,137✔
1894

1895
/************************************************************************/
1896
/*                          CPLCloneXMLTree()                           */
1897
/************************************************************************/
1898

1899
/**
1900
 * \brief Copy tree.
1901
 *
1902
 * Creates a deep copy of a CPLXMLNode tree.
1903
 *
1904
 * @param psTree the tree to duplicate.
1905
 *
1906
 * @return a copy of the whole tree.
1907
 */
1908

1909
CPLXMLNode *CPLCloneXMLTree(const CPLXMLNode *psTree)
51,904✔
1910

1911
{
1912
    CPLXMLNode *psPrevious = nullptr;
51,904✔
1913
    CPLXMLNode *psReturn = nullptr;
51,904✔
1914

1915
    while (psTree != nullptr)
135,704✔
1916
    {
1917
        CPLXMLNode *psCopy =
1918
            CPLCreateXMLNode(nullptr, psTree->eType, psTree->pszValue);
83,800✔
1919
        if (psReturn == nullptr)
83,800✔
1920
            psReturn = psCopy;
51,904✔
1921
        if (psPrevious != nullptr)
83,800✔
1922
            psPrevious->psNext = psCopy;
31,896✔
1923

1924
        if (psTree->psChild != nullptr)
83,800✔
1925
            psCopy->psChild = CPLCloneXMLTree(psTree->psChild);
47,833✔
1926

1927
        psPrevious = psCopy;
83,800✔
1928
        psTree = psTree->psNext;
83,800✔
1929
    }
1930

1931
    return psReturn;
51,904✔
1932
}
1933

1934
/************************************************************************/
1935
/*                           CPLSetXMLValue()                           */
1936
/************************************************************************/
1937

1938
/**
1939
 * \brief Set element value by path.
1940
 *
1941
 * Find (or create) the target element or attribute specified in the
1942
 * path, and assign it the indicated value.
1943
 *
1944
 * Any path elements that do not already exist will be created.  The target
1945
 * nodes value (the first CXT_Text child) will be replaced with the provided
1946
 * value.
1947
 *
1948
 * If the target node is an attribute instead of an element, the name
1949
 * should be prefixed with a #.
1950
 *
1951
 * Example:
1952
 *   CPLSetXMLValue( "Citation.Id.Description", "DOQ dataset" );
1953
 *   CPLSetXMLValue( "Citation.Id.Description.#name", "doq" );
1954
 *
1955
 * @param psRoot the subdocument to be updated.
1956
 *
1957
 * @param pszPath the dot separated path to the target element/attribute.
1958
 *
1959
 * @param pszValue the text value to assign.
1960
 *
1961
 * @return TRUE on success.
1962
 */
1963

1964
int CPLSetXMLValue(CPLXMLNode *psRoot, const char *pszPath,
195,994✔
1965
                   const char *pszValue)
1966

1967
{
1968
    char **papszTokens = CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
195,994✔
1969
    int iToken = 0;
195,994✔
1970

1971
    while (papszTokens[iToken] != nullptr)
425,480✔
1972
    {
1973
        bool bIsAttribute = false;
229,486✔
1974
        const char *pszName = papszTokens[iToken];
229,486✔
1975

1976
        if (pszName[0] == '#')
229,486✔
1977
        {
1978
            bIsAttribute = true;
184,471✔
1979
            pszName++;
184,471✔
1980
        }
1981

1982
        if (psRoot->eType != CXT_Element)
229,486✔
1983
        {
1984
            CSLDestroy(papszTokens);
×
1985
            return FALSE;
×
1986
        }
1987

1988
        CPLXMLNode *psChild = nullptr;
229,486✔
1989
        for (psChild = psRoot->psChild; psChild != nullptr;
734,860✔
1990
             psChild = psChild->psNext)
505,374✔
1991
        {
1992
            if (psChild->eType != CXT_Text && EQUAL(pszName, psChild->pszValue))
535,111✔
1993
                break;
29,737✔
1994
        }
1995

1996
        if (psChild == nullptr)
229,486✔
1997
        {
1998
            if (bIsAttribute)
199,749✔
1999
                psChild = CPLCreateXMLNode(psRoot, CXT_Attribute, pszName);
184,156✔
2000
            else
2001
                psChild = CPLCreateXMLNode(psRoot, CXT_Element, pszName);
15,593✔
2002
        }
2003

2004
        psRoot = psChild;
229,486✔
2005
        iToken++;
229,486✔
2006
    }
2007

2008
    CSLDestroy(papszTokens);
195,994✔
2009

2010
    /* -------------------------------------------------------------------- */
2011
    /*      Find the "text" child if there is one.                          */
2012
    /* -------------------------------------------------------------------- */
2013
    CPLXMLNode *psTextChild = psRoot->psChild;
195,994✔
2014

2015
    while (psTextChild != nullptr && psTextChild->eType != CXT_Text)
196,192✔
2016
        psTextChild = psTextChild->psNext;
198✔
2017

2018
    /* -------------------------------------------------------------------- */
2019
    /*      Now set a value node under this node.                           */
2020
    /* -------------------------------------------------------------------- */
2021

2022
    if (psTextChild == nullptr)
195,994✔
2023
        CPLCreateXMLNode(psRoot, CXT_Text, pszValue);
195,415✔
2024
    else
2025
    {
2026
        CPLFree(psTextChild->pszValue);
579✔
2027
        psTextChild->pszValue = CPLStrdup(pszValue);
579✔
2028
    }
2029

2030
    return TRUE;
195,994✔
2031
}
2032

2033
/************************************************************************/
2034
/*                        CPLStripXMLNamespace()                        */
2035
/************************************************************************/
2036

2037
/**
2038
 * \brief Strip indicated namespaces.
2039
 *
2040
 * The subdocument (psRoot) is recursively examined, and any elements
2041
 * with the indicated namespace prefix will have the namespace prefix
2042
 * stripped from the element names.  If the passed namespace is NULL, then
2043
 * all namespace prefixes will be stripped.
2044
 *
2045
 * Nodes other than elements should remain unaffected.  The changes are
2046
 * made "in place", and should not alter any node locations, only the
2047
 * pszValue field of affected nodes.
2048
 *
2049
 * @param psRoot the document to operate on.
2050
 * @param pszNamespace the name space prefix (not including colon), or NULL.
2051
 * @param bRecurse TRUE to recurse over whole document, or FALSE to only
2052
 * operate on the passed node.
2053
 */
2054

2055
void CPLStripXMLNamespace(CPLXMLNode *psRoot, const char *pszNamespace,
1,569,940✔
2056
                          int bRecurse)
2057

2058
{
2059
    size_t nNameSpaceLen = (pszNamespace) ? strlen(pszNamespace) : 0;
1,569,940✔
2060

2061
    while (psRoot != nullptr)
4,043,700✔
2062
    {
2063
        if (psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute)
2,473,760✔
2064
        {
2065
            if (pszNamespace != nullptr)
1,389,030✔
2066
            {
2067
                if (EQUALN(pszNamespace, psRoot->pszValue, nNameSpaceLen) &&
597✔
2068
                    psRoot->pszValue[nNameSpaceLen] == ':')
177✔
2069
                {
2070
                    memmove(psRoot->pszValue,
177✔
2071
                            psRoot->pszValue + nNameSpaceLen + 1,
177✔
2072
                            strlen(psRoot->pszValue + nNameSpaceLen + 1) + 1);
177✔
2073
                }
2074
            }
2075
            else
2076
            {
2077
                for (const char *pszCheck = psRoot->pszValue; *pszCheck != '\0';
8,712,460✔
2078
                     pszCheck++)
2079
                {
2080
                    if (*pszCheck == ':')
8,278,070✔
2081
                    {
2082
                        memmove(psRoot->pszValue, pszCheck + 1,
954,039✔
2083
                                strlen(pszCheck + 1) + 1);
954,039✔
2084
                        break;
954,039✔
2085
                    }
2086
                }
2087
            }
2088
        }
2089

2090
        if (bRecurse)
2,473,760✔
2091
        {
2092
            if (psRoot->psChild != nullptr)
2,473,760✔
2093
                CPLStripXMLNamespace(psRoot->psChild, pszNamespace, 1);
1,374,000✔
2094

2095
            psRoot = psRoot->psNext;
2,473,760✔
2096
        }
2097
        else
2098
        {
2099
            break;
×
2100
        }
2101
    }
2102
}
1,569,940✔
2103

2104
/************************************************************************/
2105
/*                          CPLParseXMLFile()                           */
2106
/************************************************************************/
2107

2108
/**
2109
 * \brief Parse XML file into tree.
2110
 *
2111
 * The named file is opened, loaded into memory as a big string, and
2112
 * parsed with CPLParseXMLString().  Errors in reading the file or parsing
2113
 * the XML will be reported by CPLError().
2114
 *
2115
 * The "large file" API is used, so XML files can come from virtualized
2116
 * files.
2117
 *
2118
 * @param pszFilename the file to open.
2119
 *
2120
 * @return NULL on failure, or the document tree on success.
2121
 */
2122

2123
CPLXMLNode *CPLParseXMLFile(const char *pszFilename)
5,064✔
2124

2125
{
2126
    /* -------------------------------------------------------------------- */
2127
    /*      Ingest the file.                                                */
2128
    /* -------------------------------------------------------------------- */
2129
    GByte *pabyOut = nullptr;
5,064✔
2130
    if (!VSIIngestFile(nullptr, pszFilename, &pabyOut, nullptr, -1))
5,064✔
2131
        return nullptr;
63✔
2132

2133
    char *pszDoc = reinterpret_cast<char *>(pabyOut);
5,001✔
2134

2135
    /* -------------------------------------------------------------------- */
2136
    /*      Parse it.                                                       */
2137
    /* -------------------------------------------------------------------- */
2138
    CPLXMLNode *psTree = CPLParseXMLString(pszDoc);
5,001✔
2139
    CPLFree(pszDoc);
5,001✔
2140

2141
    return psTree;
5,001✔
2142
}
2143

2144
/************************************************************************/
2145
/*                     CPLSerializeXMLTreeToFile()                      */
2146
/************************************************************************/
2147

2148
/**
2149
 * \brief Write document tree to a file.
2150
 *
2151
 * The passed document tree is converted into one big string (with
2152
 * CPLSerializeXMLTree()) and then written to the named file.  Errors writing
2153
 * the file will be reported by CPLError().  The source document tree is
2154
 * not altered.  If the output file already exists it will be overwritten.
2155
 *
2156
 * @param psTree the document tree to write.
2157
 * @param pszFilename the name of the file to write to.
2158
 * @return TRUE on success, FALSE otherwise.
2159
 */
2160

2161
int CPLSerializeXMLTreeToFile(const CPLXMLNode *psTree, const char *pszFilename)
2,490✔
2162

2163
{
2164
    /* -------------------------------------------------------------------- */
2165
    /*      Serialize document.                                             */
2166
    /* -------------------------------------------------------------------- */
2167
    char *pszDoc = CPLSerializeXMLTree(psTree);
2,490✔
2168
    if (pszDoc == nullptr)
2,490✔
2169
        return FALSE;
×
2170

2171
    const vsi_l_offset nLength = strlen(pszDoc);
2,490✔
2172

2173
    /* -------------------------------------------------------------------- */
2174
    /*      Create file.                                                    */
2175
    /* -------------------------------------------------------------------- */
2176
    VSILFILE *fp = VSIFOpenL(pszFilename, "wt");
2,490✔
2177
    if (fp == nullptr)
2,490✔
2178
    {
2179
        CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %.500s to write.",
7✔
2180
                 pszFilename);
2181
        CPLFree(pszDoc);
7✔
2182
        return FALSE;
7✔
2183
    }
2184

2185
    /* -------------------------------------------------------------------- */
2186
    /*      Write file.                                                     */
2187
    /* -------------------------------------------------------------------- */
2188
    if (VSIFWriteL(pszDoc, 1, static_cast<size_t>(nLength), fp) != nLength)
2,483✔
2189
    {
2190
        CPLError(CE_Failure, CPLE_FileIO,
75✔
2191
                 "Failed to write whole XML document (%.500s).", pszFilename);
2192
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
75✔
2193
        CPLFree(pszDoc);
75✔
2194
        return FALSE;
75✔
2195
    }
2196

2197
    /* -------------------------------------------------------------------- */
2198
    /*      Cleanup                                                         */
2199
    /* -------------------------------------------------------------------- */
2200
    const bool bRet = VSIFCloseL(fp) == 0;
2,408✔
2201
    if (!bRet)
2,408✔
2202
    {
2203
        CPLError(CE_Failure, CPLE_FileIO,
×
2204
                 "Failed to write whole XML document (%.500s).", pszFilename);
2205
    }
2206
    CPLFree(pszDoc);
2,408✔
2207

2208
    return bRet;
2,408✔
2209
}
2210

2211
/************************************************************************/
2212
/*                       CPLCleanXMLElementName()                       */
2213
/************************************************************************/
2214

2215
/**
2216
 * \brief Make string into safe XML token.
2217
 *
2218
 * Modifies a string in place to try and make it into a legal
2219
 * XML token that can be used as an element name.   This is accomplished
2220
 * by changing any characters not legal in a token into an underscore.
2221
 *
2222
 * NOTE: This function should implement the rules in section 2.3 of
2223
 * http://www.w3.org/TR/xml11/ but it doesn't yet do that properly.  We
2224
 * only do a rough approximation of that.
2225
 *
2226
 * @param pszTarget the string to be adjusted.  It is altered in place.
2227
 */
2228

2229
void CPLCleanXMLElementName(char *pszTarget)
386✔
2230
{
2231
    if (pszTarget == nullptr)
386✔
2232
        return;
×
2233

2234
    for (; *pszTarget != '\0'; pszTarget++)
3,497✔
2235
    {
2236
        if ((static_cast<unsigned char>(*pszTarget) & 0x80) ||
3,111✔
2237
            isalnum(static_cast<unsigned char>(*pszTarget)) ||
3,111✔
2238
            *pszTarget == '_' || *pszTarget == '.')
201✔
2239
        {
2240
            // Ok.
2241
        }
2242
        else
2243
        {
2244
            *pszTarget = '_';
×
2245
        }
2246
    }
2247
}
2248

2249
/************************************************************************/
2250
/*                     CPLXMLNodeGetRAMUsageEstimate()                  */
2251
/************************************************************************/
2252

2253
static size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode,
132,901✔
2254
                                            bool bVisitSiblings)
2255
{
2256
    size_t nRet = sizeof(CPLXMLNode);
132,901✔
2257
    // malloc() aligns on 16-byte boundaries on 64 bit.
2258
    nRet += std::max(2 * sizeof(void *), strlen(psNode->pszValue) + 1);
132,901✔
2259
    if (bVisitSiblings)
132,901✔
2260
    {
2261
        for (const CPLXMLNode *psIter = psNode->psNext; psIter;
132,901✔
2262
             psIter = psIter->psNext)
55,486✔
2263
        {
2264
            nRet += CPLXMLNodeGetRAMUsageEstimate(psIter, false);
55,486✔
2265
        }
2266
    }
2267
    if (psNode->psChild)
132,901✔
2268
    {
2269
        nRet += CPLXMLNodeGetRAMUsageEstimate(psNode->psChild, true);
73,888✔
2270
    }
2271
    return nRet;
132,901✔
2272
}
2273

2274
/** Return a conservative estimate of the RAM usage of this node, its children
2275
 * and siblings. The returned values is in bytes.
2276
 *
2277
 * @since 3.9
2278
 */
2279
size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode)
3,527✔
2280
{
2281
    return CPLXMLNodeGetRAMUsageEstimate(psNode, true);
3,527✔
2282
}
2283

2284
/************************************************************************/
2285
/*            CPLXMLTreeCloser::getDocumentElement()                    */
2286
/************************************************************************/
2287

2288
CPLXMLNode *CPLXMLTreeCloser::getDocumentElement()
72✔
2289
{
2290
    CPLXMLNode *doc = get();
72✔
2291
    // skip the Declaration and assume the next is the root element
2292
    while (doc != nullptr &&
120✔
2293
           (doc->eType != CXT_Element || doc->pszValue[0] == '?'))
120✔
2294
    {
2295
        doc = doc->psNext;
48✔
2296
    }
2297
    return doc;
72✔
2298
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc