/*! @file @id $Id$ */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 #include #include #include #include #include #include class MethodTrace { public: MethodTrace(const void* addr, const std::string& name) throw(): _addr(addr), _name(name) { std::clog<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<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(const std::string& reason, const Node& t, std::istream& is) throw(): exception(reason, t), _pos(is.tellg()), _tag(0), _char(0) { } 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<31?_char:'?') <<" (ascii="<<(int)_char<<")"; if (_tag) { 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; } 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. /*! @return @c true if the value is set and not equal to one of: @c false @c no @c 0. */ Attributes::Value::operator bool() const throw() { 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 list. /*! @param separators a string containing a list of valid separators */ 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; } //! Get the first value of an attribute containing a list of values. /*! @copydoc xml::Attributes::Value::toList @return the first element of the list. */ std::string Attributes::Value::front(const std::string& separators) const throw(empty_attribute_list) { List l(toList(separators)); if (!l.size()) throw empty_attribute_list(first); return l.front(); } //---------------------------------------------------------------------------- //! 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. @copydoc xml::Node::limits */ Node::Node(std::string name, Node::size_type min, Node::size_type max) throw(): _name(name), _parent(0), _min(min), _max(max) { } //! Copy node, reset parent. /*! The parent is reset, the node does not belong to the same parent as the source of the copy. @see xml::Node::clone() for more information on the parenting. */ Node::Node(const Node& o) throw(): _attributes(o._attributes), _name(o.name()), _parent(0), _min(o._min), _max(o._max) { for (Contents::const_iterator it(o._contents.begin()); it!=o._contents.end(); ++it) _contents.push_back((*it)->clone(this).release()); } Node::~Node() throw() { clear(); } //! Assign new node, keep parent. /*! The parent remains unchanged, the node does not belong to the same parent as the source of the copy. @see xml::Node::clone() for more information on the parenting. */ Node& Node::operator=(const Node& o) throw() { _attributes=o._attributes; _name = o.name(); _min = o._min; _max = o._max; for (Contents::const_iterator it(o._contents.begin()); it!=o._contents.end(); ++it) _contents.push_back((*it)->clone(this).release()); } //! Clones But clears the parent. /*! You get a new instance of the node, but detached from the parent. It is then ready to be inserted below a new parent. If you clone (or copy) a node, the parent is not copied. This is because otherwise the new now would need to be appended to the parent's child list. That's most probably not intended. Therefore, in a normal copy construction the parent is set to @c 0 (no parent). Then the new node can be appended to a parent if needed. In a copy assignment, the parent remains uncanged and the other data is copied. The user of this library doesn't have to and is not able to care about the parenting. */ std::auto_ptr Node::clone() const throw() { std::auto_ptr res(new Node(*this)); res->_parent = 0; return res; } //! Stream XML. /*! Streams the node including all attributes and children. It is formatted with new-lines and tabulator indentation for human readability. */ std::ostream& Node::out(std::ostream& o, unsigned int level) const throw() { if (_contents.size()) { o<first<<"=\""<second<<'"'; o<<'>'<out(o, level+1)<'; } else { o<first<<"=\""<second<<'"'; o<<"/>"; } return o; } //! Get the textual contents of a node. /*! By default this is the concatenation of all child nodes' texts. */ std::string Node::text() const throw() { std::string s; for (Contents::const_iterator it(_contents.begin()); it!=_contents.end(); ++it) s+=***it; return s; } //! Set a text (forbidden in xml::Node) /*! This method is only useful for child nodes, where it is reimplemented. In xml::Node, the parent of all nodes, it throws xml::tag_expected, since a xml::Node may only contain sub-nodes (formatted as xml-tags) and no plain text. */ Node& Node::text(const std::string& txt) throw(tag_expected, type_mismatch) { throw tag_expected(*this, txt); } //! Appends a new node at the end of all children. Node& Node::append(const Node& o) throw(cannot_have_children) { _contents.push_back(o.clone(this).release()); return *this; } //! Set a list of attributes. /*! Existing attributes with the same name are overwritten. */ Node& Node::set(const Attributes& o) throw() { _attributes.clear(); _attributes.insert(o.begin(), o.end()); return *this; } //! Clears content and attributes. Node& Node::clear() throw () { for (Contents::const_iterator it(_contents.begin()); it!=_contents.end(); ++it) delete *it; _contents.clear(); _attributes.clear(); return *this; } //! Get the node's tag name. std::string Node::name() const throw() { return _name; } //! Set minimum number of instances (in a xml::Factory). /*! @copydoc limits */ Node& Node::min(Node::size_type m) throw() { _min = m; return *this; } //! Get minimum number of instances (in a xml::Factory). /*! @copydoc limits */ Node::size_type Node::min() const throw() { return _min; } //! Set maximum number of instances (in a xml::Factory). /*! @copydoc limits */ Node& Node::max(Node::size_type m) throw() { _max = m; return *this; } //! Get maximum number of instances (in a xml::Factory). /*! @copydoc limits */ Node::size_type Node::max() const throw() { return _max; } //! @c true if node has a parent. bool Node::isChild() const throw() { return _parent; } //! Get the parent node. Node& Node::parent() const throw(no_parent) { if (!_parent) throw no_parent(*this); return *_parent; } //! Check if a specific attribute is set or not. bool Node::hasAttr(const std::string& name) const throw() { return _attributes.find(name)!=_attributes.end(); } //! Declare an attribute template and specify whether it is mandatory /*! Used for setting up attributes in xml::Factory: @code xml::Factory f(xml::Node("root").attr("aname", xml::mandatory); @endcode If a factory reads from a stream, it verifies that only optional or mandatory attributes are given and that mandatory attributes are specified. Otherwise reading throws an exception. */ Node& Node::attr(const std::string& name, bool mandatory) throw() { _attributes[name] = mandatory?"xml::mandatory":"xml::optional"; return *this; } //! Declare an attribute with given value. /*! When used for setting up attributes in xml::Factory, it specifies an optional attribute with given default value: @code xml::Factory f(xml::Node("root").attr("aname", "avalue"); @endcode If a factory reads from a stream and the specified attribute is not given, it is set to @c deflt. */ Node& Node::attr(const std::string& name, const std::string& deflt) throw() { _attributes[name] = deflt; return *this; } //! Get an attribute. /*! Returns the attribute's value (empty if the attribute is not set) */ 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(); } //! Get an attribute. /*! Returns the attribute's value (empty if the attribute is not set) */ std::string& Node::attr(const std::string& name) throw() { return _attributes[name]; } //! Get an attribute. /*! Returns an attribute class or throws an exception if the attribute is not set. This method is useful when you need conversions or other methods as specified in xml::Attributes::Value. */ 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); } //! Get the list of attributes. const Attributes& Node::attributes() const throw() { return _attributes; } //! Get the list of attributes. Attributes& Node::attributes() throw() { return _attributes; } //! Pass a minimal and maximal number for this node in a xml file. /*! Minimal and maximal values are verified when you use the node as a template in a xml::Factory. When the factory reads a stucture from a stream through xml::Factory::read, then all the limits are verified. There are several ways to declare the limits. - in the constructor xml::Node::Node - minimum and maximum using method xml::Node::limits - using methods xml::Node::min and xml::Node::max separately It is recommended not to use the constructor, but the methods xml::Node::limits, xml::Node::min and xml::Node::max, simply for code readability. The number @c 0 means unlimited: xml::Node::min(0) means the value is optional and xml::Node::max(0) means that there is no upper limit. The only exception for this rule is the root element of a structure passed to a xml::Factory which must exist exactly once. Default is no limits: 0..n, which means that the node is optional and may be instatiated multiple (infinite, unlimited) times. */ Node& Node::limits(size_type min, size_type max) throw() { _min = min; _max = max; return *this; } //! Get all immediate children of a given node 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; } //! Check if at least one child node of a given name exists. bool Node::operator()(const std::string& child) const throw() { return find(child); } //! Append a child at the end. /*! @copydoc xml::Node::append */ Node& Node::operator<<(const Node& o) throw(cannot_have_children) { return append(o); } //! Add an empty attribute. /*! @copydoc xml::Node::set */ Node& Node::operator<<(const Attributes& o) throw() { return set(o); } //! Get the number of children. Node::size_type Node::children() const throw() { return _contents.size(); } //! Get a child by child-number (the n-th child). /*! @param child number of the child to return: child>=0 and child<xml::Node::children() */ const Node& Node::operator[](Node::size_type child) const throw(out_of_range) try { return *_contents.at(child); } catch (...) { throw out_of_range(*this, child); } /*! @copydoc xml::Node::operator[](Node::size_type child) const */ Node& Node::operator[](Node::size_type child) throw(out_of_range) try { return *_contents.at(child); } catch (...) { throw out_of_range(*this, child); } //! Get the first child of a given node name. /*! @return the first child with matching node name */ 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; } /*! @copydoc xml::Node::operator[](const std::string& child) const */ Node& Node::operator[](const std::string& child) throw(access_error) { Node* const t(find(child)); if (!t) throw access_error(*this, child); return *t; } //! Get the textual contents of a node. /*! @copydoc xml::Node::text() */ std::string Node::operator*() const throw() { return text(); } //! Set a text (forbidden in xml::Node) /*! @copydoc xml::Node::text(const std::string& txt) */ Node& Node::operator=(const std::string& contents) throw(tag_expected, type_mismatch) { return text(contents); } //! Write node in XML format to a stream. /*! @copydoc xml::Node::out */ std::ostream& operator<<(std::ostream& o, const Node& t) throw() { return t.out(o); } //! Get a pointer to the first child of a given node name. /*! This method does not throw an exception if the element does not exist, but returns @c 0. */ 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; } //! Clone a node, but assign a new parent. std::auto_ptr Node::clone(Node* p) const throw() { std::auto_ptr c(clone()); c->_parent = p; return c; } //---------------------------------------------------------------------------- /*! @copydoc Node::Node(std::string name, Node::size_type min, Node::size_type max) */ String::String(std::string name, Node::size_type min, Node::size_type max) throw(): Node(name, min, max) { } //! Pass the text in the node. /*! @copydoc Node::Node(std::string name, Node::size_type min, Node::size_type max) */ String::String(std::string name, const std::string& text, Node::size_type min, Node::size_type max) throw(): Node(name, min, max), _text(text) { } std::auto_ptr String::clone() const throw() { return std::auto_ptr(new String(*this)); } std::string String::text() const throw() { return _text; } //! An xml::String contains text: Set the text. /*! Never throws an exception. */ 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<first<<"=\""<second<<'"'; o<<'>'<<_text<<"'; } else { o<first<<"=\""<second<<'"'; o<<"/>"; } return o; } //! An xml::String has no child nodes: Always throws an exception. String& String::append(const Node& o) throw(cannot_have_children) { throw cannot_have_children(*this, o); } //! An xml::String contains text: Set the text. Node& String::operator=(const std::string& contents) throw() { return text(contents); } String::operator std::string() const throw() { return text(); } String::operator bool() const throw() { bool res; std::stringstream ss(text()); ss>>res; return res; } String::operator signed char() const throw() { signed char res; std::stringstream ss(text()); ss>>res; return res; } String::operator unsigned char() const throw() { unsigned char res; std::stringstream ss(text()); ss>>res; return res; } String::operator signed short() const throw() { signed short res; std::stringstream ss(text()); ss>>res; return res; } String::operator unsigned short() const throw() { unsigned short res; std::stringstream ss(text()); ss>>res; return res; } String::operator signed int() const throw() { signed int res; std::stringstream ss(text()); ss>>res; return res; } String::operator unsigned int() const throw() { unsigned int res; std::stringstream ss(text()); ss>>res; return res; } String::operator signed long() const throw() { signed long res; std::stringstream ss(text()); ss>>res; return res; } String::operator unsigned long() const throw() { unsigned long res; std::stringstream ss(text()); ss>>res; return res; } String::operator float() const throw() { float res; std::stringstream ss(text()); ss>>res; return res; } String::operator double() const throw() { double res; std::stringstream ss(text()); ss>>res; return res; } //---------------------------------------------------------------------------- /*! @copydoc Node::Node(std::string name, Node::size_type min, Node::size_type max) */ UnsignedInteger::UnsignedInteger(std::string name, unsigned long i, size_type min, size_type max) throw(): String(name, dynamic_cast(std::stringstream()< UnsignedInteger::clone() const throw() { return std::auto_ptr(new UnsignedInteger(*this)); } //! An xml::UnsignedInteger must only contain an number. /*! En exception is thrown, if the contents does not match a number. */ 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>i; std::stringstream ss; ss<>i; return i; } //---------------------------------------------------------------------------- //! To instanciate a factory, a template must be given. /*! The template is a node that specifies the schema of the data that can be loaded from streams through a xml::Factory instance. The root element has automatically set the limits 1..1 (xml::Node::limits(1, 1), see xml::Node::limits), which means that the root element must exist exactly once. If you pass another limit, your limit is overwritten. E.g. to load an address, that contains a tag <address> with at least a name and optional an address in it's body, you may write: @code xml::Factory addrTpl(xml::Node("address") (< .limit(0, 0) < Marc Roman Wäckerlin SwissSign AG Pfingstweidstrasse 60b 8005 Zürich Schweiz @endverbatim */ Factory::Factory(const Node& t) throw(): _template(xml::Node("")<")), _line(0) { } Factory& Factory::operator=(const Node& t) throw() { _template = xml::Node("")<0; } std::ostream& operator<<(std::ostream& os, const Factory& factory) throw() { return factory.print(os, *factory); } std::ostream& Factory::print(std::ostream& os, const Node& node, unsigned int level) throw() { if (node.children()) { os<first<<"=\""<second<<'"'; os<<'>'; if (dynamic_cast(&node)) os<<" (Unsigned Integer"; else if (dynamic_cast(&node)) os<<" (String"; else os<<" (Node"; if (node.min()==1 && node.max()==1) os<<", required exactly once)"; else if (node.min()==1 && node.max()==0) os<<", required at least once)"; else if (node.min()==0 && node.max()==0) os<<", 0..n)"; else if (node.min()==0 && node.max()==1) os<<", optional)"; else os<<", "<'<first<<"=\""<second<<'"'; os<<"/>"; if (dynamic_cast(&node)) os<<" (Unsigned Integer"; else if (dynamic_cast(&node)) os<<" (String"; else os<<" (Node"; if (node.min()==1 && node.max()==1) os<<", required exactly once)"; else if (node.min()==1 && node.max()==0) os<<", required at least once)"; else if (node.min()==0 && node.max()==0) os<<", 0..n)"; else if (node.min()==0 && node.max()==1) os<<", optional)"; else os<<", "< 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, illegal_attribute, mandatory_attribute_missing, wrong_node_number) try { _line=1; _open=0; std::auto_ptr node(read(is, _template)); if (node->children()==0) throw tag_expected(_template[0], "nothing found, possibly empty stream"); return (*node)[0].clone(); } 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 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, illegal_attribute, mandatory_attribute_missing, wrong_node_number) { std::auto_ptr result(node.clone()); result->clear(); while (true) { Tag res(tag(is, node)); if (!res.found) return result; if (res.text.size()) result->text(res.text); switch (res.type) { case END: { --_open; if (res.attributes.size()) throw attributes_in_end_tag(node, is, res); if (res.name!=node.name()) throw wrong_end_tag(node, is, res); } return result; case EMPTY: { std::auto_ptr current(node[res.name].clone()); current->clear()< Factory::checkChildren(const xml::Node& tpl, std::auto_ptr node, std::istream& is) const throw(wrong_node_number) { for (Node::size_type i(0); i0 && node->list(tpl[i].name()).size()list(tpl[i].name()).size()) throw wrong_node_number(*node, is, tpl[i].name(), node->list(tpl[i].name()).size(), tpl[i].min(), tpl[i].max()); return node; } Tag Factory::tag(std::istream& is, const Node& position) throw(second_slash_in_tag, character_after_slash, tag_expected, missing_end_tag, attribute_value_not_quoted, access_error, duplicate_attribute, attributes_in_end_tag, illegal_attribute, mandatory_attribute_missing, wrong_start_tag) { char c(0); Tag tag((Tag){"", START, "", Attributes(), "", false}); while (is && is.get(c) && ws(c)); // skip ws if (is.eof()) { if (_open>0) throw missing_end_tag(position, is, tag); else return tag; } tag.found = true; 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=='<') { // ' && 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 if (tag.type!=START) throw attributes_in_end_tag(position, is, tag); std::string attrname(1, c), attrvalue; while (is && is.get(c) && 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); if (!position(tag.name)) { if (tag.name.size()) throw wrong_start_tag(position, is, tag); else throw tag_expected(position, tag.text); } if (position[tag.name].attributes().find(attrname) ==position[tag.name].attributes().end()) throw illegal_attribute(position, is, tag, attrname); tag.attributes[attrname] = attrvalue; } else { // part of a tag name tag.name+=c; } } if (tag.type==START||tag.type==EMPTY) { if (!position(tag.name)) { if (tag.name.size()) throw wrong_start_tag(position, is, tag); else throw tag_expected(position, tag.text); } const xml::Node& node(position[tag.name]); for (Attributes::const_iterator it(node.attributes().begin()); it!=node.attributes().end(); ++it) if (tag.attributes.find(it->first)==tag.attributes.end()) { if (it->second=="xml::mandatory") throw mandatory_attribute_missing(position, is, tag, it->first); else if (it->second!="xml::optional") tag.attributes.insert(*it); } } return tag; } }