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.
 
 
 
 

585 lines
20 KiB

/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#include <xml-cxx/xml.hxx>
#include <iostream>
#include <sstream>
#include <cstdlib>
#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
namespace xml {
//================================================================= EXCEPTIONS
//----------------------------------------------------------------------------
exception::exception(std::string reason) throw():
_what(reason), _node(0) {
}
exception::exception(std::string reason, const Node& t) throw():
_what(reason), _node(t.clone().release()) {
}
exception::~exception() throw() {
delete _node;
}
void exception::line(unsigned long line) throw() {
std::stringstream ss;
ss<<line;
_what += "\nat line: "+ss.str();
}
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() throw() {
delete _tag;
}
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<<")";
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) throw():
Attributes::value_type(o) {
}
//! Construct an empty attribute.
Attributes::Value::Value(const std::string& name) throw():
Attributes::value_type(name, std::string()) {
}
//! Construct an attribute with name an value.
Attributes::Value::Value(const std::string& name,
const std::string& value) throw():
Attributes::value_type(name, value) {
}
//! 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;
}
//! Get the attribute value.
const std::string& Attributes::Value::value() const throw() {
return second;
}
//! Get the attribute value.
std::string& Attributes::Value::value() throw() {
return second;
}
//! Convert the attribute to a boolean.
Attributes::Value::operator bool() const throw() {
/*! @return @c true if the value is set and not equal to one of:
@c false @c no @c 0. */
return !(second.size()||second=="false"||second=="no"||second=="0");
}
//! Convert the attribute to a number.
Attributes::Value::operator unsigned long() const throw() {
std::stringstream ss(second);
int i(0);
ss>>i;
return i;
}
//! Convert the attribute to a space separated list.
Attributes::Value::operator List() const throw() {
return toList();
}
//! Convert the attribute to a space separated list.
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;
}
//----------------------------------------------------------------------------
//! Empty attribute list
Attributes::Attributes() throw() {}
//! Attribute list with first one empty attribute given.
Attributes::Attributes(const std::string& empty) throw() {
insert(Value(empty));
}
//! Attribute list with first attribute given.
Attributes::Attributes(const std::string& key,
const std::string& value) throw() {
insert(Value(key, value));
}
//! Add a new key-value pair to the attribute list.
Attributes& Attributes::operator<<(const Attributes::Value& v) throw() {
insert(v);
return *this;
}
//! Add a new empty key to the attribute list.
Attributes& Attributes::operator<<(const std::string& key) throw() {
insert(Value(key));
return *this;
}
//----------------------------------------------------------------------------
//! Nodes must be given a name.
/*! Unnamed nodes are not allowed, therefore there's no default
constructor. */
Node::Node(std::string name) throw():
_name(name), _parent(0) {
}
Node::Node(const Node& o) throw():
_attributes(o._attributes), _name(o.name()), _parent(0) {
for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release());
}
Node& Node::operator=(const Node& o) throw() {
_attributes=o._attributes;
_name = o.name();
_parent = 0;
for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release());
}
Node::~Node() throw() {
clear();
}
//! Clones But clears the parent.
std::auto_ptr<Node> Node::clone() const throw() {
std::auto_ptr<Node> res(new Node(*this));
res->_parent = 0;
return res;
}
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;
}
std::string Node::text() const throw() {
std::string s;
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
s+=***it;
return s;
}
Node& Node::text(const std::string& txt) throw(tag_expected, type_mismatch) {
throw tag_expected(*this, txt);
}
Node& Node::append(const Node& o) throw(cannot_have_children) {
_contents.push_back(o.clone(this).release());
return *this;
}
Node& Node::set(const Attributes& o) throw() {
_attributes.insert(o.begin(), o.end());
return *this;
}
Node& Node::clear() throw () {
for (Contents::const_iterator it(_contents.begin());
it!=_contents.end(); ++it)
delete *it;
_contents.clear();
_attributes.clear();
return *this;
}
std::string Node::name() const throw() {
return _name;
}
bool Node::isChild() const throw() {
return _parent;
}
Node& Node::parent() const throw(no_parent) {
if (!_parent) throw no_parent(*this);
return *_parent;
}
bool Node::hasAttr(const std::string& name) const throw() {
return _attributes.find(name)!=_attributes.end();
}
Node& Node::attr(const std::string& name, bool mandatory) throw() {
_attributes[name] = mandatory?"mandatory":"optional";
return *this;
}
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();
}
std::string& Node::attr(const std::string& name) throw() {
return _attributes[name];
}
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);
}
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;
}
bool Node::operator()(const std::string& child) const throw() {
return find(child);
}
Node& Node::operator<<(const Node& o) throw(cannot_have_children) {
return append(o);
}
Node& Node::operator<<(const Attributes& o) throw() {
return set(o);
}
Node::size_type Node::children() const throw() {
return _contents.size();
}
const Node& Node::operator[](Node::size_type child) const
throw(access_error) try {
return *_contents.at(child);
} catch (...) {
throw out_of_range(*this, child);
}
Node& Node::operator[](Node::size_type child) throw(access_error) try {
return *_contents.at(child);
} catch (...) {
throw out_of_range(*this, child);
}
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;
}
Node& Node::operator[](const std::string& child) throw(access_error) {
Node* const t(find(child));
if (!t) throw access_error(*this, child);
return *t;
}
std::string Node::operator*() const throw() {
return text();
}
Node& Node::operator=(const std::string& contents) throw(tag_expected,
type_mismatch) {
return text(contents);
}
std::ostream& operator<<(std::ostream& o, const Node& t) throw() {
return t.out(o);
}
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;
}
std::auto_ptr<Node> Node::clone(Node* p) const throw() {
std::auto_ptr<Node> c(clone());
c->_parent = p;
return c;
}
//----------------------------------------------------------------------------
String::String(std::string name, const std::string& text) throw():
Node(name), _text(text) {
}
std::auto_ptr<Node> String::clone() const throw() {
return std::auto_ptr<Node>(new String(*this));
}
std::string String::text() const throw() {
return _text;
}
String& String::text(const std::string& txt) throw(tag_expected,
type_mismatch) {
_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;
}
String& String::append(const Node& o) throw(cannot_have_children) {
throw cannot_have_children(*this, o);
}
Node& String::operator=(const std::string& contents) throw() {
return text(contents);
}
//----------------------------------------------------------------------------
UnsignedInteger::UnsignedInteger(std::string name, unsigned long i) throw():
String(name,
dynamic_cast<std::stringstream&>(std::stringstream()<<i).str()) {
}
std::auto_ptr<Node> UnsignedInteger::clone() const throw() {
return std::auto_ptr<Node>(new UnsignedInteger(*this));
}
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"));
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;
}
unsigned long UnsignedInteger::number() const throw() {
return number(*this);
}
unsigned long UnsignedInteger::number(const Node& node) throw() {
unsigned long i(0);
std::stringstream ss(node.text());
ss>>i;
return i;
}
//----------------------------------------------------------------------------
Factory::Factory(const Node& t) throw(): _template(t.clone()), _line(0) {}
const Node& Factory::operator*() const throw() {
return *_template;
}
std::auto_ptr<Node> Factory::read(std::istream& is)
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
second_slash_in_tag,
character_after_slash, missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute,
attributes_in_end_tag) try {
_line=1;
std::auto_ptr<Node> node(_template->clone());
node->clear();
Tag res;
while (true) {
res = tag(is, *node);
*node<<res.attributes;
switch (res.type) {
case END: throw wrong_end_tag(*node, is, res);
case EMPTY: return node; // empty
case START:
if (!res.name.size()) throw tag_expected(*node, res.text);
if (node->name()!=res.name) throw wrong_start_tag(*node, is, res);
return read(is, *_template);
//! @todo store instead of ignore
case SPECIAL: break;
}
}
} 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;
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,
character_after_slash, missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute, attributes_in_end_tag) {
std::auto_ptr<Node> result(node.clone());
result->clear();
for (bool finished(false); is && !finished;) {
Tag res(tag(is, node));
if (res.text.size()) result->text(res.text);
switch (res.type) {
case END: {
if (res.attributes.size()) throw attributes_in_end_tag(node, is, res);
if (res.name!=node.name()) throw wrong_end_tag(node, is, res);
finished = true;
} break;
case EMPTY: {
*result<<(node[res.name].clone()->clear()<<res.attributes);
} break;
case START: {
*result<<(*read(is, node[res.name])<<res.attributes);
} break;
case SPECIAL: break; //! @todo ignored could be stored
}
}
return result;
}
Tag Factory::tag(std::istream& is, const Node& position)
throw(second_slash_in_tag, character_after_slash,
missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute) {
char c(0);
Tag tag((Tag){"", START, "", Attributes()});
while (is && is.get(c) && ws(c)); // skip ws
if (!is) throw missing_end_tag(position, is, tag);
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;
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;
}
default:
if (tag.type==EMPTY) throw character_after_slash(position, is, tag, c);
if (nameRead) { // read attribute
std::string attrname(1, c), attrvalue;
while (is && is.get(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);
tag.attributes[attrname] = attrvalue;
} else { // part of a tag name
tag.name+=c;
}
}
return tag;
}
}