Files
libxml-cxx/src/xml.cxx
2018-11-21 22:23:14 +00:00

1330 lines
47 KiB
C++

/*! @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
}
// ===========================================================================
}