diff --git a/doc/examples/inherit_serialization.cxx b/doc/examples/inherit_serialization.cxx new file mode 100644 index 0000000..aa87943 --- /dev/null +++ b/doc/examples/inherit_serialization.cxx @@ -0,0 +1,92 @@ +/*! @file + + @id $Id$ +*/ +// 1 2 3 4 5 6 7 8 +// 45678901234567890123456789012345678901234567890123456789012345678901234567890 + +// g++ -I../../src ../../src/xml.cxx serialization.cxx + +#include +#include +#include + +class B: public xml::Serialize { + public: + int a; + std::string txt; + protected: + void initXmlMembers() { + className("b"); + persist(a, "a"); + persist(txt, "txt"); + } +}; + +class B: public xml::Serialize { + public: + int a; + std::string txt; + protected: + void initXmlMembers() { + className("b"); + persist(a, "a"); + persist(txt, "txt"); + } +}; + +//! Class with external xml::Serialize +class C { + public: + int a; + std::string txt; +}; + +int main(int, char**) { + { // Serialization as a member + std::stringstream ss("\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + A a; + a._ser.loadXml(ss); + if (a.a==1234) a.a=4321; + a._ser.saveXml(std::cout)<\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + B b; + b.loadXml(ss); + if (b.a==1234) b.a=4321; + b.saveXml(std::cout)<\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + C c; + xml::Serialize ser(xml::Serialize("c") + .persist(c.a, "a") + .persist(c.txt, "txt")); + ser.loadXml(ss); + if (c.a==1234) c.a=4321; + ser.saveXml(std::cout)<\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + int a; + std::string txt; + xml::Serialize ser(xml::Serialize("d") + .persist(a, "a") + .persist(txt, "txt")); + ser.loadXml(ss); + if (a==1234) a=4321; + ser.saveXml(std::cout)< #include #include -/* -template class Stream: public STREAM { - public: - virtual ~Stream() {} - template virtual Stream& operator%(T& o); -}; -template class IStream: public Stream { +//! Class with built in xml::Serialize as member, no inheritance +class A { public: - virtual template IStream& operator%(T& o) { - operator>>(o); - return *this; + A(): _ser("a") { + _ser.persist(a, "a"); + _ser.persist(txt, "txt"); } + int a; + std::string txt; + xml::Serialize _ser; }; -template class OStream: public Stream { +//! Class that inherits xml::Serialize +class B: public xml::Serialize { public: - virtual template OStream& operator%(T& o) { - operator<<(o); - return *this; + int a; + std::string txt; + protected: + void initXmlMembers() { + className("b"); + persist(a, "a"); + persist(txt, "txt"); } }; - */ - -template - STREAM& operator%(STREAM& s, TYPE& o); -template - STREAM& operator%(STREAM& s, TYPE& o); -class A { +//! Class with external xml::Serialize +class C { public: int a; std::string txt; }; int main(int, char**) { + { // Serialization as a member + std::stringstream ss("\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + A a; + a._ser.loadXml(ss); + if (a.a==1234) a.a=4321; + a._ser.saveXml(std::cout)<\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + B b; + b.loadXml(ss); + if (b.a==1234) b.a=4321; + b.saveXml(std::cout)<\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + C c; + xml::Serialize ser(xml::Serialize("c") + .persist(c.a, "a") + .persist(c.txt, "txt")); + ser.loadXml(ss); + if (c.a==1234) c.a=4321; + ser.saveXml(std::cout)<\n" + "\t1234\n" + "\tDies ist ein Serialisierungs-Test\n" + ""); + int a; + std::string txt; + xml::Serialize ser(xml::Serialize("d") + .persist(a, "a") + .persist(txt, "txt")); + ser.loadXml(ss); + if (a==1234) a=4321; + ser.saveXml(std::cout)< #include #include +#include +#include /*! @mainpage @@ -82,125 +84,6 @@ @example 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< read(std::istream& is, const Node& position) throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch, @@ -801,5 +683,126 @@ namespace xml { long _open; }; + /*! @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. + + @example serialization.cxx */ + //@{ + + class Serialize { + public: + //! You must call Serialize::className() if you use this constructor! + Serialize() throw(); + Serialize(const std::string& className) throw(); + virtual ~Serialize(); + void className(const std::string& name) throw(); + template + Serialize& persist(TYPE& member, const std::string& name) { + mapName()[name] = &member; + mapMember()[&member] = name; + _xmlNames[name] = &typeid(TYPE); + xml::Node schema(*_xmlFactory); + schema< std::map& mapName() const { + static std::map MAP; + return MAP; + } + template std::map& mapMember() const { + static std::map MAP; + return MAP; + } + template void fromNode(TYPE* member, xml::Node& node) { + *member = + (TYPE)dynamic_cast(node[mapMember()[member]]); + } + template void toNode(TYPE* member, xml::Node& node) const { + std::stringstream ss; + ss<<*member; + node[mapMember()[member]].text(ss.str()); + } + std::map _xmlNames; + xml::Factory _xmlFactory; + }; + + //@} + } + #endif diff --git a/src/xml.cxx b/src/xml.cxx index 013c726..8c5782d 100644 --- a/src/xml.cxx +++ b/src/xml.cxx @@ -260,6 +260,7 @@ namespace xml { same parent as the source of the copy. @see xml::Node::clone() for more information on the parenting. */ Node& Node::operator=(const Node& o) throw() { + clear(); _attributes=o._attributes; _name = o.name(); _min = o._min; @@ -1006,4 +1007,80 @@ namespace xml { return tag; } + //============================================================== Serialization + + //---------------------------------------------------------------------------- + Serialize::Serialize() throw() {} + Serialize::Serialize(const std::string& className) throw(): + _xmlFactory(xml::Node(xml::String(className).limits(1,1))) { + } + Serialize::~Serialize() {}; + void Serialize::className(const std::string& name) throw() { + _xmlFactory=xml::Node(xml::String(name).limits(1,1)); + } + std::ostream& Serialize::saveXml(std::ostream& os) const throw() { + if (!_xmlFactory) const_cast(this)->initXmlMembers(); + xml::Node node(*_xmlFactory); + for (std::map::const_iterator + it(_xmlNames.begin()); + it!=_xmlNames.end(); ++it) { +#define QWERTZ_CHECK_TYPE_ZTREWQ___XXX(TYPE) \ + if (*it->second==typeid(TYPE)) \ + toNode(mapName()[it->first], node); \ + else + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(std::string) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(float) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(double) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(bool) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(char) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed char) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned char) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(short) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed short) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned short) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(int) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed int) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned int) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(long) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed long) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned long) + throw std::runtime_error(it->second->name()); +#undef QWERTZ_CHECK_TYPE_ZTREWQ___XXX + } + os< node(_xmlFactory.read(is)); + for (std::map::const_iterator + it(_xmlNames.begin()); + it!=_xmlNames.end(); ++it) { +#define QWERTZ_CHECK_TYPE_ZTREWQ___XXX(TYPE) \ + if (*it->second==typeid(TYPE)) \ + fromNode(mapName()[it->first], *node); \ + else + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(std::string) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(float) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(double) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(bool) + //QWERTZ_CHECK_TYPE_ZTREWQ___XXX(char) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed char) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned char) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(short) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed short) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned short) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(int) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed int) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned int) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(long) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(signed long) + QWERTZ_CHECK_TYPE_ZTREWQ___XXX(unsigned long) + throw std::runtime_error(it->second->name()); +#undef QWERTZ_CHECK_TYPE_ZTREWQ___XXX + } + return is; + } + void Serialize::initXmlMembers() {} + } diff --git a/test/serialization_test.cxx b/test/serialization_test.cxx new file mode 100644 index 0000000..94115b4 --- /dev/null +++ b/test/serialization_test.cxx @@ -0,0 +1,109 @@ +/*! @file + + @id $Id$ +*/ +// 1 2 3 4 5 6 7 8 +// 45678901234567890123456789012345678901234567890123456789012345678901234567890 + +#include +#include +#include +#include +#include + +class A: public xml::Serialization { + public: + int _anInteger; + bool _aBool; + double _aDouble; + std::string _aString; + std::string _anotherString; + unsigned long _aLong; + protected: + void initXmlMembers() { + className("A"); + persist(_anInteger, "anInteger"); + persist(_aBool, "aBool"); + persist(_aDouble, "aDouble"); + persist(_aString, "aString"); + persist(_anotherString, "anotherString"); + persist(_aLong, "aLong"); + } +}; + +class SerializationTest: public CppUnit::TestFixture { + public: + std::string _file; + A _a; + void setUp() { + _a._anInteger = 15; + _a._aBool = true; + _a._aDouble = 123.456; + _a._aString = "Hello World"; + _a._anotherString = "This is another Text"; + _a._aLong = 4123674622; + try { + std::stringstream ss; + _a.saveXml(ss); + _file = ss.str(); + } catch (...) {} + } + void memberDeclaration() { + // This is more a compile time test than a runtime test. + A a; + a._anInteger = 15; + CPPUNIT_ASSERT_EQUAL(15, a._anInteger); + a._aString = "Hello World"; + CPPUNIT_ASSERT_EQUAL(std::string("Hello World"), a._aString); + std::stringstream ss; + } + void store() { + { + std::stringstream ss; + CPPUNIT_ASSERT_NO_THROW(_a.saveXml(ss)); + CPPUNIT_ASSERT_EQUAL(std::string("\n" + "\t15\n" + "\t1\n" + "\t123.456\n" + "\tHello World\n" + "\tThis is" + " another Text\n" + "\t4123674622\n" + ""), + ss.str()); + } { // again - initialisation of A should be done only once + std::stringstream ss; + CPPUNIT_ASSERT_NO_THROW(_a.saveXml(ss)); + CPPUNIT_ASSERT_EQUAL(_file, ss.str()); + } + } + void restore() { + A a; + std::stringstream ss(_file); + CPPUNIT_ASSERT(_file.size()>0); + CPPUNIT_ASSERT_EQUAL(_file, ss.str()); + CPPUNIT_ASSERT_NO_THROW(a.loadXml(ss)); + CPPUNIT_ASSERT_EQUAL(15, a._anInteger); + CPPUNIT_ASSERT_EQUAL(true, a._aBool); + CPPUNIT_ASSERT_EQUAL(123.456, a._aDouble); + CPPUNIT_ASSERT_EQUAL(std::string("Hello World"), a._aString); + CPPUNIT_ASSERT_EQUAL(std::string("This is another Text"), + a._anotherString); + CPPUNIT_ASSERT_EQUAL(4123674622ul, a._aLong); + } + CPPUNIT_TEST_SUITE(SerializationTest); + CPPUNIT_TEST(memberDeclaration); + CPPUNIT_TEST(store); + CPPUNIT_TEST(restore); + CPPUNIT_TEST_SUITE_END(); +}; +CPPUNIT_TEST_SUITE_REGISTRATION(SerializationTest); + +int main() try { + CppUnit::TextUi::TestRunner runner; + runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); + return runner.run() ? 0 : 1; + } catch (std::exception& e) { + std::cerr<<"***Exception: "<