summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r3/xml.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/Runtime/r3/xml.cpp2339
1 files changed, 2339 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/xml.cpp b/src/VBox/Runtime/r3/xml.cpp
new file mode 100644
index 00000000..7b3317d8
--- /dev/null
+++ b/src/VBox/Runtime/r3/xml.cpp
@@ -0,0 +1,2339 @@
+/* $Id: xml.cpp $ */
+/** @file
+ * IPRT - XML Manipulation API.
+ */
+
+/*
+ * Copyright (C) 2007-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/cpp/lock.h>
+#include <iprt/cpp/xml.h>
+
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/globals.h>
+#include <libxml/xmlIO.h>
+#include <libxml/xmlsave.h>
+#include <libxml/uri.h>
+
+#include <libxml/xmlschemas.h>
+
+#include <map>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Global module initialization structure. This is to wrap non-reentrant bits
+ * of libxml, among other things.
+ *
+ * The constructor and destructor of this structure are used to perform global
+ * module initialization and cleanup. There must be only one global variable of
+ * this structure.
+ */
+static class Global
+{
+public:
+
+ Global()
+ {
+ /* Check the parser version. The docs say it will kill the app if
+ * there is a serious version mismatch, but I couldn't find it in the
+ * source code (it only prints the error/warning message to the console) so
+ * let's leave it as is for informational purposes. */
+ LIBXML_TEST_VERSION
+
+ /* Init libxml */
+ xmlInitParser();
+
+ /* Save the default entity resolver before someone has replaced it */
+ sxml.defaultEntityLoader = xmlGetExternalEntityLoader();
+ }
+
+ ~Global()
+ {
+ /* Shutdown libxml */
+ xmlCleanupParser();
+ }
+
+ struct
+ {
+ xmlExternalEntityLoader defaultEntityLoader;
+
+ /** Used to provide some thread safety missing in libxml2 (see e.g.
+ * XmlTreeBackend::read()) */
+ RTCLockMtx lock;
+ }
+ sxml; /* XXX naming this xml will break with gcc-3.3 */
+} gGlobal;
+
+
+
+namespace xml
+{
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Exceptions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+LogicError::LogicError(RT_SRC_POS_DECL)
+ : RTCError(NULL)
+{
+ char *msg = NULL;
+ RTStrAPrintf(&msg, "In '%s', '%s' at #%d",
+ pszFunction, pszFile, iLine);
+ setWhat(msg);
+ RTStrFree(msg);
+}
+
+XmlError::XmlError(xmlErrorPtr aErr)
+{
+ if (!aErr)
+ throw EInvalidArg(RT_SRC_POS);
+
+ char *msg = Format(aErr);
+ setWhat(msg);
+ RTStrFree(msg);
+}
+
+/**
+ * Composes a single message for the given error. The caller must free the
+ * returned string using RTStrFree() when no more necessary.
+ */
+/* static */ char *XmlError::Format(xmlErrorPtr aErr)
+{
+ const char *msg = aErr->message ? aErr->message : "<none>";
+ size_t msgLen = strlen(msg);
+ /* strip spaces, trailing EOLs and dot-like char */
+ while (msgLen && strchr(" \n.?!", msg [msgLen - 1]))
+ --msgLen;
+
+ char *finalMsg = NULL;
+ RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
+ msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
+
+ return finalMsg;
+}
+
+EIPRTFailure::EIPRTFailure(int aRC, const char *pcszContext, ...)
+ : RuntimeError(NULL),
+ mRC(aRC)
+{
+ char *pszContext2;
+ va_list args;
+ va_start(args, pcszContext);
+ RTStrAPrintfV(&pszContext2, pcszContext, args);
+ va_end(args);
+ char *newMsg;
+ RTStrAPrintf(&newMsg, "%s: %d (%s)", pszContext2, aRC, RTErrGetShort(aRC));
+ setWhat(newMsg);
+ RTStrFree(newMsg);
+ RTStrFree(pszContext2);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// File Class
+//
+//////////////////////////////////////////////////////////////////////////////
+
+struct File::Data
+{
+ Data()
+ : handle(NIL_RTFILE), opened(false)
+ { }
+
+ RTCString strFileName;
+ RTFILE handle;
+ bool opened : 1;
+ bool flushOnClose : 1;
+};
+
+File::File(Mode aMode, const char *aFileName, bool aFlushIt /* = false */)
+ : m(new Data())
+{
+ m->strFileName = aFileName;
+ m->flushOnClose = aFlushIt;
+
+ uint32_t flags = 0;
+ const char *pcszMode = "???";
+ switch (aMode)
+ {
+ /** @todo change to RTFILE_O_DENY_WRITE where appropriate. */
+ case Mode_Read:
+ flags = RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
+ pcszMode = "reading";
+ break;
+ case Mode_WriteCreate: // fail if file exists
+ flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE;
+ pcszMode = "writing";
+ break;
+ case Mode_Overwrite: // overwrite if file exists
+ flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE;
+ pcszMode = "overwriting";
+ break;
+ case Mode_ReadWrite:
+ flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
+ pcszMode = "reading/writing";
+ break;
+ }
+
+ int vrc = RTFileOpen(&m->handle, aFileName, flags);
+ if (RT_FAILURE(vrc))
+ throw EIPRTFailure(vrc, "Runtime error opening '%s' for %s", aFileName, pcszMode);
+
+ m->opened = true;
+ m->flushOnClose = aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ;
+}
+
+File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */)
+ : m(new Data())
+{
+ if (aHandle == NIL_RTFILE)
+ throw EInvalidArg(RT_SRC_POS);
+
+ m->handle = aHandle;
+
+ if (aFileName)
+ m->strFileName = aFileName;
+
+ m->flushOnClose = aFlushIt;
+
+ setPos(0);
+}
+
+File::~File()
+{
+ if (m->flushOnClose)
+ {
+ RTFileFlush(m->handle);
+ if (!m->strFileName.isEmpty())
+ RTDirFlushParent(m->strFileName.c_str());
+ }
+
+ if (m->opened)
+ RTFileClose(m->handle);
+ delete m;
+}
+
+const char *File::uri() const
+{
+ return m->strFileName.c_str();
+}
+
+uint64_t File::pos() const
+{
+ uint64_t p = 0;
+ int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p);
+ if (RT_SUCCESS(vrc))
+ return p;
+
+ throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
+}
+
+void File::setPos(uint64_t aPos)
+{
+ uint64_t p = 0;
+ unsigned method = RTFILE_SEEK_BEGIN;
+ int vrc = VINF_SUCCESS;
+
+ /* check if we overflow int64_t and move to INT64_MAX first */
+ if ((int64_t)aPos < 0)
+ {
+ vrc = RTFileSeek(m->handle, INT64_MAX, method, &p);
+ aPos -= (uint64_t)INT64_MAX;
+ method = RTFILE_SEEK_CURRENT;
+ }
+ /* seek the rest */
+ if (RT_SUCCESS(vrc))
+ vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p);
+ if (RT_SUCCESS(vrc))
+ return;
+
+ throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
+}
+
+int File::read(char *aBuf, int aLen)
+{
+ size_t len = aLen;
+ int vrc = RTFileRead(m->handle, aBuf, len, &len);
+ if (RT_SUCCESS(vrc))
+ return (int)len;
+
+ throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str());
+}
+
+int File::write(const char *aBuf, int aLen)
+{
+ size_t len = aLen;
+ int vrc = RTFileWrite(m->handle, aBuf, len, &len);
+ if (RT_SUCCESS(vrc))
+ return (int)len;
+
+ throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str());
+}
+
+void File::truncate()
+{
+ int vrc = RTFileSetSize(m->handle, pos());
+ if (RT_SUCCESS(vrc))
+ return;
+
+ throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MemoryBuf Class
+//
+//////////////////////////////////////////////////////////////////////////////
+
+struct MemoryBuf::Data
+{
+ Data()
+ : buf(NULL), len(0), uri(NULL), pos(0) {}
+
+ const char *buf;
+ size_t len;
+ char *uri;
+
+ size_t pos;
+};
+
+MemoryBuf::MemoryBuf(const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
+ : m(new Data())
+{
+ if (aBuf == NULL)
+ throw EInvalidArg(RT_SRC_POS);
+
+ m->buf = aBuf;
+ m->len = aLen;
+ m->uri = RTStrDup(aURI);
+}
+
+MemoryBuf::~MemoryBuf()
+{
+ RTStrFree(m->uri);
+}
+
+const char *MemoryBuf::uri() const
+{
+ return m->uri;
+}
+
+uint64_t MemoryBuf::pos() const
+{
+ return m->pos;
+}
+
+void MemoryBuf::setPos(uint64_t aPos)
+{
+ size_t off = (size_t)aPos;
+ if ((uint64_t) off != aPos)
+ throw EInvalidArg();
+
+ if (off > m->len)
+ throw EInvalidArg();
+
+ m->pos = off;
+}
+
+int MemoryBuf::read(char *aBuf, int aLen)
+{
+ if (m->pos >= m->len)
+ return 0 /* nothing to read */;
+
+ size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
+ memcpy(aBuf, m->buf + m->pos, len);
+ m->pos += len;
+
+ return (int)len;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// GlobalLock class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct GlobalLock::Data
+{
+ PFNEXTERNALENTITYLOADER pOldLoader;
+ RTCLock lock;
+
+ Data()
+ : pOldLoader(NULL),
+ lock(gGlobal.sxml.lock)
+ {
+ }
+};
+
+GlobalLock::GlobalLock()
+ : m(new Data())
+{
+}
+
+GlobalLock::~GlobalLock()
+{
+ if (m->pOldLoader)
+ xmlSetExternalEntityLoader(m->pOldLoader);
+ delete m;
+ m = NULL;
+}
+
+void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pLoader)
+{
+ m->pOldLoader = xmlGetExternalEntityLoader();
+ xmlSetExternalEntityLoader(pLoader);
+}
+
+// static
+xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI,
+ const char *aID,
+ xmlParserCtxt *aCtxt)
+{
+ return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt);
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Node class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+Node::Node(EnumType type,
+ Node *pParent,
+ PRTLISTANCHOR pListAnchor,
+ xmlNode *pLibNode,
+ xmlAttr *pLibAttr)
+ : m_Type(type)
+ , m_pParent(pParent)
+ , m_pLibNode(pLibNode)
+ , m_pLibAttr(pLibAttr)
+ , m_pcszNamespacePrefix(NULL)
+ , m_pcszNamespaceHref(NULL)
+ , m_pcszName(NULL)
+ , m_pParentListAnchor(pListAnchor)
+{
+ RTListInit(&m_listEntry);
+}
+
+Node::~Node()
+{
+}
+
+/**
+ * Returns the name of the node, which is either the element name or
+ * the attribute name. For other node types it probably returns NULL.
+ * @return
+ */
+const char *Node::getName() const
+{
+ return m_pcszName;
+}
+
+/**
+ * Returns the name of the node, which is either the element name or
+ * the attribute name. For other node types it probably returns NULL.
+ * @return
+ */
+const char *Node::getPrefix() const
+{
+ return m_pcszNamespacePrefix;
+}
+
+/**
+ * Returns the XML namespace URI, which is the attribute name. For other node types it probably
+ * returns NULL.
+ * @return
+ */
+const char *Node::getNamespaceURI() const
+{
+ return m_pcszNamespaceHref;
+}
+
+/**
+ * Variant of nameEquals that checks the namespace as well.
+ * @param pcszNamespace
+ * @param pcsz
+ * @return
+ */
+bool Node::nameEqualsNS(const char *pcszNamespace, const char *pcsz) const
+{
+ if (m_pcszName == pcsz)
+ return true;
+ if (m_pcszName == NULL)
+ return false;
+ if (pcsz == NULL)
+ return false;
+ if (strcmp(m_pcszName, pcsz))
+ return false;
+
+ // name matches: then check namespaces as well
+ if (!pcszNamespace)
+ return true;
+ // caller wants namespace:
+ if (!m_pcszNamespacePrefix)
+ // but node has no namespace:
+ return false;
+ return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
+}
+
+/**
+ * Variant of nameEquals that checks the namespace as well.
+ *
+ * @returns true if equal, false if not.
+ * @param pcsz The element name.
+ * @param cchMax The maximum number of character from @a pcsz to
+ * match.
+ * @param pcszNamespace The name space prefix or NULL (default).
+ */
+bool Node::nameEqualsN(const char *pcsz, size_t cchMax, const char *pcszNamespace /* = NULL*/) const
+{
+ /* Match the name. */
+ if (!m_pcszName)
+ return false;
+ if (!pcsz || cchMax == 0)
+ return false;
+ if (strncmp(m_pcszName, pcsz, cchMax))
+ return false;
+ if (strlen(m_pcszName) > cchMax)
+ return false;
+
+ /* Match name space. */
+ if (!pcszNamespace)
+ return true; /* NULL, anything goes. */
+ if (!m_pcszNamespacePrefix)
+ return false; /* Element has no namespace. */
+ return !strcmp(m_pcszNamespacePrefix, pcszNamespace);
+}
+
+/**
+ * Returns the value of a node. If this node is an attribute, returns
+ * the attribute value; if this node is an element, then this returns
+ * the element text content.
+ * @return
+ */
+const char *Node::getValue() const
+{
+ if ( m_pLibAttr
+ && m_pLibAttr->children
+ )
+ // libxml hides attribute values in another node created as a
+ // single child of the attribute node, and it's in the content field
+ return (const char *)m_pLibAttr->children->content;
+
+ if ( m_pLibNode
+ && m_pLibNode->children)
+ return (const char *)m_pLibNode->children->content;
+
+ return NULL;
+}
+
+/**
+ * Returns the value of a node. If this node is an attribute, returns
+ * the attribute value; if this node is an element, then this returns
+ * the element text content.
+ * @return
+ * @param cchValueLimit If the length of the returned value exceeds this
+ * limit a EIPRTFailure exception will be thrown.
+ */
+const char *Node::getValueN(size_t cchValueLimit) const
+{
+ if ( m_pLibAttr
+ && m_pLibAttr->children
+ )
+ {
+ // libxml hides attribute values in another node created as a
+ // single child of the attribute node, and it's in the content field
+ AssertStmt(strlen((const char *)m_pLibAttr->children->content) <= cchValueLimit, throw EIPRTFailure(VERR_BUFFER_OVERFLOW, "Attribute '%s' exceeds limit of %zu bytes", m_pcszName, cchValueLimit));
+ return (const char *)m_pLibAttr->children->content;
+ }
+
+ if ( m_pLibNode
+ && m_pLibNode->children)
+ {
+ AssertStmt(strlen((const char *)m_pLibNode->children->content) <= cchValueLimit, throw EIPRTFailure(VERR_BUFFER_OVERFLOW, "Element '%s' exceeds limit of %zu bytes", m_pcszName, cchValueLimit));
+ return (const char *)m_pLibNode->children->content;
+ }
+
+ return NULL;
+}
+
+/**
+ * Copies the value of a node into the given integer variable.
+ * Returns TRUE only if a value was found and was actually an
+ * integer of the given type.
+ * @return
+ */
+bool Node::copyValue(int32_t &i) const
+{
+ const char *pcsz;
+ if ( ((pcsz = getValue()))
+ && (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
+ )
+ return true;
+
+ return false;
+}
+
+/**
+ * Copies the value of a node into the given integer variable.
+ * Returns TRUE only if a value was found and was actually an
+ * integer of the given type.
+ * @return
+ */
+bool Node::copyValue(uint32_t &i) const
+{
+ const char *pcsz;
+ if ( ((pcsz = getValue()))
+ && (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
+ )
+ return true;
+
+ return false;
+}
+
+/**
+ * Copies the value of a node into the given integer variable.
+ * Returns TRUE only if a value was found and was actually an
+ * integer of the given type.
+ * @return
+ */
+bool Node::copyValue(int64_t &i) const
+{
+ const char *pcsz;
+ if ( ((pcsz = getValue()))
+ && (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
+ )
+ return true;
+
+ return false;
+}
+
+/**
+ * Copies the value of a node into the given integer variable.
+ * Returns TRUE only if a value was found and was actually an
+ * integer of the given type.
+ * @return
+ */
+bool Node::copyValue(uint64_t &i) const
+{
+ const char *pcsz;
+ if ( ((pcsz = getValue()))
+ && (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
+ )
+ return true;
+
+ return false;
+}
+
+/**
+ * Returns the line number of the current node in the source XML file.
+ * Useful for error messages.
+ * @return
+ */
+int Node::getLineNumber() const
+{
+ if (m_pLibAttr)
+ return m_pParent->m_pLibNode->line;
+
+ return m_pLibNode->line;
+}
+
+/**
+ * Private element constructor.
+ *
+ * @param pElmRoot Pointer to the root element.
+ * @param pParent Pointer to the parent element (always an ElementNode,
+ * despite the type). NULL for the root node.
+ * @param pListAnchor Pointer to the m_children member of the parent. NULL
+ * for the root node.
+ * @param pLibNode Pointer to the libxml2 node structure.
+ */
+ElementNode::ElementNode(const ElementNode *pElmRoot,
+ Node *pParent,
+ PRTLISTANCHOR pListAnchor,
+ xmlNode *pLibNode)
+ : Node(IsElement,
+ pParent,
+ pListAnchor,
+ pLibNode,
+ NULL)
+{
+ m_pElmRoot = pElmRoot ? pElmRoot : this; // If NULL is passed, then this is the root element.
+ m_pcszName = (const char *)pLibNode->name;
+
+ if (pLibNode->ns)
+ {
+ m_pcszNamespacePrefix = (const char *)m_pLibNode->ns->prefix;
+ m_pcszNamespaceHref = (const char *)m_pLibNode->ns->href;
+ }
+
+ RTListInit(&m_children);
+ RTListInit(&m_attributes);
+}
+
+ElementNode::~ElementNode()
+{
+ Node *pCur, *pNext;
+ RTListForEachSafeCpp(&m_children, pCur, pNext, Node, m_listEntry)
+ {
+ delete pCur;
+ }
+ RTListInit(&m_children);
+
+ RTListForEachSafeCpp(&m_attributes, pCur, pNext, Node, m_listEntry)
+ {
+ delete pCur;
+ }
+ RTListInit(&m_attributes);
+}
+
+
+/**
+ * Gets the next tree element in a full tree enumeration.
+ *
+ * @returns Pointer to the next element in the tree, NULL if we're done.
+ * @param pElmRoot The root of the tree we're enumerating. NULL if
+ * it's the entire tree.
+ */
+ElementNode const *ElementNode::getNextTreeElement(ElementNode const *pElmRoot /*= NULL */) const
+{
+ /*
+ * Consider children first.
+ */
+ ElementNode const *pChild = getFirstChildElement();
+ if (pChild)
+ return pChild;
+
+ /*
+ * Then siblings, aunts and uncles.
+ */
+ ElementNode const *pCur = this;
+ do
+ {
+ ElementNode const *pSibling = pCur->getNextSibilingElement();
+ if (pSibling != NULL)
+ return pSibling;
+
+ pCur = static_cast<const xml::ElementNode *>(pCur->m_pParent);
+ Assert(pCur || pCur == pElmRoot);
+ } while (pCur != pElmRoot);
+
+ return NULL;
+}
+
+
+/**
+ * Private implementation.
+ *
+ * @param pElmRoot The root element.
+ */
+/*static*/ void ElementNode::buildChildren(ElementNode *pElmRoot) // protected
+{
+ for (ElementNode *pCur = pElmRoot; pCur; pCur = pCur->getNextTreeElement(pElmRoot))
+ {
+ /*
+ * Go thru this element's attributes creating AttributeNodes for them.
+ */
+ for (xmlAttr *pLibAttr = pCur->m_pLibNode->properties; pLibAttr; pLibAttr = pLibAttr->next)
+ {
+ AttributeNode *pNew = new AttributeNode(pElmRoot, pCur, &pCur->m_attributes, pLibAttr);
+ RTListAppend(&pCur->m_attributes, &pNew->m_listEntry);
+ }
+
+ /*
+ * Go thru this element's child elements (element and text nodes).
+ */
+ for (xmlNodePtr pLibNode = pCur->m_pLibNode->children; pLibNode; pLibNode = pLibNode->next)
+ {
+ Node *pNew;
+ if (pLibNode->type == XML_ELEMENT_NODE)
+ pNew = new ElementNode(pElmRoot, pCur, &pCur->m_children, pLibNode);
+ else if (pLibNode->type == XML_TEXT_NODE)
+ pNew = new ContentNode(pCur, &pCur->m_children, pLibNode);
+ else
+ continue;
+ RTListAppend(&pCur->m_children, &pNew->m_listEntry);
+ }
+ }
+}
+
+
+/**
+ * Builds a list of direct child elements of the current element that
+ * match the given string; if pcszMatch is NULL, all direct child
+ * elements are returned.
+ * @param children out: list of nodes to which children will be appended.
+ * @param pcszMatch in: match string, or NULL to return all children.
+ * @return Number of items appended to the list (0 if none).
+ */
+int ElementNode::getChildElements(ElementNodesList &children,
+ const char *pcszMatch /*= NULL*/)
+ const
+{
+ int i = 0;
+ Node *p;
+ RTListForEachCpp(&m_children, p, Node, m_listEntry)
+ {
+ // export this child node if ...
+ if (p->isElement())
+ if ( !pcszMatch // ... the caller wants all nodes or ...
+ || !strcmp(pcszMatch, p->getName()) // ... the element name matches.
+ )
+ {
+ children.push_back(static_cast<ElementNode *>(p));
+ ++i;
+ }
+ }
+ return i;
+}
+
+/**
+ * Returns the first child element whose name matches pcszMatch.
+ *
+ * @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace.
+ * @param pcszMatch Element name to match.
+ * @return
+ */
+const ElementNode *ElementNode::findChildElementNS(const char *pcszNamespace, const char *pcszMatch) const
+{
+ Node *p;
+ RTListForEachCpp(&m_children, p, Node, m_listEntry)
+ {
+ if (p->isElement())
+ {
+ ElementNode *pelm = static_cast<ElementNode*>(p);
+ if (pelm->nameEqualsNS(pcszNamespace, pcszMatch))
+ return pelm;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Returns the first child element whose "id" attribute matches pcszId.
+ * @param pcszId identifier to look for.
+ * @return child element or NULL if not found.
+ */
+const ElementNode *ElementNode::findChildElementFromId(const char *pcszId) const
+{
+ const Node *p;
+ RTListForEachCpp(&m_children, p, Node, m_listEntry)
+ {
+ if (p->isElement())
+ {
+ const ElementNode *pElm = static_cast<const ElementNode *>(p);
+ const AttributeNode *pAttr = pElm->findAttribute("id");
+ if (pAttr && !strcmp(pAttr->getValue(), pcszId))
+ return pElm;
+ }
+ }
+ return NULL;
+}
+
+
+const ElementNode *ElementNode::findChildElementP(const char *pcszPath, const char *pcszNamespace /*= NULL*/) const
+{
+ size_t cchThis = strchr(pcszPath, '/') - pcszPath;
+ if (cchThis == (size_t)((const char *)0 - pcszPath))
+ return findChildElementNS(pcszNamespace, pcszPath);
+
+ /** @todo Can be done without recursion as we have both sibling lists and parent
+ * pointers in this variant. */
+ const Node *p;
+ RTListForEachCpp(&m_children, p, Node, m_listEntry)
+ {
+ if (p->isElement())
+ {
+ const ElementNode *pElm = static_cast<const ElementNode *>(p);
+ if (pElm->nameEqualsN(pcszPath, cchThis, pcszNamespace))
+ {
+ pElm = findChildElementP(pcszPath + cchThis, pcszNamespace);
+ if (pElm)
+ return pElm;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const ElementNode *ElementNode::getFirstChildElement() const
+{
+ const Node *p;
+ RTListForEachCpp(&m_children, p, Node, m_listEntry)
+ {
+ if (p->isElement())
+ return static_cast<const ElementNode *>(p);
+ }
+ return NULL;
+}
+
+const ElementNode *ElementNode::getLastChildElement() const
+{
+ const Node *p;
+ RTListForEachReverseCpp(&m_children, p, Node, m_listEntry)
+ {
+ if (p->isElement())
+ return static_cast<const ElementNode *>(p);
+ }
+ return NULL;
+}
+
+const ElementNode *ElementNode::getPrevSibilingElement() const
+{
+ if (!m_pParent)
+ return NULL;
+ const Node *pSibling = this;
+ for (;;)
+ {
+ pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
+ if (!pSibling)
+ return NULL;
+ if (pSibling->isElement())
+ return static_cast<const ElementNode *>(pSibling);
+ }
+}
+
+const ElementNode *ElementNode::getNextSibilingElement() const
+{
+ if (!m_pParent)
+ return NULL;
+ const Node *pSibling = this;
+ for (;;)
+ {
+ pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
+ if (!pSibling)
+ return NULL;
+ if (pSibling->isElement())
+ return static_cast<const ElementNode *>(pSibling);
+ }
+}
+
+const ElementNode *ElementNode::findPrevSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
+{
+ if (!m_pParent)
+ return NULL;
+ const Node *pSibling = this;
+ for (;;)
+ {
+ pSibling = RTListGetPrevCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
+ if (!pSibling)
+ return NULL;
+ if (pSibling->isElement())
+ {
+ const ElementNode *pElem = static_cast<const ElementNode *>(pSibling);
+ if (pElem->nameEqualsNS(pcszNamespace, pcszMatch))
+ return pElem;
+ }
+ }
+}
+
+const ElementNode *ElementNode::findNextSibilingElement(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
+{
+ if (!m_pParent)
+ return NULL;
+ const Node *pSibling = this;
+ for (;;)
+ {
+ pSibling = RTListGetNextCpp(m_pParentListAnchor, pSibling, const Node, m_listEntry);
+ if (!pSibling)
+ return NULL;
+ if (pSibling->isElement())
+ {
+ const ElementNode *pElem = static_cast<const ElementNode *>(pSibling);
+ if (pElem->nameEqualsNS(pcszNamespace, pcszMatch))
+ return pElem;
+ }
+ }
+}
+
+
+/**
+ * Looks up the given attribute node in this element's attribute map.
+ *
+ * @param pcszMatch The name of the attribute to find.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ */
+const AttributeNode *ElementNode::findAttribute(const char *pcszMatch, const char *pcszNamespace /*= NULL*/) const
+{
+ AttributeNode *p;
+ RTListForEachCpp(&m_attributes, p, AttributeNode, m_listEntry)
+ {
+ if (p->nameEqualsNS(pcszNamespace, pcszMatch))
+ return p;
+ }
+ return NULL;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a string.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param ppcsz Where to return the attribute.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, const char **ppcsz, const char *pcszNamespace /*= NULL*/) const
+{
+ const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
+ if (pAttr)
+ {
+ *ppcsz = pAttr->getValue();
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a string.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param pStr Pointer to the string object that should receive the
+ * attribute value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ *
+ * @throws Whatever the string class may throw on assignment.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const
+{
+ const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
+ if (pAttr)
+ {
+ *pStr = pAttr->getValue();
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Like getAttributeValue (ministring variant), but makes sure that all backslashes
+ * are converted to forward slashes.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param pStr Pointer to the string object that should
+ * receive the attribute path value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValuePath(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace /*= NULL*/) const
+{
+ if (getAttributeValue(pcszMatch, pStr, pcszNamespace))
+ {
+ pStr->findReplace('\\', '/');
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a signed 32-bit integer.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param piValue Where to return the value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, int32_t *piValue, const char *pcszNamespace /*= NULL*/) const
+{
+ const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
+ if (pcsz)
+ {
+ int rc = RTStrToInt32Ex(pcsz, NULL, 0, piValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as an unsigned 32-bit integer.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param puValue Where to return the value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t *puValue, const char *pcszNamespace /*= NULL*/) const
+{
+ const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
+ if (pcsz)
+ {
+ int rc = RTStrToUInt32Ex(pcsz, NULL, 0, puValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a signed 64-bit integer.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param piValue Where to return the value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t *piValue, const char *pcszNamespace /*= NULL*/) const
+{
+ const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
+ if (pcsz)
+ {
+ int rc = RTStrToInt64Ex(pcsz, NULL, 0, piValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as an unsigned 64-bit integer.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param puValue Where to return the value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t *puValue, const char *pcszNamespace /*= NULL*/) const
+{
+ const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
+ if (pcsz)
+ {
+ int rc = RTStrToUInt64Ex(pcsz, NULL, 0, puValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a boolean. This accepts "true", "false",
+ * "yes", "no", "1" or "0" as valid values.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param pfValue Where to return the value.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValue(const char *pcszMatch, bool *pfValue, const char *pcszNamespace /*= NULL*/) const
+{
+ const char *pcsz = findAttributeValue(pcszMatch, pcszNamespace);
+ if (pcsz)
+ {
+ if ( !strcmp(pcsz, "true")
+ || !strcmp(pcsz, "yes")
+ || !strcmp(pcsz, "1")
+ )
+ {
+ *pfValue = true;
+ return true;
+ }
+ if ( !strcmp(pcsz, "false")
+ || !strcmp(pcsz, "no")
+ || !strcmp(pcsz, "0")
+ )
+ {
+ *pfValue = false;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a string.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param ppcsz Where to return the attribute.
+ * @param cchValueLimit If the length of the returned value exceeds this
+ * limit a EIPRTFailure exception will be thrown.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValueN(const char *pcszMatch, const char **ppcsz, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const
+{
+ const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
+ if (pAttr)
+ {
+ *ppcsz = pAttr->getValueN(cchValueLimit);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Convenience method which attempts to find the attribute with the given
+ * name and returns its value as a string.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param pStr Pointer to the string object that should receive the
+ * attribute value.
+ * @param cchValueLimit If the length of the returned value exceeds this
+ * limit a EIPRTFailure exception will be thrown.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ *
+ * @throws Whatever the string class may throw on assignment.
+ */
+bool ElementNode::getAttributeValueN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const
+{
+ const AttributeNode *pAttr = findAttribute(pcszMatch, pcszNamespace);
+ if (pAttr)
+ {
+ *pStr = pAttr->getValueN(cchValueLimit);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Like getAttributeValue (ministring variant), but makes sure that all backslashes
+ * are converted to forward slashes.
+ *
+ * @param pcszMatch Name of attribute to find.
+ * @param pStr Pointer to the string object that should
+ * receive the attribute path value.
+ * @param cchValueLimit If the length of the returned value exceeds this
+ * limit a EIPRTFailure exception will be thrown.
+ * @param pcszNamespace The attribute name space prefix or NULL.
+ * @returns Boolean success indicator.
+ */
+bool ElementNode::getAttributeValuePathN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace /*= NULL*/) const
+{
+ if (getAttributeValueN(pcszMatch, pStr, cchValueLimit, pcszNamespace))
+ {
+ pStr->findReplace('\\', '/');
+ return true;
+ }
+
+ return false;
+}
+
+
+bool ElementNode::getElementValue(int32_t *piValue) const
+{
+ const char *pszValue = getValue();
+ if (pszValue)
+ {
+ int rc = RTStrToInt32Ex(pszValue, NULL, 0, piValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+bool ElementNode::getElementValue(uint32_t *puValue) const
+{
+ const char *pszValue = getValue();
+ if (pszValue)
+ {
+ int rc = RTStrToUInt32Ex(pszValue, NULL, 0, puValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+bool ElementNode::getElementValue(int64_t *piValue) const
+{
+ const char *pszValue = getValue();
+ if (pszValue)
+ {
+ int rc = RTStrToInt64Ex(pszValue, NULL, 0, piValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+bool ElementNode::getElementValue(uint64_t *puValue) const
+{
+ const char *pszValue = getValue();
+ if (pszValue)
+ {
+ int rc = RTStrToUInt64Ex(pszValue, NULL, 0, puValue);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+bool ElementNode::getElementValue(bool *pfValue) const
+{
+ const char *pszValue = getValue();
+ if (pszValue)
+ {
+ if ( !strcmp(pszValue, "true")
+ || !strcmp(pszValue, "yes")
+ || !strcmp(pszValue, "1")
+ )
+ {
+ *pfValue = true;
+ return true;
+ }
+ if ( !strcmp(pszValue, "false")
+ || !strcmp(pszValue, "no")
+ || !strcmp(pszValue, "0")
+ )
+ {
+ *pfValue = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Creates a new child element node and appends it to the list
+ * of children in "this".
+ *
+ * @param pcszElementName
+ * @return
+ */
+ElementNode *ElementNode::createChild(const char *pcszElementName)
+{
+ // we must be an element, not an attribute
+ if (!m_pLibNode)
+ throw ENodeIsNotElement(RT_SRC_POS);
+
+ // libxml side: create new node
+ xmlNode *pLibNode;
+ if (!(pLibNode = xmlNewNode(NULL, // namespace
+ (const xmlChar*)pcszElementName)))
+ throw std::bad_alloc();
+ xmlAddChild(m_pLibNode, pLibNode);
+
+ // now wrap this in C++
+ ElementNode *p = new ElementNode(m_pElmRoot, this, &m_children, pLibNode);
+ RTListAppend(&m_children, &p->m_listEntry);
+
+ return p;
+}
+
+
+/**
+ * Creates a content node and appends it to the list of children
+ * in "this".
+ *
+ * @param pcszContent
+ * @return
+ */
+ContentNode *ElementNode::addContent(const char *pcszContent)
+{
+ // libxml side: create new node
+ xmlNode *pLibNode = xmlNewText((const xmlChar*)pcszContent);
+ if (!pLibNode)
+ throw std::bad_alloc();
+ xmlAddChild(m_pLibNode, pLibNode);
+
+ // now wrap this in C++
+ ContentNode *p = new ContentNode(this, &m_children, pLibNode);
+ RTListAppend(&m_children, &p->m_listEntry);
+
+ return p;
+}
+
+/**
+ * Changes the contents of node and appends it to the list of
+ * children
+ *
+ * @param pcszContent
+ * @return
+ */
+ContentNode *ElementNode::setContent(const char *pcszContent)
+{
+// 1. Update content
+ xmlNodeSetContent(m_pLibNode, (const xmlChar*)pcszContent);
+
+// 2. Remove Content node from the list
+ /* Check that the order is right. */
+ xml::Node * pNode;
+ RTListForEachCpp(&m_children, pNode, xml::Node, m_listEntry)
+ {
+ bool fLast = RTListNodeIsLast(&m_children, &pNode->m_listEntry);
+
+ if (pNode->isContent())
+ {
+ RTListNodeRemove(&pNode->m_listEntry);
+ }
+
+ if (fLast)
+ break;
+ }
+
+// 3. Create a new node and append to the list
+ // now wrap this in C++
+ ContentNode *pCNode = new ContentNode(this, &m_children, m_pLibNode);
+ RTListAppend(&m_children, &pCNode->m_listEntry);
+
+ return pCNode;
+}
+
+/**
+ * Sets the given attribute; overloaded version for const char *.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName The attribute name.
+ * @param pcszValue The attribute value.
+ * @return Pointer to the attribute node that was created or modified.
+ */
+AttributeNode *ElementNode::setAttribute(const char *pcszName, const char *pcszValue)
+{
+ /*
+ * Do we already have an attribute and should we just update it?
+ */
+ AttributeNode *pAttr;
+ RTListForEachCpp(&m_attributes, pAttr, AttributeNode, m_listEntry)
+ {
+ if (pAttr->nameEquals(pcszName))
+ {
+ /* Overwrite existing libxml attribute node ... */
+ xmlAttrPtr pLibAttr = xmlSetProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue);
+
+ /* ... and update our C++ wrapper in case the attrib pointer changed. */
+ pAttr->m_pLibAttr = pLibAttr;
+ return pAttr;
+ }
+ }
+
+ /*
+ * No existing attribute, create a new one.
+ */
+ /* libxml side: xmlNewProp creates an attribute. */
+ xmlAttr *pLibAttr = xmlNewProp(m_pLibNode, (xmlChar *)pcszName, (xmlChar *)pcszValue);
+
+ /* C++ side: Create an attribute node around it. */
+ pAttr = new AttributeNode(m_pElmRoot, this, &m_attributes, pLibAttr);
+ RTListAppend(&m_attributes, &pAttr->m_listEntry);
+
+ return pAttr;
+}
+
+/**
+ * Like setAttribute (ministring variant), but replaces all backslashes with forward slashes
+ * before calling that one.
+ * @param pcszName
+ * @param strValue
+ * @return
+ */
+AttributeNode* ElementNode::setAttributePath(const char *pcszName, const RTCString &strValue)
+{
+ RTCString strTemp(strValue);
+ strTemp.findReplace('\\', '/');
+ return setAttribute(pcszName, strTemp.c_str());
+}
+
+/**
+ * Sets the given attribute; overloaded version for int32_t.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName
+ * @param i
+ * @return
+ */
+AttributeNode* ElementNode::setAttribute(const char *pcszName, int32_t i)
+{
+ char szValue[12]; // negative sign + 10 digits + \0
+ RTStrPrintf(szValue, sizeof(szValue), "%RI32", i);
+ AttributeNode *p = setAttribute(pcszName, szValue);
+ return p;
+}
+
+/**
+ * Sets the given attribute; overloaded version for uint32_t.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName
+ * @param u
+ * @return
+ */
+AttributeNode* ElementNode::setAttribute(const char *pcszName, uint32_t u)
+{
+ char szValue[11]; // 10 digits + \0
+ RTStrPrintf(szValue, sizeof(szValue), "%RU32", u);
+ AttributeNode *p = setAttribute(pcszName, szValue);
+ return p;
+}
+
+/**
+ * Sets the given attribute; overloaded version for int64_t.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName
+ * @param i
+ * @return
+ */
+AttributeNode* ElementNode::setAttribute(const char *pcszName, int64_t i)
+{
+ char szValue[21]; // negative sign + 19 digits + \0
+ RTStrPrintf(szValue, sizeof(szValue), "%RI64", i);
+ AttributeNode *p = setAttribute(pcszName, szValue);
+ return p;
+}
+
+/**
+ * Sets the given attribute; overloaded version for uint64_t.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName
+ * @param u
+ * @return
+ */
+AttributeNode* ElementNode::setAttribute(const char *pcszName, uint64_t u)
+{
+ char szValue[21]; // 20 digits + \0
+ RTStrPrintf(szValue, sizeof(szValue), "%RU64", u);
+ AttributeNode *p = setAttribute(pcszName, szValue);
+ return p;
+}
+
+/**
+ * Sets the given attribute to the given uint32_t, outputs a hexadecimal string.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName
+ * @param u
+ * @return
+ */
+AttributeNode* ElementNode::setAttributeHex(const char *pcszName, uint32_t u)
+{
+ char szValue[11]; // "0x" + 8 digits + \0
+ RTStrPrintf(szValue, sizeof(szValue), "0x%RX32", u);
+ AttributeNode *p = setAttribute(pcszName, szValue);
+ return p;
+}
+
+/**
+ * Sets the given attribute; overloaded version for bool.
+ *
+ * If an attribute with the given name exists, it is overwritten,
+ * otherwise a new attribute is created. Returns the attribute node
+ * that was either created or changed.
+ *
+ * @param pcszName The attribute name.
+ * @param f The attribute value.
+ * @return
+ */
+AttributeNode* ElementNode::setAttribute(const char *pcszName, bool f)
+{
+ return setAttribute(pcszName, (f) ? "true" : "false");
+}
+
+/**
+ * Private constructor for a new attribute node.
+ *
+ * @param pElmRoot Pointer to the root element. Needed for getting the
+ * default name space.
+ * @param pParent Pointer to the parent element (always an ElementNode,
+ * despite the type). NULL for the root node.
+ * @param pListAnchor Pointer to the m_children member of the parent. NULL
+ * for the root node.
+ * @param pLibAttr Pointer to the libxml2 attribute structure.
+ */
+AttributeNode::AttributeNode(const ElementNode *pElmRoot,
+ Node *pParent,
+ PRTLISTANCHOR pListAnchor,
+ xmlAttr *pLibAttr)
+ : Node(IsAttribute,
+ pParent,
+ pListAnchor,
+ NULL,
+ pLibAttr)
+{
+ m_pcszName = (const char *)pLibAttr->name;
+ RT_NOREF_PV(pElmRoot);
+
+ if ( pLibAttr->ns
+ && pLibAttr->ns->prefix)
+ {
+ m_pcszNamespacePrefix = (const char *)pLibAttr->ns->prefix;
+ m_pcszNamespaceHref = (const char *)pLibAttr->ns->href;
+ }
+}
+
+ContentNode::ContentNode(Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode)
+ : Node(IsContent,
+ pParent,
+ pListAnchor,
+ pLibNode,
+ NULL)
+{
+}
+
+/*
+ * NodesLoop
+ *
+ */
+
+struct NodesLoop::Data
+{
+ ElementNodesList listElements;
+ ElementNodesList::const_iterator it;
+};
+
+NodesLoop::NodesLoop(const ElementNode &node, const char *pcszMatch /* = NULL */)
+{
+ m = new Data;
+ node.getChildElements(m->listElements, pcszMatch);
+ m->it = m->listElements.begin();
+}
+
+NodesLoop::~NodesLoop()
+{
+ delete m;
+}
+
+
+/**
+ * Handy convenience helper for looping over all child elements. Create an
+ * instance of NodesLoop on the stack and call this method until it returns
+ * NULL, like this:
+ * <code>
+ * xml::ElementNode node; // should point to an element
+ * xml::NodesLoop loop(node, "child"); // find all "child" elements under node
+ * const xml::ElementNode *pChild = NULL;
+ * while (pChild = loop.forAllNodes())
+ * ...;
+ * </code>
+ * @return
+ */
+const ElementNode* NodesLoop::forAllNodes() const
+{
+ const ElementNode *pNode = NULL;
+
+ if (m->it != m->listElements.end())
+ {
+ pNode = *(m->it);
+ ++(m->it);
+ }
+
+ return pNode;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Document class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct Document::Data
+{
+ xmlDocPtr plibDocument;
+ ElementNode *pRootElement;
+ ElementNode *pComment;
+
+ Data()
+ {
+ plibDocument = NULL;
+ pRootElement = NULL;
+ pComment = NULL;
+ }
+
+ ~Data()
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ if (plibDocument)
+ {
+ xmlFreeDoc(plibDocument);
+ plibDocument = NULL;
+ }
+ if (pRootElement)
+ {
+ delete pRootElement;
+ pRootElement = NULL;
+ }
+ if (pComment)
+ {
+ delete pComment;
+ pComment = NULL;
+ }
+ }
+
+ void copyFrom(const Document::Data *p)
+ {
+ if (p->plibDocument)
+ {
+ plibDocument = xmlCopyDoc(p->plibDocument,
+ 1); // recursive == copy all
+ }
+ }
+};
+
+Document::Document()
+ : m(new Data)
+{
+}
+
+Document::Document(const Document &x)
+ : m(new Data)
+{
+ m->copyFrom(x.m);
+}
+
+Document& Document::operator=(const Document &x)
+{
+ m->reset();
+ m->copyFrom(x.m);
+ return *this;
+}
+
+Document::~Document()
+{
+ delete m;
+}
+
+/**
+ * private method to refresh all internal structures after the internal pDocument
+ * has changed. Called from XmlFileParser::read(). m->reset() must have been
+ * called before to make sure all members except the internal pDocument are clean.
+ */
+void Document::refreshInternals() // private
+{
+ m->pRootElement = new ElementNode(NULL, NULL, NULL, xmlDocGetRootElement(m->plibDocument));
+
+ ElementNode::buildChildren(m->pRootElement);
+}
+
+/**
+ * Returns the root element of the document, or NULL if the document is empty.
+ * Const variant.
+ * @return
+ */
+const ElementNode *Document::getRootElement() const
+{
+ return m->pRootElement;
+}
+
+/**
+ * Returns the root element of the document, or NULL if the document is empty.
+ * Non-const variant.
+ * @return
+ */
+ElementNode *Document::getRootElement()
+{
+ return m->pRootElement;
+}
+
+/**
+ * Creates a new element node and sets it as the root element.
+ *
+ * This will only work if the document is empty; otherwise EDocumentNotEmpty is
+ * thrown.
+ */
+ElementNode *Document::createRootElement(const char *pcszRootElementName,
+ const char *pcszComment /* = NULL */)
+{
+ if (m->pRootElement || m->plibDocument)
+ throw EDocumentNotEmpty(RT_SRC_POS);
+
+ // libxml side: create document, create root node
+ m->plibDocument = xmlNewDoc((const xmlChar *)"1.0");
+ xmlNode *plibRootNode = xmlNewNode(NULL /*namespace*/ , (const xmlChar *)pcszRootElementName);
+ if (!plibRootNode)
+ throw std::bad_alloc();
+ xmlDocSetRootElement(m->plibDocument, plibRootNode);
+
+ // now wrap this in C++
+ m->pRootElement = new ElementNode(NULL, NULL, NULL, plibRootNode);
+
+ // add document global comment if specified
+ if (pcszComment != NULL)
+ {
+ xmlNode *pComment = xmlNewDocComment(m->plibDocument, (const xmlChar *)pcszComment);
+ if (!pComment)
+ throw std::bad_alloc();
+ xmlAddPrevSibling(plibRootNode, pComment);
+
+ // now wrap this in C++
+ m->pComment = new ElementNode(NULL, NULL, NULL, pComment);
+ }
+
+ return m->pRootElement;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// XmlParserBase class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+static void xmlParserBaseGenericError(void *pCtx, const char *pszMsg, ...)
+{
+ NOREF(pCtx);
+ va_list args;
+ va_start(args, pszMsg);
+ RTLogRelPrintfV(pszMsg, args);
+ va_end(args);
+}
+
+static void xmlParserBaseStructuredError(void *pCtx, xmlErrorPtr error)
+{
+ NOREF(pCtx);
+ /* we expect that there is always a trailing NL */
+ LogRel(("XML error at '%s' line %d: %s", error->file, error->line, error->message));
+}
+
+XmlParserBase::XmlParserBase()
+{
+ m_ctxt = xmlNewParserCtxt();
+ if (m_ctxt == NULL)
+ throw std::bad_alloc();
+ /* per-thread so it must be here */
+ xmlSetGenericErrorFunc(NULL, xmlParserBaseGenericError);
+ xmlSetStructuredErrorFunc(NULL, xmlParserBaseStructuredError);
+}
+
+XmlParserBase::~XmlParserBase()
+{
+ xmlSetStructuredErrorFunc(NULL, NULL);
+ xmlSetGenericErrorFunc(NULL, NULL);
+ xmlFreeParserCtxt (m_ctxt);
+ m_ctxt = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// XmlMemParser class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+XmlMemParser::XmlMemParser()
+ : XmlParserBase()
+{
+}
+
+XmlMemParser::~XmlMemParser()
+{
+}
+
+/**
+ * Parse the given buffer and fills the given Document object with its contents.
+ * Throws XmlError on parsing errors.
+ *
+ * The document that is passed in will be reset before being filled if not empty.
+ *
+ * @param pvBuf Memory buffer to parse.
+ * @param cbSize Size of the memory buffer.
+ * @param strFilename Refernece to the name of the file we're parsing.
+ * @param doc Reference to the output document. This will be reset
+ * and filled with data according to file contents.
+ */
+void XmlMemParser::read(const void *pvBuf, size_t cbSize,
+ const RTCString &strFilename,
+ Document &doc)
+{
+ GlobalLock lock;
+// global.setExternalEntityLoader(ExternalEntityLoader);
+
+ const char *pcszFilename = strFilename.c_str();
+
+ doc.m->reset();
+ const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
+ | XML_PARSE_NONET /* forbit any network access */
+#if LIBXML_VERSION >= 20700
+ | XML_PARSE_HUGE /* don't restrict the node depth
+ to 256 (bad for snapshots!) */
+#endif
+ ;
+ if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt,
+ (const char*)pvBuf,
+ (int)cbSize,
+ pcszFilename,
+ NULL, // encoding = auto
+ options)))
+ throw XmlError(xmlCtxtGetLastError(m_ctxt));
+
+ doc.refreshInternals();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// XmlMemWriter class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+XmlMemWriter::XmlMemWriter()
+ : m_pBuf(0)
+{
+}
+
+XmlMemWriter::~XmlMemWriter()
+{
+ if (m_pBuf)
+ xmlFree(m_pBuf);
+}
+
+void XmlMemWriter::write(const Document &doc, void **ppvBuf, size_t *pcbSize)
+{
+ if (m_pBuf)
+ {
+ xmlFree(m_pBuf);
+ m_pBuf = 0;
+ }
+ int size;
+ xmlDocDumpFormatMemory(doc.m->plibDocument, (xmlChar**)&m_pBuf, &size, 1);
+ *ppvBuf = m_pBuf;
+ *pcbSize = size;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// XmlStringWriter class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+XmlStringWriter::XmlStringWriter()
+ : m_pStrDst(NULL), m_fOutOfMemory(false)
+{
+}
+
+int XmlStringWriter::write(const Document &rDoc, RTCString *pStrDst)
+{
+ /*
+ * Clear the output string and take the global libxml2 lock so we can
+ * safely configure the output formatting.
+ */
+ pStrDst->setNull();
+
+ GlobalLock lock;
+
+ xmlIndentTreeOutput = 1;
+ xmlTreeIndentString = " ";
+ xmlSaveNoEmptyTags = 0;
+
+ /*
+ * Do a pass to calculate the size.
+ */
+ size_t cbOutput = 1; /* zero term */
+
+ xmlSaveCtxtPtr pSaveCtx= xmlSaveToIO(WriteCallbackForSize, CloseCallback, &cbOutput, NULL /*pszEncoding*/, XML_SAVE_FORMAT);
+ if (!pSaveCtx)
+ return VERR_NO_MEMORY;
+
+ long rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument);
+ xmlSaveClose(pSaveCtx);
+ if (rcXml == -1)
+ return VERR_GENERAL_FAILURE;
+
+ /*
+ * Try resize the string.
+ */
+ int rc = pStrDst->reserveNoThrow(cbOutput);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the real run where we feed output to the string.
+ */
+ m_pStrDst = pStrDst;
+ m_fOutOfMemory = false;
+ pSaveCtx = xmlSaveToIO(WriteCallbackForReal, CloseCallback, this, NULL /*pszEncoding*/, XML_SAVE_FORMAT);
+ if (pSaveCtx)
+ {
+ rcXml = xmlSaveDoc(pSaveCtx, rDoc.m->plibDocument);
+ xmlSaveClose(pSaveCtx);
+ m_pStrDst = NULL;
+ if (rcXml != -1)
+ {
+ if (!m_fOutOfMemory)
+ return VINF_SUCCESS;
+
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ pStrDst->setNull();
+ m_pStrDst = NULL;
+ }
+ return rc;
+}
+
+/*static*/ int XmlStringWriter::WriteCallbackForSize(void *pvUser, const char *pachBuf, int cbToWrite)
+{
+ if (cbToWrite > 0)
+ *(size_t *)pvUser += (unsigned)cbToWrite;
+ RT_NOREF(pachBuf);
+ return cbToWrite;
+}
+
+/*static*/ int XmlStringWriter::WriteCallbackForReal(void *pvUser, const char *pachBuf, int cbToWrite)
+{
+ XmlStringWriter *pThis = static_cast<XmlStringWriter*>(pvUser);
+ if (!pThis->m_fOutOfMemory)
+ {
+ if (cbToWrite > 0)
+ {
+ try
+ {
+ pThis->m_pStrDst->append(pachBuf, (size_t)cbToWrite);
+ }
+ catch (std::bad_alloc &)
+ {
+ pThis->m_fOutOfMemory = true;
+ return -1;
+ }
+ }
+ return cbToWrite;
+ }
+ return -1; /* failure */
+}
+
+int XmlStringWriter::CloseCallback(void *pvUser)
+{
+ /* Nothing to do here. */
+ RT_NOREF(pvUser);
+ return 0;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// XmlFileParser class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct XmlFileParser::Data
+{
+ RTCString strXmlFilename;
+
+ Data()
+ {
+ }
+
+ ~Data()
+ {
+ }
+};
+
+XmlFileParser::XmlFileParser()
+ : XmlParserBase(),
+ m(new Data())
+{
+}
+
+XmlFileParser::~XmlFileParser()
+{
+ delete m;
+ m = NULL;
+}
+
+struct IOContext
+{
+ File file;
+ RTCString error;
+
+ IOContext(const char *pcszFilename, File::Mode mode, bool fFlush = false)
+ : file(mode, pcszFilename, fFlush)
+ {
+ }
+
+ void setError(const RTCError &x)
+ {
+ error = x.what();
+ }
+
+ void setError(const std::exception &x)
+ {
+ error = x.what();
+ }
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(IOContext); /* (shuts up C4626 and C4625 MSC warnings) */
+};
+
+struct ReadContext : IOContext
+{
+ ReadContext(const char *pcszFilename)
+ : IOContext(pcszFilename, File::Mode_Read)
+ {
+ }
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(ReadContext); /* (shuts up C4626 and C4625 MSC warnings) */
+};
+
+struct WriteContext : IOContext
+{
+ WriteContext(const char *pcszFilename, bool fFlush)
+ : IOContext(pcszFilename, File::Mode_Overwrite, fFlush)
+ {
+ }
+
+private:
+ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(WriteContext); /* (shuts up C4626 and C4625 MSC warnings) */
+};
+
+/**
+ * Reads the given file and fills the given Document object with its contents.
+ * Throws XmlError on parsing errors.
+ *
+ * The document that is passed in will be reset before being filled if not empty.
+ *
+ * @param strFilename in: name fo file to parse.
+ * @param doc out: document to be reset and filled with data according to file contents.
+ */
+void XmlFileParser::read(const RTCString &strFilename,
+ Document &doc)
+{
+ GlobalLock lock;
+// global.setExternalEntityLoader(ExternalEntityLoader);
+
+ m->strXmlFilename = strFilename;
+ const char *pcszFilename = strFilename.c_str();
+
+ ReadContext context(pcszFilename);
+ doc.m->reset();
+ const int options = XML_PARSE_NOBLANKS /* remove blank nodes */
+ | XML_PARSE_NONET /* forbit any network access */
+#if LIBXML_VERSION >= 20700
+ | XML_PARSE_HUGE /* don't restrict the node depth
+ to 256 (bad for snapshots!) */
+#endif
+ ;
+ if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt,
+ ReadCallback,
+ CloseCallback,
+ &context,
+ pcszFilename,
+ NULL, // encoding = auto
+ options)))
+ throw XmlError(xmlCtxtGetLastError(m_ctxt));
+
+ doc.refreshInternals();
+}
+
+// static
+int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
+{
+ ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
+
+ /* To prevent throwing exceptions while inside libxml2 code, we catch
+ * them and forward to our level using a couple of variables. */
+
+ try
+ {
+ return pContext->file.read(aBuf, aLen);
+ }
+ catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
+ catch (const RTCError &err) { pContext->setError(err); }
+ catch (const std::exception &err) { pContext->setError(err); }
+ catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
+
+ return -1 /* failure */;
+}
+
+int XmlFileParser::CloseCallback(void *aCtxt)
+{
+ /// @todo to be written
+ NOREF(aCtxt);
+
+ return -1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// XmlFileWriter class
+//
+////////////////////////////////////////////////////////////////////////////////
+
+struct XmlFileWriter::Data
+{
+ Document *pDoc;
+};
+
+XmlFileWriter::XmlFileWriter(Document &doc)
+{
+ m = new Data();
+ m->pDoc = &doc;
+}
+
+XmlFileWriter::~XmlFileWriter()
+{
+ delete m;
+}
+
+void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe)
+{
+ WriteContext context(pcszFilename, fSafe);
+
+ GlobalLock lock;
+
+ /* serialize to the stream */
+ xmlIndentTreeOutput = 1;
+ xmlTreeIndentString = " ";
+ xmlSaveNoEmptyTags = 0;
+
+ xmlSaveCtxtPtr saveCtxt;
+ if (!(saveCtxt = xmlSaveToIO(WriteCallback,
+ CloseCallback,
+ &context,
+ NULL,
+ XML_SAVE_FORMAT)))
+ throw xml::LogicError(RT_SRC_POS);
+
+ long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
+ if (rc == -1)
+ {
+ /* look if there was a forwarded exception from the lower level */
+// if (m->trappedErr.get() != NULL)
+// m->trappedErr->rethrow();
+
+ /* there must be an exception from the Output implementation,
+ * otherwise the save operation must always succeed. */
+ throw xml::LogicError(RT_SRC_POS);
+ }
+
+ xmlSaveClose(saveCtxt);
+}
+
+void XmlFileWriter::write(const char *pcszFilename, bool fSafe)
+{
+ if (!fSafe)
+ writeInternal(pcszFilename, fSafe);
+ else
+ {
+ /* Empty string and directory spec must be avoid. */
+ if (RTPathFilename(pcszFilename) == NULL)
+ throw xml::LogicError(RT_SRC_POS);
+
+ /* Construct both filenames first to ease error handling. */
+ char szTmpFilename[RTPATH_MAX];
+ int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename);
+ if (RT_FAILURE(rc))
+ throw EIPRTFailure(rc, "RTStrCopy");
+ strcat(szTmpFilename, s_pszTmpSuff);
+
+ char szPrevFilename[RTPATH_MAX];
+ rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename);
+ if (RT_FAILURE(rc))
+ throw EIPRTFailure(rc, "RTStrCopy");
+ strcat(szPrevFilename, s_pszPrevSuff);
+
+ /* Write the XML document to the temporary file. */
+ writeInternal(szTmpFilename, fSafe);
+
+ /* Make a backup of any existing file (ignore failure). */
+ uint64_t cbPrevFile;
+ rc = RTFileQuerySizeByPath(pcszFilename, &cbPrevFile);
+ if (RT_SUCCESS(rc) && cbPrevFile >= 16)
+ RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE);
+
+ /* Commit the temporary file. Just leave the tmp file behind on failure. */
+ rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE);
+ if (RT_FAILURE(rc))
+ throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename);
+
+ /* Flush the directory changes (required on linux at least). */
+ RTPathStripFilename(szTmpFilename);
+ rc = RTDirFlush(szTmpFilename);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc));
+ }
+}
+
+int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
+{
+ WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
+
+ /* To prevent throwing exceptions while inside libxml2 code, we catch
+ * them and forward to our level using a couple of variables. */
+ try
+ {
+ return pContext->file.write(aBuf, aLen);
+ }
+ catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
+ catch (const RTCError &err) { pContext->setError(err); }
+ catch (const std::exception &err) { pContext->setError(err); }
+ catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
+
+ return -1 /* failure */;
+}
+
+int XmlFileWriter::CloseCallback(void *aCtxt)
+{
+ /// @todo to be written
+ NOREF(aCtxt);
+
+ return -1;
+}
+
+/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp";
+/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev";
+
+
+} // end namespace xml
+