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
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; |
|
} |
|
|
|
}
|
|
|