/* $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-2022 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 . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * 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 : ""; 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); } /** * 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(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(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(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(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(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(p); } return NULL; } const ElementNode *ElementNode::getLastChildElement() const { const Node *p; RTListForEachReverseCpp(&m_children, p, Node, m_listEntry) { if (p->isElement()) return static_cast(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(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(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(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(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: * * 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()) * ...; * * @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(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(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(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