LCOV - code coverage report
Current view: top level - port - cpl_minixml.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 704 799 88.1 %
Date: 2024-11-21 22:18:42 Functions: 31 31 100.0 %

          Line data    Source code
       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   373307000 : static CPL_INLINE char ReadChar(ParseContext *psContext)
      87             : 
      88             : {
      89   373307000 :     const char chReturn = psContext->pszInput[psContext->nInputOffset++];
      90             : 
      91   373307000 :     if (chReturn == '\0')
      92      224760 :         psContext->nInputOffset--;
      93   373083000 :     else if (chReturn == 10)
      94     4395870 :         psContext->nInputLine++;
      95             : 
      96   373307000 :     return chReturn;
      97             : }
      98             : 
      99             : /************************************************************************/
     100             : /*                             UnreadChar()                             */
     101             : /************************************************************************/
     102             : 
     103    15293500 : static CPL_INLINE void UnreadChar(ParseContext *psContext, char chToUnread)
     104             : 
     105             : {
     106    15293500 :     if (chToUnread == '\0')
     107          35 :         return;
     108             : 
     109    15293500 :     CPLAssert(chToUnread == psContext->pszInput[psContext->nInputOffset - 1]);
     110             : 
     111    15293500 :     psContext->nInputOffset--;
     112             : 
     113    15293500 :     if (chToUnread == 10)
     114         275 :         psContext->nInputLine--;
     115             : }
     116             : 
     117             : /************************************************************************/
     118             : /*                           ReallocToken()                             */
     119             : /************************************************************************/
     120             : 
     121      957643 : static bool ReallocToken(ParseContext *psContext)
     122             : {
     123      957643 :     if (psContext->nTokenMaxSize > INT_MAX / 2)
     124             :     {
     125           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     126             :                  "Out of memory allocating %d*2 bytes",
     127           0 :                  static_cast<int>(psContext->nTokenMaxSize));
     128           0 :         VSIFree(psContext->pszToken);
     129           0 :         psContext->pszToken = nullptr;
     130           0 :         return false;
     131             :     }
     132             : 
     133      957643 :     psContext->nTokenMaxSize *= 2;
     134             :     char *pszToken = static_cast<char *>(
     135      957643 :         VSIRealloc(psContext->pszToken, psContext->nTokenMaxSize));
     136      957643 :     if (pszToken == nullptr)
     137             :     {
     138           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     139             :                  "Out of memory allocating %d bytes",
     140           0 :                  static_cast<int>(psContext->nTokenMaxSize));
     141           0 :         VSIFree(psContext->pszToken);
     142           0 :         psContext->pszToken = nullptr;
     143           0 :         return false;
     144             :     }
     145      957643 :     psContext->pszToken = pszToken;
     146      957643 :     return true;
     147             : }
     148             : 
     149             : /************************************************************************/
     150             : /*                             AddToToken()                             */
     151             : /************************************************************************/
     152             : 
     153   267120000 : static CPL_INLINE bool _AddToToken(ParseContext *psContext, char chNewChar)
     154             : 
     155             : {
     156   267120000 :     if (psContext->nTokenSize >= psContext->nTokenMaxSize - 2)
     157             :     {
     158      957643 :         if (!ReallocToken(psContext))
     159           0 :             return false;
     160             :     }
     161             : 
     162   267120000 :     psContext->pszToken[psContext->nTokenSize++] = chNewChar;
     163   267120000 :     psContext->pszToken[psContext->nTokenSize] = '\0';
     164   267120000 :     return true;
     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    44096300 : static XMLTokenType ReadToken(ParseContext *psContext, CPLErr &eLastErrorType)
     177             : 
     178             : {
     179    44096300 :     psContext->nTokenSize = 0;
     180    44096300 :     psContext->pszToken[0] = '\0';
     181             : 
     182    44096300 :     char chNext = ReadChar(psContext);
     183    95442600 :     while (isspace(static_cast<unsigned char>(chNext)))
     184    51346300 :         chNext = ReadChar(psContext);
     185             : 
     186             :     /* -------------------------------------------------------------------- */
     187             :     /*      Handle comments.                                                */
     188             :     /* -------------------------------------------------------------------- */
     189    44096300 :     if (chNext == '<' &&
     190     5988620 :         STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset, "!--"))
     191             :     {
     192       70622 :         psContext->eTokenType = TComment;
     193             : 
     194             :         // Skip "!--" characters.
     195       70622 :         ReadChar(psContext);
     196       70622 :         ReadChar(psContext);
     197       70622 :         ReadChar(psContext);
     198             : 
     199     4071910 :         while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
     200     8214440 :                                "-->") &&
     201     4071910 :                (chNext = ReadChar(psContext)) != '\0')
     202     4071910 :             AddToToken(psContext, chNext);
     203             : 
     204             :         // Skip "-->" characters.
     205       70622 :         ReadChar(psContext);
     206       70622 :         ReadChar(psContext);
     207       70622 :         ReadChar(psContext);
     208             :     }
     209             :     /* -------------------------------------------------------------------- */
     210             :     /*      Handle DOCTYPE.                                                 */
     211             :     /* -------------------------------------------------------------------- */
     212    44025700 :     else if (chNext == '<' &&
     213     5917990 :              STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
     214             :                             "!DOCTYPE"))
     215             :     {
     216          16 :         bool bInQuotes = false;
     217          16 :         psContext->eTokenType = TLiteral;
     218             : 
     219          16 :         AddToToken(psContext, '<');
     220             :         do
     221             :         {
     222        1039 :             chNext = ReadChar(psContext);
     223        1039 :             if (chNext == '\0')
     224             :             {
     225           0 :                 eLastErrorType = CE_Failure;
     226           0 :                 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           0 :                 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        1039 :             if (chNext == '[')
     242             :             {
     243           1 :                 AddToToken(psContext, chNext);
     244             : 
     245          98 :                 do
     246             :                 {
     247          99 :                     chNext = ReadChar(psContext);
     248          99 :                     if (chNext == ']')
     249           0 :                         break;
     250          99 :                     AddToToken(psContext, chNext);
     251          99 :                 } while (chNext != '\0' &&
     252          99 :                          !STARTS_WITH_CI(psContext->pszInput +
     253             :                                              psContext->nInputOffset,
     254             :                                          "]>"));
     255             : 
     256           1 :                 if (chNext == '\0')
     257             :                 {
     258           0 :                     eLastErrorType = CE_Failure;
     259           0 :                     CPLError(eLastErrorType, CPLE_AppDefined,
     260             :                              "Parse error in DOCTYPE on or before line %d, "
     261             :                              "reached end of file without ']'.",
     262             :                              psContext->nInputLine);
     263           0 :                     break;
     264             :                 }
     265             : 
     266           1 :                 if (chNext != ']')
     267             :                 {
     268           1 :                     chNext = ReadChar(psContext);
     269           1 :                     AddToToken(psContext, chNext);
     270             : 
     271             :                     // Skip ">" character, will be consumed below.
     272           1 :                     chNext = ReadChar(psContext);
     273             :                 }
     274             :             }
     275             : 
     276        1039 :             if (chNext == '\"')
     277          46 :                 bInQuotes = !bInQuotes;
     278             : 
     279        1039 :             if (chNext == '>' && !bInQuotes)
     280             :             {
     281          16 :                 AddToToken(psContext, '>');
     282          16 :                 break;
     283             :             }
     284             : 
     285        1023 :             AddToToken(psContext, chNext);
     286          16 :         } while (true);
     287             :     }
     288             :     /* -------------------------------------------------------------------- */
     289             :     /*      Handle CDATA.                                                   */
     290             :     /* -------------------------------------------------------------------- */
     291    44025600 :     else if (chNext == '<' &&
     292     5917980 :              STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
     293             :                             "![CDATA["))
     294             :     {
     295         173 :         psContext->eTokenType = TString;
     296             : 
     297             :         // Skip !CDATA[
     298         173 :         ReadChar(psContext);
     299         173 :         ReadChar(psContext);
     300         173 :         ReadChar(psContext);
     301         173 :         ReadChar(psContext);
     302         173 :         ReadChar(psContext);
     303         173 :         ReadChar(psContext);
     304         173 :         ReadChar(psContext);
     305         173 :         ReadChar(psContext);
     306             : 
     307       91580 :         while (!STARTS_WITH_CI(psContext->pszInput + psContext->nInputOffset,
     308      183334 :                                "]]>") &&
     309       91581 :                (chNext = ReadChar(psContext)) != '\0')
     310       91580 :             AddToToken(psContext, chNext);
     311             : 
     312             :         // Skip "]]>" characters.
     313         173 :         ReadChar(psContext);
     314         173 :         ReadChar(psContext);
     315         173 :         ReadChar(psContext);
     316             :     }
     317             :     /* -------------------------------------------------------------------- */
     318             :     /*      Simple single tokens of interest.                               */
     319             :     /* -------------------------------------------------------------------- */
     320    44025500 :     else if (chNext == '<' && !psContext->bInElement)
     321             :     {
     322     5917800 :         psContext->eTokenType = TOpen;
     323     5917800 :         psContext->bInElement = true;
     324             :     }
     325    38107700 :     else if (chNext == '>' && psContext->bInElement)
     326             :     {
     327     3934280 :         psContext->eTokenType = TClose;
     328     3934280 :         psContext->bInElement = false;
     329             :     }
     330    34173400 :     else if (chNext == '=' && psContext->bInElement)
     331             :     {
     332     8335830 :         psContext->eTokenType = TEqual;
     333             :     }
     334    25837600 :     else if (chNext == '\0')
     335             :     {
     336      224720 :         psContext->eTokenType = TNone;
     337             :     }
     338             :     /* -------------------------------------------------------------------- */
     339             :     /*      Handle the /> token terminator.                                 */
     340             :     /* -------------------------------------------------------------------- */
     341    25612800 :     else if (chNext == '/' && psContext->bInElement &&
     342     3937130 :              psContext->pszInput[psContext->nInputOffset] == '>')
     343             :     {
     344     1975010 :         chNext = ReadChar(psContext);
     345             :         (void)chNext;
     346     1975010 :         CPLAssert(chNext == '>');
     347             : 
     348     1975010 :         psContext->eTokenType = TSlashClose;
     349     1975010 :         psContext->bInElement = false;
     350             :     }
     351             :     /* -------------------------------------------------------------------- */
     352             :     /*      Handle the ?> token terminator.                                 */
     353             :     /* -------------------------------------------------------------------- */
     354    23637800 :     else if (chNext == '?' && psContext->bInElement &&
     355       16937 :              psContext->pszInput[psContext->nInputOffset] == '>')
     356             :     {
     357        8467 :         chNext = ReadChar(psContext);
     358             :         (void)chNext;
     359        8467 :         CPLAssert(chNext == '>');
     360             : 
     361        8467 :         psContext->eTokenType = TQuestionClose;
     362        8467 :         psContext->bInElement = false;
     363             :     }
     364             :     /* -------------------------------------------------------------------- */
     365             :     /*      Collect a quoted string.                                        */
     366             :     /* -------------------------------------------------------------------- */
     367    23629400 :     else if (psContext->bInElement && chNext == '"')
     368             :     {
     369     7212980 :         psContext->eTokenType = TString;
     370             : 
     371    62249100 :         while ((chNext = ReadChar(psContext)) != '"' && chNext != '\0')
     372    55036200 :             AddToToken(psContext, chNext);
     373             : 
     374     7212980 :         if (chNext != '"')
     375             :         {
     376           0 :             psContext->eTokenType = TNone;
     377           0 :             eLastErrorType = CE_Failure;
     378           0 :             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     7212980 :         if (strchr(psContext->pszToken, '&') != nullptr)
     386             :         {
     387         217 :             int nLength = 0;
     388             :             char *pszUnescaped =
     389         217 :                 CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
     390         217 :             strcpy(psContext->pszToken, pszUnescaped);
     391         217 :             CPLFree(pszUnescaped);
     392         217 :             psContext->nTokenSize = strlen(psContext->pszToken);
     393     7212980 :         }
     394             :     }
     395    16416400 :     else if (psContext->bInElement && chNext == '\'')
     396             :     {
     397     1122850 :         psContext->eTokenType = TString;
     398             : 
     399    19836900 :         while ((chNext = ReadChar(psContext)) != '\'' && chNext != '\0')
     400    18714000 :             AddToToken(psContext, chNext);
     401             : 
     402     1122800 :         if (chNext != '\'')
     403             :         {
     404           1 :             psContext->eTokenType = TNone;
     405           1 :             eLastErrorType = CE_Failure;
     406           1 :             CPLError(
     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     1122850 :         if (strchr(psContext->pszToken, '&') != nullptr)
     414             :         {
     415        1679 :             int nLength = 0;
     416             :             char *pszUnescaped =
     417        1679 :                 CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
     418        1679 :             strcpy(psContext->pszToken, pszUnescaped);
     419        1679 :             CPLFree(pszUnescaped);
     420        1679 :             psContext->nTokenSize = strlen(psContext->pszToken);
     421     1122850 :         }
     422             :     }
     423             :     /* -------------------------------------------------------------------- */
     424             :     /*      Collect an unquoted string, terminated by a open angle          */
     425             :     /*      bracket.                                                        */
     426             :     /* -------------------------------------------------------------------- */
     427    15293500 :     else if (!psContext->bInElement)
     428             :     {
     429     1039900 :         psContext->eTokenType = TString;
     430             : 
     431     1039900 :         AddToToken(psContext, chNext);
     432    93766800 :         while ((chNext = ReadChar(psContext)) != '<' && chNext != '\0')
     433    92726900 :             AddToToken(psContext, chNext);
     434     1039900 :         UnreadChar(psContext, chNext);
     435             : 
     436             :         // Do we need to unescape it?
     437     1039900 :         if (strchr(psContext->pszToken, '&') != nullptr)
     438             :         {
     439       19781 :             int nLength = 0;
     440             :             char *pszUnescaped =
     441       19781 :                 CPLUnescapeString(psContext->pszToken, &nLength, CPLES_XML);
     442       19781 :             strcpy(psContext->pszToken, pszUnescaped);
     443       19781 :             CPLFree(pszUnescaped);
     444       19781 :             psContext->nTokenSize = strlen(psContext->pszToken);
     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    14253600 :         psContext->eTokenType = TToken;
     455             : 
     456             :         // Add the first character to the token regardless of what it is.
     457    14253600 :         AddToToken(psContext, chNext);
     458             : 
     459    95439300 :         for (chNext = ReadChar(psContext);
     460    95439300 :              (chNext >= 'A' && chNext <= 'Z') ||
     461    91783100 :              (chNext >= 'a' && chNext <= 'z') || chNext == '-' ||
     462   111576000 :              chNext == '_' || chNext == '.' || chNext == ':' ||
     463    11453700 :              (chNext >= '0' && chNext <= '9');
     464    81185700 :              chNext = ReadChar(psContext))
     465             :         {
     466    81185700 :             AddToToken(psContext, chNext);
     467             :         }
     468             : 
     469    14253600 :         UnreadChar(psContext, chNext);
     470             :     }
     471             : 
     472    44096300 :     return psContext->eTokenType;
     473             : 
     474           0 : fail:
     475           0 :     psContext->eTokenType = TNone;
     476           0 :     return TNone;
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*                              PushNode()                              */
     481             : /************************************************************************/
     482             : 
     483     3955680 : static bool PushNode(ParseContext *psContext, CPLXMLNode *psNode,
     484             :                      CPLErr &eLastErrorType)
     485             : 
     486             : {
     487     3955680 :     if (psContext->nStackMaxSize <= psContext->nStackSize)
     488             :     {
     489             :         // Somewhat arbitrary number.
     490      226663 :         if (psContext->nStackMaxSize >= 10000)
     491             :         {
     492           1 :             eLastErrorType = CE_Failure;
     493           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     494             :                      "XML element depth beyond 10000. Giving up");
     495           1 :             VSIFree(psContext->papsStack);
     496           1 :             psContext->papsStack = nullptr;
     497           1 :             return false;
     498             :         }
     499      226662 :         psContext->nStackMaxSize += 10;
     500             : 
     501             :         StackContext *papsStack = static_cast<StackContext *>(
     502      453324 :             VSIRealloc(psContext->papsStack,
     503      226662 :                        sizeof(StackContext) * psContext->nStackMaxSize));
     504      226662 :         if (papsStack == nullptr)
     505             :         {
     506           0 :             eLastErrorType = CE_Failure;
     507           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
     508             :                      "Out of memory allocating %d bytes",
     509             :                      static_cast<int>(sizeof(StackContext)) *
     510           0 :                          psContext->nStackMaxSize);
     511           0 :             VSIFree(psContext->papsStack);
     512           0 :             psContext->papsStack = nullptr;
     513           0 :             return false;
     514             :         }
     515      226662 :         psContext->papsStack = papsStack;
     516             :     }
     517             : #ifdef DEBUG
     518             :     // To make Coverity happy, but cannot happen.
     519     3955680 :     if (psContext->papsStack == nullptr)
     520           0 :         return false;
     521             : #endif
     522             : 
     523     3955680 :     psContext->papsStack[psContext->nStackSize].psFirstNode = psNode;
     524     3955680 :     psContext->papsStack[psContext->nStackSize].psLastChild = nullptr;
     525     3955680 :     psContext->nStackSize++;
     526             : 
     527     3955680 :     return true;
     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    13402200 : static void AttachNode(ParseContext *psContext, CPLXMLNode *psNode)
     539             : 
     540             : {
     541    13402200 :     if (psContext->psFirstNode == nullptr)
     542             :     {
     543      224718 :         psContext->psFirstNode = psNode;
     544      224718 :         psContext->psLastNode = psNode;
     545             :     }
     546    13177500 :     else if (psContext->nStackSize == 0)
     547             :     {
     548       10659 :         psContext->psLastNode->psNext = psNode;
     549       10659 :         psContext->psLastNode = psNode;
     550             :     }
     551             :     else
     552             :     {
     553    13166900 :         if (psContext->papsStack[psContext->nStackSize - 1]
     554    13166900 :                 .psFirstNode->psChild == nullptr)
     555             :         {
     556     3932680 :             psContext->papsStack[psContext->nStackSize - 1]
     557     3932680 :                 .psFirstNode->psChild = psNode;
     558             :         }
     559             :         else
     560             :         {
     561     9234170 :             psContext->papsStack[psContext->nStackSize - 1]
     562     9234170 :                 .psLastChild->psNext = psNode;
     563             :         }
     564    13166900 :         psContext->papsStack[psContext->nStackSize - 1].psLastChild = psNode;
     565             :     }
     566    13402200 : }
     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      224743 : CPLXMLNode *CPLParseXMLString(const char *pszString)
     596             : 
     597             : {
     598      224743 :     if (pszString == nullptr)
     599             :     {
     600           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     601             :                  "CPLParseXMLString() called with NULL pointer.");
     602           0 :         return nullptr;
     603             :     }
     604             : 
     605             :     // Save back error context.
     606      224743 :     const CPLErr eErrClass = CPLGetLastErrorType();
     607      224743 :     const CPLErrorNum nErrNum = CPLGetLastErrorNo();
     608      449486 :     const CPLString osErrMsg = CPLGetLastErrorMsg();
     609             : 
     610             :     // Reset it now.
     611      224743 :     CPLErrorSetState(CE_None, CPLE_AppDefined, "");
     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      224743 :     if ((static_cast<unsigned char>(pszString[0]) == 0xEF) &&
     622           3 :         (static_cast<unsigned char>(pszString[1]) == 0xBB) &&
     623           3 :         (static_cast<unsigned char>(pszString[2]) == 0xBF))
     624             :     {
     625           3 :         pszString += 3;
     626             :     }
     627             : 
     628             :     /* -------------------------------------------------------------------- */
     629             :     /*      Initialize parse context.                                       */
     630             :     /* -------------------------------------------------------------------- */
     631             :     ParseContext sContext;
     632      224743 :     sContext.pszInput = pszString;
     633      224743 :     sContext.nInputOffset = 0;
     634      224743 :     sContext.nInputLine = 0;
     635      224743 :     sContext.bInElement = false;
     636      224743 :     sContext.nTokenMaxSize = 10;
     637      224743 :     sContext.pszToken = static_cast<char *>(VSIMalloc(sContext.nTokenMaxSize));
     638      224743 :     if (sContext.pszToken == nullptr)
     639           0 :         return nullptr;
     640      224743 :     sContext.nTokenSize = 0;
     641      224743 :     sContext.eTokenType = TNone;
     642      224743 :     sContext.nStackMaxSize = 0;
     643      224743 :     sContext.nStackSize = 0;
     644      224743 :     sContext.papsStack = nullptr;
     645      224743 :     sContext.psFirstNode = nullptr;
     646      224743 :     sContext.psLastNode = nullptr;
     647             : 
     648             : #ifdef DEBUG
     649      224743 :     bool bRecoverableError = true;
     650             : #endif
     651      224743 :     CPLErr eLastErrorType = CE_None;
     652             : 
     653             :     /* ==================================================================== */
     654             :     /*      Loop reading tokens.                                            */
     655             :     /* ==================================================================== */
     656    19544700 :     while (ReadToken(&sContext, eLastErrorType) != TNone)
     657             :     {
     658    19320000 :     loop_beginning:
     659             :         /* --------------------------------------------------------------------
     660             :          */
     661             :         /*      Create a new element. */
     662             :         /* --------------------------------------------------------------------
     663             :          */
     664    19320000 :         if (sContext.eTokenType == TOpen)
     665             :         {
     666     5917800 :             if (ReadToken(&sContext, eLastErrorType) != TToken)
     667             :             {
     668           2 :                 eLastErrorType = CE_Failure;
     669           2 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     670             :                          "Line %d: Didn't find element token after "
     671             :                          "open angle bracket.",
     672             :                          sContext.nInputLine);
     673           2 :                 break;
     674             :             }
     675             : 
     676     5917800 :             CPLXMLNode *psElement = nullptr;
     677     5917800 :             if (sContext.pszToken[0] != '/')
     678             :             {
     679             :                 psElement =
     680     3955680 :                     _CPLCreateXMLNode(nullptr, CXT_Element, sContext.pszToken);
     681     3955680 :                 if (!psElement)
     682           0 :                     break;
     683     3955680 :                 AttachNode(&sContext, psElement);
     684     3955680 :                 if (!PushNode(&sContext, psElement, eLastErrorType))
     685           1 :                     break;
     686             :             }
     687             :             else
     688             :             {
     689     1962120 :                 if (sContext.nStackSize == 0 ||
     690     1962120 :                     !EQUAL(sContext.pszToken + 1,
     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          13 :                     if (CPLTestBool(
     698             :                             CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
     699             :                     {
     700           0 :                         eLastErrorType = CE_Warning;
     701           0 :                         CPLError(
     702             :                             eLastErrorType, CPLE_AppDefined,
     703             :                             "Line %d: <%.500s> doesn't have matching <%.500s>.",
     704             :                             sContext.nInputLine, sContext.pszToken,
     705           0 :                             sContext.pszToken + 1);
     706           0 :                         if (sContext.nStackSize == 0)
     707           0 :                             break;
     708           0 :                         goto end_processing_close;
     709             :                     }
     710             :                     else
     711             : #endif
     712             :                     {
     713          14 :                         eLastErrorType = CE_Failure;
     714          14 :                         CPLError(
     715             :                             eLastErrorType, CPLE_AppDefined,
     716             :                             "Line %d: <%.500s> doesn't have matching <%.500s>.",
     717             :                             sContext.nInputLine, sContext.pszToken,
     718          14 :                             sContext.pszToken + 1);
     719          14 :                         break;
     720             :                     }
     721             :                 }
     722             :                 else
     723             :                 {
     724     1962110 :                     if (strcmp(sContext.pszToken + 1,
     725     1962110 :                                sContext.papsStack[sContext.nStackSize - 1]
     726     1962110 :                                    .psFirstNode->pszValue) != 0)
     727             :                     {
     728             :                         // TODO: At some point we could just error out like any
     729             :                         // other sane XML parser would do.
     730           1 :                         eLastErrorType = CE_Warning;
     731           1 :                         CPLError(
     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           1 :                             sContext.papsStack[sContext.nStackSize - 1]
     738           1 :                                 .psFirstNode->pszValue,
     739             :                             sContext.pszToken);
     740             :                     }
     741             : #ifdef DEBUG
     742     1962100 :                 end_processing_close:
     743             : #endif
     744     1962110 :                     if (ReadToken(&sContext, eLastErrorType) != TClose)
     745             :                     {
     746           3 :                         eLastErrorType = CE_Failure;
     747           3 :                         CPLError(eLastErrorType, CPLE_AppDefined,
     748             :                                  "Line %d: Missing close angle bracket "
     749             :                                  "after <%.500s.",
     750             :                                  sContext.nInputLine, sContext.pszToken);
     751           3 :                         break;
     752             :                     }
     753             : 
     754             :                     // Pop element off stack
     755     1962100 :                     sContext.nStackSize--;
     756             :                 }
     757             :             }
     758             :         }
     759             : 
     760             :         /* --------------------------------------------------------------------
     761             :          */
     762             :         /*      Add an attribute to a token. */
     763             :         /* --------------------------------------------------------------------
     764             :          */
     765    13402200 :         else if (sContext.eTokenType == TToken)
     766             :         {
     767             :             CPLXMLNode *psAttr =
     768     8335840 :                 _CPLCreateXMLNode(nullptr, CXT_Attribute, sContext.pszToken);
     769     8335840 :             if (!psAttr)
     770           0 :                 break;
     771     8335840 :             AttachNode(&sContext, psAttr);
     772             : 
     773     8335840 :             XMLTokenType nextToken = ReadToken(&sContext, eLastErrorType);
     774     8335840 :             if (nextToken != TEqual)
     775             :             {
     776             :                 // Parse stuff like <?valbuddy_schematron
     777             :                 // ../wmtsSimpleGetCapabilities.sch?>
     778           5 :                 if (sContext.nStackSize > 0 &&
     779           5 :                     sContext.papsStack[sContext.nStackSize - 1]
     780           5 :                             .psFirstNode->pszValue[0] == '?')
     781             :                 {
     782           3 :                     psAttr->eType = CXT_Text;
     783           3 :                     if (nextToken == TNone)
     784           0 :                         break;
     785           3 :                     goto loop_beginning;
     786             :                 }
     787             : 
     788           2 :                 eLastErrorType = CE_Failure;
     789           2 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     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           2 :                 bRecoverableError = false;
     797             : #endif
     798           2 :                 break;
     799             :             }
     800             : 
     801     8335830 :             if (ReadToken(&sContext, eLastErrorType) == TToken)
     802             :             {
     803             :                 /* TODO: at some point we could just error out like any other */
     804             :                 /* sane XML parser would do */
     805           2 :                 eLastErrorType = CE_Warning;
     806           2 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     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     8335830 :             else if (sContext.eTokenType != TString)
     813             :             {
     814           1 :                 eLastErrorType = CE_Failure;
     815           1 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     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           1 :                 bRecoverableError = false;
     822             : #endif
     823           1 :                 break;
     824             :             }
     825             : 
     826     8335830 :             if (!_CPLCreateXMLNode(psAttr, CXT_Text, sContext.pszToken))
     827           0 :                 break;
     828             :         }
     829             : 
     830             :         /* --------------------------------------------------------------------
     831             :          */
     832             :         /*      Close the start section of an element. */
     833             :         /* --------------------------------------------------------------------
     834             :          */
     835     5066360 :         else if (sContext.eTokenType == TClose)
     836             :         {
     837     1972180 :             if (sContext.nStackSize == 0)
     838             :             {
     839           0 :                 eLastErrorType = CE_Failure;
     840           0 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     841             :                          "Line %d: Found unbalanced '>'.", sContext.nInputLine);
     842           0 :                 break;
     843             :             }
     844             :         }
     845             : 
     846             :         /* --------------------------------------------------------------------
     847             :          */
     848             :         /*      Close the start section of an element, and pop it */
     849             :         /*      immediately. */
     850             :         /* --------------------------------------------------------------------
     851             :          */
     852     3094190 :         else if (sContext.eTokenType == TSlashClose)
     853             :         {
     854     1975010 :             if (sContext.nStackSize == 0)
     855             :             {
     856           0 :                 eLastErrorType = CE_Failure;
     857           0 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     858             :                          "Line %d: Found unbalanced '/>'.",
     859             :                          sContext.nInputLine);
     860           0 :                 break;
     861             :             }
     862             : 
     863     1975010 :             sContext.nStackSize--;
     864             :         }
     865             :         /* --------------------------------------------------------------------
     866             :          */
     867             :         /*      Close the start section of a <?...?> element, and pop it */
     868             :         /*      immediately. */
     869             :         /* --------------------------------------------------------------------
     870             :          */
     871     1119180 :         else if (sContext.eTokenType == TQuestionClose)
     872             :         {
     873        8467 :             if (sContext.nStackSize == 0)
     874             :             {
     875           0 :                 eLastErrorType = CE_Failure;
     876           0 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     877             :                          "Line %d: Found unbalanced '?>'.",
     878             :                          sContext.nInputLine);
     879           0 :                 break;
     880             :             }
     881        8467 :             else if (sContext.papsStack[sContext.nStackSize - 1]
     882        8467 :                          .psFirstNode->pszValue[0] != '?')
     883             :             {
     884           1 :                 eLastErrorType = CE_Failure;
     885           1 :                 CPLError(eLastErrorType, CPLE_AppDefined,
     886             :                          "Line %d: Found '?>' without matching '<?'.",
     887             :                          sContext.nInputLine);
     888           1 :                 break;
     889             :             }
     890             : 
     891        8466 :             sContext.nStackSize--;
     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     1110710 :         else if (sContext.eTokenType == TComment)
     901             :         {
     902             :             CPLXMLNode *psValue =
     903       70622 :                 _CPLCreateXMLNode(nullptr, CXT_Comment, sContext.pszToken);
     904       70622 :             if (!psValue)
     905           0 :                 break;
     906       70622 :             AttachNode(&sContext, psValue);
     907             :         }
     908             :         /* --------------------------------------------------------------------
     909             :          */
     910             :         /*      Handle literals.  They are returned without processing. */
     911             :         /* --------------------------------------------------------------------
     912             :          */
     913     1040090 :         else if (sContext.eTokenType == TLiteral)
     914             :         {
     915             :             CPLXMLNode *psValue =
     916          16 :                 _CPLCreateXMLNode(nullptr, CXT_Literal, sContext.pszToken);
     917          16 :             if (!psValue)
     918           0 :                 break;
     919          16 :             AttachNode(&sContext, psValue);
     920             :         }
     921             :         /* --------------------------------------------------------------------
     922             :          */
     923             :         /*      Add a text value node as a child of the current element. */
     924             :         /* --------------------------------------------------------------------
     925             :          */
     926     1040070 :         else if (sContext.eTokenType == TString && !sContext.bInElement)
     927             :         {
     928             :             CPLXMLNode *psValue =
     929     1040070 :                 _CPLCreateXMLNode(nullptr, CXT_Text, sContext.pszToken);
     930     1040070 :             if (!psValue)
     931           0 :                 break;
     932     1040070 :             AttachNode(&sContext, psValue);
     933             :         }
     934             :         /* --------------------------------------------------------------------
     935             :          */
     936             :         /*      Anything else is an error. */
     937             :         /* --------------------------------------------------------------------
     938             :          */
     939             :         else
     940             :         {
     941           1 :             eLastErrorType = CE_Failure;
     942           1 :             CPLError(eLastErrorType, CPLE_AppDefined,
     943             :                      "Parse error at line %d, unexpected token:%.500s",
     944             :                      sContext.nInputLine, sContext.pszToken);
     945           1 :             break;
     946             :         }
     947             :     }
     948             : 
     949             :     /* -------------------------------------------------------------------- */
     950             :     /*      Did we pop all the way out of our stack?                        */
     951             :     /* -------------------------------------------------------------------- */
     952      224793 :     if (CPLGetLastErrorType() != CE_Failure && sContext.nStackSize > 0 &&
     953          50 :         sContext.papsStack != nullptr)
     954             :     {
     955             : #ifdef DEBUG
     956             :         // Makes life of fuzzers easier if we accept somewhat corrupted XML
     957             :         // like <x> ...
     958         100 :         if (bRecoverableError &&
     959          50 :             CPLTestBool(CPLGetConfigOption("CPL_MINIXML_RELAXED", "FALSE")))
     960             :         {
     961           0 :             eLastErrorType = CE_Warning;
     962             :         }
     963             :         else
     964             : #endif
     965             :         {
     966          50 :             eLastErrorType = CE_Failure;
     967             :         }
     968          50 :         CPLError(
     969             :             eLastErrorType, CPLE_AppDefined,
     970             :             "Parse error at EOF, not all elements have been closed, "
     971             :             "starting with %.500s",
     972          50 :             sContext.papsStack[sContext.nStackSize - 1].psFirstNode->pszValue);
     973             :     }
     974             : 
     975             :     /* -------------------------------------------------------------------- */
     976             :     /*      Cleanup                                                         */
     977             :     /* -------------------------------------------------------------------- */
     978      224743 :     CPLFree(sContext.pszToken);
     979      224743 :     if (sContext.papsStack != nullptr)
     980      224707 :         CPLFree(sContext.papsStack);
     981             : 
     982             :     // We do not trust CPLGetLastErrorType() as if CPLTurnFailureIntoWarning()
     983             :     // has been set we would never get failures
     984      224743 :     if (eLastErrorType == CE_Failure)
     985             :     {
     986          76 :         CPLDestroyXMLNode(sContext.psFirstNode);
     987          76 :         sContext.psFirstNode = nullptr;
     988          76 :         sContext.psLastNode = nullptr;
     989             :     }
     990             : 
     991      224743 :     if (eLastErrorType == CE_None)
     992             :     {
     993             :         // Restore initial error state.
     994      224665 :         CPLErrorSetState(eErrClass, nErrNum, osErrMsg);
     995             :     }
     996             : 
     997      224743 :     return sContext.psFirstNode;
     998             : }
     999             : 
    1000             : /************************************************************************/
    1001             : /*                            _GrowBuffer()                             */
    1002             : /************************************************************************/
    1003             : 
    1004     2546490 : static bool _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
    1005             : 
    1006             : {
    1007     2546490 :     if (nNeeded + 1 >= *pnMaxLength)
    1008             :     {
    1009       26789 :         *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
    1010             :         char *pszTextNew =
    1011       26789 :             static_cast<char *>(VSIRealloc(*ppszText, *pnMaxLength));
    1012       26789 :         if (pszTextNew == nullptr)
    1013           0 :             return false;
    1014       26789 :         *ppszText = pszTextNew;
    1015             :     }
    1016     2546490 :     return true;
    1017             : }
    1018             : 
    1019             : /************************************************************************/
    1020             : /*                        CPLSerializeXMLNode()                         */
    1021             : /************************************************************************/
    1022             : 
    1023             : // TODO(schwehr): Rewrite this whole thing using C++ string.
    1024             : // CPLSerializeXMLNode has buffer overflows.
    1025      999832 : static bool CPLSerializeXMLNode(const CPLXMLNode *psNode, int nIndent,
    1026             :                                 char **ppszText, size_t *pnLength,
    1027             :                                 size_t *pnMaxLength)
    1028             : 
    1029             : {
    1030      999832 :     if (psNode == nullptr)
    1031           0 :         return true;
    1032             : 
    1033             :     /* -------------------------------------------------------------------- */
    1034             :     /*      Ensure the buffer is plenty large to hold this additional       */
    1035             :     /*      string.                                                         */
    1036             :     /* -------------------------------------------------------------------- */
    1037      999832 :     *pnLength += strlen(*ppszText + *pnLength);
    1038      999832 :     if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 + nIndent,
    1039             :                      ppszText, pnMaxLength))
    1040           0 :         return false;
    1041             : 
    1042             :     /* -------------------------------------------------------------------- */
    1043             :     /*      Text is just directly emitted.                                  */
    1044             :     /* -------------------------------------------------------------------- */
    1045      999832 :     if (psNode->eType == CXT_Text)
    1046             :     {
    1047             :         char *pszEscaped =
    1048      155165 :             CPLEscapeString(psNode->pszValue, -1, CPLES_XML_BUT_QUOTES);
    1049             : 
    1050      155165 :         CPLAssert(psNode->psChild == nullptr);
    1051             : 
    1052             :         // Escaped text might be bigger than expected.
    1053      155165 :         if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
    1054             :         {
    1055           0 :             CPLFree(pszEscaped);
    1056           0 :             return false;
    1057             :         }
    1058      155165 :         strcat(*ppszText + *pnLength, pszEscaped);
    1059             : 
    1060      155165 :         CPLFree(pszEscaped);
    1061             :     }
    1062             : 
    1063             :     /* -------------------------------------------------------------------- */
    1064             :     /*      Attributes require a little formatting.                         */
    1065             :     /* -------------------------------------------------------------------- */
    1066      844667 :     else if (psNode->eType == CXT_Attribute)
    1067             :     {
    1068      438900 :         CPLAssert(psNode->psChild != nullptr &&
    1069             :                   psNode->psChild->eType == CXT_Text);
    1070             : 
    1071      438900 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, " %s=\"",
    1072      438900 :                  psNode->pszValue);
    1073      438900 :         *pnLength += strlen(*ppszText + *pnLength);
    1074             : 
    1075             :         char *pszEscaped =
    1076      438900 :             CPLEscapeString(psNode->psChild->pszValue, -1, CPLES_XML);
    1077             : 
    1078      438900 :         if (!_GrowBuffer(strlen(pszEscaped) + *pnLength, ppszText, pnMaxLength))
    1079             :         {
    1080           0 :             CPLFree(pszEscaped);
    1081           0 :             return false;
    1082             :         }
    1083      438900 :         strcat(*ppszText + *pnLength, pszEscaped);
    1084             : 
    1085      438900 :         CPLFree(pszEscaped);
    1086             : 
    1087      438900 :         *pnLength += strlen(*ppszText + *pnLength);
    1088      438900 :         if (!_GrowBuffer(3 + *pnLength, ppszText, pnMaxLength))
    1089           0 :             return false;
    1090      438900 :         strcat(*ppszText + *pnLength, "\"");
    1091             :     }
    1092             : 
    1093             :     /* -------------------------------------------------------------------- */
    1094             :     /*      Handle comment output.                                          */
    1095             :     /* -------------------------------------------------------------------- */
    1096      405767 :     else if (psNode->eType == CXT_Comment)
    1097             :     {
    1098       12483 :         CPLAssert(psNode->psChild == nullptr);
    1099             : 
    1100       81965 :         for (int i = 0; i < nIndent; i++)
    1101       69482 :             (*ppszText)[(*pnLength)++] = ' ';
    1102             : 
    1103       12483 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<!--%s-->\n",
    1104       12483 :                  psNode->pszValue);
    1105             :     }
    1106             : 
    1107             :     /* -------------------------------------------------------------------- */
    1108             :     /*      Handle literal output (like <!DOCTYPE...>)                      */
    1109             :     /* -------------------------------------------------------------------- */
    1110      393284 :     else if (psNode->eType == CXT_Literal)
    1111             :     {
    1112           4 :         CPLAssert(psNode->psChild == nullptr);
    1113             : 
    1114          28 :         for (int i = 0; i < nIndent; i++)
    1115          24 :             (*ppszText)[(*pnLength)++] = ' ';
    1116             : 
    1117           4 :         strcpy(*ppszText + *pnLength, psNode->pszValue);
    1118           4 :         strcat(*ppszText + *pnLength, "\n");
    1119             :     }
    1120             : 
    1121             :     /* -------------------------------------------------------------------- */
    1122             :     /*      Elements actually have to deal with general children, and       */
    1123             :     /*      various formatting issues.                                      */
    1124             :     /* -------------------------------------------------------------------- */
    1125      393280 :     else if (psNode->eType == CXT_Element)
    1126             :     {
    1127      393280 :         if (nIndent)
    1128      382776 :             memset(*ppszText + *pnLength, ' ', nIndent);
    1129      393280 :         *pnLength += nIndent;
    1130      393280 :         (*ppszText)[*pnLength] = '\0';
    1131             : 
    1132      393280 :         snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength, "<%s",
    1133      393280 :                  psNode->pszValue);
    1134             : 
    1135      393280 :         if (psNode->pszValue[0] == '?')
    1136             :         {
    1137        2681 :             for (const CPLXMLNode *psChild = psNode->psChild;
    1138        8056 :                  psChild != nullptr; psChild = psChild->psNext)
    1139             :             {
    1140        5375 :                 if (psChild->eType == CXT_Text)
    1141             :                 {
    1142           3 :                     *pnLength += strlen(*ppszText + *pnLength);
    1143           3 :                     if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
    1144           0 :                         return false;
    1145           3 :                     strcat(*ppszText + *pnLength, " ");
    1146             :                 }
    1147             : 
    1148        5375 :                 if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
    1149             :                                          pnMaxLength))
    1150             :                 {
    1151           0 :                     return false;
    1152             :                 }
    1153             :             }
    1154        2681 :             if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
    1155           0 :                 return false;
    1156             : 
    1157        2681 :             strcat(*ppszText + *pnLength, "?>\n");
    1158             :         }
    1159             :         else
    1160             :         {
    1161      390599 :             bool bHasNonAttributeChildren = false;
    1162             :             // Serialize *all* the attribute children, regardless of order
    1163      390599 :             for (const CPLXMLNode *psChild = psNode->psChild;
    1164     1374050 :                  psChild != nullptr; psChild = psChild->psNext)
    1165             :             {
    1166      983447 :                 if (psChild->eType == CXT_Attribute)
    1167             :                 {
    1168      433528 :                     if (!CPLSerializeXMLNode(psChild, 0, ppszText, pnLength,
    1169             :                                              pnMaxLength))
    1170           0 :                         return false;
    1171             :                 }
    1172             :                 else
    1173      549919 :                     bHasNonAttributeChildren = true;
    1174             :             }
    1175             : 
    1176      390599 :             if (!bHasNonAttributeChildren)
    1177             :             {
    1178      115062 :                 if (!_GrowBuffer(*pnLength + 40, ppszText, pnMaxLength))
    1179           0 :                     return false;
    1180             : 
    1181      115062 :                 strcat(*ppszText + *pnLength, " />\n");
    1182             :             }
    1183             :             else
    1184             :             {
    1185      275537 :                 bool bJustText = true;
    1186             : 
    1187      275537 :                 strcat(*ppszText + *pnLength, ">");
    1188             : 
    1189      275537 :                 for (const CPLXMLNode *psChild = psNode->psChild;
    1190      959140 :                      psChild != nullptr; psChild = psChild->psNext)
    1191             :                 {
    1192      683603 :                     if (psChild->eType == CXT_Attribute)
    1193      133684 :                         continue;
    1194             : 
    1195      549919 :                     if (psChild->eType != CXT_Text && bJustText)
    1196             :                     {
    1197      120408 :                         bJustText = false;
    1198      120408 :                         *pnLength += strlen(*ppszText + *pnLength);
    1199      120408 :                         if (!_GrowBuffer(1 + *pnLength, ppszText, pnMaxLength))
    1200           0 :                             return false;
    1201      120408 :                         strcat(*ppszText + *pnLength, "\n");
    1202             :                     }
    1203             : 
    1204      549919 :                     if (!CPLSerializeXMLNode(psChild, nIndent + 2, ppszText,
    1205             :                                              pnLength, pnMaxLength))
    1206           0 :                         return false;
    1207             :                 }
    1208             : 
    1209      275537 :                 *pnLength += strlen(*ppszText + *pnLength);
    1210      275537 :                 if (!_GrowBuffer(strlen(psNode->pszValue) + *pnLength + 40 +
    1211      275537 :                                      nIndent,
    1212             :                                  ppszText, pnMaxLength))
    1213           0 :                     return false;
    1214             : 
    1215      275537 :                 if (!bJustText)
    1216             :                 {
    1217      120408 :                     if (nIndent)
    1218      112689 :                         memset(*ppszText + *pnLength, ' ', nIndent);
    1219      120408 :                     *pnLength += nIndent;
    1220      120408 :                     (*ppszText)[*pnLength] = '\0';
    1221             :                 }
    1222             : 
    1223      275537 :                 *pnLength += strlen(*ppszText + *pnLength);
    1224      275537 :                 snprintf(*ppszText + *pnLength, *pnMaxLength - *pnLength,
    1225      275537 :                          "</%s>\n", psNode->pszValue);
    1226             :             }
    1227             :         }
    1228             :     }
    1229             : 
    1230      999832 :     return true;
    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        7831 : char *CPLSerializeXMLTree(const CPLXMLNode *psNode)
    1252             : 
    1253             : {
    1254        7831 :     size_t nMaxLength = 100;
    1255        7831 :     char *pszText = static_cast<char *>(CPLCalloc(nMaxLength, sizeof(char)));
    1256        7831 :     if (pszText == nullptr)
    1257           0 :         return nullptr;
    1258             : 
    1259        7831 :     size_t nLength = 0;
    1260       18841 :     for (const CPLXMLNode *psThis = psNode; psThis != nullptr;
    1261       11010 :          psThis = psThis->psNext)
    1262             :     {
    1263       11010 :         if (!CPLSerializeXMLNode(psThis, 0, &pszText, &nLength, &nMaxLength))
    1264             :         {
    1265           0 :             VSIFree(pszText);
    1266           0 :             return nullptr;
    1267             :         }
    1268             :     }
    1269             : 
    1270        7831 :     return pszText;
    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      804891 : CPLXMLNode *CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
    1296             :                              const char *pszText)
    1297             : 
    1298             : {
    1299      804891 :     auto ret = _CPLCreateXMLNode(poParent, eType, pszText);
    1300      804891 :     if (!ret)
    1301             :     {
    1302           0 :         CPLError(CE_Fatal, CPLE_OutOfMemory, "CPLCreateXMLNode() failed");
    1303             :     }
    1304      804891 :     return ret;
    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    22542900 : static CPLXMLNode *_CPLCreateXMLNode(CPLXMLNode *poParent, CPLXMLNodeType eType,
    1315             :                                      const char *pszText)
    1316             : 
    1317             : {
    1318             : 
    1319             :     /* -------------------------------------------------------------------- */
    1320             :     /*      Create new node.                                                */
    1321             :     /* -------------------------------------------------------------------- */
    1322             :     CPLXMLNode *psNode =
    1323    22542900 :         static_cast<CPLXMLNode *>(VSICalloc(sizeof(CPLXMLNode), 1));
    1324    22543000 :     if (psNode == nullptr)
    1325             :     {
    1326           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "Cannot allocate CPLXMLNode");
    1327           0 :         return nullptr;
    1328             :     }
    1329             : 
    1330    22543000 :     psNode->eType = eType;
    1331    22543000 :     psNode->pszValue = VSIStrdup(pszText ? pszText : "");
    1332    22543000 :     if (psNode->pszValue == nullptr)
    1333             :     {
    1334           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
    1335             :                  "Cannot allocate psNode->pszValue");
    1336           0 :         VSIFree(psNode);
    1337           0 :         return nullptr;
    1338             :     }
    1339             : 
    1340             :     /* -------------------------------------------------------------------- */
    1341             :     /*      Attach to parent, if provided.                                  */
    1342             :     /* -------------------------------------------------------------------- */
    1343    22543000 :     if (poParent != nullptr)
    1344             :     {
    1345     9009680 :         if (poParent->psChild == nullptr)
    1346     8738430 :             poParent->psChild = psNode;
    1347             :         else
    1348             :         {
    1349      271253 :             CPLXMLNode *psLink = poParent->psChild;
    1350      271253 :             if (psLink->psNext == nullptr && eType == CXT_Attribute &&
    1351       53288 :                 psLink->eType == CXT_Text)
    1352             :             {
    1353       10663 :                 psNode->psNext = psLink;
    1354       10663 :                 poParent->psChild = psNode;
    1355             :             }
    1356             :             else
    1357             :             {
    1358      964160 :                 while (psLink->psNext != nullptr)
    1359             :                 {
    1360      707794 :                     if (eType == CXT_Attribute &&
    1361      271868 :                         psLink->psNext->eType == CXT_Text)
    1362             :                     {
    1363        4224 :                         psNode->psNext = psLink->psNext;
    1364        4224 :                         break;
    1365             :                     }
    1366             : 
    1367      703570 :                     psLink = psLink->psNext;
    1368             :                 }
    1369             : 
    1370      260590 :                 psLink->psNext = psNode;
    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    13533300 :         psDummyStaticNode = psNode;
    1382             :     }
    1383             : #endif
    1384             : 
    1385    22543000 :     return psNode;
    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    22809100 : void CPLDestroyXMLNode(CPLXMLNode *psNode)
    1402             : 
    1403             : {
    1404    22809100 :     while (psNode != nullptr)
    1405             :     {
    1406    22568800 :         if (psNode->pszValue != nullptr)
    1407    22568800 :             CPLFree(psNode->pszValue);
    1408             : 
    1409    22568800 :         if (psNode->psChild != nullptr)
    1410             :         {
    1411    12729100 :             CPLXMLNode *psNext = psNode->psNext;
    1412    12729100 :             psNode->psNext = psNode->psChild;
    1413             :             // Move the child and its siblings as the next
    1414             :             // siblings of the current node.
    1415    12729100 :             if (psNext != nullptr)
    1416             :             {
    1417    12186000 :                 CPLXMLNode *psIter = psNode->psChild;
    1418    20591300 :                 while (psIter->psNext != nullptr)
    1419     8405240 :                     psIter = psIter->psNext;
    1420    12186000 :                 psIter->psNext = psNext;
    1421             :             }
    1422             :         }
    1423             : 
    1424    22568800 :         CPLXMLNode *psNext = psNode->psNext;
    1425             : 
    1426    22568800 :         CPLFree(psNode);
    1427             : 
    1428    22568800 :         psNode = psNext;
    1429             :     }
    1430      240357 : }
    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       92952 : CPLXMLNode *CPLSearchXMLNode(CPLXMLNode *psRoot, const char *pszElement)
    1458             : 
    1459             : {
    1460       92952 :     if (psRoot == nullptr || pszElement == nullptr)
    1461           0 :         return nullptr;
    1462             : 
    1463       92952 :     bool bSideSearch = false;
    1464             : 
    1465       92952 :     if (*pszElement == '=')
    1466             :     {
    1467        5308 :         bSideSearch = true;
    1468        5308 :         pszElement++;
    1469             :     }
    1470             : 
    1471             :     /* -------------------------------------------------------------------- */
    1472             :     /*      Does this node match?                                           */
    1473             :     /* -------------------------------------------------------------------- */
    1474       92952 :     if ((psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute) &&
    1475       92781 :         EQUAL(pszElement, psRoot->pszValue))
    1476        4047 :         return psRoot;
    1477             : 
    1478             :     /* -------------------------------------------------------------------- */
    1479             :     /*      Search children.                                                */
    1480             :     /* -------------------------------------------------------------------- */
    1481       88905 :     CPLXMLNode *psChild = nullptr;
    1482      236870 :     for (psChild = psRoot->psChild; psChild != nullptr;
    1483      147965 :          psChild = psChild->psNext)
    1484             :     {
    1485      149228 :         if ((psChild->eType == CXT_Element ||
    1486       89425 :              psChild->eType == CXT_Attribute) &&
    1487       86545 :             EQUAL(pszElement, psChild->pszValue))
    1488         423 :             return psChild;
    1489             : 
    1490      148805 :         if (psChild->psChild != nullptr)
    1491             :         {
    1492       82567 :             CPLXMLNode *psResult = CPLSearchXMLNode(psChild, pszElement);
    1493       82567 :             if (psResult != nullptr)
    1494         840 :                 return psResult;
    1495             :         }
    1496             :     }
    1497             : 
    1498             :     /* -------------------------------------------------------------------- */
    1499             :     /*      Search siblings if we are in side search mode.                  */
    1500             :     /* -------------------------------------------------------------------- */
    1501       87642 :     if (bSideSearch)
    1502             :     {
    1503        5711 :         for (psRoot = psRoot->psNext; psRoot != nullptr;
    1504         826 :              psRoot = psRoot->psNext)
    1505             :         {
    1506        4824 :             CPLXMLNode *psResult = CPLSearchXMLNode(psRoot, pszElement);
    1507        4824 :             if (psResult != nullptr)
    1508        3998 :                 return psResult;
    1509             :         }
    1510             :     }
    1511             : 
    1512       83644 :     return nullptr;
    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     1716490 : CPLXMLNode *CPLGetXMLNode(CPLXMLNode *psRoot, const char *pszPath)
    1548             : 
    1549             : {
    1550     1716490 :     if (psRoot == nullptr || pszPath == nullptr)
    1551        1076 :         return nullptr;
    1552             : 
    1553     1715420 :     bool bSideSearch = false;
    1554             : 
    1555     1715420 :     if (*pszPath == '=')
    1556             :     {
    1557      213690 :         bSideSearch = true;
    1558      213690 :         pszPath++;
    1559             :     }
    1560             : 
    1561     1715420 :     const char *const apszTokens[2] = {pszPath, nullptr};
    1562             : 
    1563             :     // Slight optimization: avoid using CSLTokenizeStringComplex that
    1564             :     // does memory allocations when it is not really necessary.
    1565     1715420 :     bool bFreeTokens = false;
    1566     1715420 :     char **papszTokensToFree = nullptr;
    1567             :     const char *const *papszTokens;
    1568     1715420 :     if (strchr(pszPath, '.'))
    1569             :     {
    1570             :         papszTokensToFree =
    1571      230043 :             CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
    1572      230043 :         papszTokens = papszTokensToFree;
    1573      230043 :         bFreeTokens = true;
    1574             :     }
    1575             :     else
    1576             :     {
    1577     1485370 :         papszTokens = apszTokens;
    1578             :     }
    1579             : 
    1580     1715420 :     int iToken = 0;
    1581     3005170 :     while (papszTokens[iToken] != nullptr && psRoot != nullptr)
    1582             :     {
    1583     1951180 :         CPLXMLNode *psChild = nullptr;
    1584             : 
    1585     1951180 :         if (bSideSearch)
    1586             :         {
    1587      213690 :             psChild = psRoot;
    1588      213690 :             bSideSearch = false;
    1589             :         }
    1590             :         else
    1591     1737490 :             psChild = psRoot->psChild;
    1592             : 
    1593     7792600 :         for (; psChild != nullptr; psChild = psChild->psNext)
    1594             :         {
    1595     7131170 :             if (psChild->eType != CXT_Text &&
    1596     7090440 :                 EQUAL(papszTokens[iToken], psChild->pszValue))
    1597     1289750 :                 break;
    1598             :         }
    1599             : 
    1600     1951180 :         if (psChild == nullptr)
    1601             :         {
    1602      661427 :             psRoot = nullptr;
    1603      661427 :             break;
    1604             :         }
    1605             : 
    1606     1289750 :         psRoot = psChild;
    1607     1289750 :         iToken++;
    1608             :     }
    1609             : 
    1610     1715420 :     if (bFreeTokens)
    1611      230043 :         CSLDestroy(papszTokensToFree);
    1612     1715420 :     return psRoot;
    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     1713990 : const char *CPLGetXMLValue(const CPLXMLNode *psRoot, const char *pszPath,
    1646             :                            const char *pszDefault)
    1647             : 
    1648             : {
    1649     1713990 :     const CPLXMLNode *psTarget = nullptr;
    1650             : 
    1651     1713990 :     if (pszPath == nullptr || *pszPath == '\0')
    1652      106055 :         psTarget = psRoot;
    1653             :     else
    1654     1607940 :         psTarget = CPLGetXMLNode(psRoot, pszPath);
    1655             : 
    1656     1713990 :     if (psTarget == nullptr)
    1657      613665 :         return pszDefault;
    1658             : 
    1659     1100330 :     if (psTarget->eType == CXT_Attribute)
    1660             :     {
    1661      666682 :         CPLAssert(psTarget->psChild != nullptr &&
    1662             :                   psTarget->psChild->eType == CXT_Text);
    1663             : 
    1664      666682 :         return psTarget->psChild->pszValue;
    1665             :     }
    1666             : 
    1667      433646 :     if (psTarget->eType == CXT_Element)
    1668             :     {
    1669             :         // Find first non-attribute child, and verify it is a single text
    1670             :         // with no siblings.
    1671             : 
    1672      433622 :         psTarget = psTarget->psChild;
    1673             : 
    1674      483365 :         while (psTarget != nullptr && psTarget->eType == CXT_Attribute)
    1675       49743 :             psTarget = psTarget->psNext;
    1676             : 
    1677      433622 :         if (psTarget != nullptr && psTarget->eType == CXT_Text &&
    1678      430965 :             psTarget->psNext == nullptr)
    1679      430965 :             return psTarget->pszValue;
    1680             :     }
    1681             : 
    1682        2681 :     return pszDefault;
    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        5799 : void CPLAddXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
    1707             : 
    1708             : {
    1709        5799 :     if (psParent->psChild == nullptr)
    1710             :     {
    1711        2414 :         psParent->psChild = psChild;
    1712        2414 :         return;
    1713             :     }
    1714             : 
    1715             :     // Insert at head of list if first child is not attribute.
    1716        3385 :     if (psChild->eType == CXT_Attribute &&
    1717          21 :         psParent->psChild->eType != CXT_Attribute)
    1718             :     {
    1719           0 :         psChild->psNext = psParent->psChild;
    1720           0 :         psParent->psChild = psChild;
    1721           0 :         return;
    1722             :     }
    1723             : 
    1724             :     // Search for end of list.
    1725        3385 :     CPLXMLNode *psSib = nullptr;
    1726       14852 :     for (psSib = psParent->psChild; psSib->psNext != nullptr;
    1727       11467 :          psSib = psSib->psNext)
    1728             :     {
    1729             :         // Insert attributes if the next node is not an attribute.
    1730       11468 :         if (psChild->eType == CXT_Attribute && psSib->psNext != nullptr &&
    1731           5 :             psSib->psNext->eType != CXT_Attribute)
    1732             :         {
    1733           1 :             psChild->psNext = psSib->psNext;
    1734           1 :             psSib->psNext = psChild;
    1735           1 :             return;
    1736             :         }
    1737             :     }
    1738             : 
    1739        3384 :     psSib->psNext = psChild;
    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        2596 : int CPLRemoveXMLChild(CPLXMLNode *psParent, CPLXMLNode *psChild)
    1761             : 
    1762             : {
    1763        2596 :     if (psParent == nullptr)
    1764           0 :         return FALSE;
    1765             : 
    1766        2596 :     CPLXMLNode *psLast = nullptr;
    1767        2596 :     CPLXMLNode *psThis = nullptr;
    1768        5690 :     for (psThis = psParent->psChild; psThis != nullptr; psThis = psThis->psNext)
    1769             :     {
    1770        4456 :         if (psThis == psChild)
    1771             :         {
    1772        1362 :             if (psLast == nullptr)
    1773         875 :                 psParent->psChild = psThis->psNext;
    1774             :             else
    1775         487 :                 psLast->psNext = psThis->psNext;
    1776             : 
    1777        1362 :             psThis->psNext = nullptr;
    1778        1362 :             return TRUE;
    1779             :         }
    1780        3094 :         psLast = psThis;
    1781             :     }
    1782             : 
    1783        1234 :     return FALSE;
    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        4229 : void CPLAddXMLSibling(CPLXMLNode *psOlderSibling, CPLXMLNode *psNewSibling)
    1805             : 
    1806             : {
    1807        4229 :     if (psOlderSibling == nullptr)
    1808           0 :         return;
    1809             : 
    1810        4336 :     while (psOlderSibling->psNext != nullptr)
    1811         107 :         psOlderSibling = psOlderSibling->psNext;
    1812             : 
    1813        4229 :     psOlderSibling->psNext = psNewSibling;
    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       62283 : CPLXMLNode *CPLCreateXMLElementAndValue(CPLXMLNode *psParent,
    1848             :                                         const char *pszName,
    1849             :                                         const char *pszValue)
    1850             : 
    1851             : {
    1852             :     CPLXMLNode *psElementNode =
    1853       62283 :         CPLCreateXMLNode(psParent, CXT_Element, pszName);
    1854       62283 :     CPLCreateXMLNode(psElementNode, CXT_Text, pszValue);
    1855             : 
    1856       62283 :     return psElementNode;
    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       28113 : void CPLAddXMLAttributeAndValue(CPLXMLNode *psParent, const char *pszName,
    1887             :                                 const char *pszValue)
    1888             : {
    1889       28113 :     CPLAssert(psParent != nullptr);
    1890             :     CPLXMLNode *psAttributeNode =
    1891       28113 :         CPLCreateXMLNode(psParent, CXT_Attribute, pszName);
    1892       28113 :     CPLCreateXMLNode(psAttributeNode, CXT_Text, pszValue);
    1893       28113 : }
    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       33164 : CPLXMLNode *CPLCloneXMLTree(const CPLXMLNode *psTree)
    1910             : 
    1911             : {
    1912       33164 :     CPLXMLNode *psPrevious = nullptr;
    1913       33164 :     CPLXMLNode *psReturn = nullptr;
    1914             : 
    1915       87274 :     while (psTree != nullptr)
    1916             :     {
    1917             :         CPLXMLNode *psCopy =
    1918       54110 :             CPLCreateXMLNode(nullptr, psTree->eType, psTree->pszValue);
    1919       54110 :         if (psReturn == nullptr)
    1920       33164 :             psReturn = psCopy;
    1921       54110 :         if (psPrevious != nullptr)
    1922       20946 :             psPrevious->psNext = psCopy;
    1923             : 
    1924       54110 :         if (psTree->psChild != nullptr)
    1925       31637 :             psCopy->psChild = CPLCloneXMLTree(psTree->psChild);
    1926             : 
    1927       54110 :         psPrevious = psCopy;
    1928       54110 :         psTree = psTree->psNext;
    1929             :     }
    1930             : 
    1931       33164 :     return psReturn;
    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      194954 : int CPLSetXMLValue(CPLXMLNode *psRoot, const char *pszPath,
    1965             :                    const char *pszValue)
    1966             : 
    1967             : {
    1968      194954 :     char **papszTokens = CSLTokenizeStringComplex(pszPath, ".", FALSE, FALSE);
    1969      194954 :     int iToken = 0;
    1970             : 
    1971      421992 :     while (papszTokens[iToken] != nullptr)
    1972             :     {
    1973      227038 :         bool bIsAttribute = false;
    1974      227038 :         const char *pszName = papszTokens[iToken];
    1975             : 
    1976      227038 :         if (pszName[0] == '#')
    1977             :         {
    1978      183770 :             bIsAttribute = true;
    1979      183770 :             pszName++;
    1980             :         }
    1981             : 
    1982      227038 :         if (psRoot->eType != CXT_Element)
    1983             :         {
    1984           0 :             CSLDestroy(papszTokens);
    1985           0 :             return FALSE;
    1986             :         }
    1987             : 
    1988      227038 :         CPLXMLNode *psChild = nullptr;
    1989      724873 :         for (psChild = psRoot->psChild; psChild != nullptr;
    1990      497835 :              psChild = psChild->psNext)
    1991             :         {
    1992      526472 :             if (psChild->eType != CXT_Text && EQUAL(pszName, psChild->pszValue))
    1993       28637 :                 break;
    1994             :         }
    1995             : 
    1996      227038 :         if (psChild == nullptr)
    1997             :         {
    1998      198401 :             if (bIsAttribute)
    1999      183455 :                 psChild = CPLCreateXMLNode(psRoot, CXT_Attribute, pszName);
    2000             :             else
    2001       14946 :                 psChild = CPLCreateXMLNode(psRoot, CXT_Element, pszName);
    2002             :         }
    2003             : 
    2004      227038 :         psRoot = psChild;
    2005      227038 :         iToken++;
    2006             :     }
    2007             : 
    2008      194954 :     CSLDestroy(papszTokens);
    2009             : 
    2010             :     /* -------------------------------------------------------------------- */
    2011             :     /*      Find the "text" child if there is one.                          */
    2012             :     /* -------------------------------------------------------------------- */
    2013      194954 :     CPLXMLNode *psTextChild = psRoot->psChild;
    2014             : 
    2015      195151 :     while (psTextChild != nullptr && psTextChild->eType != CXT_Text)
    2016         197 :         psTextChild = psTextChild->psNext;
    2017             : 
    2018             :     /* -------------------------------------------------------------------- */
    2019             :     /*      Now set a value node under this node.                           */
    2020             :     /* -------------------------------------------------------------------- */
    2021             : 
    2022      194954 :     if (psTextChild == nullptr)
    2023      194376 :         CPLCreateXMLNode(psRoot, CXT_Text, pszValue);
    2024             :     else
    2025             :     {
    2026         578 :         CPLFree(psTextChild->pszValue);
    2027         578 :         psTextChild->pszValue = CPLStrdup(pszValue);
    2028             :     }
    2029             : 
    2030      194954 :     return TRUE;
    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     1525000 : void CPLStripXMLNamespace(CPLXMLNode *psRoot, const char *pszNamespace,
    2056             :                           int bRecurse)
    2057             : 
    2058             : {
    2059     1525000 :     size_t nNameSpaceLen = (pszNamespace) ? strlen(pszNamespace) : 0;
    2060             : 
    2061     3926770 :     while (psRoot != nullptr)
    2062             :     {
    2063     2401770 :         if (psRoot->eType == CXT_Element || psRoot->eType == CXT_Attribute)
    2064             :         {
    2065     1349280 :             if (pszNamespace != nullptr)
    2066             :             {
    2067         597 :                 if (EQUALN(pszNamespace, psRoot->pszValue, nNameSpaceLen) &&
    2068         177 :                     psRoot->pszValue[nNameSpaceLen] == ':')
    2069             :                 {
    2070         177 :                     memmove(psRoot->pszValue,
    2071         177 :                             psRoot->pszValue + nNameSpaceLen + 1,
    2072         177 :                             strlen(psRoot->pszValue + nNameSpaceLen + 1) + 1);
    2073             :                 }
    2074             :             }
    2075             :             else
    2076             :             {
    2077     8418520 :                 for (const char *pszCheck = psRoot->pszValue; *pszCheck != '\0';
    2078             :                      pszCheck++)
    2079             :                 {
    2080     8002780 :                     if (*pszCheck == ':')
    2081             :                     {
    2082      932952 :                         memmove(psRoot->pszValue, pszCheck + 1,
    2083      932952 :                                 strlen(pszCheck + 1) + 1);
    2084      932952 :                         break;
    2085             :                     }
    2086             :                 }
    2087             :             }
    2088             :         }
    2089             : 
    2090     2401770 :         if (bRecurse)
    2091             :         {
    2092     2401770 :             if (psRoot->psChild != nullptr)
    2093     1334520 :                 CPLStripXMLNamespace(psRoot->psChild, pszNamespace, 1);
    2094             : 
    2095     2401770 :             psRoot = psRoot->psNext;
    2096             :         }
    2097             :         else
    2098             :         {
    2099           0 :             break;
    2100             :         }
    2101             :     }
    2102     1525000 : }
    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        5066 : CPLXMLNode *CPLParseXMLFile(const char *pszFilename)
    2124             : 
    2125             : {
    2126             :     /* -------------------------------------------------------------------- */
    2127             :     /*      Ingest the file.                                                */
    2128             :     /* -------------------------------------------------------------------- */
    2129        5066 :     GByte *pabyOut = nullptr;
    2130        5066 :     if (!VSIIngestFile(nullptr, pszFilename, &pabyOut, nullptr, -1))
    2131          62 :         return nullptr;
    2132             : 
    2133        5004 :     char *pszDoc = reinterpret_cast<char *>(pabyOut);
    2134             : 
    2135             :     /* -------------------------------------------------------------------- */
    2136             :     /*      Parse it.                                                       */
    2137             :     /* -------------------------------------------------------------------- */
    2138        5004 :     CPLXMLNode *psTree = CPLParseXMLString(pszDoc);
    2139        5004 :     CPLFree(pszDoc);
    2140             : 
    2141        5004 :     return psTree;
    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        2637 : int CPLSerializeXMLTreeToFile(const CPLXMLNode *psTree, const char *pszFilename)
    2162             : 
    2163             : {
    2164             :     /* -------------------------------------------------------------------- */
    2165             :     /*      Serialize document.                                             */
    2166             :     /* -------------------------------------------------------------------- */
    2167        2637 :     char *pszDoc = CPLSerializeXMLTree(psTree);
    2168        2637 :     if (pszDoc == nullptr)
    2169           0 :         return FALSE;
    2170             : 
    2171        2637 :     const vsi_l_offset nLength = strlen(pszDoc);
    2172             : 
    2173             :     /* -------------------------------------------------------------------- */
    2174             :     /*      Create file.                                                    */
    2175             :     /* -------------------------------------------------------------------- */
    2176        2637 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wt");
    2177        2637 :     if (fp == nullptr)
    2178             :     {
    2179           7 :         CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %.500s to write.",
    2180             :                  pszFilename);
    2181           7 :         CPLFree(pszDoc);
    2182           7 :         return FALSE;
    2183             :     }
    2184             : 
    2185             :     /* -------------------------------------------------------------------- */
    2186             :     /*      Write file.                                                     */
    2187             :     /* -------------------------------------------------------------------- */
    2188        2630 :     if (VSIFWriteL(pszDoc, 1, static_cast<size_t>(nLength), fp) != nLength)
    2189             :     {
    2190          77 :         CPLError(CE_Failure, CPLE_FileIO,
    2191             :                  "Failed to write whole XML document (%.500s).", pszFilename);
    2192          77 :         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
    2193          77 :         CPLFree(pszDoc);
    2194          77 :         return FALSE;
    2195             :     }
    2196             : 
    2197             :     /* -------------------------------------------------------------------- */
    2198             :     /*      Cleanup                                                         */
    2199             :     /* -------------------------------------------------------------------- */
    2200        2553 :     const bool bRet = VSIFCloseL(fp) == 0;
    2201        2553 :     if (!bRet)
    2202             :     {
    2203           0 :         CPLError(CE_Failure, CPLE_FileIO,
    2204             :                  "Failed to write whole XML document (%.500s).", pszFilename);
    2205             :     }
    2206        2553 :     CPLFree(pszDoc);
    2207             : 
    2208        2553 :     return bRet;
    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         385 : void CPLCleanXMLElementName(char *pszTarget)
    2230             : {
    2231         385 :     if (pszTarget == nullptr)
    2232           0 :         return;
    2233             : 
    2234        3492 :     for (; *pszTarget != '\0'; pszTarget++)
    2235             :     {
    2236        3107 :         if ((static_cast<unsigned char>(*pszTarget) & 0x80) ||
    2237        3107 :             isalnum(static_cast<unsigned char>(*pszTarget)) ||
    2238         202 :             *pszTarget == '_' || *pszTarget == '.')
    2239             :         {
    2240             :             // Ok.
    2241             :         }
    2242             :         else
    2243             :         {
    2244           0 :             *pszTarget = '_';
    2245             :         }
    2246             :     }
    2247             : }
    2248             : 
    2249             : /************************************************************************/
    2250             : /*                     CPLXMLNodeGetRAMUsageEstimate()                  */
    2251             : /************************************************************************/
    2252             : 
    2253      105128 : static size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode,
    2254             :                                             bool bVisitSiblings)
    2255             : {
    2256      105128 :     size_t nRet = sizeof(CPLXMLNode);
    2257             :     // malloc() aligns on 16-byte boundaries on 64 bit.
    2258      105128 :     nRet += std::max(2 * sizeof(void *), strlen(psNode->pszValue) + 1);
    2259      105128 :     if (bVisitSiblings)
    2260             :     {
    2261      105128 :         for (const CPLXMLNode *psIter = psNode->psNext; psIter;
    2262       43411 :              psIter = psIter->psNext)
    2263             :         {
    2264       43411 :             nRet += CPLXMLNodeGetRAMUsageEstimate(psIter, false);
    2265             :         }
    2266             :     }
    2267      105128 :     if (psNode->psChild)
    2268             :     {
    2269       58419 :         nRet += CPLXMLNodeGetRAMUsageEstimate(psNode->psChild, true);
    2270             :     }
    2271      105128 :     return nRet;
    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        3298 : size_t CPLXMLNodeGetRAMUsageEstimate(const CPLXMLNode *psNode)
    2280             : {
    2281        3298 :     return CPLXMLNodeGetRAMUsageEstimate(psNode, true);
    2282             : }
    2283             : 
    2284             : /************************************************************************/
    2285             : /*            CPLXMLTreeCloser::getDocumentElement()                    */
    2286             : /************************************************************************/
    2287             : 
    2288          72 : CPLXMLNode *CPLXMLTreeCloser::getDocumentElement()
    2289             : {
    2290          72 :     CPLXMLNode *doc = get();
    2291             :     // skip the Declaration and assume the next is the root element
    2292         120 :     while (doc != nullptr &&
    2293         120 :            (doc->eType != CXT_Element || doc->pszValue[0] == '?'))
    2294             :     {
    2295          48 :         doc = doc->psNext;
    2296             :     }
    2297          72 :     return doc;
    2298             : }

Generated by: LCOV version 1.14