diff --git a/src/xml-cxx/xml.hxx b/src/xml-cxx/xml.hxx index d3eaf4f..2305fec 100644 --- a/src/xml-cxx/xml.hxx +++ b/src/xml-cxx/xml.hxx @@ -9,6 +9,7 @@ #define LIB_XML_CXX_HXX #include +#include #include #include #include @@ -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: "< 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 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 _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; }; } diff --git a/src/xml.cxx b/src/xml.cxx index bbd3366..94a0dbf 100644 --- a/src/xml.cxx +++ b/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**** "<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("")< 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(_template->clone()); - node->clear(); + _open=0; + std::auto_ptr 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<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 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()<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; } diff --git a/test/xml_test.cxx b/test/xml_test.cxx index 5d471ac..e82f6ae 100644 --- a/test/xml_test.cxx +++ b/test/xml_test.cxx @@ -199,13 +199,16 @@ class ComplexTest: public CppUnit::TestFixture { <<"" <<"< otherchild >< / otherchild >< otherchild / >"<"; - CPPUNIT_ASSERT_THROW(factory.read(file2), xml::access_error); + CPPUNIT_ASSERT_THROW(factory.read(file2), xml::wrong_start_tag); { std::stringstream file(""); CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_end_tag); } { std::stringstream file(""); CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_start_tag); + } { + std::stringstream file(""); + 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(""); 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") <"); + CPPUNIT_ASSERT_THROW(factory.read(file), + xml::mandatory_attribute_missing); + } { + std::stringstream file(""); + CPPUNIT_ASSERT_THROW(factory.read(file), xml::illegal_attribute); + } + } + void ranges() { + xml::Factory factory(xml::Node("base", 2, 2) + <" + ""); + CPPUNIT_ASSERT_NO_THROW(factory.read(file)); + } /*{ + std::stringstream file("" + ""); + CPPUNIT_ASSERT_THROW(factory.read(file), + xml::wrong_node_number); + } { + std::stringstream file("" + ""); + CPPUNIT_ASSERT_THROW(factory.read(file), + xml::wrong_node_number); + } { + std::stringstream file("" + ""); + 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") < read(factory.read(ss)); // read back the example