Electively serialize to/from XML or binary in C++

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

Leave a Reply

Your email address will not be published. Required fields are marked *