/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #ifndef LIB_XML_CXX_HXX #define LIB_XML_CXX_HXX #include #include #include #include #include /*! @page limitations Known Limitations - Templates cannot specify number of sub-elements. - 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). - Unlimited recursion is not possible (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") */ //! 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). @code #include #include [...] 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<node> END, //!< end node, such as </node> EMPTY, //!< empty node, such as <node/> SPECIAL //!< special node, such as //! a comment <!-- ... -->, //! a xml start indication <?xml?> //! or a document type declaration <!DOCTYPE ...> }; //! Declares an attribute to be mandatory. const bool mandatory(true); //! Declares an attribute to be optional. 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(); void line(unsigned long line) throw(); const char* what() const throw(); private: std::string _what; Node* _node; }; //---------------------------------------------------------------------------- class no_parent: public exception { public: no_parent(const Node& t) throw(): exception("node has no parent", t) {} }; //---------------------------------------------------------------------------- class tag_expected: public exception { public: tag_expected(const Node& t, const std::string& txt) throw(): exception("tag ('<') expected, text not allowed\ntext: "+txt, t) {} }; //---------------------------------------------------------------------------- class type_mismatch: public exception { public: type_mismatch(const Node& t, const std::string& txt, const std::string& comment) throw(): exception("wrong type, text contains mismatching character\n"+comment +"\ntext: "+txt, t) {} }; //---------------------------------------------------------------------------- class access_error: public exception { public: access_error(const Node& t, const std::string& name) throw(); ~access_error() throw() {} const char* what() const throw(); private: std::string _name; }; //---------------------------------------------------------------------------- class out_of_range: public exception { public: out_of_range(const Node& t, size_t pos) throw(); ~out_of_range() throw() {} const char* what() const throw(); private: size_t _pos; }; //---------------------------------------------------------------------------- class cannot_have_children: public exception { public: cannot_have_children(const Node& parent, const Node& child) throw(); ~cannot_have_children() throw(); const char* what() const throw(); private: Node* _child; }; //---------------------------------------------------------------------------- class attribute_not_available: public exception { public: attribute_not_available(const Node& t, const std::string& attr) throw(): exception("attribute \""+attr+"\" not set", t) { } }; //---------------------------------------------------------------------------- class stream_error: public exception { public: stream_error(const std::string& reason, const Node& t, 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; char _char; }; //---------------------------------------------------------------------------- class wrong_end_tag: public stream_error { public: 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, const Tag& tag, char c=0) throw(): stream_error("missing end tag, end of file reached", t, is, tag, c) { } }; class wrong_start_tag: public stream_error { public: 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, 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, const Tag& tag, char c=0) throw(): stream_error("unexpected character after empty-slash", t, is, tag, c) { } }; class attributes_in_end_tag: public stream_error { public: 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, const Tag& tag, char c, std::string attr) throw(): stream_error("attribute values must be quoted (\")\nattribute: "+attr, t, is, tag, c) { } }; class duplicate_attribute: public stream_error { public: 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) { } }; //============================================================================ //---------------------------------------------------------------------------- //! 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. @note Simply use xml::Attr instead of xml::Attributes::Value. */ class Value: public value_type { public: Value(const value_type& o) throw(); Value(const std::string& name) throw(); Value(const std::string& name, const std::string& namevalue) throw(); Value& operator=(const std::string& value) throw(); const std::string& name() const throw(); const std::string& value() const throw(); std::string& value() throw(); operator bool() const throw(); operator unsigned long() const throw(); 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(const std::string& key, const std::string& value) throw(); Attributes& operator<<(const Value& v) throw(); Attributes& operator<<(const std::string& key) throw(); }; //! Simplification: Use xml::Attr instead of xml::Attributes::Value. typedef Attributes::Value Attr; //---------------------------------------------------------------------------- //! @internal structure for parsing tags struct Tag { std::string name; NodeType type; std::string text; Attributes attributes; std::string special; }; //---------------------------------------------------------------------------- //! An xml Node. /*! XML Nodes may contain either text or other nodes, but not both at the same time. This node can hold other nodes. For a Node for text contents, see xml::String. */ class Node { private: typedef std::vector Contents; public: typedef Contents::size_type size_type; typedef std::vector List; Node(std::string name) throw(); Node(const Node& o) throw(); Node& operator=(const Node& o) throw(); virtual ~Node() throw(); virtual std::auto_ptr clone() const throw(); virtual std::ostream& out(std::ostream& o, unsigned int level=0) const throw(); virtual std::string text() const throw(); virtual Node& text(const std::string& txt) throw(tag_expected, type_mismatch); virtual Node& append(const Node& o) throw(cannot_have_children); virtual Node& set(const Attributes& o) throw(); Node& clear() throw (); std::string name() const throw(); bool isChild() const throw(); Node& parent() const throw(no_parent); bool hasAttr(const std::string& name) const throw(); Node& attr(const std::string& name, bool mandatory) throw(); std::string attr(const std::string& name) const throw(); std::string& attr(const std::string& name) throw(); const Attributes::Value attribute(const std::string& name) const throw(attribute_not_available); List list(const std::string& name) const throw(); bool operator()(const std::string& child) const throw(); Node& operator<<(const Node& o) throw(cannot_have_children); Node& operator<<(const Attributes& o) throw(); //! Get the number of children. size_type children() const throw(); //! Get the n-th child. const Node& operator[](size_type child) const throw(access_error); //! Get the n-th child. Node& operator[](size_type child) throw(access_error); //! Get the first named child. const Node& operator[](const std::string& child) const throw(access_error); //! Get the first named child. Node& operator[](const std::string& child) throw(access_error); std::string operator*() const throw(); Node& operator=(const std::string& contents) throw(tag_expected, type_mismatch); friend std::ostream& operator<<(std::ostream& o, const Node& t) throw(); protected: Attributes _attributes; private: Node* find(const std::string& child) const throw(); virtual std::auto_ptr clone(Node* p) const throw(); Node(); // not implemented Contents _contents; std::string _name; Node* _parent; }; //---------------------------------------------------------------------------- class String: public Node { public: String(std::string name, const std::string& text = std::string()) throw(); virtual std::auto_ptr clone() const throw(); virtual ~String() throw() {} virtual std::string text() const throw(); virtual String& text(const std::string& txt) throw(tag_expected, type_mismatch); virtual std::ostream& out(std::ostream& o, unsigned int level=0) const throw(); virtual String& append(const Node& o) throw(cannot_have_children); Node& operator=(const std::string& contents) throw(); protected: std::string _text; }; //---------------------------------------------------------------------------- class UnsignedInteger: public String { public: UnsignedInteger(std::string name, unsigned long i = 0) throw(); virtual std::auto_ptr clone() const throw(); virtual ~UnsignedInteger() throw() {} virtual UnsignedInteger& text(const std::string& txt) throw(tag_expected, type_mismatch); unsigned long number() const throw(); static unsigned long number(const Node& node) throw(); }; //---------------------------------------------------------------------------- class Factory { public: Factory(const Node& t) throw(); const Node& operator*() const throw(); std::auto_ptr 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); private: friend class stream_error; Factory(); // not implemented Factory(const Factory&); // not implemented bool ws(char c) throw(); std::auto_ptr read(std::istream& is, const Node& position) 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); Tag 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); std::auto_ptr _template; unsigned long _line; }; } #endif