xml.cpp revision 64eea8161bef2aa3c6516481383c830bca27abfe
/* $Id$ */
/** @file
* IPRT - XML Manipulation API.
*/
/*
* Copyright (C) 2007-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/dir.h>
#include <iprt/file.h>
#include <iprt/err.h>
#include <iprt/param.h>
#include <iprt/path.h>
#include <iprt/cpp/lock.h>
#include <iprt/cpp/xml.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/globals.h>
#include <libxml/xmlIO.h>
#include <libxml/xmlsave.h>
#include <libxml/uri.h>
#include <libxml/xmlschemas.h>
#include <map>
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/**
* Global module initialization structure. This is to wrap non-reentrant bits
* of libxml, among other things.
*
* The constructor and destructor of this structure are used to perform global
* module initialization and cleanup. There must be only one global variable of
* this structure.
*/
static class Global
{
public:
Global()
{
/* Check the parser version. The docs say it will kill the app if
* there is a serious version mismatch, but I couldn't find it in the
* source code (it only prints the error/warning message to the console) so
* let's leave it as is for informational purposes. */
LIBXML_TEST_VERSION
/* Init libxml */
xmlInitParser();
/* Save the default entity resolver before someone has replaced it */
sxml.defaultEntityLoader = xmlGetExternalEntityLoader();
}
~Global()
{
/* Shutdown libxml */
xmlCleanupParser();
}
struct
{
xmlExternalEntityLoader defaultEntityLoader;
/** Used to provide some thread safety missing in libxml2 (see e.g.
* XmlTreeBackend::read()) */
RTCLockMtx lock;
}
sxml; /* XXX naming this xml will break with gcc-3.3 */
} gGlobal;
namespace xml
{
////////////////////////////////////////////////////////////////////////////////
//
// Exceptions
//
////////////////////////////////////////////////////////////////////////////////
LogicError::LogicError(RT_SRC_POS_DECL)
: RTCError(NULL)
{
char *msg = NULL;
RTStrAPrintf(&msg, "In '%s', '%s' at #%d",
pszFunction, pszFile, iLine);
setWhat(msg);
RTStrFree(msg);
}
XmlError::XmlError(xmlErrorPtr aErr)
{
if (!aErr)
throw EInvalidArg(RT_SRC_POS);
char *msg = Format(aErr);
setWhat(msg);
RTStrFree(msg);
}
/**
* Composes a single message for the given error. The caller must free the
* returned string using RTStrFree() when no more necessary.
*/
/* static */ char *XmlError::Format(xmlErrorPtr aErr)
{
const char *msg = aErr->message ? aErr->message : "<none>";
size_t msgLen = strlen(msg);
/* strip spaces, trailing EOLs and dot-like char */
while (msgLen && strchr(" \n.?!", msg [msgLen - 1]))
--msgLen;
char *finalMsg = NULL;
RTStrAPrintf(&finalMsg, "%.*s.\nLocation: '%s', line %d (%d), column %d",
msgLen, msg, aErr->file, aErr->line, aErr->int1, aErr->int2);
return finalMsg;
}
EIPRTFailure::EIPRTFailure(int aRC, const char *pcszContext, ...)
: RuntimeError(NULL),
mRC(aRC)
{
char *pszContext2;
va_list args;
va_start(args, pcszContext);
RTStrAPrintfV(&pszContext2, pcszContext, args);
char *newMsg;
RTStrAPrintf(&newMsg, "%s: %d(%s)", pszContext2, aRC, RTErrGetShort(aRC));
setWhat(newMsg);
RTStrFree(newMsg);
RTStrFree(pszContext2);
}
////////////////////////////////////////////////////////////////////////////////
//
// File Class
//
//////////////////////////////////////////////////////////////////////////////
struct File::Data
{
Data()
: handle(NIL_RTFILE), opened(false)
{ }
RTCString strFileName;
RTFILE handle;
bool opened : 1;
bool flushOnClose : 1;
};
File::File(Mode aMode, const char *aFileName, bool aFlushIt /* = false */)
: m(new Data())
{
m->strFileName = aFileName;
m->flushOnClose = aFlushIt;
uint32_t flags = 0;
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;
break;
case Mode_WriteCreate: // fail if file exists
flags = RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE;
break;
case Mode_Overwrite: // overwrite if file exists
flags = RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE;
break;
case Mode_ReadWrite:
flags = RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE;;
}
int vrc = RTFileOpen(&m->handle, aFileName, flags);
if (RT_FAILURE(vrc))
throw EIPRTFailure(vrc, "Runtime error opening '%s' for reading", aFileName);
m->opened = true;
m->flushOnClose = aFlushIt && (flags & RTFILE_O_ACCESS_MASK) != RTFILE_O_READ;
}
File::File(RTFILE aHandle, const char *aFileName /* = NULL */, bool aFlushIt /* = false */)
: m(new Data())
{
if (aHandle == NIL_RTFILE)
throw EInvalidArg(RT_SRC_POS);
m->handle = aHandle;
if (aFileName)
m->strFileName = aFileName;
m->flushOnClose = aFlushIt;
setPos(0);
}
File::~File()
{
if (m->flushOnClose)
{
RTFileFlush(m->handle);
if (!m->strFileName.isEmpty())
RTDirFlushParent(m->strFileName.c_str());
}
if (m->opened)
RTFileClose(m->handle);
delete m;
}
const char *File::uri() const
{
return m->strFileName.c_str();
}
uint64_t File::pos() const
{
uint64_t p = 0;
int vrc = RTFileSeek(m->handle, 0, RTFILE_SEEK_CURRENT, &p);
if (RT_SUCCESS(vrc))
return p;
throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
}
void File::setPos(uint64_t aPos)
{
uint64_t p = 0;
unsigned method = RTFILE_SEEK_BEGIN;
int vrc = VINF_SUCCESS;
/* check if we overflow int64_t and move to INT64_MAX first */
if ((int64_t)aPos < 0)
{
vrc = RTFileSeek(m->handle, INT64_MAX, method, &p);
aPos -= (uint64_t)INT64_MAX;
method = RTFILE_SEEK_CURRENT;
}
/* seek the rest */
if (RT_SUCCESS(vrc))
vrc = RTFileSeek(m->handle, (int64_t) aPos, method, &p);
if (RT_SUCCESS(vrc))
return;
throw EIPRTFailure(vrc, "Runtime error seeking in file '%s'", m->strFileName.c_str());
}
int File::read(char *aBuf, int aLen)
{
size_t len = aLen;
int vrc = RTFileRead(m->handle, aBuf, len, &len);
if (RT_SUCCESS(vrc))
return (int)len;
throw EIPRTFailure(vrc, "Runtime error reading from file '%s'", m->strFileName.c_str());
}
int File::write(const char *aBuf, int aLen)
{
size_t len = aLen;
int vrc = RTFileWrite(m->handle, aBuf, len, &len);
if (RT_SUCCESS(vrc))
return (int)len;
throw EIPRTFailure(vrc, "Runtime error writing to file '%s'", m->strFileName.c_str());
}
void File::truncate()
{
int vrc = RTFileSetSize(m->handle, pos());
if (RT_SUCCESS(vrc))
return;
throw EIPRTFailure(vrc, "Runtime error truncating file '%s'", m->strFileName.c_str());
}
////////////////////////////////////////////////////////////////////////////////
//
// MemoryBuf Class
//
//////////////////////////////////////////////////////////////////////////////
struct MemoryBuf::Data
{
Data()
: buf(NULL), len(0), uri(NULL), pos(0) {}
const char *buf;
size_t len;
char *uri;
size_t pos;
};
MemoryBuf::MemoryBuf(const char *aBuf, size_t aLen, const char *aURI /* = NULL */)
: m(new Data())
{
if (aBuf == NULL)
throw EInvalidArg(RT_SRC_POS);
m->buf = aBuf;
m->len = aLen;
m->uri = RTStrDup(aURI);
}
MemoryBuf::~MemoryBuf()
{
RTStrFree(m->uri);
}
const char *MemoryBuf::uri() const
{
return m->uri;
}
uint64_t MemoryBuf::pos() const
{
return m->pos;
}
void MemoryBuf::setPos(uint64_t aPos)
{
size_t off = (size_t)aPos;
if ((uint64_t) off != aPos)
throw EInvalidArg();
if (off > m->len)
throw EInvalidArg();
m->pos = off;
}
int MemoryBuf::read(char *aBuf, int aLen)
{
if (m->pos >= m->len)
return 0 /* nothing to read */;
size_t len = m->pos + aLen < m->len ? aLen : m->len - m->pos;
memcpy(aBuf, m->buf + m->pos, len);
m->pos += len;
return (int)len;
}
////////////////////////////////////////////////////////////////////////////////
//
// GlobalLock class
//
////////////////////////////////////////////////////////////////////////////////
struct GlobalLock::Data
{
PFNEXTERNALENTITYLOADER pOldLoader;
RTCLock lock;
Data()
: pOldLoader(NULL),
lock(gGlobal.sxml.lock)
{
}
};
GlobalLock::GlobalLock()
: m(new Data())
{
}
GlobalLock::~GlobalLock()
{
if (m->pOldLoader)
xmlSetExternalEntityLoader(m->pOldLoader);
delete m;
m = NULL;
}
void GlobalLock::setExternalEntityLoader(PFNEXTERNALENTITYLOADER pLoader)
{
m->pOldLoader = xmlGetExternalEntityLoader();
xmlSetExternalEntityLoader(pLoader);
}
// static
xmlParserInput* GlobalLock::callDefaultLoader(const char *aURI,
const char *aID,
xmlParserCtxt *aCtxt)
{
return gGlobal.sxml.defaultEntityLoader(aURI, aID, aCtxt);
}
////////////////////////////////////////////////////////////////////////////////
//
// Node class
//
////////////////////////////////////////////////////////////////////////////////
Node::Node(EnumType type,
Node *pParent,
PRTLISTANCHOR pListAnchor,
xmlNode *pLibNode,
xmlAttr *pLibAttr)
: m_Type(type)
, m_pParent(pParent)
, m_pLibNode(pLibNode)
, m_pLibAttr(pLibAttr)
, m_pcszNamespacePrefix(NULL)
, m_pcszNamespaceHref(NULL)
, m_pcszName(NULL)
, m_pParentListAnchor(pListAnchor)
{
RTListInit(&m_listEntry);
}
Node::~Node()
{
}
/**
* Returns the name of the node, which is either the element name or
* the attribute name. For other node types it probably returns NULL.
* @return
*/
const char *Node::getName() const
{
return m_pcszName;
}
/**
* Returns the name of the node, which is either the element name or
* the attribute name. For other node types it probably returns NULL.
* @return
*/
const char *Node::getPrefix() const
{
return m_pcszNamespacePrefix;
}
/**
* Returns the XML namespace URI, which is the attribute name. For other node types it probably
* returns NULL.
* @return
*/
const char *Node::getNamespaceURI() const
{
return m_pcszNamespaceHref;
}
/**
* Variant of nameEquals that checks the namespace as well.
* @param pcszNamespace
* @param pcsz
* @return
*/
bool Node::nameEquals(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.
*
* @return true if equal, false if not.
* @param pcszNamespace The name space prefix or NULL.
* @param pcsz The element name.
* @param cchMax The maximum number of character from @a pcsz to
* match.
*/
bool Node::nameEqualsN(const char *pcszNamespace, const char *pcsz, size_t cchMax) 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;
}
/**
* Copies the value of a node into the given integer variable.
* Returns TRUE only if a value was found and was actually an
* integer of the given type.
* @return
*/
bool Node::copyValue(int32_t &i) const
{
const char *pcsz;
if ( ((pcsz = getValue()))
&& (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 10, &i))
)
return true;
return false;
}
/**
* Copies the value of a node into the given integer variable.
* Returns TRUE only if a value was found and was actually an
* integer of the given type.
* @return
*/
bool Node::copyValue(uint32_t &i) const
{
const char *pcsz;
if ( ((pcsz = getValue()))
&& (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 10, &i))
)
return true;
return false;
}
/**
* Copies the value of a node into the given integer variable.
* Returns TRUE only if a value was found and was actually an
* integer of the given type.
* @return
*/
bool Node::copyValue(int64_t &i) const
{
const char *pcsz;
if ( ((pcsz = getValue()))
&& (VINF_SUCCESS == RTStrToInt64Ex(pcsz, NULL, 10, &i))
)
return true;
return false;
}
/**
* Copies the value of a node into the given integer variable.
* Returns TRUE only if a value was found and was actually an
* integer of the given type.
* @return
*/
bool Node::copyValue(uint64_t &i) const
{
const char *pcsz;
if ( ((pcsz = getValue()))
&& (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 10, &i))
)
return true;
return false;
}
/**
* Returns the line number of the current node in the source XML file.
* Useful for error messages.
* @return
*/
int Node::getLineNumber() const
{
if (m_pLibAttr)
return m_pParent->m_pLibNode->line;
return m_pLibNode->line;
}
/**
* Private element constructor.
*
* @param pElmRoot Pointer to the root element.
* @param pParent Pointer to the parent element (always an ElementNode,
* despite the type). NULL for the root node.
* @param pListAnchor Pointer to the m_children member of the parent. NULL
* for the root node.
* @param pLibNode Pointer to the libxml2 node structure.
*/
ElementNode::ElementNode(const ElementNode *pElmRoot,
Node *pParent,
PRTLISTANCHOR pListAnchor,
xmlNode *pLibNode)
: Node(IsElement,
pParent,
pListAnchor,
pLibNode,
NULL)
{
m_pElmRoot = pElmRoot ? pElmRoot : this; // If NULL is passed, then this is the root element.
m_pcszName = (const char *)pLibNode->name;
if (pLibNode->ns)
{
m_pcszNamespacePrefix = (const char *)m_pLibNode->ns->prefix;
m_pcszNamespaceHref = (const char *)m_pLibNode->ns->href;
}
RTListInit(&m_children);
RTListInit(&m_attributes);
}
ElementNode::~ElementNode()
{
Node *pCur, *pNext;
RTListForEachSafeCpp(&m_children, pCur, pNext, Node, m_listEntry)
{
delete pCur;
}
RTListInit(&m_children);
RTListForEachSafeCpp(&m_attributes, pCur, pNext, Node, m_listEntry)
{
delete pCur;
}
RTListInit(&m_attributes);
}
/**
* Gets the next tree element in a full tree enumeration.
*
* @returns Pointer to the next element in the tree, NULL if we're done.
* @param pElmRoot The root of the tree we're enumerating. NULL if
* it's the entire tree.
*/
ElementNode const *ElementNode::getNextTreeElement(ElementNode const *pElmRoot /*= NULL */) const
{
/*
* Consider children first.
*/
ElementNode const *pChild = getFirstChildElement();
if (pChild)
return pChild;
/*
* Then siblings, aunts and uncles.
*/
ElementNode const *pCur = this;
do
{
ElementNode const *pSibling = pCur->getNextSibilingElement();
if (pSibling != NULL)
return pSibling;
pCur = static_cast<const xml::ElementNode *>(pCur->m_pParent);
Assert(pCur || pCur == pElmRoot);
} while (pCur != pElmRoot);
return NULL;
}
/**
* Private implementation.
*
* @param pElmRoot The root element.
*/
/*static*/ void ElementNode::buildChildren(ElementNode *pElmRoot) // protected
{
for (ElementNode *pCur = pElmRoot; pCur; pCur = pCur->getNextTreeElement(pElmRoot))
{
/*
* Go thru this element's attributes creating AttributeNodes for them.
*/
for (xmlAttr *pLibAttr = pCur->m_pLibNode->properties; pLibAttr; pLibAttr = pLibAttr->next)
{
AttributeNode *pNew = new AttributeNode(pElmRoot, pCur, &pCur->m_attributes, pLibAttr);
RTListAppend(&pCur->m_attributes, &pNew->m_listEntry);
}
/*
* Go thru this element's child elements (element and text nodes).
*/
for (xmlNodePtr pLibNode = pCur->m_pLibNode->children; pLibNode; pLibNode = pLibNode->next)
{
Node *pNew;
if (pLibNode->type == XML_ELEMENT_NODE)
pNew = new ElementNode(pElmRoot, pCur, &pCur->m_children, pLibNode);
else if (pLibNode->type == XML_TEXT_NODE)
pNew = new ContentNode(pCur, &pCur->m_children, pLibNode);
else
continue;
RTListAppend(&pCur->m_children, &pNew->m_listEntry);
}
}
}
/**
* Builds a list of direct child elements of the current element that
* match the given string; if pcszMatch is NULL, all direct child
* elements are returned.
* @param children out: list of nodes to which children will be appended.
* @param pcszMatch in: match string, or NULL to return all children.
* @return Number of items appended to the list (0 if none).
*/
int ElementNode::getChildElements(ElementNodesList &children,
const char *pcszMatch /*= NULL*/)
const
{
int i = 0;
Node *p;
RTListForEachCpp(&m_children, p, Node, m_listEntry)
{
// export this child node if ...
if (p->isElement())
if ( !pcszMatch // ... the caller wants all nodes or ...
|| !strcmp(pcszMatch, p->getName()) // ... the element name matches.
)
{
children.push_back(static_cast<ElementNode *>(p));
++i;
}
}
return i;
}
/**
* Returns the first child element whose name matches pcszMatch.
*
* @param pcszNamespace Namespace prefix (e.g. "vbox") or NULL to match any namespace.
* @param pcszMatch Element name to match.
* @return
*/
const ElementNode *ElementNode::findChildElement(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->nameEquals(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 this->findChildElement(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(pcszNamespace, pcszPath, cchThis))
{
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->nameEquals(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->nameEquals(pcszNamespace, pcszMatch))
return pElem;
}
}
}
/**
* Looks up the given attribute node in this element's attribute map.
*
* With respect to namespaces, the internal attributes map stores namespace
* prefixes with attribute names only if the attribute uses a non-default
* namespace. As a result, the following rules apply:
*
* -- To find attributes from a non-default namespace, pcszMatch must not
* be prefixed with a namespace.
*
* -- To find attributes from the default namespace (or if the document does
* not use namespaces), pcszMatch must be prefixed with the namespace
* prefix and a colon.
*
* For example, if the document uses the "vbox:" namespace by default, you
* must omit "vbox:" from pcszMatch to find such attributes, whether they
* are specifed in the xml or not.
*
* @param pcszMatch
* @return
*/
const AttributeNode *ElementNode::findAttribute(const char *pcszMatch) const
{
AttributeNode *p;
RTListForEachCpp(&m_attributes, p, AttributeNode, m_listEntry)
{
if (p->nameEquals(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 (see findAttribute() for
* namespace remarks).
* @param ppcsz Where to return the attribute.
* @returns Boolean success indicator.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, const char **ppcsz) const
{
const AttributeNode *pAttr = findAttribute(pcszMatch);
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 (see findAttribute() for
* namespace remarks).
* @param rStr Reference to the string object that should receive the
* attribute value.
* @returns Boolean success indicator.
*
* @throws Whatever the string class may throw on assignment.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, RTCString &rStr) const
{
const AttributeNode *pAttr = findAttribute(pcszMatch);
if (pAttr)
{
rStr = pAttr->getValue();
return true;
}
return false;
}
/**
* Like getAttributeValue (ministring variant), but makes sure that all backslashes
* are converted to forward slashes.
* @param pcszMatch
* @param str
* @return
*/
bool ElementNode::getAttributeValuePath(const char *pcszMatch, RTCString &str) const
{
if (getAttributeValue(pcszMatch, str))
{
str.findReplace('\\', '/');
return true;
}
return false;
}
/**
* Convenience method which attempts to find the attribute with the given
* name and returns its value as a signed integer. This calls
* RTStrToInt32Ex internally and will only output the integer if that
* function returns no error.
*
* @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
* @param i out: attribute value; overwritten only if attribute was found
* @return TRUE if attribute was found and str was thus updated.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, int32_t &i) const
{
const char *pcsz;
if ( (getAttributeValue(pcszMatch, pcsz))
&& (VINF_SUCCESS == RTStrToInt32Ex(pcsz, NULL, 0, &i))
)
return true;
return false;
}
/**
* Convenience method which attempts to find the attribute with the given
* name and returns its value as an unsigned integer.This calls
* RTStrToUInt32Ex internally and will only output the integer if that
* function returns no error.
*
* @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
* @param i out: attribute value; overwritten only if attribute was found
* @return TRUE if attribute was found and str was thus updated.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, uint32_t &i) const
{
const char *pcsz;
if ( (getAttributeValue(pcszMatch, pcsz))
&& (VINF_SUCCESS == RTStrToUInt32Ex(pcsz, NULL, 0, &i))
)
return true;
return false;
}
/**
* Convenience method which attempts to find the attribute with the given
* name and returns its value as a signed long integer. This calls
* RTStrToInt64Ex internally and will only output the integer if that
* function returns no error.
*
* @param pcszMatch Name of attribute to find (see findAttribute() for
* namespace remarks).
* @param i Where to return the attribute value on success.
* @returns Boolean success indicator.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, int64_t *piValue) const
{
const char *pcsz = findAttributeValue(pcszMatch);
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 long integer.This calls
* RTStrToUInt64Ex internally and will only output the integer if that
* function returns no error.
*
* @param pcszMatch name of attribute to find (see findAttribute() for namespace remarks)
* @param i out: attribute value; overwritten only if attribute was found
* @return TRUE if attribute was found and str was thus updated.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, uint64_t &i) const
{
const char *pcsz;
if ( (getAttributeValue(pcszMatch, pcsz))
&& (VINF_SUCCESS == RTStrToUInt64Ex(pcsz, NULL, 0, &i))
)
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 (see findAttribute() for namespace remarks)
* @param f out: attribute value; overwritten only if attribute was found
* @return TRUE if attribute was found and str was thus updated.
*/
bool ElementNode::getAttributeValue(const char *pcszMatch, bool &f) const
{
const char *pcsz;
if (getAttributeValue(pcszMatch, pcsz))
{
if ( !strcmp(pcsz, "true")
|| !strcmp(pcsz, "yes")
|| !strcmp(pcsz, "1")
)
{
f = true;
return true;
}
if ( !strcmp(pcsz, "false")
|| !strcmp(pcsz, "no")
|| !strcmp(pcsz, "0")
)
{
f = false;
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;
}
/**
* 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
* @param i
* @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 pLibNode Pointer to the libxml2 node structure.
*/
AttributeNode::AttributeNode(const ElementNode *pElmRoot,
Node *pParent,
PRTLISTANCHOR pListAnchor,
xmlAttr *pLibAttr)
: Node(IsAttribute,
pParent,
pListAnchor,
NULL,
pLibAttr)
{
m_pcszName = (const char *)pLibAttr->name;
if ( pLibAttr->ns
&& pLibAttr->ns->prefix)
{
m_pcszNamespacePrefix = (const char *)pLibAttr->ns->prefix;
m_pcszNamespaceHref = (const char *)pLibAttr->ns->href;
if ( !pElmRoot->m_pcszNamespaceHref
|| strcmp(m_pcszNamespaceHref, pElmRoot->m_pcszNamespaceHref))
{
// not default namespace:
m_strKey = m_pcszNamespacePrefix;
m_strKey.append(':');
m_strKey.append(m_pcszName);
}
}
}
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
//
////////////////////////////////////////////////////////////////////////////////
XmlParserBase::XmlParserBase()
{
m_ctxt = xmlNewParserCtxt();
if (m_ctxt == NULL)
throw std::bad_alloc();
}
XmlParserBase::~XmlParserBase()
{
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();
if (!(doc.m->plibDocument = xmlCtxtReadMemory(m_ctxt,
(const char*)pvBuf,
(int)cbSize,
pcszFilename,
NULL, // encoding = auto
XML_PARSE_NOBLANKS | XML_PARSE_NONET)))
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;
}
////////////////////////////////////////////////////////////////////////////////
//
// 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();
}
};
struct ReadContext : IOContext
{
ReadContext(const char *pcszFilename)
: IOContext(pcszFilename, File::Mode_Read)
{
}
};
struct WriteContext : IOContext
{
WriteContext(const char *pcszFilename, bool fFlush)
: IOContext(pcszFilename, File::Mode_Overwrite, fFlush)
{
}
};
/**
* 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();
if (!(doc.m->plibDocument = xmlCtxtReadIO(m_ctxt,
ReadCallback,
CloseCallback,
&context,
pcszFilename,
NULL, // encoding = auto
XML_PARSE_NOBLANKS | XML_PARSE_NONET)))
throw XmlError(xmlCtxtGetLastError(m_ctxt));
doc.refreshInternals();
}
// static
int XmlFileParser::ReadCallback(void *aCtxt, char *aBuf, int aLen)
{
ReadContext *pContext = static_cast<ReadContext*>(aCtxt);
/* To prevent throwing exceptions while inside libxml2 code, we catch
* them and forward to our level using a couple of variables. */
try
{
return pContext->file.read(aBuf, aLen);
}
catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
catch (const RTCError &err) { pContext->setError(err); }
catch (const std::exception &err) { pContext->setError(err); }
catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
return -1 /* failure */;
}
int XmlFileParser::CloseCallback(void *aCtxt)
{
/// @todo to be written
NOREF(aCtxt);
return -1;
}
////////////////////////////////////////////////////////////////////////////////
//
// XmlFileWriter class
//
////////////////////////////////////////////////////////////////////////////////
struct XmlFileWriter::Data
{
Document *pDoc;
};
XmlFileWriter::XmlFileWriter(Document &doc)
{
m = new Data();
m->pDoc = &doc;
}
XmlFileWriter::~XmlFileWriter()
{
delete m;
}
void XmlFileWriter::writeInternal(const char *pcszFilename, bool fSafe)
{
WriteContext context(pcszFilename, fSafe);
GlobalLock lock;
/* serialize to the stream */
xmlIndentTreeOutput = 1;
xmlTreeIndentString = " ";
xmlSaveNoEmptyTags = 0;
xmlSaveCtxtPtr saveCtxt;
if (!(saveCtxt = xmlSaveToIO(WriteCallback,
CloseCallback,
&context,
NULL,
XML_SAVE_FORMAT)))
throw xml::LogicError(RT_SRC_POS);
long rc = xmlSaveDoc(saveCtxt, m->pDoc->m->plibDocument);
if (rc == -1)
{
/* look if there was a forwarded exception from the lower level */
// if (m->trappedErr.get() != NULL)
// m->trappedErr->rethrow();
/* there must be an exception from the Output implementation,
* otherwise the save operation must always succeed. */
throw xml::LogicError(RT_SRC_POS);
}
xmlSaveClose(saveCtxt);
}
void XmlFileWriter::write(const char *pcszFilename, bool fSafe)
{
if (!fSafe)
writeInternal(pcszFilename, fSafe);
else
{
/* Empty string and directory spec must be avoid. */
if (RTPathFilename(pcszFilename) == NULL)
throw xml::LogicError(RT_SRC_POS);
/* Construct both filenames first to ease error handling. */
char szTmpFilename[RTPATH_MAX];
int rc = RTStrCopy(szTmpFilename, sizeof(szTmpFilename) - strlen(s_pszTmpSuff), pcszFilename);
if (RT_FAILURE(rc))
throw EIPRTFailure(rc, "RTStrCopy");
strcat(szTmpFilename, s_pszTmpSuff);
char szPrevFilename[RTPATH_MAX];
rc = RTStrCopy(szPrevFilename, sizeof(szPrevFilename) - strlen(s_pszPrevSuff), pcszFilename);
if (RT_FAILURE(rc))
throw EIPRTFailure(rc, "RTStrCopy");
strcat(szPrevFilename, s_pszPrevSuff);
/* Write the XML document to the temporary file. */
writeInternal(szTmpFilename, fSafe);
/* Make a backup of any existing file (ignore failure). */
uint64_t cbPrevFile;
rc = RTFileQuerySize(pcszFilename, &cbPrevFile);
if (RT_SUCCESS(rc) && cbPrevFile >= 16)
RTFileRename(pcszFilename, szPrevFilename, RTPATHRENAME_FLAGS_REPLACE);
/* Commit the temporary file. Just leave the tmp file behind on failure. */
rc = RTFileRename(szTmpFilename, pcszFilename, RTPATHRENAME_FLAGS_REPLACE);
if (RT_FAILURE(rc))
throw EIPRTFailure(rc, "Failed to replace '%s' with '%s'", pcszFilename, szTmpFilename);
/* Flush the directory changes (required on linux at least). */
RTPathStripFilename(szTmpFilename);
rc = RTDirFlush(szTmpFilename);
AssertMsg(RT_SUCCESS(rc) || rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED, ("%Rrc\n", rc));
}
}
int XmlFileWriter::WriteCallback(void *aCtxt, const char *aBuf, int aLen)
{
WriteContext *pContext = static_cast<WriteContext*>(aCtxt);
/* To prevent throwing exceptions while inside libxml2 code, we catch
* them and forward to our level using a couple of variables. */
try
{
return pContext->file.write(aBuf, aLen);
}
catch (const xml::EIPRTFailure &err) { pContext->setError(err); }
catch (const RTCError &err) { pContext->setError(err); }
catch (const std::exception &err) { pContext->setError(err); }
catch (...) { pContext->setError(xml::LogicError(RT_SRC_POS)); }
return -1 /* failure */;
}
int XmlFileWriter::CloseCallback(void *aCtxt)
{
/// @todo to be written
NOREF(aCtxt);
return -1;
}
/*static*/ const char * const XmlFileWriter::s_pszTmpSuff = "-tmp";
/*static*/ const char * const XmlFileWriter::s_pszPrevSuff = "-prev";
} // end namespace xml