xml.cpp revision 9b2ac77c825d403359ac91e9606b91878646df9f
/** @file
* VirtualBox XML Manipulation API.
*/
/*
* Copyright (C) 2007-2009 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#include <libxml/xmlschemas.h>
#include <map>
#include <boost/shared_ptr.hpp>
////////////////////////////////////////////////////////////////////////////////
//
// globals
//
////////////////////////////////////////////////////////////////////////////////
/**
* 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 initiaizaton and cleanup. Thee 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
* let's leave it as is for informational purposes. */
/* Init libxml */
/* Save the default entity resolver before someone has replaced it */
}
~Global()
{
/* Shutdown libxml */
}
struct
{
/** Used to provide some thread safety missing in libxml2 (see e.g.
* XmlTreeBackend::read()) */
}
sxml; /* XXX naming this xml will break with gcc-3.3 */
}
namespace xml
{
////////////////////////////////////////////////////////////////////////////////
//
// Exceptions
//
////////////////////////////////////////////////////////////////////////////////
{
}
{
if (!aErr)
throw EInvalidArg(RT_SRC_POS);
}
/**
* Composes a single message for the given error. The caller must free the
* returned string using RTStrFree() when no more necessary.
*/
// static
{
/* strip spaces, trailing EOLs and dot-like char */
--msgLen;
return finalMsg;
}
: RuntimeError(NULL),
{
}
////////////////////////////////////////////////////////////////////////////////
//
// File Class
//
//////////////////////////////////////////////////////////////////////////////
{
Data()
char *fileName;
bool opened : 1;
};
: m (new Data())
{
throw ENoMemory();
unsigned flags = 0;
switch (aMode)
{
case Mode_Read:
break;
case Mode_WriteCreate: // fail if file exists
break;
case Mode_Overwrite: // overwrite if file exists
break;
case Mode_ReadWrite:
}
if (RT_FAILURE (vrc))
throw EIPRTFailure (vrc);
m->opened = true;
}
: m (new Data())
{
if (aHandle == NIL_RTFILE)
throw EInvalidArg (RT_SRC_POS);
if (aFileName)
{
throw ENoMemory();
}
setPos (0);
}
{
if (m->opened)
RTFileClose (m->handle);
}
{
return m->fileName;
}
{
uint64_t p = 0;
if (RT_SUCCESS (vrc))
return p;
throw EIPRTFailure (vrc);
}
{
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 */
{
}
/* seek the rest */
if (RT_SUCCESS (vrc))
if (RT_SUCCESS (vrc))
return;
throw EIPRTFailure (vrc);
}
{
if (RT_SUCCESS (vrc))
return (int)len;
throw EIPRTFailure (vrc);
}
{
if (RT_SUCCESS (vrc))
return (int)len;
throw EIPRTFailure (vrc);
return -1 /* failure */;
}
{
if (RT_SUCCESS (vrc))
return;
throw EIPRTFailure (vrc);
}
////////////////////////////////////////////////////////////////////////////////
//
// MemoryBuf Class
//
//////////////////////////////////////////////////////////////////////////////
{
Data()
const char *buf;
char *uri;
};
: m (new Data())
{
throw EInvalidArg (RT_SRC_POS);
}
{
}
{
return m->uri;
}
{
return m->pos;
}
{
throw EInvalidArg();
throw EInvalidArg();
}
{
return 0 /* nothing to read */;
return (int)len;
}
////////////////////////////////////////////////////////////////////////////////
//
// GlobalLock class
//
////////////////////////////////////////////////////////////////////////////////
struct GlobalLock::Data
{
Data()
: pOldLoader(NULL),
{
}
};
: m(new Data())
{
}
GlobalLock::~GlobalLock()
{
if (m->pOldLoader)
delete m;
m = NULL;
}
{
m->pOldLoader = xmlGetExternalEntityLoader();
}
// static
const char *aID,
{
}
////////////////////////////////////////////////////////////////////////////////
//
// Node class
//
////////////////////////////////////////////////////////////////////////////////
{
const char *pcszName; // element or attribute name, points either into plibNode or plibAttr;
// NULL if this is a content node
struct compare_const_char
{
{
}
};
// attributes, if this is an element; can be empty
// child elements, if this is an element; can be empty
};
m(new Data)
{
}
{
delete m;
}
{
// go thru this element's attributes
while (plibAttr)
{
// store
}
// go thru this element's child elements
while (plibNode)
{
else
// store
// recurse for this child element to get its own children
pNew->buildChildren();
}
}
{
return m->pcszName;
}
/**
* 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
*/
{
if ( (m->plibAttr)
)
// libxml hides attribute values in another node created as a
// single child of the attribute node, and it's in the content field
if ( (m->plibNode)
)
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
*/
{
const char *pcsz;
)
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
*/
{
const char *pcsz;
)
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
*/
{
const char *pcsz;
)
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
*/
{
const char *pcsz;
)
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)
}
{
}
/**
* 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).
*/
const char *pcszMatch /*= NULL*/)
const
{
int i = 0;
it,
++it)
{
// export this child node if ...
if ( (!pcszMatch) // the caller wants all nodes or
)
{
++i;
}
}
return i;
}
/**
* Returns the first child element whose name matches pcszMatch.
* @param pcszMatch
* @return
*/
const
{
it,
++it)
{
{
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.
*/
{
it,
++it)
{
{
const AttributeNode *pAttr;
)
return pelm;
}
}
return NULL;
}
/**
*
* @param pcszMatch
* @return
*/
{
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 str out: attribute value
* @return TRUE if attribute was found and str was thus updated.
*/
{
{
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.
* @param i out: attribute value
* @return TRUE if attribute was found and str was thus updated.
*/
{
const char *pcsz;
)
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.
* @param i out: attribute value
* @return TRUE if attribute was found and str was thus updated.
*/
{
const char *pcsz;
)
return true;
return false;
}
/**
* Creates a new child element node and appends it to the list
* of children in "this".
*
* @param pcszElementName
* @return
*/
{
// we must be an element, not an attribute
if (!m->plibNode)
throw ENodeIsNotElement(RT_SRC_POS);
// libxml side: create new node
(const xmlChar*)pcszElementName)))
throw ENoMemory();
// now wrap this in C++
ElementNode *p = new ElementNode;
return p;
}
/**
* Creates a content node and appends it to the list of children
* in "this".
*
* @param pcszElementName
* @return
*/
{
// libxml side: create new node
throw ENoMemory();
// now wrap this in C++
ContentNode *p = new ContentNode;
return p;
}
/**
* Sets the given attribute.
*
* 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 pcszValue
* @return
*/
{
{
// libxml side: xmlNewProp creates an attribute
// C++ side: create an attribute node around it
// store
}
else
{
// @todo
throw LogicError("Attribute exists");
}
return NULL;
}
: Node(IsAttribute)
{
}
{
}
/*
* NodesLoop
*
*/
{
};
{
m = new Data;
}
{
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::Node node; // should point to an element
* xml::NodesLoop loop(node, "child"); // find all "child" elements under node
* const xml::Node *pChild = NULL;
* while (pChild = loop.forAllNodes())
* ...;
* </code>
* @param node
* @param pcszMatch
* @return
*/
{
{
++(m->it);
}
return pNode;
}
////////////////////////////////////////////////////////////////////////////////
//
// Document class
//
////////////////////////////////////////////////////////////////////////////////
{
Data()
{
plibDocument = NULL;
pRootElement = NULL;
}
~Data()
{
reset();
}
void reset()
{
if (plibDocument)
{
plibDocument = NULL;
}
if (pRootElement)
{
delete pRootElement;
pRootElement = NULL;
}
}
{
if (p->plibDocument)
{
1); // recursive == copy all
}
}
};
: m(new Data)
{
}
: m(new Data)
{
m->copyFrom(x.m);
}
{
m->reset();
m->copyFrom(x.m);
return *this;
}
{
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.
*/
{
m->pRootElement = new ElementNode();
m->pRootElement->buildChildren();
}
/**
* Returns the root element of the document, or NULL if the document is empty.
* @return
*/
{
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.
*/
{
if (m->pRootElement || m->plibDocument)
throw EDocumentNotEmpty(RT_SRC_POS);
// libxml side: create document, create root node
(const xmlChar*)pcszRootElementName)))
throw ENoMemory();
// now wrap this in C++
m->pRootElement = new ElementNode();
return m->pRootElement;
}
////////////////////////////////////////////////////////////////////////////////
//
// XmlParserBase class
//
////////////////////////////////////////////////////////////////////////////////
{
m_ctxt = xmlNewParserCtxt();
throw ENoMemory();
}
{
}
////////////////////////////////////////////////////////////////////////////////
//
// XmlFileParser class
//
////////////////////////////////////////////////////////////////////////////////
struct XmlFileParser::Data
{
Data()
{
if (!(ctxt = xmlNewParserCtxt()))
}
~Data()
{
}
};
: XmlParserBase(),
m(new Data())
{
}
{
delete m;
m = NULL;
}
struct IOContext
{
{
}
{
}
{
}
};
struct ReadContext : IOContext
{
ReadContext(const char *pcszFilename)
{
}
};
struct WriteContext : IOContext
{
WriteContext(const char *pcszFilename)
{
}
};
/**
* 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 pcszFilename in: name fo file to parse.
* @param doc out: document to be reset and filled with data according to file contents.
*/
{
// global.setExternalEntityLoader(ExternalEntityLoader);
m->strXmlFilename = pcszFilename;
&context,
NULL, // encoding = auto
}
// static
{
/* To prevent throwing exceptions while inside libxml2 code, we catch
* them and forward to our level using a couple of variables. */
try
{
}
return -1 /* failure */;
}
{
/// @todo to be written
return -1;
}
////////////////////////////////////////////////////////////////////////////////
//
// XmlFileWriter class
//
////////////////////////////////////////////////////////////////////////////////
struct XmlFileWriter::Data
{
};
{
m = new Data();
}
{
delete m;
}
{
/* serialize to the stream */
xmlIndentTreeOutput = 1;
xmlTreeIndentString = " ";
xmlSaveNoEmptyTags = 0;
&context,
NULL,
if (rc == -1)
{
/* look if there was a forwared 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. */
}
}
{
/* To prevent throwing exceptions while inside libxml2 code, we catch
* them and forward to our level using a couple of variables. */
try
{
}
return -1 /* failure */;
}
{
/// @todo to be written
return -1;
}
} // end namespace xml