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.

1312 lines
47 KiB

16 years ago
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
16 years ago
#include <xml-cxx/xml.hxx>
16 years ago
#include <iostream>
#include <sstream>
#include <cstdlib>
16 years ago
#include <algorithm>
16 years ago
unsigned int MethodTrace::_level(0);
namespace xml {
16 years ago
//================================================================= EXCEPTIONS
//----------------------------------------------------------------------------
exception::exception(std::string reason) throw():
_what(reason), _node(0) {
}
16 years ago
exception::exception(std::string reason, const Node& t) throw():
_what(reason), _node(t.clone().release()) {
}
exception::~exception() throw() {
delete _node;
}
16 years ago
void exception::line(unsigned long line) throw() {
std::stringstream ss;
ss<<line;
_what += "\nat line: "+ss.str();
}
16 years ago
const char* exception::what() const throw() {
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) throw():
exception("child not available", t), _name(name) {
}
const char* access_error::what() const throw() {
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) throw():
exception("child not available", t), _pos(pos) {
}
const char* out_of_range::what() const throw() {
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) throw():
exception("node does not accept child nodes", parent),
_child(child.clone().release()) {
}
cannot_have_children::~cannot_have_children() throw() {
delete _child;
}
const char* cannot_have_children::what() const throw() {
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) throw():
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) throw():
exception(reason, t), _pos(is.tellg()), _tag(0), _char(0) {
}
stream_error::~stream_error() throw() {
delete _tag;
16 years ago
}
const char* stream_error::what() const throw() {
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;
}
16 years ago
w = ss.str();
}
return w.c_str();
}
//============================================================================
//----------------------------------------------------------------------------
16 years ago
//! Copy an attribute.
Attributes::Value::Value(const value_type& o) throw():
Attributes::value_type(o) {
}
//! Construct an empty attribute.
Attributes::Value::Value(const std::string& name) throw():
Attributes::value_type(name, std::string()) {
}
16 years ago
//! Construct an attribute with name an value.
Attributes::Value::Value(const std::string& name,
const std::string& value) throw():
Attributes::value_type(name, value) {
}
16 years ago
//! Assign a value.
Attributes::Value& Attributes::Value::operator=(const std::string& value)
throw() {
second = value;
return *this;
}
//! Get the attribute name.
const std::string& Attributes::Value::name() const throw() {
return first;
}
16 years ago
//! Get the attribute value.
const std::string& Attributes::Value::value() const throw() {
return second;
}
16 years ago
//! Get the attribute value.
std::string& Attributes::Value::value() throw() {
return second;
}
16 years ago
//! 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 throw() {
15 years ago
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 throw() {
return !(!second.size()||second=="false"||second=="no"||second=="0");
}
16 years ago
//! Convert the attribute to a number.
Attributes::Value::operator unsigned long() const throw() {
15 years ago
return toNumber();
}
//! Convert the attribute to a number.
unsigned long Attributes::Value::toNumber() const throw() {
std::stringstream ss(second);
int i(0);
ss>>i;
return i;
}
16 years ago
//! Convert the attribute to a space separated list.
Attributes::Value::operator List() const throw() {
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 throw() {
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.
16 years ago
/*! @copydoc xml::Attributes::Value::toList
@return the first element of the list. */
std::string Attributes::Value::front(const std::string& separators) const
throw(empty_attribute_list) {
List l(toList(separators));
if (!l.size()) throw empty_attribute_list(first);
return l.front();
}
//----------------------------------------------------------------------------
16 years ago
//! Empty attribute list
Attributes::Attributes() throw() {}
//! Attribute list with first one empty attribute given.
Attributes::Attributes(const std::string& empty) throw() {
16 years ago
insert(Value(empty));
}
16 years ago
//! Attribute list with first attribute given.
Attributes::Attributes(const std::string& key,
const std::string& value) throw() {
insert(Value(key, value));
}
16 years ago
//! Add a new key-value pair to the attribute list.
Attributes& Attributes::operator<<(const Attributes::Value& v) throw() {
insert(v);
return *this;
}
16 years ago
//! Add a new empty key to the attribute list.
Attributes& Attributes::operator<<(const std::string& key) throw() {
insert(Value(key));
return *this;
}
16 years ago
//----------------------------------------------------------------------------
16 years ago
//! 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) throw():
_name(name), _parent(0), _min(min), _max(max) {
16 years ago
}
//! Copy node, reset parent.
/*! The parent is reset, the node does not belong to the same parent
16 years ago
as the source of the copy.
16 years ago
@see xml::Node::clone() for more information on the parenting. */
16 years ago
Node::Node(const Node& o) throw():
_attributes(o._attributes), _name(o.name()), _parent(0),
_min(o._min), _max(o._max) {
16 years ago
for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release());
}
Node::~Node() throw() {
clear();
}
//! Assign new node, keep parent.
/*! The parent remains unchanged, the node does not belong to the
16 years ago
same parent as the source of the copy.
16 years ago
@see xml::Node::clone() for more information on the parenting. */
16 years ago
Node& Node::operator=(const Node& o) throw() {
clear();
16 years ago
_attributes=o._attributes;
_name = o.name();
_min = o._min;
_max = o._max;
16 years ago
for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release());
}
//! Clones But resets the parent reference.
/*! You get a new instance of the node, but detached from the
16 years ago
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. */
16 years ago
std::auto_ptr<Node> Node::clone() const throw() {
std::auto_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. */
16 years ago
std::ostream& Node::out(std::ostream& o, unsigned int level) const throw() {
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. */
16 years ago
std::string Node::text() const throw() {
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, type_mismatch) {
16 years ago
throw tag_expected(*this, txt);
}
//! Appends a new node at the end of all children.
16 years ago
Node& Node::append(const Node& o) throw(cannot_have_children) {
_contents.push_back(o.clone(this).release());
return *this;
}
16 years ago
//! Removes a given child node.
Node& Node::remove(Node& n) throw(access_error) {
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) throw(access_error) {
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) throw(out_of_range) {
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. */
16 years ago
Node& Node::set(const Attributes& o) throw() {
_attributes.clear();
16 years ago
_attributes.insert(o.begin(), o.end());
return *this;
}
//! Clears content and attributes.
16 years ago
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.
16 years ago
std::string Node::name() const throw() {
return _name;
}
//! Set a new node's tag name.
Node& Node::name(const std::string& n) throw() {
_name = n;
return *this;
}
//! Set minimum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node& Node::min(Node::size_type m) throw() {
_min = m;
return *this;
}
//! Get minimum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node::size_type Node::min() const throw() {
return _min;
}
//! Set maximum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node& Node::max(Node::size_type m) throw() {
_max = m;
return *this;
}
//! Get maximum number of instances (in a xml::Factory).
/*! @copydoc limits */
Node::size_type Node::max() const throw() {
return _max;
}
//! @c true if node has a parent.
16 years ago
bool Node::isChild() const throw() {
return _parent;
}
//! Get the parent node.
16 years ago
Node& Node::parent() const throw(no_parent) {
if (!_parent) throw no_parent(*this);
return *_parent;
}
//! Check if a specific attribute is set or not.
16 years ago
bool Node::hasAttr(const std::string& name) const throw() {
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. */
16 years ago
Node& Node::attr(const std::string& name, bool mandatory) throw() {
_attributes[name] = mandatory?"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. */
15 years ago
Node& Node::attr(const std::string& name, const std::string& deflt) throw() {
_attributes[name] = deflt;
16 years ago
return *this;
}
//! Get an attribute.
/*! Returns the attribute's value (empty if the attribute is not set) */
16 years ago
std::string Node::attr(const std::string& name) const throw() {
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) throw() {
16 years ago
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 throw(attribute_not_available) {
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 throw() {
return _attributes;
}
//! Get the list of attributes.
Attributes& Node::attributes() throw() {
return _attributes;
}
//! 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) throw() {
_min = min;
16 years ago
_max = max;
return *this;
}
16 years ago
//! Get all immediate children of a given node name.
Node::List Node::list(const std::string& name) const throw() {
List res;
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
if ((*it)->_name==name) res.push_back(*it);
return res;
}
16 years ago
//! Check if at least one child node of a given name exists.
16 years ago
bool Node::operator()(const std::string& child) const throw() {
return find(child);
}
16 years ago
//! Append a child at the end.
/*! @copydoc xml::Node::append */
16 years ago
Node& Node::operator<<(const Node& o) throw(cannot_have_children) {
return append(o);
}
16 years ago
//! Add an empty attribute.
/*! @copydoc xml::Node::set */
16 years ago
Node& Node::operator<<(const Attributes& o) throw() {
return set(o);
}
16 years ago
//! Get the number of children.
16 years ago
Node::size_type Node::children() const throw() {
return _contents.size();
}
16 years ago
//! 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> */
16 years ago
const Node& Node::operator[](Node::size_type child) const
throw(out_of_range) try {
16 years ago
return *_contents.at(child);
} catch (...) {
throw out_of_range(*this, child);
}
16 years ago
/*! @copydoc xml::Node::operator[](Node::size_type child) const */
Node& Node::operator[](Node::size_type child) throw(out_of_range) try {
16 years ago
return *_contents.at(child);
} catch (...) {
throw out_of_range(*this, child);
}
16 years ago
//! Get the first child of a given node name.
/*! @return the first child with matching node name */
16 years ago
const Node& Node::operator[](const std::string& child) const
throw(access_error) {
const Node* const t(find(child));
if (!t) throw access_error(*this, child);
return *t;
}
16 years ago
/*! @copydoc xml::Node::operator[](const std::string& child) const */
16 years ago
Node& Node::operator[](const std::string& child) throw(access_error) {
Node* const t(find(child));
if (!t) throw access_error(*this, child);
return *t;
}
16 years ago
//! Get the textual contents of a node.
/*! @copydoc xml::Node::text() */
16 years ago
std::string Node::operator*() const throw() {
return text();
}
16 years ago
//! Set a text (forbidden in xml::Node)
/*! @copydoc xml::Node::text(const std::string& txt) */
Node& Node::operator=(const std::string& contents) throw(tag_expected,
type_mismatch) {
16 years ago
return text(contents);
}
16 years ago
//! Write node in XML format to a stream.
/*! @copydoc xml::Node::out */
16 years ago
std::ostream& operator<<(std::ostream& o, const Node& t) throw() {
return t.out(o);
}
16 years ago
//! 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. */
16 years ago
Node* Node::find(const std::string& child) const throw() {
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it) {
if ((*it)->name()==child) return *it;
}
return 0;
}
16 years ago
//! Clone a node, but assign a new parent.
16 years ago
std::auto_ptr<Node> Node::clone(Node* p) const throw() {
std::auto_ptr<Node> c(clone());
c->_parent = p;
return c;
}
//----------------------------------------------------------------------------
16 years ago
/*! @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) throw():
Node(name, min, max) {
}
16 years ago
//! 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) throw():
Node(name, min, max), _text(text) {
16 years ago
}
std::auto_ptr<Node> String::clone() const throw() {
return std::auto_ptr<Node>(new String(*this));
}
std::string String::text() const throw() {
return _text;
}
16 years ago
//! An xml::String contains text: Set the text.
/*! Never throws an exception. */
String& String::text(const std::string& txt) throw(tag_expected,
type_mismatch) {
16 years ago
_text = txt;
return *this;
}
std::ostream& String::out(std::ostream& o, unsigned int level) const throw() {
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;
}
16 years ago
//! An xml::String has no child nodes: Always throws an exception.
16 years ago
String& String::append(const Node& o) throw(cannot_have_children) {
throw cannot_have_children(*this, o);
}
16 years ago
//! An xml::String contains text: Set the text.
16 years ago
Node& String::operator=(const std::string& contents) throw() {
return text(contents);
}
String::operator std::string() const throw() {
return text();
}
String::operator bool() const throw() {
bool res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator char() const throw() {
char res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed char() const throw() {
signed char res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned char() const throw() {
unsigned char res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed short() const throw() {
signed short res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned short() const throw() {
unsigned short res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed int() const throw() {
signed int res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned int() const throw() {
unsigned int res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator signed long() const throw() {
signed long res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator unsigned long() const throw() {
unsigned long res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator float() const throw() {
float res;
std::stringstream ss(text());
ss>>res;
return res;
}
String::operator double() const throw() {
double res;
std::stringstream ss(text());
ss>>res;
return res;
}
16 years ago
//----------------------------------------------------------------------------
16 years ago
/*! @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) throw():
16 years ago
String(name,
dynamic_cast<std::stringstream&>(std::stringstream()<<i).str(),
min, max) {
16 years ago
}
std::auto_ptr<Node> UnsignedInteger::clone() const throw() {
return std::auto_ptr<Node>(new UnsignedInteger(*this));
}
16 years ago
//! An xml::UnsignedInteger must only contain an number.
/*! En exception is thrown, if the contents does not match a number. */
16 years ago
UnsignedInteger& UnsignedInteger::text(const std::string& txt)
throw(tag_expected, type_mismatch) {
std::string::size_type
start(txt.find_first_not_of(" \t\n\r")),
last(txt.find_last_not_of(" \t\n\r"));
16 years ago
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)
16 years ago
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;
}
16 years ago
//! Returns the contents as number.
16 years ago
unsigned long UnsignedInteger::number() const throw() {
return number(*this);
}
16 years ago
//! Returns the contents as number.
16 years ago
unsigned long UnsignedInteger::number(const Node& node) throw() {
unsigned long i(0);
std::stringstream ss(node.text());
ss>>i;
return i;
}
16 years ago
//----------------------------------------------------------------------------
16 years ago
//! To instanciate a factory, a template must be given.
Factory::Factory(const Node& t) throw():
_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() throw():
_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) throw() {
_template = xml::Node("<xml::start>")<<t;
_template[0].min(1);
_template[0].max(1);
}
//! Get the template.
const Node& Factory::operator*() const throw(factory_not_valid) try {
return _template[0];
} catch (...) {
throw factory_not_valid();
}
//! Access the (root node of the) template.
const Node*const Factory::operator->() const throw(factory_not_valid) try {
return &_template[0];
} catch (...) {
throw factory_not_valid();
}
//! Check whether the factory has been given a valid template.
Factory::operator bool() const throw() {
return _template.children()>0;
16 years ago
}
//! Print the factory template's schema in human readable format.
/*! Calls xml::Factory::print */
std::ostream& operator<<(std::ostream& os, const Factory& factory)
throw(factory_not_valid) {
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) throw() {
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);
16 years ago
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.
16 years ago
std::auto_ptr<Node> Factory::read(std::istream& is)
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
second_slash_in_tag,
16 years ago
character_after_slash, missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute,
attributes_in_end_tag,
illegal_attribute, mandatory_attribute_missing,
wrong_node_number, factory_not_valid) {
if (_template.children()==0) throw factory_not_valid();
try {
_line=1;
_open=0;
std::auto_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;
}
16 years ago
}
void Factory::reset() throw() {
_line = 0;
_open = 0;
_template = xml::Node("<xml::start>");
}
Node& Factory::operator*() throw(factory_not_valid) try {
return _template[0];
} catch (...) {
throw factory_not_valid();
}
Node*const Factory::operator->() throw(factory_not_valid) try {
return &_template[0];
} catch (...) {
throw factory_not_valid();
}
16 years ago
bool Factory::ws(char c) throw() {
static char last(0);
if ((c=='\n'||c=='\r')&&last!='\n'&&last!='\r') ++_line;
last = c;
16 years ago
return c==' '||c=='\t'||c=='\n'||c=='\r';
}
std::auto_ptr<Node> Factory::read(std::istream& is, const Node& node)
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
second_slash_in_tag,
16 years ago
character_after_slash, missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute, attributes_in_end_tag,
illegal_attribute, mandatory_attribute_missing,
wrong_node_number) {
16 years ago
std::auto_ptr<Node> result(node.clone());
result->clear();
while (true) {
16 years ago
Tag res(tag(is, node));
if (!res.found) return result;
16 years ago
if (res.text.size()) result->text(res.text);
switch (res.type) {
case END: {
--_open;
16 years ago
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;
16 years ago
case EMPTY: {
std::auto_ptr<Node> current(node[res.name].clone());
current->clear()<<res.attributes;
*result<<*checkChildren(node[res.name], current, is);
16 years ago
} break;
case START: {
++_open;
*result<<(*checkChildren(node[res.name], read(is, node[res.name]), is)
<<res.attributes);
16 years ago
} break;
case SPECIAL: break; //! @todo ignored could be stored
16 years ago
}
}
}
std::auto_ptr<Node> Factory::checkChildren(const xml::Node& tpl,
std::auto_ptr<Node> node,
std::istream& is) const
throw(wrong_node_number) {
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 node;
}
16 years ago
Tag Factory::tag(std::istream& is, const Node& position)
throw(second_slash_in_tag, character_after_slash, tag_expected,
16 years ago
missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute, attributes_in_end_tag,
illegal_attribute, mandatory_attribute_missing,
wrong_start_tag) {
16 years ago
char c(0);
Tag tag((Tag){"", START, "", Attributes(), "", false});
16 years ago
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;
16 years ago
if (c!='<') do tag.text+=c; while (is && is.get(c) && c!='<');
bool nameRead(false);
16 years ago
for (char last(c); is && is.get(c) && c!='>'; last=c) switch (c) {
case '\n': case '\r':
if (last!='\n'&&last!='\r') ++_line;
case ' ': case '\t':
16 years ago
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
16 years ago
tag.special+=last;
do tag.special+=c; while (c!='>' && is && is.get(c));
16 years ago
tag.type=SPECIAL;
return tag;
}
16 years ago
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);
16 years ago
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 '='
16 years ago
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
16 years ago
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);
16 years ago
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);
}
}
16 years ago
return tag;
}
//============================================================== Serialization
//----------------------------------------------------------------------------
Serialize::Serialize() throw() {}
Serialize::Serialize(const std::string& className) throw():
_xmlFactory(xml::Node(xml::String(className).limits(1,1))) {
}
Serialize::Serialize(const Serialize& other) throw() {
copy(other);
}
Serialize::~Serialize() {
reset();
}
Serialize& Serialize::operator=(const Serialize& other) throw() {
copy(other);
return *this;
}
Serialize& Serialize::className(const std::string& name) throw() {
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) throw() {
16 years ago
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) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(char& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned char& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed char& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned short& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed short& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned int& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed int& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(unsigned long& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(signed long& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(float& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(double& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
Serialize& Serialize::persist(std::string& member,
const std::string& name) throw() {
return persistSimpleType(member, name);
}
std::ostream& Serialize::saveXml(std::ostream& os,
const std::string& name) const throw() {
checkInit();
xml::Node node(*_xmlFactory);
if (name.size()) node.name(name);
16 years ago
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::auto_ptr<xml::Node> node(factory.read(is));
16 years ago
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 throw() {
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);
}
16 years ago
void Serialize::initXmlMembers() {}
void Serialize::clear() throw() {
for (std::map<std::string, Any>::const_iterator
it(_xmlNames.begin());
it!=_xmlNames.end(); ++it)
clear(it->second);
}
void Serialize::reset() throw() {
_xmlFactory.reset();
_xmlNames.clear();
}
void Serialize::copy(const Serialize& o) throw() {
reset();
initXmlMembers();
}
16 years ago
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);
}
16 years ago
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) throw() {
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());
}
16 years ago
bool Serialize::optional() const throw() {
return false;
}
std::set<Serialize::FromNodeFunc> Serialize::_fromNode;
std::set<Serialize::ToNodeFunc> Serialize::_toNode;
std::set<Serialize::ClearFunc> Serialize::_clear;
//=============================================================== Assign Types
16 years ago
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;
}
16 years ago
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;
}
16 years ago
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;
}
16 years ago
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;
}
16 years ago
template<> bool assignFromNode<Serialize>(Any member,
const xml::Node& node) {
if (!any_cast<Serialize>(&member)) return false;
16 years ago
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;
}
16 years ago
template<> bool assignToNode<Serialize>(const Any member,
xml::Node& node) {
if (!any_cast<Serialize>(&member)) return false;
16 years ago
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;
16 years ago
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
}
// ===========================================================================
16 years ago
}