From 94f5fd4970acab1dd45bf7022445da0749f1370b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=A4ckerlin?= Date: Wed, 22 Apr 2009 16:10:10 +0000 Subject: [PATCH] serialization; more docu --- src/xml-cxx/xml.hxx | 167 +++++++++++++++++++++++++++++++++++++++++++- src/xml.cxx | 86 ++++++++++++++++++++++- test/makefile.am | 3 +- test/xml_test.cxx | 2 +- 4 files changed, 252 insertions(+), 6 deletions(-) diff --git a/src/xml-cxx/xml.hxx b/src/xml-cxx/xml.hxx index c4b4abf..73d0a96 100644 --- a/src/xml-cxx/xml.hxx +++ b/src/xml-cxx/xml.hxx @@ -82,6 +82,147 @@ @example doc/examples/address.cxx */ +/*! @defgroup serialization Class Serialization + + @section serIntro Introduction + + Boost library (http://boost.org) offers a serialization framework, + which is able to serialize even complex class structures, you only + need to overwrite one or two serialization macros. The + disadvantages are that a lot of macros are needed and it becomes + quite complex as soon as you need inheritance. Also the generated + XML is not very enhanced, especially for Lists and + optional. Editing the boost serialization code by hand is a pain. + + Classes could also be serialized using gSOAP (http://gsoap.sf.net) + which is designed for the SOA-Protocol. This serialization is much + more flexible, but it requires a pseudo C++ declaration and a C++ + parser/generator. Also it has very bad memory management, since it + is plain C internally. + + Our requirements are: + - No precompiler, plain C++. + - Automatic memory management. + - Nice looking XML code that is easy to edit manually. + - Good error messages (exception) in case of bad XML files. + - As few ugly overflow as possible. + + @section serActual Actual Status + + Instead of: + + @code + class A { + protected: + int _anInteger; + bool _aBool; + double _aDouble; + std::string _aString; + std::string _anotherString; + unsigned long _aLong; + }; + @endcode + + You have to write: + + @code + class A { + protected: + XML_DECLARE_MEMBER(int, anInteger); + XML_DECLARE_MEMBER(bool, aBool); + XML_DECLARE_MEMBER(double, aDouble); + XML_DECLARE_MEMBER(std::string, aString); + XML_DECLARE_MEMBER(std::string, anotherString); + XML_DECLARE_MEMBER(unsigned long, aLong); + private: + XML_INIT_BEGIN(A); + XML_INIT_MEMBER(A, anInteger); + XML_INIT_MEMBER(A, aBool); + XML_INIT_MEMBER(A, aDouble); + XML_INIT_MEMBER(A, aString); + XML_INIT_MEMBER(A, anotherString); + XML_INIT_MEMBER(A, aLong); + XML_INIT_END; + }; + @endcode + + You get: + - All the members (with @c _ as member prefix) + - method void saveXml(std::ostream&) const + - method void loadXml(std::istream&) + + @todo Up to now: Only base types plus std::string are supported, + no list, no inheritance is possible and no optional members. */ +//@{ +#define XML_DECLARE_MEMBER(TYPE, MEMBER) \ + void MEMBER##FromNode(xml::Node& node) { \ + _##MEMBER = (TYPE)dynamic_cast(node[#MEMBER]); \ + } \ + void MEMBER##ToNode(xml::Node& node) const { \ + std::stringstream ss; \ + ss<<_##MEMBER; \ + node[#MEMBER].text(ss.str()); \ + } \ + TYPE _##MEMBER + +#define XML_INIT_BEGIN(CLASS_NAME) \ + const CLASS_NAME& saveXml(std::ostream& os) const { \ + const_cast(this)->initXmlMembers(); \ + xml::Node node(*_xmlFactory); \ + for (std::vector::const_iterator \ + it(_saveXmlMemberList.begin()); \ + it!=_saveXmlMemberList.end(); ++it) \ + (this->*(*it))(node); \ + os< node(_xmlFactory.read(is)); \ + for (std::vector::const_iterator \ + it(_loadXmlMemberList.begin()); \ + it!=_loadXmlMemberList.end(); ++it) \ + (this->*(*it))(*node); \ + } \ + private: \ + typedef void(CLASS_NAME::*XmlSaveMethod)(xml::Node&) const; \ + typedef void(CLASS_NAME::*XmlLoadMethod)(xml::Node&); \ + std::vector _saveXmlMemberList; \ + std::vector _loadXmlMemberList; \ + void initXmlMembers() { \ + if (_xmlFactory) return; \ + xml::Node schema(#CLASS_NAME) +#define XML_INIT_MEMBER(CLASS_NAME, MEMBER) \ + schema<XML_NODE(tagname) you can use + xml::node::tagname as constant std::string with + contents @c "tagname" in your code. + + @code + XML_NAME(base); + XML_NAME(child); + + int main(int, char**) { + xml::Factory test(xml::Node(xml::node::base) + return 0; + } + @endcode */ +#define XML_NODE(NAME) \ + namespace xml {\ + namespace node {\ + static const std::string xml::Node NAME(#name);\ + }\ + } + //! Represents classes for handling C++ access to XML files with strict schema. /*! The schema ist not presented through xsd, but it can be declared in C++. @@ -181,6 +322,13 @@ namespace xml { } }; //---------------------------------------------------------------------------- + class factory_not_valid: public exception { + public: + factory_not_valid() throw(): + exception("a factory must be given a template node") { + } + }; + //---------------------------------------------------------------------------- class no_parent: public exception { public: no_parent(const Node& t) throw(): exception("node has no parent", t) {} @@ -487,6 +635,18 @@ namespace xml { throw(); virtual String& append(const Node& o) throw(cannot_have_children); Node& operator=(const std::string& contents) throw(); + operator std::string() const throw(); + operator bool() const throw(); + operator signed char() const throw(); + operator unsigned char() const throw(); + operator signed short() const throw(); + operator unsigned short() const throw(); + operator signed int() const throw(); + operator unsigned int() const throw(); + operator signed long() const throw(); + operator unsigned long() const throw(); + operator float() const throw(); + operator double() const throw(); protected: std::string _text; }; @@ -508,7 +668,11 @@ namespace xml { class Factory { public: Factory(const Node& t) throw(); - const Node& operator*() const throw(); + Factory() throw(); + Factory& operator=(const Node& t) throw(); + Factory& append(const Node& node) throw(); + const Node& operator*() const throw(factory_not_valid); + operator bool() const throw(); friend std::ostream& operator<<(std::ostream& os, const Factory& factory) throw(); static std::ostream& print(std::ostream& os, const Node& node, @@ -522,7 +686,6 @@ namespace xml { wrong_node_number); private: friend class stream_error; - Factory(); // not implemented Factory(const Factory&); // not implemented bool ws(char c) throw(); std::auto_ptr read(std::istream& is, const Node& position) diff --git a/src/xml.cxx b/src/xml.cxx index 7a3f6ad..a859727 100644 --- a/src/xml.cxx +++ b/src/xml.cxx @@ -612,6 +612,75 @@ namespace xml { Node& String::operator=(const std::string& contents) throw() { return text(contents); } + String::operator std::string() const throw() { + return text(); + } + String::operator bool() const throw() { + bool res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator signed char() const throw() { + signed char res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator unsigned char() const throw() { + unsigned char res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator signed short() const throw() { + signed short res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator unsigned short() const throw() { + unsigned short res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator signed int() const throw() { + signed int res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator unsigned int() const throw() { + unsigned int res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator signed long() const throw() { + signed long res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator unsigned long() const throw() { + unsigned long res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator float() const throw() { + float res; + std::stringstream ss(text()); + ss>>res; + return res; + } + String::operator double() const throw() { + double res; + std::stringstream ss(text()); + ss>>res; + return res; + } //---------------------------------------------------------------------------- /*! @copydoc Node::Node(std::string name, @@ -707,8 +776,21 @@ namespace xml { _template[0].min(1); _template[0].max(1); } - const Node& Factory::operator*() const throw() { + Factory::Factory() throw(): + _template(xml::Node("")), _line(0) { + } + Factory& Factory::operator=(const Node& t) throw() { + _template = xml::Node("")<0; } std::ostream& operator<<(std::ostream& os, const Factory& factory) throw() { return factory.print(os, *factory); @@ -779,7 +861,7 @@ namespace xml { _open=0; std::auto_ptr node(read(is, _template)); if (node->children()==0) - throw tag_expected(_template[0], _template[0].name()); + throw tag_expected(_template[0], "nothing found, possibly empty stream"); return (*node)[0].clone(); } catch (exception& e) { e.line(_line); diff --git a/test/makefile.am b/test/makefile.am index 0f40071..e07c17c 100644 --- a/test/makefile.am +++ b/test/makefile.am @@ -7,10 +7,11 @@ AM_CXXFLAGS += -I ${top_srcdir}/src AM_LDFLAGS = -L${top_builddir}/src LDADD = -lcppunit -lxml-cxx -check_PROGRAMS=xml_test +check_PROGRAMS = xml_test serialization_test TESTS=${check_PROGRAMS} xml_test_SOURCES = xml_test.cxx +serialization_test_SOURCES = serialization_test.cxx CLEANFILES = MAINTAINERCLEANFILES = makefile.in diff --git a/test/xml_test.cxx b/test/xml_test.cxx index dece04d..58342ed 100644 --- a/test/xml_test.cxx +++ b/test/xml_test.cxx @@ -123,7 +123,7 @@ class StringTest: public CppUnit::TestFixture { } void operatorBrackets() { xml::String t("test"); - CPPUNIT_ASSERT_THROW(t["zxy"], xml::access_error); + CPPUNIT_ASSERT_THROW(t[std::string("zxy")], xml::access_error); } void text() { xml::String t("test");