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.
 
 
 
 

1329 lines
47 KiB

/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#include <mrw/string.hxx>
#include <xml-cxx/xml.hxx>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <algorithm>
namespace xml {
unsigned int MethodTrace::_level(0);
//================================================================= EXCEPTIONS
//----------------------------------------------------------------------------
exception::exception(std::string reason) noexcept:
_what(reason), _node(0) {
}
exception::exception(std::string reason, const Node& t) noexcept:
_what(reason), _node(t.clone().release()) {
}
exception::~exception() noexcept {
delete _node;
}
void exception::line(unsigned long line) noexcept {
std::stringstream ss;
ss<<line;
_what += "\nat line: "+ss.str();
}
const char* exception::what() const noexcept {
static std::string w;
if (!w.size()) {
if (_node)
if (_node->isChild())
w = _what+"\ntag: "+_node->name()+"\nparent: "+_node->parent().name();
else
w = _what+"\ntag: "+_node->name()+" (root element)";
else
w = _what;
}
return w.c_str();
}
//----------------------------------------------------------------------------
access_error::access_error(const Node& t, const std::string& name) noexcept:
exception("child not available", t), _name(name) {
}
const char* access_error::what() const noexcept {
static std::string w;
if (!w.size()) w = std::string(exception::what())+"\nchild name: "+_name;
return w.c_str();
}
//----------------------------------------------------------------------------
out_of_range::out_of_range(const Node& t, size_t pos) noexcept:
exception("child not available", t), _pos(pos) {
}
const char* out_of_range::what() const noexcept {
static std::stringstream w;
if (!w.str().size()) w<<exception::what()<<"\nchild number: "<<_pos;
return w.str().c_str();
}
//----------------------------------------------------------------------------
cannot_have_children::cannot_have_children(const Node& parent,
const Node& child) noexcept:
exception("node does not accept child nodes", parent),
_child(child.clone().release()) {
}
cannot_have_children::~cannot_have_children() noexcept {
delete _child;
}
const char* cannot_have_children::what() const noexcept {
static std::string w;
if (!w.size())
w = std::string(exception::what())+"\nchild name: "+_child->name();
return w.c_str();
}
//----------------------------------------------------------------------------
stream_error::stream_error(const std::string& reason, const Node& t,
std::istream& is, const Tag& tag, char c) noexcept:
exception(reason, t), _pos(is.tellg()), _tag(new Tag(tag)), _char(c) {
}
stream_error::stream_error(const std::string& reason, const Node& t,
std::istream& is) noexcept:
exception(reason, t), _pos(is.tellg()), _tag(0), _char(0) {
}
stream_error::~stream_error() noexcept {
delete _tag;
}
const char* stream_error::what() const noexcept {
static std::string w;
if (!w.size()) {
std::stringstream ss;
ss<<exception::what()
<<"\nposition in stream: "<<_pos;
if (_char)
ss<<"\nactual character: "<<(_char>31?_char:'?')
<<" (ascii="<<(int)_char<<")";
if (_tag) {
ss<<"\ntag type: "<<(_tag->type==START?"START"
:(_tag->type==END?"END"
:(_tag->type==EMPTY?"EMPTY"
:"???? <UNKNOWN>")));
if (_tag->name.size()) ss<<"\nnode name read: "<<_tag->name;
if (_tag->text.size()) ss<<"\ncontained text: "<<_tag->text;
}
w = ss.str();
}
return w.c_str();
}
//============================================================================
//----------------------------------------------------------------------------
//! Copy an attribute.
Attributes::Value::Value(const value_type& o) noexcept:
Attributes::value_type(o) {
}
//! Construct an empty attribute.
Attributes::Value::Value(const std::string& name) noexcept:
Attributes::value_type(name, std::string()) {
}
//! Construct an attribute with name an value.
Attributes::Value::Value(const std::string& name,
const std::string& value) noexcept:
Attributes::value_type(name, value) {
}
//! Assign a value.
Attributes::Value& Attributes::Value::operator=(const std::string& value)
noexcept {
second = value;
return *this;
}
//! Get the attribute name.
const std::string& Attributes::Value::name() const noexcept {
return first;
}
//! Get the attribute value.
const std::string& Attributes::Value::value() const noexcept {
return second;
}
//! Get the attribute value.
std::string& Attributes::Value::value() noexcept {
return second;
}
//! Convert the attribute to a boolean.
/*! @return @c true if the value is set and not equal to one of:
@c false @c no @c 0. */
Attributes::Value::operator bool() const noexcept {
return bool();
}
//! Convert the attribute to a boolean.
/*! @return @c true if the value is set and not equal to one of:
@c false @c no @c 0. */
bool Attributes::Value::toBool() const noexcept {
return !(!second.size()||second=="false"||second=="no"||second=="0");
}
//! Convert the attribute to a number.
Attributes::Value::operator unsigned long() const noexcept {
return toNumber();
}
//! Convert the attribute to a number.
unsigned long Attributes::Value::toNumber() const noexcept {
std::stringstream ss(second);
int i(0);
ss>>i;
return i;
}
//! Convert the attribute to a space separated list.
Attributes::Value::operator List() const noexcept {
return toList();
}
//! Convert the attribute to list.
/*! @param separators a string containing a list of valid separators */
Attributes::List Attributes::Value::toList(const std::string& separators)
const noexcept {
List l;
for (std::string::size_type it(0), pos(0);
it!=std::string::npos &&
((pos=second.find_first_of(separators, it)), true);
it=pos!=std::string::npos?++pos:std::string::npos)
if (pos==std::string::npos)
l.push_back(second.substr(it));
else
l.push_back(second.substr(it, pos-it));
return l;
}
//! Get the first value of an attribute containing a list of values.
/*! @copydoc xml::Attributes::Value::toList
@return the first element of the list. */
std::string Attributes::Value::front(const std::string& separators) const
{
List l(toList(separators));
if (!l.size()) throw empty_attribute_list(first);
return l.front();
}
//----------------------------------------------------------------------------
//! Empty attribute list
Attributes::Attributes() noexcept {}
//! Attribute list with first one empty attribute given.
Attributes::Attributes(const std::string& empty) noexcept {
insert(Value(empty));
}
//! Attribute list with first attribute given.
Attributes::Attributes(const std::string& key,
const std::string& value) noexcept {
insert(Value(key, value));
}
//! Add a new key-value pair to the attribute list.
Attributes& Attributes::operator<<(const Attributes::Value& v) noexcept {
insert(v);
return *this;
}
//! Add a new empty key to the attribute list.
Attributes& Attributes::operator<<(const std::string& key) noexcept {
insert(Value(key));
return *this;
}
//----------------------------------------------------------------------------
//! Nodes must be given a name.
/*! Unnamed nodes are not allowed, therefore there's no default
constructor.
@copydoc xml::Node::limits */
Node::Node(std::string name,
Node::size_type min, Node::size_type max) noexcept:
_name(name), _parent(0), _min(min), _max(max) {
}
//! Copy node, reset parent.
/*! The parent is reset, the node does not belong to the same parent
as the source of the copy.
@see xml::Node::clone() for more information on the parenting. */
Node::Node(const Node& o) noexcept:
_attributes(o._attributes), _name(o.name()), _parent(0),
_min(o._min), _max(o._max) {
for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release());
}
Node::~Node() noexcept {
clear();
}
//! Assign new node, keep parent.
/*! The parent remains unchanged, the node does not belong to the
same parent as the source of the copy.
@see xml::Node::clone() for more information on the parenting. */
Node& Node::operator=(const Node& o) noexcept {
clear();
_attributes=o._attributes;
_name = o.name();
_min = o._min;
_max = o._max;
for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release());
return *this;
}
//! Clones But resets the parent reference.
/*! You get a new instance of the node, but detached from the
parent. It is then ready to be inserted below a new parent.
If you clone (or copy) a node, the parent is not copied. This is
because otherwise the new now would need to be appended to the
parent's child list. That's most probably not
intended. Therefore, in a normal copy construction the parent is
set to @c 0 (no parent). Then the new node can be appended to a
parent if needed. In a copy assignment, the parent remains
uncanged and the other data is copied.
The user of this library doesn't have to and is not able to care
about the parenting. */
std::unique_ptr<Node> Node::clone() const noexcept {
std::unique_ptr<Node> res(new Node(*this));
res->_parent = 0;
return res;
}
//! Stream XML.
/*! Streams the node including all attributes and children. It is
formatted with new-lines and tabulator indentation for human
readability. */
std::ostream& Node::out(std::ostream& o, unsigned int level) const noexcept {
if (_contents.size()) {
o<<std::string(level, '\t')<<'<'<<name();
for (Attributes::const_iterator it(_attributes.begin());
it!=_attributes.end(); ++it)
o<<' '<<it->first<<"=\""<<it->second<<'"';
o<<'>'<<std::endl;
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
(*it)->out(o, level+1)<<std::endl;
o<<std::string(level, '\t')<<"</"<<name()<<'>';
} else {
o<<std::string(level, '\t')<<'<'<<name();
for (Attributes::const_iterator it(_attributes.begin());
it!=_attributes.end(); ++it)
o<<' '<<it->first<<"=\""<<it->second<<'"';
o<<"/>";
}
return o;
}
//! Get the textual contents of a node.
/*! By default this is the concatenation of all child nodes' texts. */
std::string Node::text() const noexcept {
std::string s;
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
s+=***it;
return s;
}
//! Set a text (forbidden in xml::Node)
/*! This method is only useful for child nodes, where it is
reimplemented. In xml::Node, the parent of all nodes, it throws
xml::tag_expected, since a xml::Node may only contain sub-nodes
(formatted as xml-tags) and no plain text. */
Node& Node::text(const std::string& txt) {
throw tag_expected(*this, txt);
}
//! Appends a new node at the end of all children.
Node& Node::append(const Node& o) {
_contents.push_back(o.clone(this).release());
return *this;
}
//! Removes a given child node.
Node& Node::remove(Node& n) {
Contents::iterator it(std::find(_contents.begin(), _contents.end(), &n));
if (it==_contents.end()) throw access_error(*this, n.name());
_contents.erase(it);
return *this;
}
//! Removes the first child node of a given name.
Node& Node::remove(const std::string& n) {
Node* t(find(n));
if (!t) throw access_error(*this, n);
return remove(*t);
}
//! Removes the child node of a given position.
Node& Node::remove(size_type n) {
if (n>=children()) throw out_of_range(*this, n);
_contents.erase(_contents.begin()+n);
return *this;
}
//! Set a list of attributes.
/*! Existing attributes with the same name are overwritten. */
Node& Node::set(const Attributes& o) noexcept {
_attributes.clear();
_attributes.insert(o.begin(), o.end());
return *this;
}
//! Clears content and attributes.
Node& Node::clear() throw () {
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
delete *it;
_contents.clear();
_attributes.clear();
return *this;
}
//! Get the node's tag name.
std::string Node::name() const noexcept {
return _name;
}
//! Set a new node's tag name.
Node& Node::name(const std::string& n) noexcept {
_name = n;
return *this;
}
//! Set minimum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node& Node::min(Node::size_type m) noexcept {
_min = m;
return *this;
}
//! Get minimum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node::size_type Node::min() const noexcept {
return _min;
}
//! Set maximum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node& Node::max(Node::size_type m) noexcept {
_max = m;
return *this;
}
//! Get maximum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node::size_type Node::max() const noexcept {
return _max;
}
//! @c true if node has a parent.
bool Node::isChild() const noexcept {
return _parent;
}
//! Get the parent node.
Node& Node::parent() const {
if (!_parent) throw no_parent(*this);
return *_parent;
}
//! Check if a specific attribute is set or not.
bool Node::hasAttr(const std::string& name) const noexcept {
return _attributes.find(name)!=_attributes.end();
}
//! Declare an attribute template and specify whether it is mandatory
/*! Used for setting up attributes in xml::Factory:
@code
xml::Factory f(xml::Node("root").attr("aname", xml::mandatory);
@endcode
If a factory reads from a stream, it verifies that only optional
or mandatory attributes are given and that mandatory attributes
are specified. Otherwise reading throws an exception. */
Node& Node::attr(const std::string& name, bool m) noexcept {
_attributes[name] = m?"xml::mandatory":"xml::optional";
return *this;
}
//! Declare an attribute with given value.
/*! When used for setting up attributes in xml::Factory, it
specifies an optional attribute with given default value:
@code
xml::Factory f(xml::Node("root").attr("aname", "avalue");
@endcode
If a factory reads from a stream and the specified attribute is
not given, it is set to @c deflt. */
Node& Node::attr(const std::string& name, const std::string& deflt) noexcept {
_attributes[name] = deflt;
return *this;
}
//! Get an attribute.
/*! Returns the attribute's value (empty if the attribute is not set) */
std::string Node::attr(const std::string& name) const noexcept {
Attributes::const_iterator it(_attributes.find(name));
if (it!=_attributes.end()) return it->second;
return std::string();
}
//! Get an attribute.
/*! Returns the attribute's value (empty if the attribute is not set) */
std::string& Node::attr(const std::string& name) noexcept {
return _attributes[name];
}
//! Get an attribute.
/*! Returns an attribute class or throws an exception if the
attribute is not set. This method is useful when you need
conversions or other methods as specified in
xml::Attributes::Value. */
const Attributes::Value Node::attribute(const std::string& name)
const {
Attributes::const_iterator it(_attributes.find(name));
if (it!=_attributes.end()) return *it;
throw attribute_not_available(*this, name);
}
//! Get the list of attributes.
const Attributes& Node::attributes() const noexcept {
return _attributes;
}
//! Get the list of attributes.
Attributes& Node::attributes() noexcept {
return _attributes;
}
//! Get the first child node
/*! Returns the first child node or throws an exception, if there are
no children. */
Node& Node::first() {
Contents::iterator it(_contents.begin());
if (it==_contents.end()) throw out_of_range(*this, 0);
return **it;
}
//! Get the first child node
/*! Returns the first child node or throws an exception, if there are
no children. */
const Node& Node::first() const {
Contents::const_iterator it(_contents.begin());
if (it==_contents.end()) throw out_of_range(*this, 0);
return **it;
}
//! Get the last child node
/*! Returns the last child node or throws an exception, if there are
no children. */
const Node& Node::last() const {
Contents::const_reverse_iterator it(_contents.rbegin());
if (it==_contents.rend()) throw out_of_range(*this, 0);
return **it;
}
//! Get the last child node
/*! Returns the last child node or throws an exception, if there are
no children. */
Node& Node::last() {
Contents::reverse_iterator it(_contents.rbegin());
if (it==_contents.rend()) throw out_of_range(*this, 0);
return **it;
}
//! Pass a minimal and maximal number for this node in a xml file.
/*! Minimal and maximal values are verified when you use the node as
a template in a xml::Factory. When the factory reads a stucture
from a stream through xml::Factory::read, then all the limits
are verified.
There are several ways to declare the limits.
- in the constructor xml::Node::Node
- minimum and maximum using method xml::Node::limits
- using methods xml::Node::min and xml::Node::max separately
It is recommended not to use the constructor, but the methods
xml::Node::limits, xml::Node::min and xml::Node::max, simply for
code readability.
The number @c 0 means unlimited: xml::Node::min(0) means the
value is optional and xml::Node::max(0) means that there is no
upper limit. The only exception for this rule is the root
element of a structure passed to a xml::Factory which must exist
exactly once.
Default is no limits: <code>0..n</code>,
which means that the node is optional and may be instatiated
multiple (infinite, unlimited) times. */
Node& Node::limits(size_type min, size_type max) noexcept {
_min = min;
_max = max;
return *this;
}
//! Get all immediate children of a given node name.
Node::List Node::list(const std::string& name) const noexcept {
List res;
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
if ((*it)->_name==name) res.push_back(*it);
return res;
}
//! Check if at least one child node of a given name exists.
bool Node::operator()(const std::string& child) const noexcept {
return find(child);
}
//! Append a child at the end.
/*! @copydoc xml::Node::append */
Node& Node::operator<<(const Node& o) {
return append(o);
}
//! Add an empty attribute.
/*! @copydoc xml::Node::set */
Node& Node::operator<<(const Attributes& o) noexcept {
return set(o);
}
//! Get the number of children.
Node::size_type Node::children() const noexcept {
return _contents.size();
}
//! Get a child by child-number (the n-th child).
/*! @param child number of the child to return: <code>child&gt;=0</code> and
<code>child&lt;xml::Node::children()</code> */
const Node& Node::operator[](Node::size_type child) const
try {
return *_contents.at(child);
} catch (...) {
throw out_of_range(*this, child);
}
/*! @copydoc xml::Node::operator[](Node::size_type child) const */
Node& Node::operator[](Node::size_type child) try {
return *_contents.at(child);
} catch (...) {
throw out_of_range(*this, child);
}
//! Get the first child of a given node name.
/*! @return the first child with matching node name */
const Node& Node::operator[](const std::string& child) const
{
const Node* const t(find(child));
if (!t) throw access_error(*this, child);
return *t;
}
/*! @copydoc xml::Node::operator[](const std::string& child) const */
Node& Node::operator[](const std::string& child) {
Node* const t(find(child));
if (!t) throw access_error(*this, child);
return *t;
}
//! Get the textual contents of a node.
/*! @copydoc xml::Node::text() */
std::string Node::operator*() const noexcept {
return text();
}
//! Set a text (forbidden in xml::Node)
/*! @copydoc xml::Node::text(const std::string& txt) */
Node& Node::operator=(const std::string& contents) {
return text(contents);
}
//! Write node in XML format to a stream.
/*! @copydoc xml::Node::out */
std::ostream& operator<<(std::ostream& o, const Node& t) noexcept {
return t.out(o);
}
//! Get a pointer to the first child of a given node name.
/*! This method does not throw an exception if the element does not
exist, but returns @c 0. */
Node* Node::find(const std::string& child) const noexcept {
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it) {
if ((*it)->name()==child) return *it;
}
return 0;
}
//! Clone a node, but assign a new parent.
std::unique_ptr<Node> Node::clone(Node* p) const noexcept {
std::unique_ptr<Node> c(clone());
c->_parent = p;
return c;
}
//----------------------------------------------------------------------------
/*! @copydoc Node::Node(std::string name,
Node::size_type min, Node::size_type max) */
String::String(std::string name,
Node::size_type min, Node::size_type max) noexcept:
Node(name, min, max) {
}
//! Pass the text in the node.
/*! @copydoc Node::Node(std::string name,
Node::size_type min, Node::size_type max) */
String::String(std::string name, const std::string& text,
Node::size_type min, Node::size_type max) noexcept:
Node(name, min, max), _text(text) {
}
std::unique_ptr<Node> String::clone() const noexcept {
return std::unique_ptr<Node>(new String(*this));
}
std::string String::text() const noexcept {
return _text;
}
//! An xml::String contains text: Set the text.
/*! Never throws an exception. */
String& String::text(const std::string& txt) {
_text = txt;
return *this;
}
std::ostream& String::out(std::ostream& o, unsigned int level) const noexcept {
if (_text.size()) {
o<<std::string(level, '\t')<<'<'<<name();
for (Attributes::const_iterator it(_attributes.begin());
it!=_attributes.end(); ++it)
o<<' '<<it->first<<"=\""<<it->second<<'"';
o<<'>'<<_text<<"</"<<name()<<'>';
} else {
o<<std::string(level, '\t')<<'<'<<name();
for (Attributes::const_iterator it(_attributes.begin());
it!=_attributes.end(); ++it)
o<<' '<<it->first<<"=\""<<it->second<<'"';
o<<"/>";
}
return o;
}
//! An xml::String has no child nodes: Always throws an exception.
String& String::append(const Node& o) {
throw cannot_have_children(*this, o);
}
//! An xml::String contains text: Set the text.
Node& String::operator=(const std::string& contents) noexcept {
return text(contents);
}
String::operator std::string() const noexcept {
return text();
}
String::operator bool() const noexcept {
bool res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator char() const noexcept {
char res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed char() const noexcept {
signed char res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned char() const noexcept {
unsigned char res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed short() const noexcept {
signed short res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned short() const noexcept {
unsigned short res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed int() const noexcept {
signed int res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned int() const noexcept {
unsigned int res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed long() const noexcept {
signed long res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned long() const noexcept {
unsigned long res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator float() const noexcept {
float res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator double() const noexcept {
double res;
std::stringstream ss(text());
ss>>res;
return res;
}
//----------------------------------------------------------------------------
/*! @copydoc Node::Node(std::string name,
Node::size_type min, Node::size_type max) */
UnsignedInteger::UnsignedInteger(std::string name, unsigned long i,
size_type min, size_type max) noexcept:
String(name, mrw::string(i), min, max) {
}
std::unique_ptr<Node> UnsignedInteger::clone() const noexcept {
return std::unique_ptr<Node>(new UnsignedInteger(*this));
}
//! An xml::UnsignedInteger must only contain an number.
/*! En exception is thrown, if the contents does not match a number. */
UnsignedInteger& UnsignedInteger::text(const std::string& txt)
{
std::string::size_type
start(txt.find_first_not_of(" \t\n\r")),
last(txt.find_last_not_of(" \t\n\r"));
if (start==std::string::npos) { // empty - set to 0
_text = "0";
return *this;
}
std::string::size_type pos(txt.find_first_not_of("0123456789", start));
if (pos<last || (pos==last && last==start))
throw type_mismatch(*this, txt,
"unsigned integer may only contain numbers");
unsigned long i(0);
std::stringstream(txt)>>i;
std::stringstream ss;
ss<<i;
_text = ss.str();
return *this;
}
//! Returns the contents as number.
unsigned long UnsignedInteger::number() const noexcept {
return number(*this);
}
//! Returns the contents as number.
unsigned long UnsignedInteger::number(const Node& node) noexcept {
unsigned long i(0);
std::stringstream ss(node.text());
ss>>i;
return i;
}
//----------------------------------------------------------------------------
//! To instanciate a factory, a template must be given.
Factory::Factory(const Node& t) noexcept:
_template(xml::Node("<xml::start>")<<t), _line(0) {
_template[0].min(1);
_template[0].max(1);
}
//! Instanciates an invalid factory. A template must be assigned later.
Factory::Factory() noexcept:
_template(xml::Node("<xml::start>")), _line(0) {
}
//! Assign a template.
/*! If you don't pass a template at instanciation, you must call
this method later, or you will get xml::factory_not_valid
exceptions when you try to use the factory. */
Factory& Factory::operator=(const Node& t) noexcept {
_template = xml::Node("<xml::start>")<<t;
_template[0].min(1);
_template[0].max(1);
return *this;
}
//! Get the template.
const Node& Factory::operator*() const try {
return _template[0];
} catch (...) {
throw factory_not_valid();
}
//! Access the (root node of the) template.
const Node* Factory::operator->() const try {
return &_template[0];
} catch (...) {
throw factory_not_valid();
}
//! Check whether the factory has been given a valid template.
Factory::operator bool() const noexcept {
return _template.children()>0;
}
//! Print the factory template's schema in human readable format.
/*! Calls xml::Factory::print */
std::ostream& operator<<(std::ostream& os, const Factory& factory)
{
return factory.print(os, *factory);
}
//! Print a node's schema description in human readable format.
/*! @todo May be changed to a XML Schema output later. */
std::ostream& Factory::print(std::ostream& os, const Node& node,
unsigned int level) noexcept {
if (node.children()) {
os<<std::string(level, '\t')<<'<'<<node.name();
for (Attributes::const_iterator it(node.attributes().begin());
it!=node.attributes().end(); ++it)
os<<' '<<it->first<<"=\""<<it->second<<'"';
os<<'>';
if (dynamic_cast<const UnsignedInteger*>(&node))
os<<" (Unsigned Integer";
else if (dynamic_cast<const String*>(&node))
os<<" (String";
else
os<<" (Node";
if (node.min()==1 && node.max()==1)
os<<", required exactly once)";
else if (node.min()==1 && node.max()==0)
os<<", required at least once)";
else if (node.min()==0 && node.max()==0)
os<<", 0..n)";
else if (node.min()==0 && node.max()==1)
os<<", optional)";
else
os<<", "<<node.min()<<".."<<node.max()<<")";
os<<std::endl;
for (Node::size_type i(0); i<node.children(); ++i)
print(os, node[i], level+1);
os<<std::string(level, '\t')<<"</"<<node.name()<<'>'<<std::endl;
} else {
os<<std::string(level, '\t')<<'<'<<node.name();
for (Attributes::const_iterator it(node.attributes().begin());
it!=node.attributes().end(); ++it)
os<<' '<<it->first<<"=\""<<it->second<<'"';
os<<"/>";
if (dynamic_cast<const UnsignedInteger*>(&node))
os<<" (Unsigned Integer";
else if (dynamic_cast<const String*>(&node))
os<<" (String";
else
os<<" (Node";
if (node.min()==1 && node.max()==1)
os<<", required exactly once)";
else if (node.min()==1 && node.max()==0)
os<<", required at least once)";
else if (node.min()==0 && node.max()==0)
os<<", 0..n)";
else if (node.min()==0 && node.max()==1)
os<<", optional)";
else
os<<", "<<node.min()<<".."<<node.max()<<")";
os<<std::endl;
}
return os;
}
//! Restore a xml::Node tree from a stream, according to the given schema.
std::unique_ptr<Node> Factory::read(std::istream& is) {
if (_template.children()==0) throw factory_not_valid();
try {
_line=1;
_open=0;
std::unique_ptr<Node> node(read(is, _template));
if (node->children()==0)
throw tag_expected(_template[0],
"nothing found, possibly empty stream");
return (*node)[0].clone();
} catch (exception& e) {
e.line(_line);
throw;
}
}
void Factory::reset() noexcept {
_line = 0;
_open = 0;
_template = xml::Node("<xml::start>");
}
Node& Factory::operator*() try {
return _template[0];
} catch (...) {
throw factory_not_valid();
}
Node* Factory::operator->() try {
return &_template[0];
} catch (...) {
throw factory_not_valid();
}
bool Factory::ws(char c) noexcept {
static char last(0);
if ((c=='\n'||c=='\r')&&last!='\n'&&last!='\r') ++_line;
last = c;
return c==' '||c=='\t'||c=='\n'||c=='\r';
}
std::unique_ptr<Node> Factory::read(std::istream& is, const Node& node) {
std::unique_ptr<Node> result(node.clone());
result->clear();
while (true) {
Tag res(tag(is, node));
if (!res.found) return result;
if (res.text.size()) result->text(res.text);
switch (res.type) {
case END: {
--_open;
if (res.attributes.size()) throw attributes_in_end_tag(node, is, res);
if (res.name!=node.name()) throw wrong_end_tag(node, is, res);
} return result;
case EMPTY: {
std::unique_ptr<Node> current(node[res.name].clone());
current->clear()<<res.attributes;
*result<<*checkChildren(node[res.name], std::move(current), is);
} break;
case START: {
++_open;
*result<<(*checkChildren(node[res.name], read(is, node[res.name]), is)
<<res.attributes);
} break;
case SPECIAL: break; //! @todo ignored could be stored
}
}
}
std::unique_ptr<Node> Factory::checkChildren(const xml::Node& tpl,
std::unique_ptr<Node> node,
std::istream& is) const
{
for (Node::size_type i(0); i<tpl.children(); ++i)
if ((tpl[i].min()>0 && node->list(tpl[i].name()).size()<tpl[i].min())
|| (0<tpl[i].max() && tpl[i].max()<node->list(tpl[i].name()).size()))
throw wrong_node_number(*node, is, tpl[i].name(),
node->list(tpl[i].name()).size(),
tpl[i].min(), tpl[i].max());
return std::move(node);
}
Tag Factory::tag(std::istream& is, const Node& position) {
char c(0);
Tag tag{"", START, "", Attributes(), "", false};
while (is && is.get(c) && ws(c)); // skip ws
if (is.eof()) {
if (_open>0)
throw missing_end_tag(position, is, tag);
else
return tag;
}
tag.found = true;
if (c!='<') do tag.text+=c; while (is && is.get(c) && c!='<');
bool nameRead(false);
for (char last(c); is && is.get(c) && c!='>'; last=c) switch (c) {
case '\n': case '\r':
if (last!='\n'&&last!='\r') ++_line;
// no break; fall through to non line breaking white spaces
[[fallthrough]];
case ' ': case '\t':
if (!nameRead && tag.name.size()) nameRead=true;
break;
case '/':
if (!nameRead && tag.name.size()) nameRead=true;
if (tag.type!=START) throw second_slash_in_tag(position, is, tag, c);
tag.type = nameRead?EMPTY:END;
break;
case '!': case '?':
if (last=='<') { // <! tag or <?xml.. starts
tag.special+=last;
do tag.special+=c; while (c!='>' && is && is.get(c));
tag.type=SPECIAL;
return tag;
}
// no break; not matched, fall through to error handling
[[fallthrough]];
default:
if (tag.type==EMPTY) throw character_after_slash(position, is, tag, c);
if (nameRead) { // read attribute
if (tag.type!=START) throw attributes_in_end_tag(position, is, tag);
std::string attrname(1, c), attrvalue;
while (is && is.get(c) && c!='=' && c!='>' && c!='/'
&& !ws(c)) attrname+=c;
while (ws(c) && is && is.get(c)); // skip ws, search '='
if (c=='=') { // get the value
while (is && is.get(c) && ws(c)); // skip ws
if (c!='"')
throw attribute_value_not_quoted(position, is, tag, c, attrname);
while (is && is.get(c) && c!='"') attrvalue+=c;
if (c!='"')
throw attribute_value_not_quoted(position, is, tag, c, attrname);
} else is.unget(); // read too far
if (tag.attributes.find(attrname)!=tag.attributes.end())
throw duplicate_attribute(position, is, tag, attrname);
if (!position(tag.name)) {
if (tag.name.size())
throw wrong_start_tag(position, is, tag);
else
throw tag_expected(position, tag.text);
}
if (position[tag.name].attributes().find(attrname)
==position[tag.name].attributes().end())
throw illegal_attribute(position, is, tag, attrname);
tag.attributes[attrname] = attrvalue;
} else { // part of a tag name
tag.name+=c;
}
}
if (tag.type==START||tag.type==EMPTY) {
if (!position(tag.name)) {
if (tag.name.size())
throw wrong_start_tag(position, is, tag);
else
throw tag_expected(position, tag.text);
}
const xml::Node& node(position[tag.name]);
for (Attributes::const_iterator it(node.attributes().begin());
it!=node.attributes().end(); ++it)
if (tag.attributes.find(it->first)==tag.attributes.end()) {
if (it->second=="xml::mandatory")
throw mandatory_attribute_missing(position, is, tag, it->first);
else if (it->second!="xml::optional")
tag.attributes.insert(*it);
}
}
return tag;
}
//============================================================== Serialization
//----------------------------------------------------------------------------
Serialize::Serialize() noexcept {}
Serialize::Serialize(const std::string& className) noexcept:
_xmlFactory(xml::Node(xml::String(className).limits(1,1))) {
}
Serialize::Serialize(const Serialize& other) noexcept {
copy(other);
}
Serialize::~Serialize() {
reset();
}
Serialize& Serialize::operator=(const Serialize& other) noexcept {
copy(other);
return *this;
}
Serialize& Serialize::className(const std::string& name) noexcept {
xml::Node node(xml::Node(xml::String(name).limits(1,1)));
if (_xmlFactory) {
for (xml::Node::size_type i(0); i<_xmlFactory->children(); ++i)
node<<(*_xmlFactory)[i];
}
_xmlFactory=node;
return *this;
}
Serialize& Serialize::persist(Serialize& ser,
const std::string& name) noexcept {
if (ser.optional()) {
_xmlNames[name] = &ser;
ser.className(name);
*_xmlFactory<<*ser._xmlFactory;
std::stringstream ss;
} else {
ser.checkInit();
_xmlNames[name] = &ser;
xml::Node schema(*_xmlFactory);
xml::Node node(xml::Node(name).limits(1,1));
for (xml::Node::size_type i(0); i<ser._xmlFactory->children(); ++i)
node<<(*ser._xmlFactory)[i];
_xmlFactory = schema<<node;
return *this;
}
return *this;
}
Serialize& Serialize::persist(bool& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(char& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned char& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed char& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned short& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed short& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned int& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed int& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned long& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed long& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(float& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(double& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(std::string& member,
const std::string& name) noexcept {
return persistSimpleType(member, name);
}
std::ostream& Serialize::saveXml(std::ostream& os,
const std::string& name) const noexcept {
checkInit();
xml::Node node(*_xmlFactory);
if (name.size()) node.name(name);
for (std::map<std::string, Any>::const_iterator
it(_xmlNames.begin());
it!=_xmlNames.end(); ++it)
toNode(it->second, node[it->first]);
os<<node;
return os;
}
std::istream& Serialize::loadXml(std::istream& is, const std::string& name) {
checkInit();
xml::Factory factory(_xmlFactory);
if (name.size()) factory->name(name);
std::unique_ptr<xml::Node> node(factory.read(is));
for (std::map<std::string, Any>::const_iterator
it(_xmlNames.begin());
it!=_xmlNames.end(); ++it)
if ((*node)(it->first))
fromNode(it->second, (*node)[it->first]);
else
clear(it->second);
return is;
}
std::string Serialize::schema() const noexcept {
checkInit();
std::stringstream ss;
ss<<*_xmlFactory;
return ss.str();
}
void Serialize::registerFromNode(Serialize::FromNodeFunc fromNodeFunc) {
_fromNode.insert(fromNodeFunc);
}
void Serialize::registerToNode(Serialize::ToNodeFunc toNodeFunc) {
_toNode.insert(toNodeFunc);
}
void Serialize::registerClear(Serialize::ClearFunc clearFunc) {
_clear.insert(clearFunc);
}
void Serialize::initXmlMembers() {}
void Serialize::clear() {
for (std::map<std::string, Any>::const_iterator
it(_xmlNames.begin());
it!=_xmlNames.end(); ++it)
clear(it->second);
}
void Serialize::reset() noexcept {
_xmlFactory.reset();
_xmlNames.clear();
}
void Serialize::copy(const Serialize&) noexcept {
reset();
initXmlMembers();
}
void Serialize::fromNode(Any member, const xml::Node& node) {
for (std::set<FromNodeFunc>::const_iterator it(_fromNode.begin());
it!=_fromNode.end(); ++it)
if ((**it)(member, node)) return; // found match
throw type_not_registered(member.type().name(), node.name(), node);
}
void Serialize::toNode(const Any member, xml::Node& node) const {
for (std::set<ToNodeFunc>::const_iterator it(_toNode.begin());
it!=_toNode.end(); ++it)
if ((**it)(member, node)) return; // found match
throw type_not_registered(member.type().name(), node.name(), node);
}
void Serialize::clear(Any member) {
for (std::set<ClearFunc>::const_iterator it(_clear.begin());
it!=_clear.end(); ++it)
if ((**it)(member)) return; // found match
throw type_not_registered(member.type().name());
}
bool Serialize::optional() const noexcept {
return false;
}
std::set<Serialize::FromNodeFunc> Serialize::_fromNode;
std::set<Serialize::ToNodeFunc> Serialize::_toNode;
std::set<Serialize::ClearFunc> Serialize::_clear;
//=============================================================== Assign Types
template<typename TYPE> bool assignFromNode(Any member,
const xml::Node& node) {
if (!any_cast<TYPE>(&member)) return false;
*any_cast<TYPE>(member) =
(TYPE)dynamic_cast<const xml::String&>(node);
return true;
}
template<typename TYPE> bool assignToNode(const Any member,
xml::Node& node) {
if (!any_cast<TYPE>(&member)) return false;
std::stringstream ss;
ss<<*any_cast<TYPE>(member);
node.text(ss.str());
return true;
}
template<> bool assignFromNode<bool>(Any member,
const xml::Node& node) {
if (member.type()!=typeid(bool)) return false;
std::string s(*dynamic_cast<const xml::String&>(node));
std::string::size_type
start(s.find_first_not_of(" \t\n\r")),
end(s.find_last_not_of(" \t\n\r"));
if (start==std::string::npos) {
*any_cast<bool>(member) = false;
} else {
s = s.substr(start, end-start+1);
*any_cast<bool>(member) =
s!="0" && s!="false" && s!="no" && s!="off";
}
return true;
}
template<> bool assignToNode<bool>(const Any member,
xml::Node& node) {
if (member.type()!=typeid(bool)) return false;
node.text(*any_cast<bool>(member)?"true":"false");
return true;
}
template<> bool assignFromNode<Serialize>(Any member,
const xml::Node& node) {
if (!any_cast<Serialize>(&member)) return false;
if (any_cast<Serialize>(member)->optional()) {
(*any_cast<Serialize>(member)).fromNode(member, node);
return true;
}
//! @todo improve this (inefficient)
std::stringstream ss; // simple but inefficient: rewrite and reread
ss<<node;
any_cast<Serialize>(member)->loadXml(ss, node.name());
/*
Serialize* ser(any_cast<Serialize>(member));
for (xml::Node::size_type i(0); i<node.children(); ++i)
ser->fromNode(ser->_xmlNames[*node[i]], node[i]);*/
return true;
}
template<> bool assignToNode<Serialize>(const Any member,
xml::Node& node) {
if (!any_cast<Serialize>(&member)) return false;
if (any_cast<Serialize>(member)->optional()) {
any_cast<Serialize>(member)->toNode(member, node);
return true;
}
std::stringstream ss;
any_cast<Serialize>(member)->saveXml(ss, node.name());
xml::Factory factory(node);
node = *factory.read(ss);
return true;
}
//================================================================ Clear Types
template<typename TYPE> bool clearMember(Any member) {
if (!any_cast<TYPE>(&member)) return false;
*any_cast<TYPE>(member) = 0;
return true;
}
template<> bool clearMember<bool>(Any member) {
if (member.type()!=typeid(bool)) return false;
*any_cast<bool>(member) = false;
return true;
}
template<> bool clearMember<std::string>(Any member) {
if (member.type()!=typeid(bool)) return false;
any_cast<std::string>(member)->clear();
return true;
}
template<> bool clearMember<Serialize>(Any member) {
if (!any_cast<Serialize>(&member)) return false;
any_cast<Serialize>(member)->clear();
return true;
}
// Init To and From Node ---------------------------------------------------
namespace {
template<int NUM=MAX_NUM> struct RegisterTemplateForStdTypes {
RegisterTemplateForStdTypes<NUM-1> next; // recurse to next until 0
RegisterTemplateForStdTypes() {
Serialize::registerToNode
(&assignToNode<typename ToType<NUM>::Type>);
Serialize::registerFromNode
(&assignFromNode<typename ToType<NUM>::Type>);
Serialize::registerClear
(&clearMember<typename ToType<NUM>::Type>);
}
};
template<> struct RegisterTemplateForStdTypes<0> {}; // stop recursion
static RegisterTemplateForStdTypes<> init; // execute initialisation
}
// ===========================================================================
}