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.

1010 lines
36 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>
#include <cassert>
16 years ago
#include <iomanip>
class MethodTrace {
public:
MethodTrace(const void* addr, const std::string& name) throw():
_addr(addr), _name(name) {
std::clog<<std::hex<<std::setw(15)<<_addr<<": "
<<std::dec<<std::setw(2+_level)
<<std::setfill(' ')<<"\\ "<<_name<<std::endl;
++_level;
}
~MethodTrace() throw() {
--_level;
std::clog<<std::hex<<std::setw(15)<<_addr<<": "<<std::dec
<<std::setw(2+_level)<<std::setfill(' ')
<<"/ "<<_name<<std::endl;
}
private:
const void* _addr;
const std::string _name;
static unsigned int _level;
};
unsigned int MethodTrace::_level(0);
#define TRACE MethodTrace XXX_METHOD(this, __PRETTY_FUNCTION__)
#define LOG(X) std::clog<<__PRETTY_FUNCTION__<<"\t**** "<<X<<std::endl
16 years ago
namespace xml {
//================================================================= 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() {
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() {
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() {
_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());
}
16 years ago
//! Clones But clears the parent.
/*! 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;
}
//! 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 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. */
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 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.
/*! The template is a node that specifies the schema of the data
that can be loaded from streams through a xml::Factory
instance.
The root element has automatically set the limits 1..1
(<code>xml::Node::limits(1, 1)</code>, see xml::Node::limits),
which means that the root element must exist exactly once. If
you pass another limit, your limit is overwritten.
E.g. to load an address, that contains a tag &lt;address&gt;
with at least a name and optional an address in it's body, you
may write:
@code
xml::Factory addrTpl(xml::Node("address")
(<<xml::Node("name").limit(1, 1)
(<<xml::String("first").limit(1, 1)
<<xml::String("middle") // 0..n -> .limit(0, 0)
<<xml::String("last").limit(1, 1))
<<(xml::Node("location").max(1)
<<xml::String("line").min(1))
<<xml::String("country").max(1)));
@endcode
According to this example, a valid XML file could be:
@verbatim
<address>
<name>
<first>Marc</first>
<middle>Roman</middle>
<last>Wäckerlin</last>
</name>
<location>
<line>SwissSign AG</line>
<line>Pfingstweidstrasse 60b</line>
<line>8005 Zürich</line>
</location>
<country>Schweiz</country>
</address>
@endverbatim */
Factory::Factory(const Node& t) throw():
_template(xml::Node("<xml::start>")<<t), _line(0) {
_template[0].min(1);
_template[0].max(1);
}
Factory::Factory() throw():
_template(xml::Node("<xml::start>")), _line(0) {
}
Factory& Factory::operator=(const Node& t) throw() {
_template = xml::Node("<xml::start>")<<t;
_template[0].min(1);
_template[0].max(1);
}
const Node& Factory::operator*() const throw(factory_not_valid) try {
return _template[0];
} catch (...) {
throw factory_not_valid();
}
Factory::operator bool() const throw() {
return _template.children()>0;
16 years ago
}
std::ostream& operator<<(std::ostream& os, const Factory& factory) throw() {
return factory.print(os, *factory);
}
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;
}
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) try {
16 years ago
_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();
16 years ago
} catch (exception& e) {
e.line(_line);
throw;
}
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;
}
}