better serialization, but test does not work actually

master
Marc Wäckerlin 15 years ago
parent a9ad45ad4d
commit 37fd5e8695
  1. 92
      doc/examples/inherit_serialization.cxx
  2. 87
      doc/examples/serialization.cxx
  3. 243
      src/xml-cxx/xml.hxx
  4. 77
      src/xml.cxx
  5. 109
      test/serialization_test.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 <xml-cxx/xml.hxx>
#include <iostream>
#include <sstream>
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("<a>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</a>");
A a;
a._ser.loadXml(ss);
if (a.a==1234) a.a=4321;
a._ser.saveXml(std::cout)<<std::endl;
} { // Inherited Serialization
std::stringstream ss("<b>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</b>");
B b;
b.loadXml(ss);
if (b.a==1234) b.a=4321;
b.saveXml(std::cout)<<std::endl;
} { // External xml::Serialize: "ser" must live in no longer than "c"!
std::stringstream ss("<c>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</c>");
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)<<std::endl;
} { // Use xml::Serialize to store anything, e.g. local variables
// Important: "ser" must live in no longer than the variables!
std::stringstream ss("<d>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</d>");
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)<<std::endl;
}
return 0;
}

@ -5,46 +5,89 @@
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
// g++ -I../../src ../../src/xml.cxx node_macros.cxx
// g++ -I../../src ../../src/xml.cxx serialization.cxx
#include <xml-cxx/xml.hxx>
#include <iostream>
#include <sstream>
/*
template<class STREAM> class Stream: public STREAM {
public:
virtual ~Stream() {}
template<typename T> virtual Stream& operator%(T& o);
};
template<class STREAM> class IStream: public Stream<STREAM> {
//! Class with built in xml::Serialize as member, no inheritance
class A {
public:
virtual template<typename T> 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 STREAM> class OStream: public Stream<STREAM> {
//! Class that inherits xml::Serialize
class B: public xml::Serialize {
public:
virtual template<typename T> 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<class STREAM, typename TYPE>
STREAM& operator%(STREAM& s, TYPE& o);
template<class STREAM, typename TYPE>
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("<a>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</a>");
A a;
a._ser.loadXml(ss);
if (a.a==1234) a.a=4321;
a._ser.saveXml(std::cout)<<std::endl;
} { // Inherited Serialization
std::stringstream ss("<b>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</b>");
B b;
b.loadXml(ss);
if (b.a==1234) b.a=4321;
b.saveXml(std::cout)<<std::endl;
} { // External xml::Serialize: "ser" must live in no longer than "c"!
std::stringstream ss("<c>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</c>");
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)<<std::endl;
} { // Use xml::Serialize to store anything, e.g. local variables
// Important: "ser" must live in no longer than the variables!
std::stringstream ss("<d>\n"
"\t<a>1234</a>\n"
"\t<txt>Dies ist ein Serialisierungs-Test</txt>\n"
"</d>");
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)<<std::endl;
}
return 0;
}

@ -14,6 +14,8 @@
#include <vector>
#include <map>
#include <memory>
#include <typeinfo>
#include <stdexcept>
/*! @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 <code>void saveXml(std::ostream&) const</code>
- method <code>void loadXml(std::istream&)</code>
@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<xml::String&>(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<CLASS_NAME*>(this)->initXmlMembers(); \
xml::Node node(*_xmlFactory); \
for (std::vector<XmlSaveMethod>::const_iterator \
it(_saveXmlMemberList.begin()); \
it!=_saveXmlMemberList.end(); ++it) \
(this->*(*it))(node); \
os<<node; \
} \
CLASS_NAME& loadXml(std::istream& is) { \
initXmlMembers(); \
std::auto_ptr<xml::Node> node(_xmlFactory.read(is)); \
for (std::vector<XmlLoadMethod>::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<XmlSaveMethod> _saveXmlMemberList; \
std::vector<XmlLoadMethod> _loadXmlMemberList; \
void initXmlMembers() { \
if (_xmlFactory) return; \
xml::Node schema(#CLASS_NAME)
#define XML_INIT_MEMBER(CLASS_NAME, MEMBER) \
schema<<xml::String(#MEMBER).limits(1,1); \
_saveXmlMemberList.push_back(&CLASS_NAME::MEMBER##ToNode); \
_loadXmlMemberList.push_back(&CLASS_NAME::MEMBER##FromNode)
#define XML_INIT_END \
_xmlFactory = schema;\
} \
xml::Factory _xmlFactory
//@}
/*! @defgroup xmlConst XML Constant Declarations
There are macros to help you with declaring constants. Chose a C++
@ -777,7 +660,6 @@ namespace xml {
wrong_node_number);
private:
friend class stream_error;
Factory(const Factory&); // not implemented
bool ws(char c) throw();
std::auto_ptr<Node> 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 <code>void saveXml(std::ostream&) const</code>
- method <code>void loadXml(std::istream&)</code>
@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<typename TYPE>
Serialize& persist(TYPE& member, const std::string& name) {
mapName<TYPE>()[name] = &member;
mapMember<TYPE>()[&member] = name;
_xmlNames[name] = &typeid(TYPE);
xml::Node schema(*_xmlFactory);
schema<<xml::String(name).limits(1,1);
_xmlFactory = schema;
return *this;
}
std::ostream& saveXml(std::ostream& os) const throw();
std::istream& loadXml(std::istream& is);
protected:
virtual void initXmlMembers();
private:
template<typename TYPE> std::map<std::string, TYPE*>& mapName() const {
static std::map<std::string, TYPE*> MAP;
return MAP;
}
template<typename TYPE> std::map<TYPE*, std::string>& mapMember() const {
static std::map<TYPE*, std::string> MAP;
return MAP;
}
template<typename TYPE> void fromNode(TYPE* member, xml::Node& node) {
*member =
(TYPE)dynamic_cast<xml::String&>(node[mapMember<TYPE>()[member]]);
}
template<typename TYPE> void toNode(TYPE* member, xml::Node& node) const {
std::stringstream ss;
ss<<*member;
node[mapMember<TYPE>()[member]].text(ss.str());
}
std::map<std::string, const std::type_info*> _xmlNames;
xml::Factory _xmlFactory;
};
//@}
}
#endif

@ -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<Serialize*>(this)->initXmlMembers();
xml::Node node(*_xmlFactory);
for (std::map<std::string, const std::type_info*>::const_iterator
it(_xmlNames.begin());
it!=_xmlNames.end(); ++it) {
#define QWERTZ_CHECK_TYPE_ZTREWQ___XXX(TYPE) \
if (*it->second==typeid(TYPE)) \
toNode(mapName<TYPE>()[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;
return os;
}
std::istream& Serialize::loadXml(std::istream& is) {
if (!_xmlFactory) initXmlMembers();
std::auto_ptr<xml::Node> node(_xmlFactory.read(is));
for (std::map<std::string, const std::type_info*>::const_iterator
it(_xmlNames.begin());
it!=_xmlNames.end(); ++it) {
#define QWERTZ_CHECK_TYPE_ZTREWQ___XXX(TYPE) \
if (*it->second==typeid(TYPE)) \
fromNode(mapName<TYPE>()[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() {}
}

@ -0,0 +1,109 @@
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#include <xml-cxx/xml.hxx>
#include <cppunit/TestFixture.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
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("<A>\n"
"\t<anInteger>15</anInteger>\n"
"\t<aBool>1</aBool>\n"
"\t<aDouble>123.456</aDouble>\n"
"\t<aString>Hello World</aString>\n"
"\t<anotherString>This is"
" another Text</anotherString>\n"
"\t<aLong>4123674622</aLong>\n"
"</A>"),
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: "<<e.what()<<std::endl;
return 1;
}
Loading…
Cancel
Save