C++ class for reading and writing XML structures. No need for a C++ code parser or special pre compiler. Specify a schema entirly in native C++. The schema is verified when XML is read and exceptions are thrown when the XML to be parse is invalid.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1493 lines
61 KiB
1493 lines
61 KiB
/*! @file |
|
|
|
@id $Id$ |
|
*/ |
|
// 1 2 3 4 5 6 7 8 |
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
|
|
#ifndef LIB_XML_CXX_HXX |
|
#define LIB_XML_CXX_HXX |
|
|
|
#include <istream> |
|
#include <sstream> |
|
#include <string> |
|
#include <vector> |
|
#include <set> |
|
#include <map> |
|
#include <memory> |
|
#include <typeinfo> |
|
#include <stdexcept> |
|
#include <xml-cxx/any.hxx> |
|
|
|
//! @cond DEBUG |
|
#include <cassert> |
|
#include <iostream> |
|
#include <iomanip> |
|
namespace xml { |
|
class MethodTrace { |
|
public: |
|
MethodTrace(const void* addr, const std::string& name) noexcept: |
|
_addr(addr), _name(name) { |
|
std::clog<<std::hex<<std::setw(15)<<_addr<<": " |
|
<<std::dec<<std::setw(2+_level) |
|
<<std::setfill(' ')<<"\\ "<<_name<<std::endl; |
|
++_level; |
|
} |
|
~MethodTrace() noexcept { |
|
--_level; |
|
std::clog<<std::hex<<std::setw(15)<<_addr<<": "<<std::dec |
|
<<std::setw(2+_level)<<std::setfill(' ') |
|
<<"/ "<<_name<<std::endl; |
|
} |
|
private: |
|
const void* _addr; |
|
const std::string _name; |
|
static unsigned int _level; |
|
}; |
|
#define TRACE MethodTrace XXX_METHOD(this, __PRETTY_FUNCTION__) |
|
#define LOG(X) std::clog<<__PRETTY_FUNCTION__<<"\t**** "<<X<<std::endl |
|
#define ASSERT(X, Y) { \ |
|
if (!(Y)) { \ |
|
LOG(X); \ |
|
} \ |
|
assert(Y); \ |
|
} |
|
} |
|
//! @endcond |
|
|
|
/*! @page rationale Rationale - Limitations of other libraries |
|
|
|
The initial idea was to map C++ data structures to XML files |
|
(e.g. for configuration files) that can easily be edited by |
|
hand. This library does not need any kind of C++ code parser or |
|
proprietary pre compiler. You can specify a schema entirly in |
|
native C++. Access to the XML structures is through typical C++ |
|
operators which rresults in a simple and intuitive syntax. The |
|
schema is verified when XML is read and exceptions are thrown when |
|
the XML to be pares is invalid. Exceptions specify exactly the |
|
location and reason of the problem, so that the editor of the XML |
|
file can easily find and correct the problem. Due to the |
|
verification, it is not necessary to check every access: Only |
|
optional attributes and tags must be tested for their existence, |
|
before they can be accessed. |
|
|
|
There are a lot of different approaches for using XML in C++, all |
|
of them have their specific limitations. This library should be |
|
better. |
|
|
|
The design is based on my experiance with gsoap |
|
(http://gsoap.sf.net), boost serialization (http://boost.org) and |
|
Qt XML (http://qtsoftware.com). |
|
|
|
@section qtxml Qt XML, a typical DOM approach |
|
|
|
One is the XML part of the Qt library. These classes can read XML |
|
into a DOM tree, but then the user has to check for every detail. |
|
This looks like: |
|
|
|
@code |
|
QDomDocument doc; |
|
if (!doc.setContent(_http.readAll())); // error |
|
QDomNodeList releases(doc.elementsByTagName("release")); |
|
for(int i(0); i<releases.size(); ++i) |
|
if (releases.at(i).attributes().contains("type") && |
|
releases.at(i).attributes().namedItem("type").nodeValue()==_name) { |
|
_software = releases.at(i).firstChildElement("software"); |
|
_firmware = releases.at(i).firstChildElement("firmware"); |
|
if (_software.isNull() || _firmware.isNull() || |
|
releases.at(i).firstChildElement("notes").isNull()); // error |
|
... |
|
@endcode |
|
|
|
This is a typical example of a DOM parser. The main disadvantage |
|
here is that we cannot declare a schema. After parsing an XML |
|
file, we cannot know whether it is valid with respect to our |
|
definition or not. This means that every single access tested. |
|
|
|
xml::Factory lets you specify a schema template and guarantees |
|
that the parsed file passed a lot of tests to make sure it fits |
|
into the schema. If any test fails, the factory throws an |
|
exception. |
|
|
|
@section boostserialization Boost Serialization |
|
|
|
Boost serialization is quite flexible and easy to use, but there |
|
are several pitfalls, and worst, the generated XML cannot easily |
|
be edited by hand. One of the main problems: If you store lists, |
|
you cannot simply add an arbitrary number of list items, but you |
|
must first serialize the list size as a number. If you edit the |
|
file by hand, the number must exactly match the number of items, |
|
or parsing will fail. Error messages (the exceptions) don't help |
|
finding the problem within the parsed XML code. The XML format it |
|
generates is definitely not made to be edited by hand. |
|
|
|
In fact, in my project that resulted in this new class (CoMoL, see |
|
http://comol.sourceforge.net), we first used Boost serialization |
|
to read and write XML files. The configuration was then done on |
|
the GUI. But this was not comfortable enough, so the configuration |
|
was mostly edited by hand. It was a pain then to find any typos in |
|
the XML and the storage was too unflexible. So we needed a new |
|
apporach, and well here it is. CoMoL now uses the full flexibility |
|
of @ref freexml including optional tags and attributes. |
|
|
|
@section gsoap Using gSOAP for Serialization of C++ Structures |
|
|
|
When I was working at Siemens, we often used gSOAP |
|
(https://en.wikipedia.org/wiki/GSOAP) when we needed more flexibility in XML |
|
declaration than what's possible with @ref boostserialization. But |
|
gSOAP has several problems: |
|
|
|
- It is a C framework, not native C++, with all the problems |
|
that result from this, i.e. memory management is absolutely |
|
awful. |
|
- It is a quite a complex problem to copy a gSOAP structure |
|
without memory access problems. |
|
- Moreover gSOAP is not real C++ code, but it requires a pre |
|
processor that generates C++ from a pseudo C++ structure. |
|
- It is not designed to Store C++ in XML, but to implement the |
|
gSOAP protocol. |
|
- And last but not least, the license is not free for all usage. |
|
|
|
@example address.cxx Example |
|
|
|
This is a simple example on how to declare a XML schema and how to |
|
use a xml::Factory to restore it from a file. */ |
|
|
|
//! @addtogroup freexml |
|
//@{ |
|
/*! @defgroup xmlConst XML Constant Declarations |
|
|
|
There are macros to help you with declaring constants. Chose a C++ |
|
header file, where you want to declare constant names for your xml |
|
nodes. |
|
|
|
Then for xml::Node you will use, call XML_NODE(name) and for every |
|
xml::String call XML_STRING(othername). After the declaration, you |
|
can use the xml::Node as constant @c xml::node::name, the |
|
xml::String as constant @c xml::string::name and @c std::string |
|
constants for the given node names as @c xml::name::name and @c |
|
xml::name::othername. |
|
|
|
@note If you want to use the xml::Node, xml::String constants in a |
|
non constant environment, i.e. to add children, attributes or |
|
limits, you must call xml::Node::clone to get a non constant copy. |
|
|
|
@note Node names must be unique. You can not even use the same |
|
name for a XML_NODE and a XML_STRING declaration. |
|
|
|
@see @ref node_macros.cxx |
|
|
|
@example node_macros.cxx |
|
|
|
The example code is equivalent to: |
|
|
|
@code |
|
int main(int, char**) { |
|
xml::Factory test(xml::Node("base") |
|
<<(xml::Node("child") |
|
<<xml::String("element")); |
|
std::stringstream ss("<base>\n" |
|
" <child>\n" |
|
" <element>Hello</element>\n" |
|
" </child>\n" |
|
"</base>"); |
|
std::unique_ptr<xml::Node> file(test.read(ss)); |
|
std::cout<<"The element is: " |
|
<<(*file)["child"]["element"] |
|
<<std::endl; |
|
return 0; |
|
} |
|
@endcode |
|
|
|
The advantage is the fact that you have no more quoted strings to |
|
cope with. So your potential runtime errors through typos become |
|
compile time errors, which is more robust and easier to find. If |
|
you want to write less code, you are free to use <code>using |
|
namespace xml::name</code> in your C++ implementation files, since |
|
names are more often used than nodes. (@em Never use @c using in a |
|
C++ header file, you would pollute the scope of all the |
|
includers.) */ |
|
//@} |
|
//! @addtogroup xmlConst |
|
//@{ |
|
|
|
//! Define a string for a node name |
|
/*! It is called inside XML_NODE and XML_STRING, so if you work with |
|
these two, you don't have to care about XML_NAME. But you can use |
|
XML_NAME alone if you don't want the other two macros. |
|
|
|
Declares a constant of type @c std::string with name @c xml::name::NAME. |
|
|
|
@see XML_NODE |
|
@see XML_STRING */ |
|
#define XML_NAME(NAME) \ |
|
namespace xml {\ |
|
namespace name {\ |
|
static const std::string NAME(#NAME); \ |
|
}\ |
|
} |
|
|
|
//! Define a constant for a xml::Node and for a string containing its name |
|
/*! Put this macro in the global part of your header files. After |
|
declaration of e.g. <code>XML_NODE(tagname)</code> you can use |
|
<code>xml::node::tagname</code> as constant xml::Node and |
|
<code>xml::name::tagname</code> as a constant std::string with |
|
contents @c "tagname" in your code. |
|
@see XML_STRING same for xml::String |
|
@see XML_NAME called by XML_NODE */ |
|
#define XML_NODE(NAME) \ |
|
XML_NAME(NAME);\ |
|
namespace xml {\ |
|
namespace node {\ |
|
static const xml::Node NAME(#NAME);\ |
|
}\ |
|
} |
|
|
|
//! Define a constant for a xml::String and for a string containing its name |
|
/*! Put this macro in the global part of your header files. After |
|
declaration of e.g. <code>XML_STRING(tagname)</code> you can use |
|
<code>xml::string::tagname</code> as constant xml::String and |
|
<code>xml::name::tagname</code> as a constant std::string with |
|
contents @c "tagname" in your code. |
|
@see XML_NODE same for xml::Node |
|
@see XML_NAME called by XML_STRING */ |
|
#define XML_STRING(NAME) \ |
|
XML_NAME(NAME);\ |
|
namespace xml {\ |
|
namespace string {\ |
|
static const xml::String NAME(#NAME);\ |
|
}\ |
|
} |
|
|
|
//! @todo Define a constant for a xml::Node and for a string containing its name |
|
/*! Put this macro in the global part of your header files. After |
|
declaration of e.g. <code>XML_NODE(tagname)</code> you can use |
|
<code>xml::node::tagname</code> as constant xml::Node and |
|
<code>xml::name::tagname</code> as a constant std::string with |
|
contents @c "tagname" in your code. |
|
@see XML_STRING same for xml::String |
|
@see XML_NAME called by XML_NODE */ |
|
#define XML_PARENT(NAME, ...) \ |
|
XML_NAME(NAME);\ |
|
namespace xml {\ |
|
namespace node {\ |
|
static const xml::Node NAME(#NAME);\ |
|
}\ |
|
} |
|
|
|
//@} |
|
|
|
/*! @defgroup freexml Arbitrary XML Schema Definition and Storage |
|
|
|
Class xml::Node declares an XML DOM node. Storing XML structures |
|
has never been a problem, but to read them back again, |
|
xml::Factory is needed, which must be given an XML schema |
|
description. The XML schema is fully declared in C++, simply by |
|
shifting the allowed nodes and attributes into the factory and by |
|
setting limits. |
|
|
|
A xml::Factory represents a factory that owns a template and can |
|
instanciate XML trees that are valid for the given template from |
|
streams. If anything is not valid, an exception is thrown. The @c |
|
what() method of the exception gives additional information about |
|
the problem. |
|
|
|
In the following example, we want to represent XML data that are |
|
contained in a <persons> tag, and may contain a list of @c |
|
person. Each @c person has a mandatory attribute @c id and |
|
optional @c member-of. @c person has a @c name and may contain a |
|
list of @c friends, where each @c friend has an attribute @c |
|
id. (The @c id attribute of course should reference to the @c id |
|
of another @c name, but this relationship cannot be declared.) |
|
|
|
All tags are by default specified as 0..n (optional and any number |
|
there of). |
|
|
|
@code |
|
#include <xml-cxx/xml.hxx> |
|
#include <iostream> |
|
[...] |
|
xml::Factory test(xml::Node("persons") // root node |
|
<<(xml::Node("person") // child of persons |
|
.attr("id", xml::mandatory) |
|
.attr("member-of", xml::optional)) |
|
<<xml::String("name") // the person's name |
|
<<(xml::Node("friends") // friends of person |
|
<<(xml::Node("friend") // a friend |
|
.attr("id", xml::mandatory))))); |
|
[...] |
|
try { |
|
std::unique_ptr<xml::Node> persons(test.read(std::ifstream("file.xml))); |
|
// Here we can be sure, that our structure is valid, |
|
// but we must check optional elements before access, otherwise |
|
// we get an exception. |
|
[...] |
|
for (xml::Node::size_type i(0); i<persons.children(); ++i) { |
|
std::cout<<"Person: "<<*persons[i]["name"]; // exception if no "name" |
|
if (persons[i]("friends")) // check if "friends" is set |
|
std::cout<<" has "<<persons[i]["friends"].children()<<" friends" |
|
else |
|
std::cout<<" has no friend list"; |
|
std::cout<<std::endl; |
|
} |
|
[...] |
|
} catch (const std::exception& x) { |
|
std::cerr<<"**** Error in file \"file.xml\":"<<std::endl |
|
<<x.what()<<std::endl; |
|
} |
|
@endcode */ |
|
|
|
//! Everything is in namespace xml |
|
namespace xml { |
|
|
|
std::string version(); |
|
|
|
//! @addtogroup freexml |
|
//@{ |
|
|
|
//! @cond INTERNAL |
|
|
|
//============================================================================ |
|
//! Type of an xml node. |
|
/*! Only start nodes and empty nodes may have attributes. */ |
|
enum NodeType { |
|
START, //!< start node, such as <code><node></code> |
|
END, //!< end node, such as <code></node></code> |
|
EMPTY, //!< empty node, such as <code><node/></code> |
|
SPECIAL //!< special node, such as |
|
//! a comment <code><!-- ... --></code>, |
|
//! a xml start indication <code><?xml?></code> |
|
//! or a document type declaration <code><!DOCTYPE ...></code> |
|
}; |
|
|
|
//! @endcond |
|
|
|
//! Declares an attribute to be mandatory. |
|
const bool mandatory(true); |
|
//! Declares an attribute to be optional. |
|
const bool optional(false); |
|
|
|
//================================================================= EXCEPTIONS |
|
struct Tag; |
|
class Attributes; |
|
class Node; |
|
class Factory; |
|
|
|
//@} |
|
//! @defgroup exceptions Exception classes |
|
//@{ |
|
|
|
//---------------------------------------------------------------------------- |
|
class exception: public std::exception { |
|
public: |
|
exception(std::string reason) noexcept; |
|
exception(std::string reason, const Node& t) noexcept; |
|
~exception() noexcept; |
|
void line(unsigned long line) noexcept; |
|
const char* what() const noexcept; |
|
private: |
|
std::string _what; |
|
Node* _node; |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class type_not_registered: public exception { |
|
public: |
|
type_not_registered(std::string type, std::string name, const Node& t): |
|
exception("serialized node type is not registered\ntype: " |
|
+type+"\nname: "+name, t) { |
|
} |
|
type_not_registered(std::string type): |
|
exception("serialized node type is not registered\ntype: "+type) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class empty_attribute_list: public exception { |
|
public: |
|
empty_attribute_list(const std::string& name) noexcept: |
|
exception("list of attribute is empty access to first element failed" |
|
"\nattribute: "+name) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class factory_not_valid: public exception { |
|
public: |
|
factory_not_valid() noexcept: |
|
exception("a factory must be given a template node") { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class no_parent: public exception { |
|
public: |
|
no_parent(const Node& t) noexcept: exception("node has no parent", t) {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class tag_expected: public exception { |
|
public: |
|
tag_expected(const Node& t, const std::string& txt) noexcept: |
|
exception("tag ('<') expected, text not allowed\ntext: "+txt, t) {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class type_mismatch: public exception { |
|
public: |
|
type_mismatch(const Node& t, const std::string& txt, |
|
const std::string& comment) noexcept: |
|
exception("wrong type, text contains mismatching character\n"+comment |
|
+"\ntext: "+txt, t) {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class access_error: public exception { |
|
public: |
|
access_error(const Node& t, const std::string& name) noexcept; |
|
~access_error() noexcept {} |
|
const char* what() const noexcept; |
|
private: |
|
std::string _name; |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class out_of_range: public exception { |
|
public: |
|
out_of_range(const Node& t, size_t pos) noexcept; |
|
~out_of_range() noexcept {} |
|
const char* what() const noexcept; |
|
private: |
|
size_t _pos; |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class cannot_have_children: public exception { |
|
public: |
|
cannot_have_children(const Node& parent, const Node& child) noexcept; |
|
~cannot_have_children() noexcept; |
|
const char* what() const noexcept; |
|
private: |
|
Node* _child; |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class attribute_not_available: public exception { |
|
public: |
|
attribute_not_available(const Node& t, const std::string& attr) noexcept: |
|
exception("attribute \""+attr+"\" not set", t) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class stream_error: public exception { |
|
public: |
|
stream_error(const std::string& reason, const Node& t, |
|
std::istream& is, const Tag& tag, char c=0) noexcept; |
|
stream_error(const std::string& reason, const Node& t, |
|
std::istream& is) noexcept; |
|
~stream_error() noexcept; |
|
const char* what() const noexcept; |
|
private: |
|
std::streampos _pos; |
|
Tag* _tag; |
|
char _char; |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class wrong_end_tag: public stream_error { |
|
public: |
|
wrong_end_tag(const Node& t, std::istream& is, const Tag& tag, char c=0) |
|
noexcept: |
|
stream_error("mismatching end tag", t, is, tag, c) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class missing_end_tag: public stream_error { |
|
public: |
|
missing_end_tag(const Node& t, std::istream& is, const Tag& tag, char c=0) |
|
noexcept: |
|
stream_error("missing end tag, end of file reached", t, is, tag, c) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class wrong_start_tag: public stream_error { |
|
public: |
|
wrong_start_tag(const Node& t, std::istream& is, const Tag& tag, char c=0) |
|
noexcept: |
|
stream_error("start tag does not match expected tag", t, is, tag, c) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class second_slash_in_tag: public stream_error { |
|
public: |
|
second_slash_in_tag(const Node& t, std::istream& is, const Tag& tag, |
|
char c=0) |
|
noexcept: |
|
stream_error("a tag may have no more than one slash", t, is, tag, c) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class character_after_slash: public stream_error { |
|
public: |
|
character_after_slash(const Node& t, std::istream& is, const Tag& tag, |
|
char c=0) |
|
noexcept: |
|
stream_error("unexpected character after empty-slash", |
|
t, is, tag, c) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class attributes_in_end_tag: public stream_error { |
|
public: |
|
attributes_in_end_tag(const Node& t, std::istream& is, const Tag& tag) |
|
noexcept: |
|
stream_error("attributes are not allowed in end tags",t, is, tag) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class attribute_value_not_quoted: public stream_error { |
|
public: |
|
attribute_value_not_quoted(const Node& t, std::istream& is, |
|
const Tag& tag, |
|
char c, std::string attr) noexcept: |
|
stream_error("attribute values must be quoted (\")\nattribute: "+attr, |
|
t, is, tag, c) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class duplicate_attribute: public stream_error { |
|
public: |
|
duplicate_attribute(const Node& t, std::istream& is, const Tag& tag, |
|
std::string attr) noexcept: |
|
stream_error("attribute duplicated\nattribute: "+attr, |
|
t, is, tag, 0) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class illegal_attribute: public stream_error { |
|
public: |
|
illegal_attribute(const Node& t, std::istream& is, const Tag& tag, |
|
std::string attr) noexcept: |
|
stream_error("illegal attribute found\nattribute: "+attr, |
|
t, is, tag, 0) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class mandatory_attribute_missing: public stream_error { |
|
public: |
|
mandatory_attribute_missing(const Node& t, std::istream& is, |
|
const Tag& tag, std::string attr) noexcept: |
|
stream_error("mandatory attribute missing\nattribute: "+attr, |
|
t, is, tag, 0) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class wrong_node_number: public stream_error { |
|
public: |
|
wrong_node_number(const Node& t, std::istream& is, |
|
const std::string& name, |
|
unsigned long num, |
|
unsigned long min, unsigned long max) noexcept: |
|
stream_error("wrong number of child nodes\nname of child: "+name |
|
+"\nnumber of nodes: "+conv(num) |
|
+"\nminimuml number: "+conv(min) |
|
+"\nmaximum number: "+conv(max), t, is) { |
|
} |
|
private: |
|
static std::string conv(unsigned long i) noexcept { |
|
std::stringstream ss; |
|
ss<<i; |
|
return ss.str(); |
|
} |
|
}; |
|
//! @} |
|
|
|
//============================================================================ |
|
|
|
//! @addtogroup freexml |
|
//@{ |
|
|
|
//---------------------------------------------------------------------------- |
|
//! Map for attribute values. |
|
/*! Attributes can be set using method xml::Node::attr(). Check for |
|
an attribute with xml::Node::hasAttr(). Attributes must be |
|
unique, which means that every attribute must be set at maximum |
|
once. This is corect: <code><node |
|
attribute="value"></code>, this is not allowed: |
|
<code><node attribute="value" attribute="value"></code> */ |
|
class Attributes: public std::map<std::string, std::string> { |
|
public: |
|
//! Attributes may contain a list of space separated values. |
|
typedef std::vector<std::string> List; |
|
//! Attribute values ar mainly a std::pair. |
|
/*! In addition to a normal std::pair, attributes offer an |
|
assignment operator to set the value, and can be constructed |
|
as empty attribute, given only a key. |
|
@note Simply use xml::Attr instead of xml::Attributes::Value. */ |
|
class Value: public value_type { |
|
public: |
|
Value(const value_type& o) noexcept; |
|
Value(const std::string& name) noexcept; |
|
Value(const std::string& name, const std::string& namevalue) noexcept; |
|
Value& operator=(const std::string& value) noexcept; |
|
const std::string& name() const noexcept; |
|
const std::string& value() const noexcept; |
|
std::string& value() noexcept; |
|
operator bool() const noexcept; |
|
bool toBool() const noexcept; |
|
operator unsigned long() const noexcept; |
|
unsigned long toNumber() const noexcept; |
|
operator List() const noexcept; |
|
List toList(const std::string& separators=" \t\n\r") const noexcept; |
|
std::string front(const std::string& separators=" \t\n\r") const; |
|
private: |
|
Value(); // not implemented, key must always be given |
|
}; |
|
Attributes() noexcept; |
|
Attributes(const std::string& empty) noexcept; |
|
Attributes(const std::string& key, const std::string& value) noexcept; |
|
Attributes& operator<<(const Value& v) noexcept; |
|
Attributes& operator<<(const std::string& key) noexcept; |
|
}; |
|
//! Simplification: Use xml::Attr instead of xml::Attributes::Value. |
|
typedef Attributes::Value Attr; |
|
|
|
|
|
//---------------------------------------------------------------------------- |
|
//! @internal structure for parsing tags |
|
struct Tag { |
|
std::string name; |
|
NodeType type; |
|
std::string text; |
|
Attributes attributes; |
|
std::string special; |
|
bool found; |
|
}; |
|
|
|
//---------------------------------------------------------------------------- |
|
//! An xml Node that contains child nodes but no text. |
|
/*! XML Nodes may contain either text or other nodes, but not both |
|
at the same time. This node can hold other nodes. For a Node for |
|
text contents, see xml::String. */ |
|
class Node { |
|
private: |
|
typedef std::vector<Node*> Contents; |
|
public: |
|
typedef Contents::size_type size_type; |
|
typedef std::vector<Node*> List; |
|
Node(std::string name, size_type min=0, size_type max=0) noexcept; |
|
Node(const Node& o) noexcept; |
|
virtual ~Node() noexcept; |
|
virtual Node& operator=(const Node& o) noexcept; |
|
virtual std::unique_ptr<Node> clone() const noexcept; |
|
virtual std::ostream& out(std::ostream& o, unsigned int level=0) const |
|
noexcept; |
|
virtual std::string text() const noexcept; |
|
virtual Node& text(const std::string& txt); |
|
virtual Node& append(const Node& o); |
|
virtual Node& remove(Node& n); |
|
virtual Node& remove(const std::string& n); |
|
virtual Node& remove(size_type n); |
|
virtual Node& set(const Attributes& o) noexcept; |
|
Node& clear() throw (); |
|
std::string name() const noexcept; |
|
Node& name(const std::string& n) noexcept; |
|
Node& min(size_type m) noexcept; |
|
size_type min() const noexcept; |
|
Node& max(size_type m) noexcept; |
|
size_type max() const noexcept; |
|
bool isChild() const noexcept; |
|
Node& parent() const; |
|
bool hasAttr(const std::string& name) const noexcept; |
|
Node& attr(const std::string& name, bool mandatory) noexcept; |
|
Node& attr(const std::string& name, const std::string& deflt) noexcept; |
|
std::string attr(const std::string& name) const noexcept; |
|
std::string& attr(const std::string& name) noexcept; |
|
const Attributes::Value attribute(const std::string& name) const; |
|
const Attributes& attributes() const noexcept; |
|
Attributes& attributes() noexcept; |
|
const Node& first() const; |
|
Node& first(); |
|
const Node& last() const; |
|
Node& last(); |
|
Node& limits(size_type min=0, size_type max=0) noexcept; |
|
List list(const std::string& name) const noexcept; |
|
bool operator()(const std::string& child) const noexcept; |
|
Node& operator<<(const Node& o); |
|
Node& operator<<(const Attributes& o) noexcept; |
|
size_type children() const noexcept; |
|
const Node& operator[](size_type child) const; |
|
Node& operator[](size_type child); |
|
const Node& operator[](const std::string& child) const; |
|
Node& operator[](const std::string& child); |
|
std::string operator*() const noexcept; |
|
Node& operator=(const std::string& contents); |
|
friend std::ostream& operator<<(std::ostream& o, const Node& t) noexcept; |
|
protected: |
|
Attributes _attributes; |
|
private: |
|
Node* find(const std::string& child) const noexcept; |
|
virtual std::unique_ptr<Node> clone(Node* p) const noexcept; |
|
Node(); // not implemented |
|
Contents _contents; |
|
std::string _name; |
|
Node* _parent; |
|
size_type _min; |
|
size_type _max; |
|
}; |
|
|
|
//---------------------------------------------------------------------------- |
|
//! A leaf node that contains text but no child nodes. |
|
class String: public Node { |
|
public: |
|
String(std::string name, |
|
Node::size_type min=0, Node::size_type max=0) noexcept; |
|
String(std::string name, const std::string& text, |
|
Node::size_type min=0, Node::size_type max=0) noexcept; |
|
virtual ~String() noexcept {} |
|
virtual std::unique_ptr<Node> clone() const noexcept; |
|
virtual std::string text() const noexcept; |
|
virtual String& text(const std::string& txt); |
|
virtual std::ostream& out(std::ostream& o, unsigned int level=0) const |
|
noexcept; |
|
virtual String& append(const Node& o); |
|
Node& operator=(const std::string& contents) noexcept; |
|
operator std::string() const noexcept; |
|
operator bool() const noexcept; |
|
operator char() const noexcept; |
|
operator signed char() const noexcept; |
|
operator unsigned char() const noexcept; |
|
operator signed short() const noexcept; |
|
operator unsigned short() const noexcept; |
|
operator signed int() const noexcept; |
|
operator unsigned int() const noexcept; |
|
operator signed long() const noexcept; |
|
operator unsigned long() const noexcept; |
|
operator float() const noexcept; |
|
operator double() const noexcept; |
|
protected: |
|
std::string _text; |
|
}; |
|
|
|
//---------------------------------------------------------------------------- |
|
//! A leaf node that contains only numbers and no child nodes. |
|
class UnsignedInteger: public String { |
|
public: |
|
UnsignedInteger(std::string name, unsigned long i=0, |
|
size_type min=0, size_type max=0) noexcept; |
|
virtual std::unique_ptr<Node> clone() const noexcept; |
|
virtual ~UnsignedInteger() noexcept {} |
|
virtual UnsignedInteger& text(const std::string& txt); |
|
unsigned long number() const noexcept; |
|
static unsigned long number(const Node& node) noexcept; |
|
}; |
|
|
|
//---------------------------------------------------------------------------- |
|
//! Factory to restore XML structures from a stream. |
|
/*! A xml::Factory must be given a template that declares the |
|
structure, before the factory can be used. This can be done |
|
either at instanciation or later by assignment. |
|
|
|
The template is a xml::Node that specifies the schema of the data |
|
that can be loaded from streams through a xml::Factory |
|
instance. |
|
|
|
The root element has automatically set the limits 1..1 |
|
(<code>xml::Node::limits(1, 1)</code>, see xml::Node::limits), |
|
which means that the root element must exist exactly once. If |
|
you pass another limit, your limit is overwritten and ignored. |
|
|
|
E.g. to load an address, that contains a tag <address> |
|
with at least a name and optional an address in it's body, you |
|
may write: |
|
|
|
@code |
|
xml::Factory addrTpl(xml::Node("address") |
|
(<<xml::Node("name").limit(1, 1) |
|
(<<xml::String("first").limit(1, 1) |
|
<<xml::String("middle") // 0..n -> .limit(0, 0) |
|
<<xml::String("last").limit(1, 1)) |
|
<<(xml::Node("location").max(1) |
|
<<xml::String("line").min(1)) |
|
<<xml::String("country").max(1))); |
|
@endcode |
|
|
|
According to this example, a valid XML file could be: |
|
|
|
@verbatim |
|
<address> |
|
<name> |
|
<first>Marc</first> |
|
<middle>Roman</middle> |
|
<last>Wäckerlin</last> |
|
</name> |
|
<location> |
|
<line>SwissSign AG</line> |
|
<line>Pfingstweidstrasse 60b</line> |
|
<line>8005 Zürich</line> |
|
</location> |
|
<country>Schweiz</country> |
|
</address> |
|
@endverbatim */ |
|
class Factory { |
|
public: |
|
Factory(const Node& t) noexcept; |
|
Factory() noexcept; |
|
Factory& operator=(const Node& t) noexcept; |
|
Factory& append(const Node& node) noexcept; |
|
const Node& operator*() const; |
|
const Node* operator->() const; |
|
operator bool() const noexcept; |
|
friend std::ostream& operator<<(std::ostream& os, |
|
const Factory& factory); |
|
static std::ostream& print(std::ostream& os, const Node& node, |
|
unsigned int level=0) noexcept; |
|
std::unique_ptr<Node> read(std::istream& is); |
|
void reset() noexcept; |
|
private: |
|
friend class stream_error; |
|
friend class Serialize; |
|
template<class T> friend class Optional; |
|
template<class T> friend class Container; |
|
template<class T> friend class AssociativeContainer; |
|
template<class T> friend class AssociativeMap; |
|
Node& operator*(); |
|
Node* operator->(); |
|
bool ws(char c) noexcept; |
|
std::unique_ptr<Node> read(std::istream& is, const Node& position); |
|
std::unique_ptr<Node> checkChildren(const xml::Node& tpl, |
|
std::unique_ptr<Node> node, |
|
std::istream& is) const; |
|
Tag tag(std::istream& is, const Node& position); |
|
Node _template; |
|
unsigned long _line; |
|
long _open; |
|
}; |
|
//@} |
|
|
|
/*! @defgroup groupserialization 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 |
|
|
|
The following member types are supported |
|
- All built-in C++ types are supported, except enum |
|
- @c std::string is supported |
|
- Contained classes are supported |
|
- Inheritance |
|
- @ref serContainer |
|
|
|
@todo The following will be supported soon (ideas): |
|
- choices (one of) |
|
- choices (polymorfism) |
|
- optional members (pointer) |
|
- enum (class xml::Enum) |
|
|
|
Pointers cannot be stored. |
|
|
|
@section serBestPract Best Practice and Inheritance |
|
|
|
There are many ways of implemenation (see example @ref |
|
serialization.cxx). best practice is to inherit xml::Serialize |
|
and to overwrite xml::Serialize::initXmlMembers, as shown in the |
|
example @ref serialization.cxx. |
|
|
|
@warning If you do not follow the best practice, you must know |
|
what you are doing to prevent crashing: You must know that |
|
xml::Serialize stores pointers to the variables given in |
|
xml::Serialize::persist. So be careful and don't access |
|
xml::Serialize after the referenced variables have been removed |
|
from memory. |
|
|
|
@subsection inheritance Inheritance |
|
|
|
If you follow the best practice and inherit from another class, |
|
you must first call method xml::Serialize::initXmlMembers of the |
|
parent class, then call xml::Serialize::className to set the new |
|
name of the child class. |
|
|
|
@section examples Examples |
|
|
|
@see @ref serialization.cxx for the different approaches |
|
@see @ref contain_serialization.cxx for containment |
|
@see @ref inherit_serialization.cxx for inheritance |
|
|
|
@example serialization.cxx |
|
|
|
In this example you see several apporoaches on how to connect |
|
variables to XML data structures to serialize them in |
|
XML. Please note, that <b>only class @c B</b> shows the |
|
recommended way of doing it. |
|
|
|
@warning Please note that xml::Serialize stores pointers to the |
|
variables that are serialized. If you access xml::Serialize |
|
outside of the life-cycle of any of the persistent variables, |
|
then your program may crash (in the best case) or even behave in |
|
an unexpected way. |
|
|
|
@example contain_serialization.cxx |
|
|
|
Handle containment in the recommended way. It's very simple: If |
|
all classes inherit from xml::Serialize, then containment |
|
behaves as expected. |
|
|
|
@example inherit_serialization.cxx |
|
|
|
This is an example for inheritance according the recommended way: |
|
- There's nothing special for the parent. |
|
- The child must do the following in xml::Serialize::initXmlMembers |
|
(the order is important!): |
|
-# call xml::Serialize::initXmlMembers of the parent |
|
-# call xml::Serialize::className to set the new class name |
|
-# call xml::Serialize::persist for all child members |
|
- The only difference is, that ... |
|
- ... the child does not inherit xml::Serialize, but a child of it |
|
- ... the child must first call xml::Serialize::initXmlMembers of |
|
the parent in it's own xml::Serialize::initXmlMembers */ |
|
//! @addtogroup groupserialization |
|
//@{ |
|
|
|
class Serialize { |
|
public: |
|
typedef bool(*FromNodeFunc)(Any, const xml::Node&); |
|
typedef bool(*ToNodeFunc)(const Any, xml::Node&); |
|
typedef bool(*ClearFunc)(Any); |
|
//! You must call Serialize::className() if you use this constructor! |
|
Serialize() noexcept; |
|
Serialize(const std::string& className) noexcept; |
|
Serialize(const Serialize& other) noexcept; |
|
virtual ~Serialize(); |
|
Serialize& operator=(const Serialize& other) noexcept; |
|
virtual Serialize& className(const std::string& name) noexcept; |
|
Serialize& persist(Serialize& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(bool& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(char& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(unsigned char& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(signed char& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(unsigned short& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(signed short& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(unsigned int& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(signed int& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(unsigned long& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(signed long& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(float& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(double& member, |
|
const std::string& name) noexcept; |
|
Serialize& persist(std::string& member, |
|
const std::string& name) noexcept; |
|
virtual std::ostream& saveXml(std::ostream& os, |
|
const std::string& name = std::string()) |
|
const noexcept; |
|
virtual std::istream& loadXml(std::istream& is, |
|
const std::string& name = std::string()); |
|
std::string schema() const noexcept; |
|
static void registerFromNode(FromNodeFunc fromNodeFunc); |
|
static void registerToNode(ToNodeFunc toNodeFunc); |
|
static void registerClear(ClearFunc clearFunc); |
|
virtual void clear(); |
|
protected: |
|
virtual void initXmlMembers(); |
|
void checkInit(const Serialize* const ser=0) const { |
|
if (ser) { |
|
if (!ser->_xmlFactory) const_cast<Serialize*>(ser)->initXmlMembers(); |
|
} else { |
|
if (!_xmlFactory) const_cast<Serialize*>(this)->initXmlMembers(); |
|
} |
|
} |
|
/*! @todo Why does @c protected: not work here?!? Children can't |
|
access the members if they are protected! */ |
|
public: |
|
//! @cond INTERNAL |
|
template<typename TYPE> friend bool assignFromNode(Any member, |
|
const xml::Node& node); |
|
template<typename TYPE> friend bool assigntoNode(Any member, |
|
const xml::Node& node); |
|
virtual bool optional() const noexcept; |
|
void clear(Any member); |
|
void reset() noexcept; |
|
void copy(const Serialize& o) noexcept; |
|
template<typename TYPE> |
|
Serialize& persistSimpleType(TYPE& member, |
|
const std::string& name) noexcept { |
|
_xmlNames[name] = &member; |
|
xml::Node schema(*_xmlFactory); |
|
schema<<xml::String(name).limits(1,1); |
|
_xmlFactory = schema; |
|
return *this; |
|
} |
|
virtual void fromNode(Any member, const xml::Node& node); |
|
virtual void toNode(const Any member, xml::Node& node) const; |
|
std::map<std::string, Any> _xmlNames; |
|
xml::Factory _xmlFactory; |
|
static std::set<FromNodeFunc> _fromNode; |
|
static std::set<ToNodeFunc> _toNode; |
|
static std::set<ClearFunc> _clear; |
|
//! @endcond |
|
}; |
|
|
|
template <class TYPE> class Optional: public Serialize { |
|
public: |
|
Optional() noexcept: _valid(false) {} |
|
Optional(const Optional& o) noexcept: |
|
_member(o._member), _valid(o.valid) { |
|
} |
|
Optional(const TYPE& mem) noexcept: |
|
_member(mem), _valid(true) { |
|
} |
|
virtual ~Optional() noexcept {} |
|
Optional& operator=(const Optional& o) noexcept { |
|
_member = o._member; |
|
_valid = o._valid; |
|
return *this; |
|
} |
|
Optional& operator=(const TYPE& mem) noexcept { |
|
_member = mem; |
|
_valid = true; |
|
return *this; |
|
} |
|
operator bool() const noexcept { |
|
return _valid; |
|
} |
|
const TYPE& operator*() const noexcept { |
|
return _member; |
|
} |
|
TYPE& operator*() noexcept { |
|
return _member; |
|
} |
|
const TYPE* operator->() const noexcept { |
|
return &_member; |
|
} |
|
TYPE* operator->() noexcept { |
|
return &_member; |
|
} |
|
virtual void clear() noexcept { |
|
_valid = false; |
|
} |
|
virtual Optional& className(const std::string& name) noexcept { |
|
if (!_xmlFactory) { |
|
Serialize::className(name); |
|
persist(_member, name); |
|
// make the child the root, and it's optional |
|
_xmlFactory = (*_xmlFactory)[0]; |
|
_xmlFactory->limits(0, 1); |
|
} |
|
return *this; |
|
} |
|
protected: |
|
virtual bool optional() const noexcept { |
|
return true; |
|
} |
|
virtual void fromNode(Any member, const xml::Node& node) { |
|
_valid = true; |
|
Serialize::fromNode(Any(&_member), node); |
|
} |
|
virtual void toNode(const Any member, xml::Node& node) const { |
|
if (!_valid) { |
|
node.parent().remove(node); |
|
return; |
|
} |
|
const Any mem(&const_cast<Optional*>(this)->_member); |
|
Serialize::toNode(mem, node); |
|
} |
|
private: |
|
TYPE _member; |
|
bool _valid; |
|
}; |
|
|
|
//! @addtogroup groupserialization |
|
//@{ |
|
/*! @defgroup serContainer Serialization of Container |
|
|
|
libxml-cpp can serialize container, such as Lists, Vectors or |
|
Maps. Classes that serialize cannot contain standard C++ |
|
container directly, but they must contain container defined |
|
here. For every standard container except @c std::bitset there |
|
is a XML representation available. |
|
|
|
The following containers are defined: |
|
- xml::DeQue (inherits @c std::deque and xml::Serialize) |
|
- xml::List (inherits @c std::list and xml::Serialize) |
|
- xml::Map (inherits @c std::map and xml::Serialize) |
|
- xml::MultiMap (inherits @c std::multimap and xml::Serialize) |
|
- xml::MultiSet (inherits @c std::multiset and xml::Serialize) |
|
- xml::Set (inherits @c std::set and xml::Serialize) |
|
- xml::Vector (inherits @c std::vector and xml::Serialize) |
|
|
|
E.g. use @c xml::List instead of @c std::list. |
|
|
|
I don't see any necessity to implement @c std::priority_queue, |
|
@c std::queue and @c std::stack, they are only restricted |
|
interfaces to another container and don't allow random access |
|
(which is needed to store them). |
|
|
|
@example list_serialization.cxx |
|
@example optional_serialization.cxx */ |
|
//@} |
|
|
|
//! @cond INTERNAL |
|
template<class CONTAINER_TYPE> class Container: |
|
public CONTAINER_TYPE, |
|
public Serialize { |
|
public: |
|
Container() {} |
|
Container(const Container& o): CONTAINER_TYPE(o), Serialize(o) {} |
|
Container(const std::string& className) noexcept: Serialize(className) {} |
|
virtual ~Container() {} |
|
virtual std::istream& loadXml(std::istream& is, |
|
const std::string& name = std::string()) { |
|
checkInit(); |
|
xml::Factory factory(_xmlFactory); |
|
if (name.size()) factory->name(name); |
|
std::unique_ptr<xml::Node> node(factory.read(is)); |
|
CONTAINER_TYPE::clear(); |
|
for (xml::Node::size_type i(0); i<node->children(); ++i) { |
|
typename CONTAINER_TYPE::value_type tmp; |
|
Serialize::fromNode(&tmp, (*node)[i]); // reads into tmp |
|
this->push_back(tmp); |
|
} |
|
return is; |
|
} |
|
virtual std::ostream& saveXml(std::ostream& os, |
|
const std::string& name = std::string()) |
|
const noexcept { |
|
checkInit(); |
|
xml::Node node(*_xmlFactory); |
|
if (name.size()) node.name(name); |
|
std::unique_ptr<xml::Node> tpl(node[0].clone()); |
|
node.clear(); |
|
for (typename Container::const_iterator it = this->begin(); |
|
it!=this->end(); ++it) { |
|
typename CONTAINER_TYPE::value_type tmp; |
|
tmp = *it; |
|
std::unique_ptr<xml::Node> item(tpl->clone()); |
|
Serialize::toNode(&tmp, *item); |
|
node<<*item; |
|
} |
|
os<<node; |
|
return os; |
|
} |
|
protected: |
|
virtual void initXmlMembers() { |
|
std::string itemName("item"); |
|
typename CONTAINER_TYPE::value_type tmp; |
|
if (isSerialize<typename CONTAINER_TYPE::value_type>()) { |
|
Serialize* ser(Mapper<typename CONTAINER_TYPE::value_type> |
|
::toSerialize(tmp)); |
|
checkInit(ser); |
|
itemName = ser->_xmlFactory->name(); |
|
} |
|
_xmlFactory = xml::Node("dummyroot"); // dummy root, (uninitialized exc) |
|
persist(tmp, itemName); // add as child of dummyroot |
|
(*_xmlFactory)[0].limits(0, 0); // any number of children possible |
|
} |
|
virtual void clear() noexcept { |
|
CONTAINER_TYPE::clear(); |
|
} |
|
}; |
|
|
|
template<class CONTAINER_TYPE> class AssociativeContainer: |
|
public CONTAINER_TYPE, |
|
public Serialize { |
|
public: |
|
AssociativeContainer() {} |
|
AssociativeContainer(const AssociativeContainer& o): |
|
CONTAINER_TYPE(o), Serialize(o) { |
|
} |
|
AssociativeContainer(const std::string& className) noexcept: |
|
Serialize(className) { |
|
} |
|
virtual ~AssociativeContainer() {} |
|
virtual std::istream& loadXml(std::istream& is, |
|
const std::string& name = std::string()) { |
|
checkInit(); |
|
xml::Factory factory(_xmlFactory); |
|
if (name.size()) factory->name(name); |
|
std::unique_ptr<xml::Node> node(factory.read(is)); |
|
CONTAINER_TYPE::clear(); |
|
for (xml::Node::size_type i(0); i<node->children(); ++i) { |
|
typename CONTAINER_TYPE::value_type tmp; |
|
Serialize::fromNode(&tmp, (*node)[i]); // reads into tmp |
|
this->insert(tmp); |
|
} |
|
return is; |
|
} |
|
virtual std::ostream& saveXml(std::ostream& os, |
|
const std::string& name = std::string()) |
|
const noexcept { |
|
checkInit(); |
|
xml::Node node(*_xmlFactory); |
|
if (name.size()) node.name(name); |
|
std::unique_ptr<xml::Node> tpl(node[0].clone()); |
|
node.clear(); |
|
for (typename CONTAINER_TYPE::const_iterator it = this->begin(); |
|
it!=this->end(); ++it) { |
|
typename CONTAINER_TYPE::value_type tmp; |
|
tmp = *it; |
|
std::unique_ptr<xml::Node> item(tpl->clone()); |
|
Serialize::toNode(&tmp, *item); |
|
node<<*item; |
|
} |
|
os<<node; |
|
return os; |
|
} |
|
protected: |
|
virtual void initXmlMembers() { |
|
std::string itemName("item"); |
|
typename CONTAINER_TYPE::value_type tmp; |
|
if (isSerialize<typename CONTAINER_TYPE::value_type>()) { |
|
Serialize* ser(Mapper<typename CONTAINER_TYPE::value_type> |
|
::toSerialize(tmp)); |
|
assert(ser); |
|
assert(ser!=this); |
|
assert((void*)ser==(void*)&tmp); |
|
checkInit(ser); |
|
itemName = ser->_xmlFactory->name(); |
|
} |
|
_xmlFactory = xml::Node("dummyroot"); // dummy root, (uninitialized exc) |
|
persist(tmp, itemName); // add as child of dummyroot |
|
(*_xmlFactory)[0].limits(0, 0); // any number of children possible |
|
} |
|
virtual void clear() noexcept { |
|
CONTAINER_TYPE::clear(); |
|
} |
|
}; |
|
|
|
template<class CONTAINER_TYPE> class AssociativeMap: |
|
public CONTAINER_TYPE, |
|
public Serialize { |
|
public: |
|
AssociativeMap() {} |
|
AssociativeMap(const AssociativeMap& o): |
|
CONTAINER_TYPE(o), Serialize(o) { |
|
} |
|
AssociativeMap(const std::string& className) noexcept: |
|
Serialize(className) { |
|
} |
|
virtual ~AssociativeMap() {} |
|
virtual std::istream& loadXml(std::istream& is, |
|
const std::string& name = std::string()) { |
|
checkInit(); |
|
xml::Factory factory(_xmlFactory); |
|
if (name.size()) factory->name(name); |
|
std::unique_ptr<xml::Node> node(factory.read(is)); |
|
CONTAINER_TYPE::clear(); |
|
for (xml::Node::size_type i(0); i<node->children(); ++i) { |
|
typename CONTAINER_TYPE::key_type key; |
|
typename CONTAINER_TYPE::mapped_type data; |
|
Serialize::fromNode(&key, (*node)[i]); // reads into tmp |
|
Serialize::fromNode(&data, (*node)[++i]); // key&value |
|
this->insert(typename CONTAINER_TYPE::value_type(key, data)); |
|
} |
|
return is; |
|
} |
|
virtual std::ostream& saveXml(std::ostream& os, |
|
const std::string& name = std::string()) |
|
const noexcept { |
|
checkInit(); |
|
xml::Node node(*_xmlFactory); |
|
if (name.size()) node.name(name); |
|
std::unique_ptr<xml::Node> tpl1(node[0].clone()); |
|
std::unique_ptr<xml::Node> tpl2(node[1].clone()); |
|
node.clear(); // "node" is now invalid |
|
for (typename AssociativeMap::const_iterator it = this->begin(); |
|
it!=this->end(); ++it) { |
|
typename CONTAINER_TYPE::key_type key; |
|
typename CONTAINER_TYPE::mapped_type data; |
|
key = it->first; |
|
data = it->second; |
|
std::unique_ptr<xml::Node> item1(tpl1->clone()); |
|
Serialize::toNode(&key, *item1); |
|
std::unique_ptr<xml::Node> item2(tpl2->clone()); |
|
Serialize::toNode(&data, *item2); |
|
node<<*item1<<*item2; |
|
} |
|
os<<node; |
|
return os; |
|
} |
|
protected: |
|
virtual void initXmlMembers() { |
|
std::string keyName("key"); |
|
std::string valueName("value"); |
|
typename CONTAINER_TYPE::key_type key; |
|
typename CONTAINER_TYPE::mapped_type data; |
|
if (isSerialize<typename CONTAINER_TYPE::key_type>()) { |
|
const Serialize* ser(Mapper<typename CONTAINER_TYPE::key_type> |
|
::toSerialize(key)); |
|
checkInit(ser); |
|
keyName = ser->_xmlFactory->name(); |
|
} |
|
if (isSerialize<typename CONTAINER_TYPE::mapped_type>()) { |
|
Serialize* ser(Mapper<typename CONTAINER_TYPE::mapped_type> |
|
::toSerialize(data)); |
|
checkInit(ser); |
|
valueName = ser->_xmlFactory->name(); |
|
} |
|
_xmlFactory = xml::Node("dummyroot"); // dummy root, (uninitialized exc) |
|
persist(key, keyName); // add as child of dummyroot |
|
persist(data, valueName); // add as child of dummyroot |
|
(*_xmlFactory)[0].limits(0, 0); // any number of children possible |
|
(*_xmlFactory)[1].limits(0, 0); // any number of children possible |
|
} |
|
virtual void clear() noexcept { |
|
CONTAINER_TYPE::clear(); |
|
} |
|
}; |
|
//! @endcond |
|
|
|
} |
|
|
|
//! @cond INTERNAL |
|
//! @addtogroup serContainer |
|
//@{ |
|
# ifdef __XML_CXX_DECLARE_CONTAINER_CLASS__ |
|
# error Macro __XML_CXX_DECLARE_CONTAINER_CLASS__ has been used elsewhere |
|
# endif |
|
# define __XML_CXX_DECLARE_CONTAINER_CLASS__(CONTAINER, STD_CONTAINER) \ |
|
namespace xml { \ |
|
template<class TYPE, class ALLOC=std::allocator<TYPE> > \ |
|
class CONTAINER: \ |
|
public Container<STD_CONTAINER<TYPE, ALLOC> > { \ |
|
public: \ |
|
CONTAINER() {} \ |
|
CONTAINER(const CONTAINER& o): \ |
|
Container<STD_CONTAINER<TYPE, ALLOC> >(o) { \ |
|
} \ |
|
CONTAINER(const std::string& className) noexcept: \ |
|
Container<STD_CONTAINER<TYPE, ALLOC> >(className) { \ |
|
} \ |
|
virtual ~CONTAINER() {} \ |
|
}; \ |
|
} |
|
# include <list> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(List, std::list) |
|
# include <vector> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(Vector, std::vector) |
|
# include <deque> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(Deque, std::deque) |
|
# undef __XML_CXX_DECLARE_CONTAINER_CLASS__ |
|
# define __XML_CXX_DECLARE_CONTAINER_CLASS__(CONTAINER, STD_CONTAINER) \ |
|
namespace xml { \ |
|
template<class TYPE, class CONT=std::deque<TYPE> > \ |
|
class CONTAINER: \ |
|
public AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, CONT> > { \ |
|
public: \ |
|
CONTAINER() {} \ |
|
CONTAINER(const CONTAINER& o): \ |
|
AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, CONT> >(o) { \ |
|
} \ |
|
CONTAINER(const std::string& className) noexcept: \ |
|
AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, CONT> > \ |
|
(className) { \ |
|
} \ |
|
virtual ~CONTAINER() {} \ |
|
}; \ |
|
} |
|
# include <stack> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(Stack, std::stack) |
|
# include <queue> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(Queue, std::queue) |
|
# undef __XML_CXX_DECLARE_CONTAINER_CLASS__ |
|
# define __XML_CXX_DECLARE_CONTAINER_CLASS__(CONTAINER, STD_CONTAINER) \ |
|
namespace xml { \ |
|
template \ |
|
<class TYPE, class CONT=std::vector<TYPE>, \ |
|
class COMPARE=std::less<TYPE>, \ |
|
class ALLOC=std::allocator<TYPE> > \ |
|
class CONTAINER: \ |
|
public AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, COMPARE, ALLOC> > { \ |
|
public: \ |
|
CONTAINER() {} \ |
|
CONTAINER(const CONTAINER& o): \ |
|
AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, COMPARE, ALLOC> >(o) { \ |
|
} \ |
|
CONTAINER(const std::string& className) noexcept: \ |
|
AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, COMPARE, ALLOC> > \ |
|
(className) { \ |
|
} \ |
|
virtual ~CONTAINER() {} \ |
|
}; \ |
|
} |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(PriorityQueue, std::priority_queue) |
|
# undef __XML_CXX_DECLARE_CONTAINER_CLASS__ |
|
# define __XML_CXX_DECLARE_CONTAINER_CLASS__(CONTAINER, STD_CONTAINER) \ |
|
namespace xml { \ |
|
template<class TYPE, class COMPARE=std::less<TYPE>, \ |
|
class ALLOC=std::allocator<TYPE> > \ |
|
class CONTAINER: \ |
|
public AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, COMPARE, ALLOC> > { \ |
|
public: \ |
|
CONTAINER() {} \ |
|
CONTAINER(const CONTAINER& o): \ |
|
AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, COMPARE, ALLOC> >(o) { \ |
|
} \ |
|
CONTAINER(const std::string& className) noexcept: \ |
|
AssociativeContainer \ |
|
<STD_CONTAINER<TYPE, COMPARE, ALLOC> > \ |
|
(className) { \ |
|
} \ |
|
virtual ~CONTAINER() {} \ |
|
}; \ |
|
} |
|
# include <set> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(Set, std::set) |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(MultiSet, std::multiset) |
|
# undef __XML_CXX_DECLARE_CONTAINER_CLASS__ |
|
# define __XML_CXX_DECLARE_CONTAINER_CLASS__(CONTAINER, STD_CONTAINER) \ |
|
namespace xml { \ |
|
template<class KEY, class VALUE, class COMPARE=std::less<KEY>, \ |
|
class ALLOC=std::allocator<std::pair<const KEY, VALUE> > > \ |
|
class CONTAINER: public AssociativeMap \ |
|
<STD_CONTAINER<KEY, VALUE, COMPARE, ALLOC> > { \ |
|
public: \ |
|
CONTAINER() {} \ |
|
CONTAINER(const CONTAINER& o): \ |
|
AssociativeMap \ |
|
<STD_CONTAINER<KEY, VALUE, COMPARE, ALLOC> >(o) { \ |
|
} \ |
|
CONTAINER(const std::string& className) noexcept: \ |
|
AssociativeMap \ |
|
<STD_CONTAINER<KEY, VALUE, COMPARE, ALLOC> > \ |
|
(className) { \ |
|
} \ |
|
virtual ~CONTAINER() {} \ |
|
}; \ |
|
} |
|
# include <map> |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(Map, std::map) |
|
__XML_CXX_DECLARE_CONTAINER_CLASS__(MultiMap, std::multimap) |
|
# undef __XML_CXX_DECLARE_CONTAINER_CLASS__ |
|
//@} |
|
//! @endcond |
|
|
|
#endif
|
|
|