XML Deserialization
Now that serialization was so easy, deserialization can’t get much harder, can it?
#ifndef VTE_CORE_XMLDESERIALIZER_H
#define VTE_CORE_XMLDESERIALIZER_H
#include "Deserializer.h"
#include <core/Exception.h>
#include <core/tinyxml/tinyxml.h>
namespace vte
{
namespace core
{
class XMLParseException : public Exception
{
public:
XMLParseException( const std::string& message )
: Exception( message )
{
}
};
class XMLDeserializer : public Deserializer
{
public:
XMLDeserializer( const std::string& fileName );
~XMLDeserializer();
void beginAttribute( const std::string& name );
u32 readNumberOfElements();
void readParameter( const std::string& name, std::string& value );
void readParameter( const std::string& name, s32& value, std::string& readable );
void readParameter( const std::string& name, bool& value );
void readParameter( const std::string& name, u32& value );
void readParameter( const std::string& name, f32& value );
void readParameter( const std::string& name, vec3& value );
void readParameter( const std::string& name, irr::video::SColorf& value );
void endAttribute();
protected:
const std::string getValue( const std::string& name );
std::string fileName;
TiXmlDocument doc;
TiXmlElement* currentElement;
};
} // namespace core
} // namespace vte
#endif
One important difference is that the deserializer has to know what to deserialize. In this case, we’ll pass an XML filename into the constructor. You could also pass a TiXmlDocument reference or whatever suits your needs.
vte::core::XMLDeserializer::XMLDeserializer( const std::string& fileName )
: fileName( fileName ),
currentElement( 0 )
{
bool result = doc.LoadFile( fileName );
if( !result )
{
std::ostringstream str;
str << "Unable to parse XML file '" << fileName << ":" << std::endl;
str << doc.ErrorDesc() << " in line " << doc.ErrorRow() << ", column " << doc.ErrorCol() << std::endl;
throw XMLParseException( str.str() );
}
}
vte::core::XMLDeserializer::~XMLDeserializer()
{
}
beginAttribute again starts the processing of a new child of currentElement or root.
void vte::core::XMLDeserializer::beginAttribute( const std::string& name )
{
if( currentElement == 0 )
{
currentElement = doc.FirstChild( name )->ToElement();
}
else
{
currentElement = currentElement->FirstChild( name )->ToElement();
}
VTE_ASSERT( currentElement != 0 );
}
readNumberOfAttributes now simply counts the number of children. Note that TinyXML counts comments as a separate node type, so we should exclude them.
u32 vte::core::XMLDeserializer::readNumberOfElements()
{
VTE_ASSERT( currentElement != 0 );
TiXmlNode* child = currentElement->FirstChild();
u32 count = 0;
while( child != 0 )
{
if( child->Type() != TiXmlNode::COMMENT )
{
count++;
}
child = child->NextSibling();
}
return count;
}
Deserialization of single attributes is nearly as easy as serialization. I wrote a little helper function which returns the value of an XML attribute of the current node or throws an exception if no such attribute was found.
const std::string vte::core::XMLDeserializer::getValue( const std::string& name )
{
const std::string* str = currentElement->Attribute( name );
if( 0 == str )
{
std::ostringstream str;
str << "Missing attribute '" << name << "' for element '" << currentElement->Value() << "' ";
str << "in line " << currentElement->Row() << ", column " << currentElement->Column() << " ";
str << "in file '" << fileName << "'";
throw XMLParseException( str.str() );
}
return *str;
}
Now the readParameter methods are easy to implement. There’s just one thing in readParameter( const string&, s32&, string& ) (the method I used for enum serialization, remember?). Whenever the deserialization hits a value which starts with a digit, it attempts to do a default lexical_cast to s32 instead of returning the descriptive string. This, of course, rules out the usage of descriptive enum aliases which start with a digit, so you might want to drop this feature.
void vte::core::XMLDeserializer::readParameter( const std::string& name, std::string& value )
{
VTE_ASSERT( currentElement != 0 );
value = getValue( name );
}
void vte::core::XMLDeserializer::readParameter( const std::string& name, s32& value, std::string& readable )
{
VTE_ASSERT( currentElement != 0 );
const std::string str = getValue( name );
if( str.find_first_not_of( "0123456789" ) != std::string::npos )
{
readable = str;
}
else
{
value = lexical_cast< s32 >( str );
}
}
Again, I leave the rest of the methods to you.
4 Comments