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.

426 lines
17 KiB

15 years ago
/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
15 years ago
#ifndef LIB_XML_CXX_HXX
#define LIB_XML_CXX_HXX
15 years ago
#include <istream>
#include <string>
#include <vector>
#include <map>
#include <memory>
/*! @page limitations Known Limitations
- Templates cannot specify number of sub-elements.
- XML-Comments are only ignored.
15 years ago
- 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. &ltp&gt;&ltp&gt;&ltp&gt;&lt/p&gt;&lt/p&gt;&lt/p&gt;)
- 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 &lt;persons&gt; 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).
15 years ago
@code
#include <xml-cxx/xml.hxx>
#include <iostream>
[...]
xml::Factory test(xml::Node("persons") // root node
<<(xml::Node("person") // child of persons
.attr("id", xml::mandatory)
.attr("member-of", xml::optional))
<<xml::String("name") // the person's name
<<(xml::Node("friends") // friends of person
<<(xml::Node("friend") // a friend
.attr("id", xml::mandatory)))));
15 years ago
[...]
try {
std::auto_ptr<xml::Node> 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.
15 years ago
[...]
for (xml::Node::size_type i(0); i<persons.children(); ++i) {
std::cout<<"Person: "<<*persons[i]["name"]; // exception if no "name"
if (persons[i]("friends")) // check if "friends" is set
std::cout<<" has "<<persons[i]["friends"].children()<<" friends"
else
std::cout<<" has no friend list";
std::cout<<std::endl;
}
15 years ago
[...]
} catch (const std::exception& x) {
std::cerr<<"**** Error in file \"file.xml\":"<<std::endl
<<x.what()<<std::endl;
}
15 years ago
@endcode */
namespace xml {
15 years ago
//============================================================================
15 years ago
//! Type of an xml node.
/*! Only start nodes and empty nodes may have attributes. */
enum NodeType {
START, //!< start node, such as <code>&lt;node&gt;</code>
END, //!< end node, such as <code>&lt;/node&gt;</code>
EMPTY, //!< empty node, such as <code>&lt;node/&gt;</code>
SPECIAL //!< special node, such as
//! a comment <code>&lt;!-- ... --&gt;</code>,
//! a xml start indication <code>&lt;?xml?&gt;</code>
//! or a document type declaration <code>&lt;!DOCTYPE ...&gt;</code>
};
//! Declares an attribute to be mandatory.
const bool mandatory(true);
//! Declares an attribute to be optional.
const bool optional(false);
15 years ago
//================================================================= EXCEPTIONS
struct Tag;
class Attributes;
15 years ago
class Node;
class Factory;
//----------------------------------------------------------------------------
class exception: public std::exception {
public:
exception(std::string reason) throw();
15 years ago
exception(std::string reason, const Node& t) throw();
~exception() throw();
15 years ago
void line(unsigned long line) throw();
15 years ago
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:
15 years ago
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) {}
};
//----------------------------------------------------------------------------
15 years ago
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) {
}
};
//----------------------------------------------------------------------------
15 years ago
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();
15 years ago
const char* what() const throw();
private:
std::istream::streampos _pos;
Tag* _tag;
15 years ago
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)
15 years ago
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)
15 years ago
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)
15 years ago
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)
15 years ago
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)
15 years ago
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)
15 years ago
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,
15 years ago
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,
15 years ago
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: <code>&lt;node
attribute="value"&gt;</code>, this is not allowed:
<code>&lt;node attribute="value" attribute="value"&gt;</code> */
class Attributes: public std::map<std::string, std::string> {
public:
//! Attributes may contain a list of space separated values.
typedef std::vector<std::string> 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
15 years ago
as empty attribute, given only a key.
@note Simply use xml::Attr instead of xml::Attributes::Value. */
class Value: public value_type {
public:
15 years ago
Value(const value_type& o) throw();
Value(const std::string& name) throw();
Value(const std::string& name, const std::string& namevalue) throw();
15 years ago
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();
15 years ago
Attributes(const std::string& key, const std::string& value) throw();
Attributes& operator<<(const Value& v) throw();
Attributes& operator<<(const std::string& key) throw();
};
15 years ago
//! Simplification: Use xml::Attr instead of xml::Attributes::Value.
typedef Attributes::Value Attr;
//----------------------------------------------------------------------------
15 years ago
//! @internal structure for parsing tags
struct Tag {
std::string name;
NodeType type;
std::string text;
Attributes attributes;
std::string special;
};
15 years ago
//----------------------------------------------------------------------------
15 years ago
//! 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. */
15 years ago
class Node {
private:
typedef std::vector<Node*> Contents;
public:
typedef Contents::size_type size_type;
typedef std::vector<Node*> List;
15 years ago
Node(std::string name) throw();
Node(const Node& o) throw();
Node& operator=(const Node& o) throw();
virtual ~Node() throw();
virtual std::auto_ptr<Node> 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);
15 years ago
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();
15 years ago
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);
15 years ago
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<Node> 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<Node> 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();
15 years ago
protected:
std::string _text;
};
//----------------------------------------------------------------------------
15 years ago
class UnsignedInteger: public String {
public:
15 years ago
UnsignedInteger(std::string name, unsigned long i = 0) throw();
virtual std::auto_ptr<Node> clone() const throw();
15 years ago
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();
15 years ago
};
//----------------------------------------------------------------------------
class Factory {
public:
Factory(const Node& t) throw();
const Node& operator*() const throw();
std::auto_ptr<Node> read(std::istream& is)
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
15 years ago
second_slash_in_tag, character_after_slash,
missing_end_tag, attribute_value_not_quoted, access_error,
duplicate_attribute, attributes_in_end_tag);
15 years ago
private:
friend class stream_error;
Factory(); // not implemented
Factory(const Factory&); // not implemented
15 years ago
bool ws(char c) throw();
15 years ago
std::auto_ptr<Node> read(std::istream& is, const Node& position)
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
15 years ago
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<Node> _template;
15 years ago
unsigned long _line;
15 years ago
};
}
#endif