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