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 : }
|