diff --git a/src/xml-cxx/xml.hxx b/src/xml-cxx/xml.hxx index 8554ae1..b276875 100644 --- a/src/xml-cxx/xml.hxx +++ b/src/xml-cxx/xml.hxx @@ -17,7 +17,7 @@ /*! @page limitations Known Limitations - Templates cannot specify number of sub-elements. - - XML-Comments are not supported. + - XML-Comments are only ignored. - Mixed tags and text is not supported. Tags may either contain other tags only (type xml::Node) or text only (type xml::String). @@ -25,45 +25,78 @@ (e.g. <p><p><p></p></p></p>) - No check yet for optional and mandatory attributes in xml::Factory - Exceptions should be optional, best effort otherwise (option "strict") */ -namespace xml { +//! Represents classes for handling C++ access to XML files with strict schema. +/*! The schema ist not presented through xsd, but it can be declared in C++. + + A xml::Factory represents a factory that owns a template and can + instanciate XML trees that are valid for the given template from + streams. If anything is not valid, an exception is thrown. The @c + what() method of the exception gives additional information about + the problem. + + In the following example, we want to represent XML data that are + contained in a <persons> tag, and may contain a list of @c + person. Each @c person has a mandatory attribute @c id and + optional @c member-of. @c person has a @c name and may contain a + list of @c friends, where each @c friend has an attribute @c + id. (The @c id attribute of course should reference to the @c id + of another @c name, but this relationship cannot be declared.) + + All tags are by default specified as 0..n (optional and any number + there of). + + + #include <xml-cxx/xml.hxx> + #include <iostream> + #include <<stream> + [...] + xml::Factory test(xml::Node("persons") // root node + <<(xml::Node("person") // child of persons + .attr("id", xml::mandatory) + .attr("member-of", xml::optional)) + < persons(test.read(std::ifstream("file.xml))); + // Here we can be sure, that our structure is valid, + // but we must check optional elements before access, otherwise + // we get an exception. + [...] + for (xml::Node::size_type i(0); i */ +namespace xml { + //============================================================================ - class Attributes: public std::map { - public: - class Value: public value_type { - public: - Value(const std::string& name) throw(); - value_type& operator=(const std::string& value) throw(); - private: - Value(); // not implemented - }; - Attributes() throw(); - Attributes(const std::string& empty) throw(); - Attributes& operator<<(const value_type& v) throw(); - Attributes& operator<<(const std::string& key) throw(); - Attributes& operator=(const std::string& value) throw(); - private: - iterator _active; - }; - typedef Attributes::Value Attr; enum NodeType {START, END, EMPTY, SPECIAL}; - struct Tag { - std::string name; - NodeType type; - std::string text; - Attributes attributes; - std::string special; - }; const bool mandatory = true; const bool optional = false; //================================================================= EXCEPTIONS + struct Tag; + class Attributes; class Node; class Factory; //---------------------------------------------------------------------------- class exception: public std::exception { public: + exception(std::string reason) throw(); exception(std::string reason, const Node& t) throw(); ~exception() throw(); const char* what() const throw(); @@ -121,53 +154,55 @@ namespace xml { class stream_error: public exception { public: stream_error(const std::string& reason, const Node& t, - std::istream& is, Tag tag, char c=0) throw(); - ~stream_error() throw() {} + std::istream& is, const Tag& tag, char c=0) throw(); + ~stream_error() throw(); const char* what() const throw(); private: std::istream::streampos _pos; - Tag _tag; + Tag* _tag; char _char; }; //---------------------------------------------------------------------------- class wrong_end_tag: public stream_error { public: - wrong_end_tag(const Node& t, std::istream& is, Tag tag, char c=0) + wrong_end_tag(const Node& t, std::istream& is, const Tag& tag, char c=0) throw(): stream_error("mismatching end tag", t, is, tag, c) { } }; class missing_end_tag: public stream_error { public: - missing_end_tag(const Node& t, std::istream& is, Tag tag, char c=0) + missing_end_tag(const Node& t, std::istream& is, const Tag& tag, char c=0) throw(): stream_error("missing end tag, end of file reached", t, is, tag, c) { } }; class empty_stream: public stream_error { public: - empty_stream(const Node& t, std::istream& is, Tag tag, char c=0) + empty_stream(const Node& t, std::istream& is, const Tag& tag, char c=0) throw(): stream_error("no xml tag found, stream is empty", t, is, tag, c) { } }; class wrong_start_tag: public stream_error { public: - wrong_start_tag(const Node& t, std::istream& is, Tag tag, char c=0) + wrong_start_tag(const Node& t, std::istream& is, const Tag& tag, char c=0) throw(): stream_error("start tag does not match expected tag", t, is, tag, c) { } }; class second_slash_in_tag: public stream_error { public: - second_slash_in_tag(const Node& t, std::istream& is, Tag tag, char c=0) + second_slash_in_tag(const Node& t, std::istream& is, const Tag& tag, + char c=0) throw(): stream_error("a tag may have no more than one slash", t, is, tag, c) { } }; class character_after_slash: public stream_error { public: - character_after_slash(const Node& t, std::istream& is, Tag tag, char c=0) + character_after_slash(const Node& t, std::istream& is, const Tag& tag, + char c=0) throw(): stream_error("unexpected character after empty-slash", t, is, tag, c) { @@ -175,14 +210,15 @@ namespace xml { }; class attributes_in_end_tag: public stream_error { public: - attributes_in_end_tag(const Node& t, std::istream& is, Tag tag) + attributes_in_end_tag(const Node& t, std::istream& is, const Tag& tag) throw(): stream_error("attributes are not allowed in end tags",t, is, tag) { } }; class attribute_value_not_quoted: public stream_error { public: - attribute_value_not_quoted(const Node& t, std::istream& is, Tag tag, + attribute_value_not_quoted(const Node& t, std::istream& is, + const Tag& tag, char c, std::string attr) throw(): stream_error("attribute values must be quoted (\")\nattribute: "+attr, t, is, tag, c) { @@ -190,7 +226,7 @@ namespace xml { }; class duplicate_attribute: public stream_error { public: - duplicate_attribute(const Node& t, std::istream& is, Tag tag, + duplicate_attribute(const Node& t, std::istream& is, const Tag& tag, std::string attr) throw(): stream_error("attribute duplicated\nattribute: "+attr, t, is, tag, 0) { @@ -199,6 +235,66 @@ namespace xml { //============================================================================ + //---------------------------------------------------------------------------- + //! Map for attribute values. + /*! Attributes can be set using method xml::Node::attr(). Check for + an attribute with xml::Node::hasAttr(). Attributes must be + unique, which means that every attribute must be set at maximum + once. This is corect: <node + attribute="value">, this is not allowed: + <node attribute="value" attribute="value"> */ + class Attributes: public std::map { + public: + //! Attributes may contain a list of space separated values. + typedef std::vector List; + //! Attribute values ar mainly a std::pair. + /*! In addition to a normal std::pair, attributes offer an + assignment operator to set the value, and can be constructed + as empty attribute, given only a key. */ + class Value: public value_type { + public: + //! Construct an empty attribute. + Value(const std::string& name) throw(); + //! Construct an attribute with name an value. + Value(const std::string& name, const std::string& namevalue) throw(); + //! Assign a value. + value_type& operator=(const std::string& value) throw(); + //! Get the attribute name. + const std::string& name() const throw(); + //! Get the attribute value. + const std::string& value() const throw(); + //! Get the attribute value. + std::string& value() throw(); + //! Convert the attribute to a boolean. + operator bool() const throw(); + //! Convert the attribute to a number. + operator unsigned long() const throw(); + //! Convert the attribute to a space separated list. + operator List() const throw(); + List toList(const std::string& separators=" \t\n\r") const throw(); + private: + Value(); // not implemented, key must always be given + }; + Attributes() throw(); + Attributes(const std::string& empty) throw(); + Attributes& operator<<(const value_type& v) throw(); + Attributes& operator<<(const std::string& key) throw(); + Attributes& operator=(const std::string& value) throw(); + private: + iterator _active; + }; + typedef Attributes::Value Attr; + + + //---------------------------------------------------------------------------- + struct Tag { + std::string name; + NodeType type; + std::string text; + Attributes attributes; + std::string special; + }; + //---------------------------------------------------------------------------- class Node { private: @@ -284,9 +380,6 @@ namespace xml { }; //---------------------------------------------------------------------------- - class List: public Node { - }; - class Factory { public: Factory(const Node& t) throw(); diff --git a/src/xml.cxx b/src/xml.cxx index 1ac02b2..8e4e963 100644 --- a/src/xml.cxx +++ b/src/xml.cxx @@ -37,37 +37,12 @@ unsigned int MethodTrace::_level(0); namespace xml { - //============================================================================ - Attributes::Value::Value(const std::string& name) throw(): - Attributes::value_type(name, std::string()) { - } - Attributes::value_type& Attributes::Value::operator=(const std::string& value) - throw() { - second = value; - return *this; - } - Attributes::Attributes() throw(): _active(end()) {} - Attributes::Attributes(const std::string& empty) throw() { - _active = insert(Value(empty)).first; - } - Attributes& Attributes::operator<<(const value_type& v) throw() { - _active = insert(v).first; - return *this; - } - Attributes& Attributes::operator<<(const std::string& key) throw() { - _active = insert(Value(key)).first; - return *this; - } - Attributes& Attributes::operator=(const std::string& value) throw() { - if (_active==end()) return *this; - _active->second = value; - _active = end(); - return *this; - } - //================================================================= 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()) { } @@ -122,8 +97,11 @@ namespace xml { } //---------------------------------------------------------------------------- stream_error::stream_error(const std::string& reason, const Node& t, - std::istream& is, Tag tag, char c) throw(): - exception(reason, t), _pos(is.tellg()), _tag(tag), _char(c) { + 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; @@ -134,12 +112,12 @@ namespace xml { 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" + ss<<"\ntag type: "<<(_tag->type==START?"START" + :(_tag->type==END?"END" + :(_tag->type==EMPTY?"EMPTY" :"???? "))); - if (_tag.name.size()) ss<<"\nnode name read: "<<_tag.name; - if (_tag.text.size()) ss<<"\ncontained text: "<<_tag.text; + 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(); @@ -147,6 +125,70 @@ namespace xml { //============================================================================ + //---------------------------------------------------------------------------- + Attributes::Value::Value(const std::string& name) throw(): + Attributes::value_type(name, std::string()) { + } + Attributes::Value::Value(const std::string& name, + const std::string& value) throw(): + Attributes::value_type(name, value) { + } + const std::string& Attributes::Value::name() const throw() { + return first; + } + const std::string& Attributes::Value::value() const throw() { + return second; + } + std::string& Attributes::Value::value() throw() { + return second; + } + 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"); + } + Attributes::Value::operator unsigned long() const throw() { + std::stringstream ss(second); + int i(0); + ss>>i; + return i; + } + Attributes::Value::operator List() const throw() { + return toList(); + } + Attributes::List Attributes::Value::toList(const std::string& separators) + const throw() { + List l; + for (std::string::size_type it(0), pos(0); + (pos=second.find_first_of(separators, it)), it!=std::string::npos; + it=pos!=std::string::npos?++pos:std::string::npos) + l.push_back(std::string(second.begin()+it, second.begin()+pos)); + return l; + } + //---------------------------------------------------------------------------- + Attributes::value_type& Attributes::Value::operator=(const std::string& value) + throw() { + second = value; + return *this; + } + Attributes::Attributes() throw(): _active(end()) {} + Attributes::Attributes(const std::string& empty) throw() { + _active = insert(Value(empty)).first; + } + Attributes& Attributes::operator<<(const value_type& v) throw() { + _active = insert(v).first; + return *this; + } + Attributes& Attributes::operator<<(const std::string& key) throw() { + _active = insert(Value(key)).first; + return *this; + } + Attributes& Attributes::operator=(const std::string& value) throw() { + if (_active==end()) return *this; + _active->second = value; + _active = end(); + return *this; + } //---------------------------------------------------------------------------- Node::Node(std::string name) throw(): _name(name), _parent(0) {