new tests, cleanup, prepared for node-limits
This commit is contained in:
		| @@ -9,6 +9,7 @@ | ||||
| #define LIB_XML_CXX_HXX | ||||
|  | ||||
| #include <istream> | ||||
| #include <sstream> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| @@ -244,6 +245,37 @@ namespace xml { | ||||
|                        t, is, tag, 0) { | ||||
|       } | ||||
|   }; | ||||
|   class illegal_attribute: public stream_error { | ||||
|     public: | ||||
|       illegal_attribute(const Node& t, std::istream& is, const Tag& tag, | ||||
|                         std::string attr) throw(): | ||||
|           stream_error("illegal attribute found\nattribute: "+attr, | ||||
|                        t, is, tag, 0) { | ||||
|       } | ||||
|   }; | ||||
|   class mandatory_attribute_missing: public stream_error { | ||||
|     public: | ||||
|       mandatory_attribute_missing(const Node& t, std::istream& is, | ||||
|                                   const Tag& tag, std::string attr) throw(): | ||||
|           stream_error("mandatory attribute missing\nattribute: "+attr, | ||||
|                        t, is, tag, 0) { | ||||
|       } | ||||
|   }; | ||||
|   class wrong_node_number: public stream_error { | ||||
|     public: | ||||
|       wrong_node_number(const Node& t, std::istream& is, | ||||
|                         const Tag& tag, const std::string& name, | ||||
|                         unsigned long num, | ||||
|                         unsigned long min, unsigned long max) throw(): | ||||
|           stream_error(((std::stringstream&) | ||||
|                         (std::stringstream() | ||||
|                          <<"wrong number of child nodes\nname of child: "<<name | ||||
|                          <<"\nnumber of nodes: "<<num | ||||
|                          <<"\nminimuml number: "<<min | ||||
|                          <<"\nmaximum number: "<<max)).str(), | ||||
|                        t, is, tag, 0) { | ||||
|       } | ||||
|   }; | ||||
|  | ||||
|   //============================================================================ | ||||
|  | ||||
| @@ -298,6 +330,7 @@ namespace xml { | ||||
|       std::string text; | ||||
|       Attributes attributes; | ||||
|       std::string special; | ||||
|       bool found; | ||||
|   }; | ||||
|  | ||||
|   //---------------------------------------------------------------------------- | ||||
| @@ -311,7 +344,7 @@ namespace xml { | ||||
|     public: | ||||
|       typedef Contents::size_type size_type; | ||||
|       typedef std::vector<Node*> List; | ||||
|       Node(std::string name) throw(); | ||||
|       Node(std::string name, size_type min=0, size_type max=0) throw(); | ||||
|       Node(const Node& o) throw(); | ||||
|       Node& operator=(const Node& o) throw(); | ||||
|       virtual ~Node() throw(); | ||||
| @@ -329,10 +362,14 @@ namespace xml { | ||||
|       Node& parent() const throw(no_parent); | ||||
|       bool hasAttr(const std::string& name) const throw(); | ||||
|       Node& attr(const std::string& name, bool mandatory) throw(); | ||||
|       Node& attr(const std::string& name, const std::string& deflt) 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); | ||||
|       const Attributes& attributes() const throw(); | ||||
|       Attributes& attributes() throw(); | ||||
|       Node& limits(size_type min=0, size_type max=0) throw(); | ||||
|       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); | ||||
| @@ -340,9 +377,9 @@ namespace xml { | ||||
|       //! 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); | ||||
|       const Node& operator[](size_type child) const throw(out_of_range); | ||||
|       //! Get the n-th child. | ||||
|       Node& operator[](size_type child) throw(access_error); | ||||
|       Node& operator[](size_type child) throw(out_of_range); | ||||
|       //! Get the first named child. | ||||
|       const Node& operator[](const std::string& child) const | ||||
|           throw(access_error); | ||||
| @@ -361,6 +398,8 @@ namespace xml { | ||||
|       Contents _contents; | ||||
|       std::string _name; | ||||
|       Node* _parent; | ||||
|       typedef std::pair<Node::size_type, Node::size_type> Limits; | ||||
|       Limits _limits; | ||||
|   }; | ||||
|  | ||||
|   //---------------------------------------------------------------------------- | ||||
| @@ -401,7 +440,9 @@ namespace xml { | ||||
|           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); | ||||
|                 duplicate_attribute, attributes_in_end_tag, | ||||
|                 illegal_attribute, mandatory_attribute_missing, | ||||
|                 wrong_node_number); | ||||
|     private: | ||||
|       friend class stream_error; | ||||
|       Factory(); // not implemented | ||||
| @@ -412,13 +453,18 @@ namespace xml { | ||||
|                 second_slash_in_tag, character_after_slash, | ||||
|                 missing_end_tag, | ||||
|                 attribute_value_not_quoted, access_error, duplicate_attribute, | ||||
|                 attributes_in_end_tag); | ||||
|                 attributes_in_end_tag, | ||||
|                 illegal_attribute, mandatory_attribute_missing, | ||||
|                 wrong_node_number); | ||||
|       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; | ||||
|           throw(second_slash_in_tag, wrong_start_tag, character_after_slash, | ||||
|                 missing_end_tag, attributes_in_end_tag, tag_expected, | ||||
|                 attribute_value_not_quoted, access_error, duplicate_attribute, | ||||
|                 illegal_attribute, mandatory_attribute_missing, | ||||
|                 wrong_node_number); | ||||
|       Node _template; | ||||
|       unsigned long _line; | ||||
|       long _open; | ||||
|   }; | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										120
									
								
								src/xml.cxx
									
									
									
									
									
								
							
							
						
						
									
										120
									
								
								src/xml.cxx
									
									
									
									
									
								
							| @@ -33,7 +33,7 @@ class MethodTrace { | ||||
| }; | ||||
| unsigned int MethodTrace::_level(0); | ||||
| #define TRACE MethodTrace XXX_METHOD(this, __PRETTY_FUNCTION__) | ||||
| #define LOG(X) std::clog<<__PRETTY_FUNCTION__<<"\t**** "<<X<<std::endl | ||||
| #define LOG(X) std::cout<<__PRETTY_FUNCTION__<<"\t**** "<<X<<std::endl | ||||
|  | ||||
| namespace xml { | ||||
|    | ||||
| @@ -219,11 +219,12 @@ namespace xml { | ||||
|   //! Nodes must be given a name. | ||||
|   /*! Unnamed nodes are not allowed, therefore there's no default | ||||
|       constructor. */ | ||||
|   Node::Node(std::string name) throw(): | ||||
|       _name(name), _parent(0) { | ||||
|   Node::Node(std::string name, size_type min, size_type max) throw(): | ||||
|       _name(name), _parent(0), _limits(min, max) { | ||||
|   } | ||||
|   Node::Node(const Node& o) throw(): | ||||
|       _attributes(o._attributes), _name(o.name()), _parent(0) { | ||||
|       _attributes(o._attributes), _name(o.name()), _parent(0), | ||||
|       _limits(o._limits) { | ||||
|     for (Contents::const_iterator it(o._contents.begin()); | ||||
|          it!=o._contents.end(); ++it) | ||||
|       _contents.push_back((*it)->clone(this).release()); | ||||
| @@ -232,6 +233,7 @@ namespace xml { | ||||
|     _attributes=o._attributes; | ||||
|     _name = o.name(); | ||||
|     _parent = 0; | ||||
|     _limits = o._limits; | ||||
|     for (Contents::const_iterator it(o._contents.begin()); | ||||
|          it!=o._contents.end(); ++it) | ||||
|       _contents.push_back((*it)->clone(this).release()); | ||||
| @@ -305,7 +307,12 @@ namespace xml { | ||||
|     return _attributes.find(name)!=_attributes.end(); | ||||
|   } | ||||
|   Node& Node::attr(const std::string& name, bool mandatory) throw() { | ||||
|     _attributes[name] = mandatory?"mandatory":"optional"; | ||||
|     _attributes[name] = mandatory?"xml::mandatory":"xml::optional"; | ||||
|     return *this; | ||||
|   } | ||||
|   Node& Node::attr(const std::string& name, const std::string& deflt) | ||||
|       throw() { | ||||
|     _attributes[name] = deflt; | ||||
|     return *this; | ||||
|   } | ||||
|   std::string Node::attr(const std::string& name) const throw() { | ||||
| @@ -322,6 +329,17 @@ namespace xml { | ||||
|     if (it!=_attributes.end()) return *it; | ||||
|     throw attribute_not_available(*this, name); | ||||
|   } | ||||
|   const Attributes& Node::attributes() const throw() { | ||||
|     return _attributes; | ||||
|   } | ||||
|   Attributes& Node::attributes() throw() { | ||||
|     return _attributes; | ||||
|   } | ||||
|   Node& Node::limits(size_type min, size_type max) throw() { | ||||
|     _limits.first = min; | ||||
|     _limits.second = max; | ||||
|     return *this; | ||||
|   } | ||||
|   Node::List Node::list(const std::string& name) const throw() { | ||||
|     List res; | ||||
|     for (Contents::const_iterator it(_contents.begin()); | ||||
| @@ -342,12 +360,12 @@ namespace xml { | ||||
|     return _contents.size(); | ||||
|   } | ||||
|   const Node& Node::operator[](Node::size_type child) const | ||||
|       throw(access_error) try { | ||||
|       throw(out_of_range) try { | ||||
|     return *_contents.at(child); | ||||
|   } catch (...) { | ||||
|     throw out_of_range(*this, child); | ||||
|   } | ||||
|   Node& Node::operator[](Node::size_type child) throw(access_error) try { | ||||
|   Node& Node::operator[](Node::size_type child) throw(out_of_range) try { | ||||
|     return *_contents.at(child); | ||||
|   } catch (...) { | ||||
|     throw out_of_range(*this, child); | ||||
| @@ -463,34 +481,40 @@ namespace xml { | ||||
|   } | ||||
|  | ||||
|   //---------------------------------------------------------------------------- | ||||
|   Factory::Factory(const Node& t) throw(): _template(t.clone()), _line(0) {} | ||||
|   Factory::Factory(const Node& t) throw(): | ||||
|       _template(xml::Node("<xml::start>")<<t), _line(0) {} | ||||
|   const Node& Factory::operator*() const throw() { | ||||
|     return *_template; | ||||
|     return _template[0]; | ||||
|   } | ||||
|   std::auto_ptr<Node> 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) try { | ||||
|             attributes_in_end_tag, | ||||
|             illegal_attribute, mandatory_attribute_missing, | ||||
|             wrong_node_number) try { | ||||
|     _line=1; | ||||
|     std::auto_ptr<Node> node(_template->clone()); | ||||
|     node->clear(); | ||||
|     _open=0; | ||||
|     std::auto_ptr<Node> node(read(is, _template)); | ||||
|     if (node->children()==0) | ||||
|       throw tag_expected(_template[0], _template[0].name()); | ||||
|     return (*node)[0].clone(); | ||||
|     /*node->clear(); | ||||
|     Tag res; | ||||
|     while (true) { | ||||
|       res = tag(is, *node); | ||||
|       res = tag(is, _template); | ||||
|       *node<<res.attributes; | ||||
|       switch (res.type) { | ||||
|         case END: throw wrong_end_tag(*node, is, res); | ||||
|         case EMPTY: return node; // empty | ||||
|         case EMPTY: | ||||
|           return node; // empty | ||||
|         case START: | ||||
|           if (!res.name.size()) throw tag_expected(*node, res.text); | ||||
|           if (node->name()!=res.name) throw wrong_start_tag(*node, is, res); | ||||
|           return read(is, *_template); | ||||
|           return read(is, _template[res.name]); | ||||
|           //! @todo store instead of ignore | ||||
|         case SPECIAL: break; | ||||
|       } | ||||
|     } | ||||
|         }*/ | ||||
|   } catch (exception& e) { | ||||
|     e.line(_line); | ||||
|     throw; | ||||
| @@ -505,37 +529,48 @@ namespace xml { | ||||
|       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) { | ||||
|             access_error, duplicate_attribute, attributes_in_end_tag, | ||||
|             illegal_attribute, mandatory_attribute_missing, | ||||
|             wrong_node_number) { | ||||
|     std::auto_ptr<Node> result(node.clone()); | ||||
|     result->clear(); | ||||
|     for (bool finished(false); is && !finished;) { | ||||
|     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); | ||||
|           finished = true; | ||||
|         } break; | ||||
|         } return result; | ||||
|         case EMPTY: { | ||||
|           *result<<(node[res.name].clone()->clear()<<res.attributes); | ||||
|         } break; | ||||
|         case START: { | ||||
|           ++_open; | ||||
|           *result<<(*read(is, node[res.name])<<res.attributes); | ||||
|         } break; | ||||
|         case SPECIAL: break; //! @todo ignored could be stored | ||||
|       } | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
|   Tag Factory::tag(std::istream& is, const Node& position) | ||||
|       throw(second_slash_in_tag, character_after_slash, | ||||
|       throw(second_slash_in_tag, character_after_slash, tag_expected, | ||||
|             missing_end_tag, attribute_value_not_quoted, | ||||
|             access_error, duplicate_attribute) { | ||||
|             access_error, duplicate_attribute, attributes_in_end_tag, | ||||
|             illegal_attribute, mandatory_attribute_missing, | ||||
|             wrong_start_tag, wrong_node_number) { | ||||
|     char c(0); | ||||
|     Tag tag((Tag){"", START, "", Attributes()}); | ||||
|     Tag tag((Tag){"", START, "", Attributes(), "", false}); | ||||
|     while (is && is.get(c) && ws(c)); // skip ws | ||||
|     if (!is) throw missing_end_tag(position, is, tag); | ||||
|     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) { | ||||
| @@ -559,6 +594,7 @@ namespace xml { | ||||
|       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!='>' && !ws(c)) attrname+=c; | ||||
|           while (ws(c) && is && is.get(c)); // skip ws, search '=' | ||||
| @@ -569,16 +605,40 @@ namespace xml { | ||||
|             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 | ||||
|           } 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); | ||||
|       } | ||||
|       for (Attributes::const_iterator it | ||||
|              (position[tag.name].attributes().begin()); | ||||
|            it!=position[tag.name].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; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -199,13 +199,16 @@ class ComplexTest: public CppUnit::TestFixture { | ||||
|            <<"<child/>" | ||||
|            <<"< otherchild ><  / otherchild  >< otherchild / >"<<std::endl | ||||
|            <<"</base>"; | ||||
|       CPPUNIT_ASSERT_THROW(factory.read(file2), xml::access_error); | ||||
|       CPPUNIT_ASSERT_THROW(factory.read(file2), xml::wrong_start_tag); | ||||
|       { | ||||
|         std::stringstream file("<base></xyz>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_end_tag); | ||||
|       } { | ||||
|         std::stringstream file("<xyz></xyz>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_start_tag); | ||||
|       } { | ||||
|         std::stringstream file("<xyz/>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_start_tag); | ||||
|       } { | ||||
|         std::stringstream file("base"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::tag_expected); | ||||
| @@ -234,7 +237,7 @@ class ComplexTest: public CppUnit::TestFixture { | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::missing_end_tag); | ||||
|       } { | ||||
|         std::stringstream file; | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::missing_end_tag); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::tag_expected); | ||||
|       } { | ||||
|         std::stringstream file("<base><child a=b></base>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), | ||||
| @@ -249,14 +252,50 @@ class ComplexTest: public CppUnit::TestFixture { | ||||
|     } | ||||
|     void attributes() { | ||||
|       xml::Factory factory(xml::Node("base") | ||||
|                            .attr("one", xml::mandatory) | ||||
|                            .attr("two", xml::optional) | ||||
|                            <<(xml::Node("child") | ||||
|                               <<xml::String("childofchild") | ||||
|                               <<xml::UnsignedInteger("number")) | ||||
|                            <<xml::Node("otherchild")); | ||||
|       { | ||||
|         std::stringstream file("<base></base>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), | ||||
|                              xml::mandatory_attribute_missing); | ||||
|       } { | ||||
|         std::stringstream file("<base one two three></base>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), xml::illegal_attribute); | ||||
|       }       | ||||
|     } | ||||
|     void ranges() { | ||||
|       xml::Factory factory(xml::Node("base", 2, 2) | ||||
|                            <<xml::Node("mandatory", 1) | ||||
|                            <<xml::Node("optional", 0, 1)); | ||||
|       { | ||||
|         std::stringstream file("<base><mandatory></mandatory></base>" | ||||
|                                "<base><mandatory/><optional/></base>"); | ||||
|         CPPUNIT_ASSERT_NO_THROW(factory.read(file)); | ||||
|       } /*{ | ||||
|         std::stringstream file("<base><mandatory></mandatory></base>" | ||||
|                                "<base><mandatory/><optional/></base>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), | ||||
|                              xml::wrong_node_number); | ||||
|       } { | ||||
|         std::stringstream file("<base><mandatory></mandatory></base>" | ||||
|                                "<base><mandatory/><optional/></base>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), | ||||
|                              xml::wrong_node_number); | ||||
|       } { | ||||
|         std::stringstream file("<base><mandatory></mandatory></base>" | ||||
|                                "<base><mandatory/><optional/></base>"); | ||||
|         CPPUNIT_ASSERT_THROW(factory.read(file), | ||||
|                              xml::wrong_node_number); | ||||
|       }*/ | ||||
|     } | ||||
|     CPPUNIT_TEST_SUITE(ComplexTest); | ||||
|     CPPUNIT_TEST(nodes); | ||||
|     CPPUNIT_TEST(attributes); | ||||
|     CPPUNIT_TEST(ranges); | ||||
|     CPPUNIT_TEST_SUITE_END(); | ||||
| }; | ||||
| CPPUNIT_TEST_SUITE_REGISTRATION(ComplexTest); | ||||
| @@ -293,7 +332,7 @@ class FunTest: public CppUnit::TestFixture { | ||||
|                            <<(xml::Node("head") | ||||
|                               <<xml::String("title")) | ||||
|                            <<(xml::Node("body") | ||||
|                               <<xml::String("h1") | ||||
|                               <<xml::String("h1").attr("class", xml::optional) | ||||
|                               <<xml::String("h2") | ||||
|                               <<xml::String("p"))); | ||||
|       std::auto_ptr<xml::Node> read(factory.read(ss)); // read back the example | ||||
|   | ||||
		Reference in New Issue
	
	Block a user