VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/xml.cpp@ 76553

Last change on this file since 76553 was 76553, checked in by vboxsync, 5 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.3 KB
Line 
1/* $Id: xml.cpp 76553 2019-01-01 01:45:53Z vboxsync $ */
2/** @file
3 * IPRT - XML Manipulation API.
4 */
5
6/*
7 * Copyright (C) 2007-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/dir.h>
32#include <iprt/file.h>
33#include <iprt/err.h>
34#include <iprt/log.h>
35#include <iprt/param.h>
36#include <iprt/path.h>
37#include <iprt/cpp/lock.h>
38#include <iprt/cpp/xml.h>
39
40#include <libxml/tree.h>
41#include <libxml/parser.h>
42#include <libxml/globals.h>
43#include <libxml/xmlIO.h>
44#include <libxml/xmlsave.h>
45#include <libxml/uri.h>
46
47#include <libxml/xmlschemas.h>
48
49#include <map>
50
51
52/*********************************************************************************************************************************
53* Global Variables *
54*********************************************************************************************************************************/
55/**
56 * Global module initialization structure. This is to wrap non-reentrant bits
57 * of libxml, among other things.
58 *
59 * The constructor and destructor of this structure are used to perform global
60 * module initialization and cleanup. There must be only one global variable of
61 * this structure.
62 */
63static class Global
64{
65public:
66
67 Global()
68 {
69 /* Check the parser version. The docs say it will kill the app if
70 * there is a serious version mismatch, but I couldn't find it in the
71 * source code (it only prints the error/warning message to the console) so
72 * let's leave it as is for informational purposes. */
73 LIBXML_TEST_VERSION
74
75 /* Init libxml */
76 xmlInitParser();
77
78 /* Save the default entity resolver before someone has replaced it */
79 sxml.defaultEntityLoader = xmlGetExternalEntityLoader();
80 }
81
82 ~Global()
83 {
84 /* Shutdown libxml */
85 xmlCleanupParser();
86 }
87
88 struct
89 {
90 xmlExternalEntityLoader defaultEntityLoader;
91
92 /** Used to provide some thread safety missing in libxml2 (see e.g.
93 * XmlTreeBackend::read()) */
94 RTCLockMtx lock;
95 }
96 sxml; /* XXX naming this xml will break with gcc-3.3 */
97} gGlobal;
98
99
100
101namespace xml
102{
103
104////////////////////////////////////////////////////////////////////////////////
105//
106// Exceptions
107//
108////////////////////////////////////////////////////////////////////////////////
109
110LogicError::LogicError(RT_SRC_POS_DECL)
111 : RTCError(NULL)
112{
113 char *msg = NULL;
114 RTStrAPrintf(&msg, "In '%s', '%s' at #%d",
115 pszFunction, pszFile, iLine);
116 setWhat(msg);
117 RTStrFree(msg);
118}
119
120XmlError::XmlError(xmlErrorPtr aErr)
121{
122 if (!aErr)
123 throw EInvalidArg(RT_SRC_POS);
124
125 char *msg = Format(aErr);
126 setWhat(msg);
127 RTStrFree(msg);
128}
129
130/**
131 * Composes a single message for the given error. The caller must free the
132 * returned string using RTStrFree() when no more necessary.
133 */
134/* static */ char *XmlError::Format(xmlErrorPtr aErr)
135{
136 const char *msg = aErr->message ? aErr->message : "<none>";
137 size_t msgLen = strlen(msg);
138 /* strip spaces, trailing EOLs and dot-like char */
139 while (msgLen && strchr(" \n.?!", msg [msgLen - 1]))
140 --msgLen;
141
142 char *finalMsg = NULL;
143 RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
144 msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
145
146 return finalMsg;
147}
148
149EIPRTFailure::EIPRTFailure(int aRC, const char *pcszContext, ...)
150 : RuntimeError(NULL),
151 mRC(aRC)
152{
153 char *pszContext2;
154 va_list args;
155 va_start(args, pcszContext);
156 RTStrAPrintfV(&pszContext2, pcszContext, args);
157 va_end(args);
158 char *newMsg;
159 RTStrAPrintf(&newMsg, "%s: %d (%s)", pszContext2, aRC, RTErrGetShort(aRC));
160 setWhat(newMsg);
161 RTStrFree(newMsg);
162 RTStrFree(pszContext2);
163}
164
165////////////////////////////////////////////////////////////////////////////////
166//
167// File Class
168//
169//////////////////////////////////////////////////////////////////////////////
170
171struct File::Data
172{
173 Data()
174 : handle(NIL_RTFILE), opened(false)
175 { }
176
177 RTCString strFileName;
178 RTFILE handle;
179 bool opened : 1;
180 bool flushOnClose : 1;
181};
182
183File::File(Mode aMode, const char *aFileName, bool aFlushIt /* = false */)
184 : m(new Data())
185{
186 m->strFileName = aFileName;
187 m->flushOnClose = aFlushIt;
188
189 uint32_t flags = 0;
190 const char *pcszMode = "???";
191 switch (aMode)
192 {
193 /** @todo change to RTFILE_O_DENY_WRITE where appropriate. */
194 case Mode_Read:
195 flags = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
196 pcszMode = "reading";
197 break;
198 case Mode_WriteCreate: // fail if file exists
199 flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE;
200 pcszMode = "writing";
201 break;
202 case Mode_Overwrite: // overwrite if file exists
203 flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE;
204 pcszMode = "overwriting";
205 break;
206 case Mode_ReadWrite:
207 flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
208 pcszMode = "reading/writing";
209 break;
210 }
211
212 int vrc = RTFileOpen(&m->handle, aFileName, flags);
213 if (RT_FAILURE(vrc))
214 throw EIPRTFailure(vrc, "Runtime error opening '%s' for %s", aFileName, pcszMode);
215
216 m->opened = true;
217 m->flushOnClose = aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ;
218}
219
220File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */)
221 : m(new Data())
222{
223 if (aHandle == NIL_RTFILE)
224 throw EInvalidArg(RT_SRC_POS);
225
226 m->handle = aHandle;
227
228 if (aFileName)
229 m->strFileName = aFileName;
230
231 m->flushOnClose = aFlushIt;
232
233 setPos(0);
234}
235
236File::~File()
237{
238 if (m->flushOnClose)
239 {
240 RTFileFlush(m->handle);
241 if (!m->strFileName.isEmpty())
242 RTDirFlushParent(m->strFileName.c_str());
243 }
244
245 if (m->opened)
246 RTFileClose(m->handle);
247 delete m;
248}
249
250const char *File::uri() const
251{
252 return m->strFileName.c_str();
253}
254
255uint64_t File::pos() const
256{
257 uint64_t p = 0;
258 int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p);
259 if (RT_SUCCESS(vrc))
260 return p;
261
262 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
263}
264
265void File::setPos(uint64_t aPos)
266{
267 uint64_t p = 0;
268 unsigned method = RTFILE_SEEK_BEGIN;
269 int vrc = VINF_SUCCESS;
270
271 /* check if we overflow int64_t and move to INT64_MAX first */
272 if ((int64_t)aPos < 0)
273 {
274 vrc = RTFileSeek(m->handle, INT64_MAX, method, &p);
275 aPos -= (uint64_t)INT64_MAX;
276 method = RTFILE_SEEK_CURRENT;
277 }
278 /* seek the rest */
279 if (RT_SUCCESS(vrc))
280 vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p);
281 if (RT_SUCCESS(vrc))
282 return;
283
284 throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
285}
286
287int File::read(char *aBuf, int aLen)
288{
289 size_t len = aLen;
290 int vrc = RTFileRead(m->handle, aBuf, len, &len);
291 if (RT_SUCCESS(vrc))
292 return (int)len;
293
294 throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str());
295}
296
297int File::write(const char *aBuf, int aLen)
298{
299 size_t len = aLen;
300 int vrc = RTFileWrite(m->handle, aBuf, len, &len);
301 if (RT_SUCCESS(vrc))
302 return (int)len;
303
304 throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str());
305}
306
307void File::truncate()
308{
309 int vrc = RTFileSetSize(m->handle, pos());
310 if (RT_SUCCESS(vrc))
311 return;
312
313 throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str());
314}
315
316////////////////////////////////////////////////////////////////////////////////
317//
318// MemoryBuf Class
319//
320//////////////////////////////////////////////////////////////////////////////
321
322struct MemoryBuf::Data
323{
324 Data()
325 : buf(NULL), len(0), uri(NULL), pos(0) {}
326
327 const char *buf;
328 size_t len;
329 char *uri;
330
331 size_t pos;
332};
333
334MemoryBuf::MemoryBuf(const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
335 : m(new Data())
336{
337 if (aBuf == NULL)
338 throw EInvalidArg(RT_SRC_POS);
339
340 m->buf = aBuf;
341 m->len = aLen;
342 m->uri = RTStrDup(aURI);
343}
344
345MemoryBuf::~MemoryBuf()
346{
347 RTStrFree(m->uri);
348}
349
350const char *MemoryBuf::uri() const
351{
352 return m->uri;
353}
354
355uint64_t MemoryBuf::pos() const
356{
357 return m->pos;
358}
359
360void MemoryBuf::setPos(uint64_t aPos)
361{
362 size_t off = (size_t)aPos;
363 if ((uint64_t) off != aPos)
364 throw EInvalidArg();
365
366 if (off > m->len)
367 throw EInvalidArg();
368
369 m->pos = off;
370}
371
372int MemoryBuf::read(char *aBuf, int aLen)
373{
374 if (m->pos >= m->len)
375 return 0 /* nothing to read */;
376
377 size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
378 memcpy(aBuf, m->buf + m->pos, len);
379 m->pos += len;
380
381 return (int)len;
382}
383
384////////////////////////////////////////////////////////////////////////////////
385//
386// GlobalLock class
387//
388////////////////////////////////////////////////////////////////////////////////
389
390struct GlobalLock::Data
391{
392 PFNEXTERNALENTITYLOADER pOldLoader;
393 RTCLock lock;
394
395 Data()
396 : pOldLoader(NULL),
397 lock(gGlobal.sxml.lock)
398 {
399 }
400};
401
402GlobalLock::GlobalLock()
403 : m(new Data())
404{
405}
406
407GlobalLock::~GlobalLock()
408{
409 if (m->pOldLoader)
410 xmlSetExternalEntityLoader(m->pOldLoader);
411 delete m;
412 m = NULL;
413}
414
415void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pLoader)
416{
417 m->pOldLoader = xmlGetExternalEntityLoader();
418 xmlSetExternalEntityLoader(pLoader);
419}
420
421// static
422xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI,
423 const char *aID,
424 xmlParserCtxt *aCtxt)
425{
426 return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt);
427}
428
429
430
431////////////////////////////////////////////////////////////////////////////////
432//
433// Node class
434//
435////////////////////////////////////////////////////////////////////////////////
436
437Node::Node(EnumType type,
438 Node *pParent,
439 PRTLISTANCHOR pListAnchor,
440 xmlNode *pLibNode,
441 xmlAttr *pLibAttr)
442 : m_Type(type)
443 , m_pParent(pParent)
444 , m_pLibNode(pLibNode)
445 , m_pLibAttr(pLibAttr)
446 , m_pcszNamespacePrefix(NULL)
447 , m_pcszNamespaceHref(NULL)
448 , m_pcszName(NULL)
449 , m_pParentListAnchor(pListAnchor)
450{
451 RTListInit(&m_listEntry);
452}
453
454Node::~Node()
455{
456}
457
458/**
459 * Returns the name of the node, which is either the element name or
460 * the attribute name. For other node types it probably returns NULL.
461 * @return
462 */
463const char *Node::getName() const
464{
465 return m_pcszName;
466}
467
468/**
469 * Returns the name of the node, which is either the element name or
470 * the attribute name. For other node types it probably returns NULL.
471 * @return
472 */
473const char *Node::getPrefix() const
474{
475 return m_pcszNamespacePrefix;
476}
477
478/**
479 * Returns the XML namespace URI, which is the attribute name. For other node types it probably
480 * returns NULL.
481 * @return
482 */
483const char *Node::getNamespaceURI() const
484{
485 return m_pcszNamespaceHref;
486}
487
488/**
489 * Variant of nameEquals that checks the namespace as well.
490 * @param pcszNamespace
491 * @param pcsz
492 * @return
493 */
494bool Node::nameEqualsNS(const char *pcszNamespace, const char *pcsz) const
495{
496 if (m_pcszName == pcsz)
497 return true;
498 if (m_pcszName == NULL)
499 return false;
500 if (pcsz == NULL)
501 return false;
502 if (strcmp(m_pcszName, pcsz))
503 return false;
504
505 // name matches: then check namespaces as well
506 if (!pcszNamespace)
507 return true;
508 // caller wants namespace:
509 if (!m_pcszNamespacePrefix)
510 // but node has no namespace:
511 return false;
512 return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
513}
514
515/**
516 * Variant of nameEquals that checks the namespace as well.
517 *
518 * @returns true if equal, false if not.
519 * @param pcsz The element name.
520 * @param cchMax The maximum number of character from @a pcsz to
521 * match.
522 * @param pcszNamespace The name space prefix or NULL (default).
523 */
524bool Node::nameEqualsN(const char *pcsz, size_t cchMax, const char *pcszNamespace /* = NULL*/) const
525{
526 /* Match the name. */
527 if (!m_pcszName)
528 return false;
529 if (!pcsz || cchMax == 0)
530 return false;
531 if (strncmp(m_pcszName, pcsz, cchMax))
532 return false;
533 if (strlen(m_pcszName) > cchMax)
534 return false;
535
536 /* Match name space. */
537 if (!pcszNamespace)
538 return true; /* NULL, anything goes. */
539 if (!m_pcszNamespacePrefix)
540 return false; /* Element has no namespace. */
541 return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
542}
543
544/**
545 * Returns the value of a node. If this node is an attribute, returns
546 * the attribute value; if this node is an element, then this returns
547 * the element text content.
548 * @return
549 */
550const char *Node::getValue() const
551{
552 if ( m_pLibAttr
553 && m_pLibAttr->children
554 )
555 // libxml hides attribute values in another node created as a
556 // single child of the attribute node, and it's in the content field
557 return (const char *)m_pLibAttr->children->content;
558
559 if ( m_pLibNode
560 && m_pLibNode->children)
561 return (const char *)m_pLibNode->children->content;
562
563 return NULL;
564}
565
566/**
567 * Copies the value of a node into the given integer variable.
568 * Returns TRUE only if a value was found and was actually an
569 * integer of the given type.
570 * @return
571 */
572bool Node::copyValue(int32_t &i) const
573{
574 const char *pcsz;
575 if ( ((pcsz = getValue()))
576 && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
577 )
578 return true;
579
580 return false;
581}
582
583/**
584 * Copies the value of a node into the given integer variable.
585 * Returns TRUE only if a value was found and was actually an
586 * integer of the given type.
587 * @return
588 */
589bool Node::copyValue(uint32_t &i) const
590{
591 const char *pcsz;
592 if ( ((pcsz = getValue()))
593 && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
594 )
595 return true;
596
597 return false;
598}
599
600/**
601 * Copies the value of a node into the given integer variable.
602 * Returns TRUE only if a value was found and was actually an
603 * integer of the given type.
604 * @return
605 */
606bool Node::copyValue(int64_t &i) const
607{
608 const char *pcsz;
609 if ( ((pcsz = getValue()))
610 && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
611 )
612 return true;
613
614 return false;
615}
616
617/**
618 * Copies the value of a node into the given integer variable.
619 * Returns TRUE only if a value was found and was actually an
620 * integer of the given type.
621 * @return
622 */
623bool Node::copyValue(uint64_t &i) const
624{
625 const char *pcsz;
626 if ( ((pcsz = getValue()))
627 && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
628 )
629 return true;
630
631 return false;
632}
633
634/**
635 * Returns the line number of the current node in the source XML file.
636 * Useful for error messages.
637 * @return
638 */
639int Node::getLineNumber() const
640{
641 if (m_pLibAttr)
642 return m_pParent->m_pLibNode->line;
643
644 return m_pLibNode->line;
645}
646
647/**
648 * Private element constructor.
649 *
650 * @param pElmRoot Pointer to the root element.
651 * @param pParent Pointer to the parent element (always an ElementNode,
652 * despite the type). NULL for the root node.
653 * @param pListAnchor Pointer to the m_children member of the parent. NULL
654 * for the root node.
655 * @param pLibNode Pointer to the libxml2 node structure.
656 */
657ElementNode::ElementNode(const ElementNode *pElmRoot,
658 Node *pParent,
659 PRTLISTANCHOR pListAnchor,
660 xmlNode *pLibNode)
661 : Node(IsElement,
662 pParent,
663 pListAnchor,
664 pLibNode,
665 NULL)
666{
667 m_pElmRoot = pElmRoot ? pElmRoot : this; // If NULL is passed, then this is the root element.
668 m_pcszName = (const char *)pLibNode->name;
669
670 if (pLibNode->ns)
671 {
672 m_pcszNamespacePrefix = (const char *)m_pLibNode->ns->prefix;
673 m_pcszNamespaceHref = (const char *)m_pLibNode->ns->href;
674 }
675
676 RTListInit(&m_children);
677 RTListInit(&m_attributes);
678}
679
680ElementNode::~ElementNode()
681{
682 Node *pCur, *pNext;
683 RTListForEachSafeCpp(&m_children, pCur, pNext, Node, m_listEntry)
684 {
685 delete pCur;
686 }
687 RTListInit(&m_children);
688
689 RTListForEachSafeCpp(&m_attributes, pCur, pNext, Node, m_listEntry)
690 {
691 delete pCur;
692 }
693 RTListInit(&m_attributes);
694}
695
696
697/**
698 * Gets the next tree element in a full tree enumeration.
699 *
700 * @returns Pointer to the next element in the tree, NULL if we're done.
701 * @param pElmRoot The root of the tree we're enumerating. NULL if
702 * it's the entire tree.
703 */
704ElementNode const *ElementNode::getNextTreeElement(ElementNode const *pElmRoot /*= NULL */) const
705{
706 /*
707 * Consider children first.
708 */
709 ElementNode const *pChild = getFirstChildElement();
710 if (pChild)
711 return pChild;
712
713 /*
714 * Then siblings, aunts and uncles.
715 */
716 ElementNode const *pCur = this;
717 do
718 {
719 ElementNode const *pSibling = pCur->getNextSibilingElement();
720 if (pSibling != NULL)
721 return pSibling;
722
723 pCur = static_cast<const xml::ElementNode *>(pCur->m_pParent);
724 Assert(pCur || pCur == pElmRoot);
725 } while (pCur != pElmRoot);
726
727 return NULL;
728}
729
730
731/**
732 * Private implementation.
733 *
734 * @param pElmRoot The root element.
735 */
736/*static*/ void ElementNode::buildChildren(ElementNode *pElmRoot) // protected
737{
738 for (ElementNode *pCur = pElmRoot; pCur; pCur = pCur->getNextTreeElement(pElmRoot))
739 {
740 /*
741 * Go thru this element's attributes creating AttributeNodes for them.
742 */
743 for (xmlAttr *pLibAttr = pCur->m_pLibNode->properties; pLibAttr; pLibAttr = pLibAttr->next)
744 {
745 AttributeNode *pNew = new AttributeNode(pElmRoot, pCur, &pCur->m_attributes, pLibAttr);
746 RTListAppend(&pCur->m_attributes, &pNew->m_listEntry);
747 }
748
749 /*
750 * Go thru this element's child elements (element and text nodes).
751 */
752 for (xmlNodePtr pLibNode = pCur->m_pLibNode->children; pLibNode; pLibNode = pLibNode->next)
753 {
754 Node *pNew;
755 if (pLibNode->type == XML_ELEMENT_NODE)
756 pNew = new ElementNode(pElmRoot, pCur, &pCur->m_children, pLibNode);
757 else if (pLibNode->type == XML_TEXT_NODE)
758 pNew = new ContentNode(pCur, &pCur->m_children, pLibNode);
759 else
760 continue;
761 RTListAppend(&pCur->m_children, &pNew->m_listEntry);
762 }
763 }
764}
765
766
767/**
768 * Builds a list of direct child elements of the current element that
769 * match the given string; if pcszMatch is NULL, all direct child
770 * elements are returned.
771 * @param children out: list of nodes to which children will be appended.
772 * @param pcszMatch in: match string, or NULL to return all children.
773 * @return Number of items appended to the list (0 if none).
774 */
775int ElementNode::getChildElements(ElementNodesList &children,
776 const char *pcszMatch /*= NULL*/)
777 const
778{
779 int i = 0;
780 Node *p;
781 RTListForEachCpp(&m_children, p, Node, m_listEntry)
782 {
783 // export this child node if ...
784 if (p->isElement())
785 if ( !pcszMatch // ... the caller wants all nodes or ...
786 || !strcmp(pcszMatch, p->getName()) // ... the element name matches.
787 )
788 {
789 children.push_back(static_cast<ElementNode *>(p));
790 ++i;
791 }
792 }
793 return i;
794}
795
796/**
797 * Returns the first child element whose name matches pcszMatch.
798 *
799 * @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace.
800 * @param pcszMatch Element name to match.
801 * @return
802 */
803const ElementNode *ElementNode::findChildElementNS(const char *pcszNamespace, const char *pcszMatch) const
804{
805 Node *p;
806 RTListForEachCpp(&m_children, p, Node, m_listEntry)
807 {
808 if (p->isElement())
809 {
810 ElementNode *pelm = static_cast<ElementNode*>(p);
811 if (pelm->nameEqualsNS(pcszNamespace, pcszMatch))
812 return pelm;
813 }
814 }
815 return NULL;
816}
817
818/**
819 * Returns the first child element whose "id" attribute matches pcszId.
820 * @param pcszId identifier to look for.
821 * @return child element or NULL if not found.
822 */
823const ElementNode *ElementNode::findChildElementFromId(const char *pcszId) const
824{
825 const Node *p;
826 RTListForEachCpp(&m_children, p, Node, m_listEntry)
827 {
828 if (p->isElement())
829 {
830 const ElementNode *pElm = static_cast<const ElementNode *>(p);
831 const AttributeNode *pAttr = pElm->findAttribute("id");
832 if (pAttr && !strcmp(pAttr->getValue(), pcszId))
833 return pElm;
834 }
835 }
836 return NULL;
837}
838
839
840const ElementNode *ElementNode::findChildElementP(const char *pcszPath, const char *pcszNamespace /*= NULL*/) const
841{
842 size_t cchThis = strchr(pcszPath, '/') - pcszPath;
843 if (cchThis == (size_t)((const char *)0 - pcszPath))
844 return findChildElementNS(pcszNamespace, pcszPath);
845
846 /** @todo Can be done without recursion as we have both sibling lists and parent
847 * pointers in this variant. */
848 const Node *p;
849 RTListForEachCpp(&m_children, p, Node, m_listEntry)
850 {
851 if (p->isElement())
852 {
853 const ElementNode *pElm = static_cast<const ElementNode *>(p);
854 if (pElm->nameEqualsN(pcszPath, cchThis, pcszNamespace))
855 {
856 pElm = findChildElementP(pcszPath + cchThis, pcszNamespace);
857 if (pElm)
858 return pElm;
859 }
860 }
861 }
862
863 return NULL;
864}
865
866const ElementNode *ElementNode::getFirstChildElement() const
867{
868 const Node *p;
869 RTListForEachCpp(&m_children, p, Node, m_listEntry)
870 {
871 if (p->isElement())
872 return static_cast<const ElementNode *>(p);
873 }
874 return NULL;
875}
876
877const ElementNode *ElementNode::getLastChildElement() const
878{
879 const Node *p;
880 RTListForEachReverseCpp(&m_children, p, Node, m_listEntry)
881 {
882 if (p->isElement())
883 return static_cast<const ElementNode *>(p);
884 }
885 return NULL;
886}
887
888const ElementNode *ElementNode::getPrevSibilingElement() const
889{
890 if (!m_pParent)
891 return NULL;
892 const Node *pSibling = this;
893 for (;;)
894 {
895 pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
896 if (!pSibling)
897 return NULL;
898 if (pSibling->isElement())
899 return static_cast<const ElementNode *>(pSibling);
900 }
901}
902
903const ElementNode *ElementNode::getNextSibilingElement() const
904{
905 if (!m_pParent)
906 return NULL;
907 const Node *pSibling = this;
908 for (;;)
909 {
910 pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
911 if (!pSibling)
912 return NULL;
913 if (pSibling->isElement())
914 return static_cast<const ElementNode *>(pSibling);
915 }
916}
917
918const ElementNode *ElementNode::findPrevSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
919{
920 if (!m_pParent)
921 return NULL;
922 const Node *pSibling = this;
923 for (;;)
924 {
925 pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
926 if (!pSibling)
927 return NULL;
928 if (pSibling->isElement())
929 {
930 const ElementNode *pElem = static_cast<const ElementNode *>(pSibling);
931 if (pElem->nameEqualsNS(pcszNamespace, pcszMatch))
932 return pElem;
933 }
934 }
935}
936
937const ElementNode *ElementNode::findNextSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
938{
939 if (!m_pParent)
940 return NULL;
941 const Node *pSibling = this;
942 for (;;)
943 {
944 pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
945 if (!pSibling)
946 return NULL;
947 if (pSibling->isElement())
948 {
949 const ElementNode *pElem = static_cast<const ElementNode *>(pSibling);
950 if (pElem->nameEqualsNS(pcszNamespace, pcszMatch))
951 return pElem;
952 }
953 }
954}
955
956
957/**
958 * Looks up the given attribute node in this element's attribute map.
959 *
960 * @param pcszMatch The name of the attribute to find.
961 * @param pcszNamespace The attribute name space prefix or NULL.
962 */
963const AttributeNode *ElementNode::findAttribute(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
964{
965 AttributeNode *p;
966 RTListForEachCpp(&m_attributes, p, AttributeNode, m_listEntry)
967 {
968 if (p->nameEqualsNS(pcszNamespace, pcszMatch))
969 return p;
970 }
971 return NULL;
972}
973
974/**
975 * Convenience method which attempts to find the attribute with the given
976 * name and returns its value as a string.
977 *
978 * @param pcszMatch Name of attribute to find.
979 * @param ppcsz Where to return the attribute.
980 * @param pcszNamespace The attribute name space prefix or NULL.
981 * @returns Boolean success indicator.
982 */
983bool ElementNode::getAttributeValue(const char *pcszMatch, const char **ppcsz, const char *pcszNamespace /*= NULL*/) const
984{
985 const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
986 if (pAttr)
987 {
988 *ppcsz = pAttr->getValue();
989 return true;
990 }
991 return false;
992}
993
994/**
995 * Convenience method which attempts to find the attribute with the given
996 * name and returns its value as a string.
997 *
998 * @param pcszMatch Name of attribute to find.
999 * @param pStr Pointer to the string object that should receive the
1000 * attribute value.
1001 * @param pcszNamespace The attribute name space prefix or NULL.
1002 * @returns Boolean success indicator.
1003 *
1004 * @throws Whatever the string class may throw on assignment.
1005 */
1006bool ElementNode::getAttributeValue(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const
1007{
1008 const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
1009 if (pAttr)
1010 {
1011 *pStr = pAttr->getValue();
1012 return true;
1013 }
1014
1015 return false;
1016}
1017
1018/**
1019 * Like getAttributeValue (ministring variant), but makes sure that all backslashes
1020 * are converted to forward slashes.
1021 *
1022 * @param pcszMatch Name of attribute to find.
1023 * @param pStr Pointer to the string object that should
1024 * receive the attribute path value.
1025 * @param pcszNamespace The attribute name space prefix or NULL.
1026 * @returns Boolean success indicator.
1027 */
1028bool ElementNode::getAttributeValuePath(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const
1029{
1030 if (getAttributeValue(pcszMatch, pStr, pcszNamespace))
1031 {
1032 pStr->findReplace('\\', '/');
1033 return true;
1034 }
1035
1036 return false;
1037}
1038
1039/**
1040 * Convenience method which attempts to find the attribute with the given
1041 * name and returns its value as a signed 32-bit integer.
1042 *
1043 * @param pcszMatch Name of attribute to find.
1044 * @param piValue Where to return the value.
1045 * @param pcszNamespace The attribute name space prefix or NULL.
1046 * @returns Boolean success indicator.
1047 */
1048bool ElementNode::getAttributeValue(const char *pcszMatch, int32_t *piValue, const char *pcszNamespace /*= NULL*/) const
1049{
1050 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1051 if (pcsz)
1052 {
1053 int rc = RTStrToInt32Ex(pcsz, NULL, 0, piValue);
1054 if (rc == VINF_SUCCESS)
1055 return true;
1056 }
1057 return false;
1058}
1059
1060/**
1061 * Convenience method which attempts to find the attribute with the given
1062 * name and returns its value as an unsigned 32-bit integer.
1063 *
1064 * @param pcszMatch Name of attribute to find.
1065 * @param puValue Where to return the value.
1066 * @param pcszNamespace The attribute name space prefix or NULL.
1067 * @returns Boolean success indicator.
1068 */
1069bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t *puValue, const char *pcszNamespace /*= NULL*/) const
1070{
1071 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1072 if (pcsz)
1073 {
1074 int rc = RTStrToUInt32Ex(pcsz, NULL, 0, puValue);
1075 if (rc == VINF_SUCCESS)
1076 return true;
1077 }
1078 return false;
1079}
1080
1081/**
1082 * Convenience method which attempts to find the attribute with the given
1083 * name and returns its value as a signed 64-bit integer.
1084 *
1085 * @param pcszMatch Name of attribute to find.
1086 * @param piValue Where to return the value.
1087 * @param pcszNamespace The attribute name space prefix or NULL.
1088 * @returns Boolean success indicator.
1089 */
1090bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t *piValue, const char *pcszNamespace /*= NULL*/) const
1091{
1092 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1093 if (pcsz)
1094 {
1095 int rc = RTStrToInt64Ex(pcsz, NULL, 0, piValue);
1096 if (rc == VINF_SUCCESS)
1097 return true;
1098 }
1099 return false;
1100}
1101
1102/**
1103 * Convenience method which attempts to find the attribute with the given
1104 * name and returns its value as an unsigned 64-bit integer.
1105 *
1106 * @param pcszMatch Name of attribute to find.
1107 * @param puValue Where to return the value.
1108 * @param pcszNamespace The attribute name space prefix or NULL.
1109 * @returns Boolean success indicator.
1110 */
1111bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t *puValue, const char *pcszNamespace /*= NULL*/) const
1112{
1113 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1114 if (pcsz)
1115 {
1116 int rc = RTStrToUInt64Ex(pcsz, NULL, 0, puValue);
1117 if (rc == VINF_SUCCESS)
1118 return true;
1119 }
1120 return false;
1121}
1122
1123/**
1124 * Convenience method which attempts to find the attribute with the given
1125 * name and returns its value as a boolean. This accepts "true", "false",
1126 * "yes", "no", "1" or "0" as valid values.
1127 *
1128 * @param pcszMatch Name of attribute to find.
1129 * @param pfValue Where to return the value.
1130 * @param pcszNamespace The attribute name space prefix or NULL.
1131 * @returns Boolean success indicator.
1132 */
1133bool ElementNode::getAttributeValue(const char *pcszMatch, bool *pfValue, const char *pcszNamespace /*= NULL*/) const
1134{
1135 const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
1136 if (pcsz)
1137 {
1138 if ( !strcmp(pcsz, "true")
1139 || !strcmp(pcsz, "yes")
1140 || !strcmp(pcsz, "1")
1141 )
1142 {
1143 *pfValue = true;
1144 return true;
1145 }
1146 if ( !strcmp(pcsz, "false")
1147 || !strcmp(pcsz, "no")
1148 || !strcmp(pcsz, "0")
1149 )
1150 {
1151 *pfValue = false;
1152 return true;
1153 }
1154 }
1155
1156 return false;
1157}
1158
1159
1160bool ElementNode::getElementValue(int32_t *piValue) const
1161{
1162 const char *pszValue = getValue();
1163 if (pszValue)
1164 {
1165 int rc = RTStrToInt32Ex(pszValue, NULL, 0, piValue);
1166 if (rc == VINF_SUCCESS)
1167 return true;
1168 }
1169 return false;
1170}
1171
1172bool ElementNode::getElementValue(uint32_t *puValue) const
1173{
1174 const char *pszValue = getValue();
1175 if (pszValue)
1176 {
1177 int rc = RTStrToUInt32Ex(pszValue, NULL, 0, puValue);
1178 if (rc == VINF_SUCCESS)
1179 return true;
1180 }
1181 return false;
1182}
1183
1184bool ElementNode::getElementValue(int64_t *piValue) const
1185{
1186 const char *pszValue = getValue();
1187 if (pszValue)
1188 {
1189 int rc = RTStrToInt64Ex(pszValue, NULL, 0, piValue);
1190 if (rc == VINF_SUCCESS)
1191 return true;
1192 }
1193 return false;
1194}
1195
1196bool ElementNode::getElementValue(uint64_t *puValue) const
1197{
1198 const char *pszValue = getValue();
1199 if (pszValue)
1200 {
1201 int rc = RTStrToUInt64Ex(pszValue, NULL, 0, puValue);
1202 if (rc == VINF_SUCCESS)
1203 return true;
1204 }
1205 return false;
1206}
1207
1208bool ElementNode::getElementValue(bool *pfValue) const
1209{
1210 const char *pszValue = getValue();
1211 if (pszValue)
1212 {
1213 if ( !strcmp(pszValue, "true")
1214 || !strcmp(pszValue, "yes")
1215 || !strcmp(pszValue, "1")
1216 )
1217 {
1218 *pfValue = true;
1219 return true;
1220 }
1221 if ( !strcmp(pszValue, "false")
1222 || !strcmp(pszValue, "no")
1223 || !strcmp(pszValue, "0")
1224 )
1225 {
1226 *pfValue = true;
1227 return true;
1228 }
1229 }
1230 return false;
1231}
1232
1233
1234/**
1235 * Creates a new child element node and appends it to the list
1236 * of children in "this".
1237 *
1238 * @param pcszElementName
1239 * @return
1240 */
1241ElementNode *ElementNode::createChild(const char *pcszElementName)
1242{
1243 // we must be an element, not an attribute
1244 if (!m_pLibNode)
1245 throw ENodeIsNotElement(RT_SRC_POS);
1246
1247 // libxml side: create new node
1248 xmlNode *pLibNode;
1249 if (!(pLibNode = xmlNewNode(NULL, // namespace
1250 (const xmlChar*)pcszElementName)))
1251 throw std::bad_alloc();
1252 xmlAddChild(m_pLibNode, pLibNode);
1253
1254 // now wrap this in C++
1255 ElementNode *p = new ElementNode(m_pElmRoot, this, &m_children, pLibNode);
1256 RTListAppend(&m_children, &p->m_listEntry);
1257
1258 return p;
1259}
1260
1261
1262/**
1263 * Creates a content node and appends it to the list of children
1264 * in "this".
1265 *
1266 * @param pcszContent
1267 * @return
1268 */
1269ContentNode *ElementNode::addContent(const char *pcszContent)
1270{
1271 // libxml side: create new node
1272 xmlNode *pLibNode = xmlNewText((const xmlChar*)pcszContent);
1273 if (!pLibNode)
1274 throw std::bad_alloc();
1275 xmlAddChild(m_pLibNode, pLibNode);
1276
1277 // now wrap this in C++
1278 ContentNode *p = new ContentNode(this, &m_children, pLibNode);
1279 RTListAppend(&m_children, &p->m_listEntry);
1280
1281 return p;
1282}
1283
1284/**
1285 * Changes the contents of node and appends it to the list of
1286 * children
1287 *
1288 * @param pcszContent
1289 * @return
1290 */
1291ContentNode *ElementNode::setContent(const char *pcszContent)
1292{
1293// 1. Update content
1294 xmlNodeSetContent(m_pLibNode, (const xmlChar*)pcszContent);
1295
1296// 2. Remove Content node from the list
1297 /* Check that the order is right. */
1298 xml::Node * pNode;
1299 RTListForEachCpp(&m_children, pNode, xml::Node, m_listEntry)
1300 {
1301 bool fLast = RTListNodeIsLast(&m_children, &pNode->m_listEntry);
1302
1303 if (pNode->isContent())
1304 {
1305 RTListNodeRemove(&pNode->m_listEntry);
1306 }
1307
1308 if (fLast)
1309 break;
1310 }
1311
1312// 3. Create a new node and append to the list
1313 // now wrap this in C++
1314 ContentNode *pCNode = new ContentNode(this, &m_children, m_pLibNode);
1315 RTListAppend(&m_children, &pCNode->m_listEntry);
1316
1317 return pCNode;
1318}
1319
1320/**
1321 * Sets the given attribute; overloaded version for const char *.
1322 *
1323 * If an attribute with the given name exists, it is overwritten,
1324 * otherwise a new attribute is created. Returns the attribute node
1325 * that was either created or changed.
1326 *
1327 * @param pcszName The attribute name.
1328 * @param pcszValue The attribute value.
1329 * @return Pointer to the attribute node that was created or modified.
1330 */
1331AttributeNode *ElementNode::setAttribute(const char *pcszName, const char *pcszValue)
1332{
1333 /*
1334 * Do we already have an attribute and should we just update it?
1335 */
1336 AttributeNode *pAttr;
1337 RTListForEachCpp(&m_attributes, pAttr, AttributeNode, m_listEntry)
1338 {
1339 if (pAttr->nameEquals(pcszName))
1340 {
1341 /* Overwrite existing libxml attribute node ... */
1342 xmlAttrPtr pLibAttr = xmlSetProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue);
1343
1344 /* ... and update our C++ wrapper in case the attrib pointer changed. */
1345 pAttr->m_pLibAttr = pLibAttr;
1346 return pAttr;
1347 }
1348 }
1349
1350 /*
1351 * No existing attribute, create a new one.
1352 */
1353 /* libxml side: xmlNewProp creates an attribute. */
1354 xmlAttr *pLibAttr = xmlNewProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue);
1355
1356 /* C++ side: Create an attribute node around it. */
1357 pAttr = new AttributeNode(m_pElmRoot, this, &m_attributes, pLibAttr);
1358 RTListAppend(&m_attributes, &pAttr->m_listEntry);
1359
1360 return pAttr;
1361}
1362
1363/**
1364 * Like setAttribute (ministring variant), but replaces all backslashes with forward slashes
1365 * before calling that one.
1366 * @param pcszName
1367 * @param strValue
1368 * @return
1369 */
1370AttributeNode* ElementNode::setAttributePath(const char *pcszName, const RTCString &strValue)
1371{
1372 RTCString strTemp(strValue);
1373 strTemp.findReplace('\\', '/');
1374 return setAttribute(pcszName, strTemp.c_str());
1375}
1376
1377/**
1378 * Sets the given attribute; overloaded version for int32_t.
1379 *
1380 * If an attribute with the given name exists, it is overwritten,
1381 * otherwise a new attribute is created. Returns the attribute node
1382 * that was either created or changed.
1383 *
1384 * @param pcszName
1385 * @param i
1386 * @return
1387 */
1388AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i)
1389{
1390 char szValue[12]; // negative sign + 10 digits + \0
1391 RTStrPrintf(szValue, sizeof(szValue), "%RI32", i);
1392 AttributeNode *p = setAttribute(pcszName, szValue);
1393 return p;
1394}
1395
1396/**
1397 * Sets the given attribute; overloaded version for uint32_t.
1398 *
1399 * If an attribute with the given name exists, it is overwritten,
1400 * otherwise a new attribute is created. Returns the attribute node
1401 * that was either created or changed.
1402 *
1403 * @param pcszName
1404 * @param u
1405 * @return
1406 */
1407AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t u)
1408{
1409 char szValue[11]; // 10 digits + \0
1410 RTStrPrintf(szValue, sizeof(szValue), "%RU32", u);
1411 AttributeNode *p = setAttribute(pcszName, szValue);
1412 return p;
1413}
1414
1415/**
1416 * Sets the given attribute; overloaded version for int64_t.
1417 *
1418 * If an attribute with the given name exists, it is overwritten,
1419 * otherwise a new attribute is created. Returns the attribute node
1420 * that was either created or changed.
1421 *
1422 * @param pcszName
1423 * @param i
1424 * @return
1425 */
1426AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i)
1427{
1428 char szValue[21]; // negative sign + 19 digits + \0
1429 RTStrPrintf(szValue, sizeof(szValue), "%RI64", i);
1430 AttributeNode *p = setAttribute(pcszName, szValue);
1431 return p;
1432}
1433
1434/**
1435 * Sets the given attribute; overloaded version for uint64_t.
1436 *
1437 * If an attribute with the given name exists, it is overwritten,
1438 * otherwise a new attribute is created. Returns the attribute node
1439 * that was either created or changed.
1440 *
1441 * @param pcszName
1442 * @param u
1443 * @return
1444 */
1445AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t u)
1446{
1447 char szValue[21]; // 20 digits + \0
1448 RTStrPrintf(szValue, sizeof(szValue), "%RU64", u);
1449 AttributeNode *p = setAttribute(pcszName, szValue);
1450 return p;
1451}
1452
1453/**
1454 * Sets the given attribute to the given uint32_t, outputs a hexadecimal string.
1455 *
1456 * If an attribute with the given name exists, it is overwritten,
1457 * otherwise a new attribute is created. Returns the attribute node
1458 * that was either created or changed.
1459 *
1460 * @param pcszName
1461 * @param u
1462 * @return
1463 */
1464AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t u)
1465{
1466 char szValue[11]; // "0x" + 8 digits + \0
1467 RTStrPrintf(szValue, sizeof(szValue), "0x%RX32", u);
1468 AttributeNode *p = setAttribute(pcszName, szValue);
1469 return p;
1470}
1471
1472/**
1473 * Sets the given attribute; overloaded version for bool.
1474 *
1475 * If an attribute with the given name exists, it is overwritten,
1476 * otherwise a new attribute is created. Returns the attribute node
1477 * that was either created or changed.
1478 *
1479 * @param pcszName The attribute name.
1480 * @param f The attribute value.
1481 * @return
1482 */
1483AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f)
1484{
1485 return setAttribute(pcszName, (f) ? "true" : "false");
1486}
1487
1488/**
1489 * Private constructor for a new attribute node.
1490 *
1491 * @param pElmRoot Pointer to the root element. Needed for getting the
1492 * default name space.
1493 * @param pParent Pointer to the parent element (always an ElementNode,
1494 * despite the type). NULL for the root node.
1495 * @param pListAnchor Pointer to the m_children member of the parent. NULL
1496 * for the root node.
1497 * @param pLibAttr Pointer to the libxml2 attribute structure.
1498 */
1499AttributeNode::AttributeNode(const ElementNode *pElmRoot,
1500 Node *pParent,
1501 PRTLISTANCHOR pListAnchor,
1502 xmlAttr *pLibAttr)
1503 : Node(IsAttribute,
1504 pParent,
1505 pListAnchor,
1506 NULL,
1507 pLibAttr)
1508{
1509 m_pcszName = (const char *)pLibAttr->name;
1510 RT_NOREF_PV(pElmRoot);
1511
1512 if ( pLibAttr->ns
1513 && pLibAttr->ns->prefix)
1514 {
1515 m_pcszNamespacePrefix = (const char *)pLibAttr->ns->prefix;
1516 m_pcszNamespaceHref = (const char *)pLibAttr->ns->href;
1517 }
1518}
1519
1520ContentNode::ContentNode(Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode)
1521 : Node(IsContent,
1522 pParent,
1523 pListAnchor,
1524 pLibNode,
1525 NULL)
1526{
1527}
1528
1529/*
1530 * NodesLoop
1531 *
1532 */
1533
1534struct NodesLoop::Data
1535{
1536 ElementNodesList listElements;
1537 ElementNodesList::const_iterator it;
1538};
1539
1540NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
1541{
1542 m = new Data;
1543 node.getChildElements(m->listElements, pcszMatch);
1544 m->it = m->listElements.begin();
1545}
1546
1547NodesLoop::~NodesLoop()
1548{
1549 delete m;
1550}
1551
1552
1553/**
1554 * Handy convenience helper for looping over all child elements. Create an
1555 * instance of NodesLoop on the stack and call this method until it returns
1556 * NULL, like this:
1557 * <code>
1558 * xml::ElementNode node; // should point to an element
1559 * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
1560 * const xml::ElementNode *pChild = NULL;
1561 * while (pChild = loop.forAllNodes())
1562 * ...;
1563 * </code>
1564 * @return
1565 */
1566const ElementNode* NodesLoop::forAllNodes() const
1567{
1568 const ElementNode *pNode = NULL;
1569
1570 if (m->it != m->listElements.end())
1571 {
1572 pNode = *(m->it);
1573 ++(m->it);
1574 }
1575
1576 return pNode;
1577}
1578
1579////////////////////////////////////////////////////////////////////////////////
1580//
1581// Document class
1582//
1583////////////////////////////////////////////////////////////////////////////////
1584
1585struct Document::Data
1586{
1587 xmlDocPtr plibDocument;
1588 ElementNode *pRootElement;
1589 ElementNode *pComment;
1590
1591 Data()
1592 {
1593 plibDocument = NULL;
1594 pRootElement = NULL;
1595 pComment = NULL;
1596 }
1597
1598 ~Data()
1599 {
1600 reset();
1601 }
1602
1603 void reset()
1604 {
1605 if (plibDocument)
1606 {
1607 xmlFreeDoc(plibDocument);
1608 plibDocument = NULL;
1609 }
1610 if (pRootElement)
1611 {
1612 delete pRootElement;
1613 pRootElement = NULL;
1614 }
1615 if (pComment)
1616 {
1617 delete pComment;
1618 pComment = NULL;
1619 }
1620 }
1621
1622 void copyFrom(const Document::Data *p)
1623 {
1624 if (p->plibDocument)
1625 {
1626 plibDocument = xmlCopyDoc(p->plibDocument,
1627 1); // recursive == copy all
1628 }
1629 }
1630};
1631
1632Document::Document()
1633 : m(new Data)
1634{
1635}
1636
1637Document::Document(const Document &x)
1638 : m(new Data)
1639{
1640 m->copyFrom(x.m);
1641}
1642
1643Document& Document::operator=(const Document &x)
1644{
1645 m->reset();
1646 m->copyFrom(x.m);
1647 return *this;
1648}
1649
1650Document::~Document()
1651{
1652 delete m;
1653}
1654
1655/**
1656 * private method to refresh all internal structures after the internal pDocument
1657 * has changed. Called from XmlFileParser::read(). m->reset() must have been
1658 * called before to make sure all members except the internal pDocument are clean.
1659 */
1660void Document::refreshInternals() // private
1661{
1662 m->pRootElement = new ElementNode(NULL, NULL, NULL, xmlDocGetRootElement(m->plibDocument));
1663
1664 ElementNode::buildChildren(m->pRootElement);
1665}
1666
1667/**
1668 * Returns the root element of the document, or NULL if the document is empty.
1669 * Const variant.
1670 * @return
1671 */
1672const ElementNode *Document::getRootElement() const
1673{
1674 return m->pRootElement;
1675}
1676
1677/**
1678 * Returns the root element of the document, or NULL if the document is empty.
1679 * Non-const variant.
1680 * @return
1681 */
1682ElementNode *Document::getRootElement()
1683{
1684 return m->pRootElement;
1685}
1686
1687/**
1688 * Creates a new element node and sets it as the root element.
1689 *
1690 * This will only work if the document is empty; otherwise EDocumentNotEmpty is
1691 * thrown.
1692 */
1693ElementNode *Document::createRootElement(const char *pcszRootElementName,
1694 const char *pcszComment /* = NULL */)
1695{
1696 if (m->pRootElement || m->plibDocument)
1697 throw EDocumentNotEmpty(RT_SRC_POS);
1698
1699 // libxml side: create document, create root node
1700 m->plibDocument = xmlNewDoc((const xmlChar *)"1.0");
1701 xmlNode *plibRootNode = xmlNewNode(NULL /*namespace*/ , (const xmlChar *)pcszRootElementName);
1702 if (!plibRootNode)
1703 throw std::bad_alloc();
1704 xmlDocSetRootElement(m->plibDocument, plibRootNode);
1705
1706 // now wrap this in C++
1707 m->pRootElement = new ElementNode(NULL, NULL, NULL, plibRootNode);
1708
1709 // add document global comment if specified
1710 if (pcszComment != NULL)
1711 {
1712 xmlNode *pComment = xmlNewDocComment(m->plibDocument, (const xmlChar *)pcszComment);
1713 if (!pComment)
1714 throw std::bad_alloc();
1715 xmlAddPrevSibling(plibRootNode, pComment);
1716
1717 // now wrap this in C++
1718 m->pComment = new ElementNode(NULL, NULL, NULL, pComment);
1719 }
1720
1721 return m->pRootElement;
1722}
1723
1724////////////////////////////////////////////////////////////////////////////////
1725//
1726// XmlParserBase class
1727//
1728////////////////////////////////////////////////////////////////////////////////
1729
1730static void xmlParserBaseGenericError(void *pCtx, const char *pszMsg, ...)
1731{
1732 NOREF(pCtx);
1733 va_list args;
1734 va_start(args, pszMsg);
1735 RTLogRelPrintfV(pszMsg, args);
1736 va_end(args);
1737}
1738
1739static void xmlParserBaseStructuredError(void *pCtx, xmlErrorPtr error)
1740{
1741 NOREF(pCtx);
1742 /* we expect that there is always a trailing NL */
1743 LogRel(("XML error at '%s' line %d: %s", error->file, error->line, error->message));
1744}
1745
1746XmlParserBase::XmlParserBase()
1747{
1748 m_ctxt = xmlNewParserCtxt();
1749 if (m_ctxt == NULL)
1750 throw std::bad_alloc();
1751 /* per-thread so it must be here */
1752 xmlSetGenericErrorFunc(NULL, xmlParserBaseGenericError);
1753 xmlSetStructuredErrorFunc(NULL, xmlParserBaseStructuredError);
1754}
1755
1756XmlParserBase::~XmlParserBase()
1757{
1758 xmlSetStructuredErrorFunc(NULL, NULL);
1759 xmlSetGenericErrorFunc(NULL, NULL);
1760 xmlFreeParserCtxt (m_ctxt);
1761 m_ctxt = NULL;
1762}
1763
1764////////////////////////////////////////////////////////////////////////////////
1765//
1766// XmlMemParser class
1767//
1768////////////////////////////////////////////////////////////////////////////////
1769
1770XmlMemParser::XmlMemParser()
1771 : XmlParserBase()
1772{
1773}
1774
1775XmlMemParser::~XmlMemParser()
1776{
1777}
1778
1779/**
1780 * Parse the given buffer and fills the given Document object with its contents.
1781 * Throws XmlError on parsing errors.
1782 *
1783 * The document that is passed in will be reset before being filled if not empty.
1784 *
1785 * @param pvBuf Memory buffer to parse.
1786 * @param cbSize Size of the memory buffer.
1787 * @param strFilename Refernece to the name of the file we're parsing.
1788 * @param doc Reference to the output document. This will be reset
1789 * and filled with data according to file contents.
1790 */
1791void XmlMemParser::read(const void *pvBuf, size_t cbSize,
1792 const RTCString &strFilename,
1793 Document &doc)
1794{
1795 GlobalLock lock;
1796// global.setExternalEntityLoader(ExternalEntityLoader);
1797
1798 const char *pcszFilename = strFilename.c_str();
1799
1800 doc.m->reset();
1801 const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
1802 | XML_PARSE_NONET /* forbit any network access */
1803#if LIBXML_VERSION >= 20700
1804 | XML_PARSE_HUGE /* don't restrict the node depth
1805 to 256 (bad for snapshots!) */
1806#endif
1807 ;
1808 if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt,
1809 (const char*)pvBuf,
1810 (int)cbSize,
1811 pcszFilename,
1812 NULL, // encoding = auto
1813 options)))
1814 throw XmlError(xmlCtxtGetLastError(m_ctxt));
1815
1816 doc.refreshInternals();
1817}
1818
1819////////////////////////////////////////////////////////////////////////////////
1820//
1821// XmlMemWriter class
1822//
1823////////////////////////////////////////////////////////////////////////////////
1824
1825XmlMemWriter::XmlMemWriter()
1826 : m_pBuf(0)
1827{
1828}
1829
1830XmlMemWriter::~XmlMemWriter()
1831{
1832 if (m_pBuf)
1833 xmlFree(m_pBuf);
1834}
1835
1836void XmlMemWriter::write(const Document &doc, void **ppvBuf, size_t *pcbSize)
1837{
1838 if (m_pBuf)
1839 {
1840 xmlFree(m_pBuf);
1841 m_pBuf = 0;
1842 }
1843 int size;
1844 xmlDocDumpFormatMemory(doc.m->plibDocument, (xmlChar**)&m_pBuf, &size, 1);
1845 *ppvBuf = m_pBuf;
1846 *pcbSize = size;
1847}
1848
1849
1850////////////////////////////////////////////////////////////////////////////////
1851//
1852// XmlStringWriter class
1853//
1854////////////////////////////////////////////////////////////////////////////////
1855
1856XmlStringWriter::XmlStringWriter()
1857 : m_pStrDst(NULL), m_fOutOfMemory(false)
1858{
1859}
1860
1861int XmlStringWriter::write(const Document &rDoc, RTCString *pStrDst)
1862{
1863 /*
1864 * Clear the output string and take the global libxml2 lock so we can
1865 * safely configure the output formatting.
1866 */
1867 pStrDst->setNull();
1868
1869 GlobalLock lock;
1870
1871 xmlIndentTreeOutput = 1;
1872 xmlTreeIndentString = " ";
1873 xmlSaveNoEmptyTags = 0;
1874
1875 /*
1876 * Do a pass to calculate the size.
1877 */
1878 size_t cbOutput = 1; /* zero term */
1879
1880 xmlSaveCtxtPtr pSaveCtx= xmlSaveToIO(WriteCallbackForSize, CloseCallback, &cbOutput, NULL /*pszEncoding*/, XML_SAVE_FORMAT);
1881 if (!pSaveCtx)
1882 return VERR_NO_MEMORY;
1883
1884 long rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument);
1885 xmlSaveClose(pSaveCtx);
1886 if (rcXml == -1)
1887 return VERR_GENERAL_FAILURE;
1888
1889 /*
1890 * Try resize the string.
1891 */
1892 int rc = pStrDst->reserveNoThrow(cbOutput);
1893 if (RT_SUCCESS(rc))
1894 {
1895 /*
1896 * Do the real run where we feed output to the string.
1897 */
1898 m_pStrDst = pStrDst;
1899 m_fOutOfMemory = false;
1900 pSaveCtx = xmlSaveToIO(WriteCallbackForReal, CloseCallback, this, NULL /*pszEncoding*/, XML_SAVE_FORMAT);
1901 if (pSaveCtx)
1902 {
1903 rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument);
1904 xmlSaveClose(pSaveCtx);
1905 m_pStrDst = NULL;
1906 if (rcXml != -1)
1907 {
1908 if (!m_fOutOfMemory)
1909 return VINF_SUCCESS;
1910
1911 rc = VERR_NO_STR_MEMORY;
1912 }
1913 else
1914 rc = VERR_GENERAL_FAILURE;
1915 }
1916 else
1917 rc = VERR_NO_MEMORY;
1918 pStrDst->setNull();
1919 m_pStrDst = NULL;
1920 }
1921 return rc;
1922}
1923
1924/*static*/ int XmlStringWriter::WriteCallbackForSize(void *pvUser, const char *pachBuf, int cbToWrite)
1925{
1926 if (cbToWrite > 0)
1927 *(size_t *)pvUser += (unsigned)cbToWrite;
1928 RT_NOREF(pachBuf);
1929 return cbToWrite;
1930}
1931
1932/*static*/ int XmlStringWriter::WriteCallbackForReal(void *pvUser, const char *pachBuf, int cbToWrite)
1933{
1934 XmlStringWriter *pThis = static_cast<XmlStringWriter*>(pvUser);
1935 if (!pThis->m_fOutOfMemory)
1936 {
1937 if (cbToWrite > 0)
1938 {
1939 try
1940 {
1941 pThis->m_pStrDst->append(pachBuf, (size_t)cbToWrite);
1942 }
1943 catch (std::bad_alloc &)
1944 {
1945 pThis->m_fOutOfMemory = true;
1946 return -1;
1947 }
1948 }
1949 return cbToWrite;
1950 }
1951 return -1; /* failure */
1952}
1953
1954int XmlStringWriter::CloseCallback(void *pvUser)
1955{
1956 /* Nothing to do here. */
1957 RT_NOREF(pvUser);
1958 return 0;
1959}
1960
1961
1962
1963////////////////////////////////////////////////////////////////////////////////
1964//
1965// XmlFileParser class
1966//
1967////////////////////////////////////////////////////////////////////////////////
1968
1969struct XmlFileParser::Data
1970{
1971 RTCString strXmlFilename;
1972
1973 Data()
1974 {
1975 }
1976
1977 ~Data()
1978 {
1979 }
1980};
1981
1982XmlFileParser::XmlFileParser()
1983 : XmlParserBase(),
1984 m(new Data())
1985{
1986}
1987
1988XmlFileParser::~XmlFileParser()
1989{
1990 delete m;
1991 m = NULL;
1992}
1993
1994struct IOContext
1995{
1996 File file;
1997 RTCString error;
1998
1999 IOContext(const char *pcszFilename, File::Mode mode, bool fFlush = false)
2000 : file(mode, pcszFilename, fFlush)
2001 {
2002 }
2003
2004 void setError(const RTCError &x)
2005 {
2006 error = x.what();
2007 }
2008
2009 void setError(const std::exception &x)
2010 {
2011 error = x.what();
2012 }
2013
2014private:
2015 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(IOContext); /* (shuts up C4626 and C4625 MSC warnings) */
2016};
2017
2018struct ReadContext : IOContext
2019{
2020 ReadContext(const char *pcszFilename)
2021 : IOContext(pcszFilename, File::Mode_Read)
2022 {
2023 }
2024
2025private:
2026 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(ReadContext); /* (shuts up C4626 and C4625 MSC warnings) */
2027};
2028
2029struct WriteContext : IOContext
2030{
2031 WriteContext(const char *pcszFilename, bool fFlush)
2032 : IOContext(pcszFilename, File::Mode_Overwrite, fFlush)
2033 {
2034 }
2035
2036private:
2037 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(WriteContext); /* (shuts up C4626 and C4625 MSC warnings) */
2038};
2039
2040/**
2041 * Reads the given file and fills the given Document object with its contents.
2042 * Throws XmlError on parsing errors.
2043 *
2044 * The document that is passed in will be reset before being filled if not empty.
2045 *
2046 * @param strFilename in: name fo file to parse.
2047 * @param doc out: document to be reset and filled with data according to file contents.
2048 */
2049void XmlFileParser::read(const RTCString &strFilename,
2050 Document &doc)
2051{
2052 GlobalLock lock;
2053// global.setExternalEntityLoader(ExternalEntityLoader);
2054
2055 m->strXmlFilename = strFilename;
2056 const char *pcszFilename = strFilename.c_str();
2057
2058 ReadContext context(pcszFilename);
2059 doc.m->reset();
2060 const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
2061 | XML_PARSE_NONET /* forbit any network access */
2062#if LIBXML_VERSION >= 20700
2063 | XML_PARSE_HUGE /* don't restrict the node depth
2064 to 256 (bad for snapshots!) */
2065#endif
2066 ;
2067 if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt,
2068 ReadCallback,
2069 CloseCallback,
2070 &context,
2071 pcszFilename,
2072 NULL, // encoding = auto
2073 options)))
2074 throw XmlError(xmlCtxtGetLastError(m_ctxt));
2075
2076 doc.refreshInternals();
2077}
2078
2079// static
2080int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
2081{
2082 ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
2083
2084 /* To prevent throwing exceptions while inside libxml2 code, we catch
2085 * them and forward to our level using a couple of variables. */
2086
2087 try
2088 {
2089 return pContext->file.read(aBuf, aLen);
2090 }
2091 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
2092 catch (const RTCError &err) { pContext->setError(err); }
2093 catch (const std::exception &err) { pContext->setError(err); }
2094 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
2095
2096 return -1 /* failure */;
2097}
2098
2099int XmlFileParser::CloseCallback(void *aCtxt)
2100{
2101 /// @todo to be written
2102 NOREF(aCtxt);
2103
2104 return -1;
2105}
2106
2107////////////////////////////////////////////////////////////////////////////////
2108//
2109// XmlFileWriter class
2110//
2111////////////////////////////////////////////////////////////////////////////////
2112
2113struct XmlFileWriter::Data
2114{
2115 Document *pDoc;
2116};
2117
2118XmlFileWriter::XmlFileWriter(Document &doc)
2119{
2120 m = new Data();
2121 m->pDoc = &doc;
2122}
2123
2124XmlFileWriter::~XmlFileWriter()
2125{
2126 delete m;
2127}
2128
2129void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe)
2130{
2131 WriteContext context(pcszFilename, fSafe);
2132
2133 GlobalLock lock;
2134
2135 /* serialize to the stream */
2136 xmlIndentTreeOutput = 1;
2137 xmlTreeIndentString = " ";
2138 xmlSaveNoEmptyTags = 0;
2139
2140 xmlSaveCtxtPtr saveCtxt;
2141 if (!(saveCtxt = xmlSaveToIO(WriteCallback,
2142 CloseCallback,
2143 &context,
2144 NULL,
2145 XML_SAVE_FORMAT)))
2146 throw xml::LogicError(RT_SRC_POS);
2147
2148 long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
2149 if (rc == -1)
2150 {
2151 /* look if there was a forwarded exception from the lower level */
2152// if (m->trappedErr.get() != NULL)
2153// m->trappedErr->rethrow();
2154
2155 /* there must be an exception from the Output implementation,
2156 * otherwise the save operation must always succeed. */
2157 throw xml::LogicError(RT_SRC_POS);
2158 }
2159
2160 xmlSaveClose(saveCtxt);
2161}
2162
2163void XmlFileWriter::write(const char *pcszFilename, bool fSafe)
2164{
2165 if (!fSafe)
2166 writeInternal(pcszFilename, fSafe);
2167 else
2168 {
2169 /* Empty string and directory spec must be avoid. */
2170 if (RTPathFilename(pcszFilename) == NULL)
2171 throw xml::LogicError(RT_SRC_POS);
2172
2173 /* Construct both filenames first to ease error handling. */
2174 char szTmpFilename[RTPATH_MAX];
2175 int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename);
2176 if (RT_FAILURE(rc))
2177 throw EIPRTFailure(rc, "RTStrCopy");
2178 strcat(szTmpFilename, s_pszTmpSuff);
2179
2180 char szPrevFilename[RTPATH_MAX];
2181 rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename);
2182 if (RT_FAILURE(rc))
2183 throw EIPRTFailure(rc, "RTStrCopy");
2184 strcat(szPrevFilename, s_pszPrevSuff);
2185
2186 /* Write the XML document to the temporary file. */
2187 writeInternal(szTmpFilename, fSafe);
2188
2189 /* Make a backup of any existing file (ignore failure). */
2190 uint64_t cbPrevFile;
2191 rc = RTFileQuerySize(pcszFilename, &cbPrevFile);
2192 if (RT_SUCCESS(rc) && cbPrevFile >= 16)
2193 RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE);
2194
2195 /* Commit the temporary file. Just leave the tmp file behind on failure. */
2196 rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE);
2197 if (RT_FAILURE(rc))
2198 throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename);
2199
2200 /* Flush the directory changes (required on linux at least). */
2201 RTPathStripFilename(szTmpFilename);
2202 rc = RTDirFlush(szTmpFilename);
2203 AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc));
2204 }
2205}
2206
2207int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
2208{
2209 WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
2210
2211 /* To prevent throwing exceptions while inside libxml2 code, we catch
2212 * them and forward to our level using a couple of variables. */
2213 try
2214 {
2215 return pContext->file.write(aBuf, aLen);
2216 }
2217 catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
2218 catch (const RTCError &err) { pContext->setError(err); }
2219 catch (const std::exception &err) { pContext->setError(err); }
2220 catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
2221
2222 return -1 /* failure */;
2223}
2224
2225int XmlFileWriter::CloseCallback(void *aCtxt)
2226{
2227 /// @todo to be written
2228 NOREF(aCtxt);
2229
2230 return -1;
2231}
2232
2233/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp";
2234/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev";
2235
2236
2237} // end namespace xml
2238
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use