diff options
Diffstat (limited to 'src/VBox/Runtime/r3/xml.cpp')
-rw-r--r-- | src/VBox/Runtime/r3/xml.cpp | 2357 |
1 files changed, 2357 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..a6661760 --- /dev/null +++ b/src/VBox/Runtime/r3/xml.cpp @@ -0,0 +1,2357 @@ +/* $Id: xml.cpp $ */ +/** @file + * IPRT - XML Manipulation API. + * + * @note Not available in no-CRT mode because it relies too heavily on + * exceptions, as well as using std::list and std::map. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cpp/xml.h> + +#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 <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 *pszContextFmt, ...) + : RuntimeError(NULL) + , mRC(aRC) +{ + va_list va; + va_start(va, pszContextFmt); + m_strMsg.printfVNoThrow(pszContextFmt, va); + va_end(va); + m_strMsg.appendPrintfNoThrow(" %Rrc (%Rrs)", aRC, aRC); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// File Class +// +////////////////////////////////////////////////////////////////////////////// + +struct File::Data +{ + Data(RTFILE a_hHandle, const char *a_pszFilename, bool a_fFlushOnClose) + : strFileName(a_pszFilename) + , handle(a_hHandle) + , opened(a_hHandle != NIL_RTFILE) + , flushOnClose(a_fFlushOnClose) + { } + + ~Data() + { + if (flushOnClose) + { + RTFileFlush(handle); + if (!strFileName.isEmpty()) + RTDirFlushParent(strFileName.c_str()); + } + + if (opened) + { + RTFileClose(handle); + 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(NULL) +{ + /* Try open the file first, as the destructor will not be invoked if we throw anything from here. For details see: + https://stackoverflow.com/questions/9971782/destructor-not-invoked-when-an-exception-is-thrown-in-the-constructor */ + 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; + } + RTFILE hFile = NIL_RTFILE; + int vrc = RTFileOpen(&hFile, aFileName, flags); + if (RT_FAILURE(vrc)) + throw EIPRTFailure(vrc, "Runtime error opening '%s' for %s", aFileName, pcszMode); + + /* Now we can create the data and stuff: */ + try + { + m = new Data(hFile, aFileName, aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ); + } + catch (std::bad_alloc &) + { + RTFileClose(hFile); + throw; + } +} + +File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */) + : m(NULL) +{ + if (aHandle == NIL_RTFILE) + throw EInvalidArg(RT_SRC_POS); + + m = new Data(aHandle, aFileName, aFlushIt); + + setPos(0); +} + +File::~File() +{ + if (m) + { + delete m; + m = NULL; + } +} + +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 pfnOldLoader; + RTCLock lock; + + Data() + : pfnOldLoader(NULL) + , lock(gGlobal.sxml.lock) + { + } +}; + +GlobalLock::GlobalLock() + : m(new Data()) +{ +} + +GlobalLock::~GlobalLock() +{ + if (m->pfnOldLoader) + xmlSetExternalEntityLoader(m->pfnOldLoader); + delete m; + m = NULL; +} + +void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pfnLoader) +{ + m->pfnOldLoader = (PFNEXTERNALENTITYLOADER)xmlGetExternalEntityLoader(); + xmlSetExternalEntityLoader(pfnLoader); +} + +// 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); +} + + +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, ...) RT_NOTHROW_DEF +{ + NOREF(pCtx); + va_list args; + va_start(args, pszMsg); + RTLogRelPrintfV(pszMsg, args); + va_end(args); +} + +static void xmlParserBaseStructuredError(void *pCtx, xmlErrorPtr error) RT_NOTHROW_DEF +{ + 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) RT_NOTHROW_DEF +{ + if (cbToWrite > 0) + *(size_t *)pvUser += (unsigned)cbToWrite; + RT_NOREF(pachBuf); + return cbToWrite; +} + +/*static*/ int XmlStringWriter::WriteCallbackForReal(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_DEF +{ + 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 */ +} + +/*static*/ int XmlStringWriter::CloseCallback(void *pvUser) RT_NOTHROW_DEF +{ + /* 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) RT_NOTHROW_DEF +{ + 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 */; +} + +/*static*/ int XmlFileParser::CloseCallback(void *aCtxt) RT_NOTHROW_DEF +{ + /// @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)); + } +} + +/*static*/ int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen) RT_NOTHROW_DEF +{ + 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 */; +} + +/*static*/ int XmlFileWriter::CloseCallback(void *aCtxt) RT_NOTHROW_DEF +{ + /// @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 + |