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.
 
 
 
 

425 lines
17 KiB

/*! @file
@id $Id$
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
#ifndef LIB_XML_CXX_HXX
#define LIB_XML_CXX_HXX
#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.
- 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).
@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)))));
[...]
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.
[...]
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;
}
[...]
} catch (const std::exception& x) {
std::cerr<<"**** Error in file \"file.xml\":"<<std::endl
<<x.what()<<std::endl;
}
@endcode */
namespace xml {
//============================================================================
//! 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);
//================================================================= 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: <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
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<Node*> Contents;
public:
typedef Contents::size_type size_type;
typedef std::vector<Node*> List;
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);
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<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();
protected:
std::string _text;
};
//----------------------------------------------------------------------------
class UnsignedInteger: public String {
public:
UnsignedInteger(std::string name, unsigned long i = 0) throw();
virtual std::auto_ptr<Node> 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<Node> 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<Node> 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<Node> _template;
unsigned long _line;
};
}
#endif