new tests, cleanup, prepared for node-limits

master
Marc Wäckerlin 16 years ago
parent 98334b4565
commit f9389d1082
  1. 64
      src/xml-cxx/xml.hxx
  2. 120
      src/xml.cxx
  3. 45
      test/xml_test.cxx

@ -9,6 +9,7 @@
#define LIB_XML_CXX_HXX #define LIB_XML_CXX_HXX
#include <istream> #include <istream>
#include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
@ -244,6 +245,37 @@ namespace xml {
t, is, tag, 0) { 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; std::string text;
Attributes attributes; Attributes attributes;
std::string special; std::string special;
bool found;
}; };
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -311,7 +344,7 @@ namespace xml {
public: public:
typedef Contents::size_type size_type; typedef Contents::size_type size_type;
typedef std::vector<Node*> List; 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(const Node& o) throw();
Node& operator=(const Node& o) throw(); Node& operator=(const Node& o) throw();
virtual ~Node() throw(); virtual ~Node() throw();
@ -329,10 +362,14 @@ namespace xml {
Node& parent() const throw(no_parent); Node& parent() const throw(no_parent);
bool hasAttr(const std::string& name) const throw(); bool hasAttr(const std::string& name) const throw();
Node& attr(const std::string& name, bool mandatory) 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) const throw();
std::string& attr(const std::string& name) throw(); std::string& attr(const std::string& name) throw();
const Attributes::Value attribute(const std::string& name) const Attributes::Value attribute(const std::string& name)
const throw(attribute_not_available); 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(); List list(const std::string& name) const throw();
bool operator()(const std::string& child) const throw(); bool operator()(const std::string& child) const throw();
Node& operator<<(const Node& o) throw(cannot_have_children); Node& operator<<(const Node& o) throw(cannot_have_children);
@ -340,9 +377,9 @@ namespace xml {
//! Get the number of children. //! Get the number of children.
size_type children() const throw(); size_type children() const throw();
//! Get the n-th child. //! 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. //! 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. //! Get the first named child.
const Node& operator[](const std::string& child) const const Node& operator[](const std::string& child) const
throw(access_error); throw(access_error);
@ -361,6 +398,8 @@ namespace xml {
Contents _contents; Contents _contents;
std::string _name; std::string _name;
Node* _parent; 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, throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
second_slash_in_tag, character_after_slash, second_slash_in_tag, character_after_slash,
missing_end_tag, attribute_value_not_quoted, access_error, 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: private:
friend class stream_error; friend class stream_error;
Factory(); // not implemented Factory(); // not implemented
@ -412,13 +453,18 @@ namespace xml {
second_slash_in_tag, character_after_slash, second_slash_in_tag, character_after_slash,
missing_end_tag, missing_end_tag,
attribute_value_not_quoted, access_error, duplicate_attribute, 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) Tag tag(std::istream& is, const Node& position)
throw(second_slash_in_tag, character_after_slash, throw(second_slash_in_tag, wrong_start_tag, character_after_slash,
missing_end_tag, missing_end_tag, attributes_in_end_tag, tag_expected,
attribute_value_not_quoted, access_error, duplicate_attribute); attribute_value_not_quoted, access_error, duplicate_attribute,
std::auto_ptr<Node> _template; illegal_attribute, mandatory_attribute_missing,
wrong_node_number);
Node _template;
unsigned long _line; unsigned long _line;
long _open;
}; };
} }

@ -33,7 +33,7 @@ class MethodTrace {
}; };
unsigned int MethodTrace::_level(0); unsigned int MethodTrace::_level(0);
#define TRACE MethodTrace XXX_METHOD(this, __PRETTY_FUNCTION__) #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 { namespace xml {
@ -219,11 +219,12 @@ namespace xml {
//! Nodes must be given a name. //! Nodes must be given a name.
/*! Unnamed nodes are not allowed, therefore there's no default /*! Unnamed nodes are not allowed, therefore there's no default
constructor. */ constructor. */
Node::Node(std::string name) throw(): Node::Node(std::string name, size_type min, size_type max) throw():
_name(name), _parent(0) { _name(name), _parent(0), _limits(min, max) {
} }
Node::Node(const Node& o) throw(): 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()); for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it) it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release()); _contents.push_back((*it)->clone(this).release());
@ -232,6 +233,7 @@ namespace xml {
_attributes=o._attributes; _attributes=o._attributes;
_name = o.name(); _name = o.name();
_parent = 0; _parent = 0;
_limits = o._limits;
for (Contents::const_iterator it(o._contents.begin()); for (Contents::const_iterator it(o._contents.begin());
it!=o._contents.end(); ++it) it!=o._contents.end(); ++it)
_contents.push_back((*it)->clone(this).release()); _contents.push_back((*it)->clone(this).release());
@ -305,7 +307,12 @@ namespace xml {
return _attributes.find(name)!=_attributes.end(); return _attributes.find(name)!=_attributes.end();
} }
Node& Node::attr(const std::string& name, bool mandatory) throw() { 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; return *this;
} }
std::string Node::attr(const std::string& name) const throw() { std::string Node::attr(const std::string& name) const throw() {
@ -322,6 +329,17 @@ namespace xml {
if (it!=_attributes.end()) return *it; if (it!=_attributes.end()) return *it;
throw attribute_not_available(*this, name); 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() { Node::List Node::list(const std::string& name) const throw() {
List res; List res;
for (Contents::const_iterator it(_contents.begin()); for (Contents::const_iterator it(_contents.begin());
@ -342,12 +360,12 @@ namespace xml {
return _contents.size(); return _contents.size();
} }
const Node& Node::operator[](Node::size_type child) const const Node& Node::operator[](Node::size_type child) const
throw(access_error) try { throw(out_of_range) try {
return *_contents.at(child); return *_contents.at(child);
} catch (...) { } catch (...) {
throw out_of_range(*this, child); 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); return *_contents.at(child);
} catch (...) { } catch (...) {
throw out_of_range(*this, child); 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() { const Node& Factory::operator*() const throw() {
return *_template; return _template[0];
} }
std::auto_ptr<Node> Factory::read(std::istream& is) std::auto_ptr<Node> Factory::read(std::istream& is)
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch, throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
second_slash_in_tag, second_slash_in_tag,
character_after_slash, missing_end_tag, attribute_value_not_quoted, character_after_slash, missing_end_tag, attribute_value_not_quoted,
access_error, duplicate_attribute, access_error, duplicate_attribute,
attributes_in_end_tag) try { attributes_in_end_tag,
illegal_attribute, mandatory_attribute_missing,
wrong_node_number) try {
_line=1; _line=1;
std::auto_ptr<Node> node(_template->clone()); _open=0;
node->clear(); 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; Tag res;
while (true) { while (true) {
res = tag(is, *node); res = tag(is, _template);
*node<<res.attributes; *node<<res.attributes;
switch (res.type) { switch (res.type) {
case END: throw wrong_end_tag(*node, is, res); case END: throw wrong_end_tag(*node, is, res);
case EMPTY: return node; // empty case EMPTY:
return node; // empty
case START: case START:
if (!res.name.size()) throw tag_expected(*node, res.text); return read(is, _template[res.name]);
if (node->name()!=res.name) throw wrong_start_tag(*node, is, res);
return read(is, *_template);
//! @todo store instead of ignore //! @todo store instead of ignore
case SPECIAL: break; case SPECIAL: break;
} }
} }*/
} catch (exception& e) { } catch (exception& e) {
e.line(_line); e.line(_line);
throw; throw;
@ -505,37 +529,48 @@ namespace xml {
throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch, throw(wrong_end_tag, wrong_start_tag, tag_expected, type_mismatch,
second_slash_in_tag, second_slash_in_tag,
character_after_slash, missing_end_tag, attribute_value_not_quoted, 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()); std::auto_ptr<Node> result(node.clone());
result->clear(); result->clear();
for (bool finished(false); is && !finished;) { while (true) {
Tag res(tag(is, node)); Tag res(tag(is, node));
if (!res.found) return result;
if (res.text.size()) result->text(res.text); if (res.text.size()) result->text(res.text);
switch (res.type) { switch (res.type) {
case END: { case END: {
--_open;
if (res.attributes.size()) throw attributes_in_end_tag(node, is, res); if (res.attributes.size()) throw attributes_in_end_tag(node, is, res);
if (res.name!=node.name()) throw wrong_end_tag(node, is, res); if (res.name!=node.name()) throw wrong_end_tag(node, is, res);
finished = true; } return result;
} break;
case EMPTY: { case EMPTY: {
*result<<(node[res.name].clone()->clear()<<res.attributes); *result<<(node[res.name].clone()->clear()<<res.attributes);
} break; } break;
case START: { case START: {
++_open;
*result<<(*read(is, node[res.name])<<res.attributes); *result<<(*read(is, node[res.name])<<res.attributes);
} break; } break;
case SPECIAL: break; //! @todo ignored could be stored case SPECIAL: break; //! @todo ignored could be stored
} }
} }
return result;
} }
Tag Factory::tag(std::istream& is, const Node& position) 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, 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); char c(0);
Tag tag((Tag){"", START, "", Attributes()}); Tag tag((Tag){"", START, "", Attributes(), "", false});
while (is && is.get(c) && ws(c)); // skip ws 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!='<'); if (c!='<') do tag.text+=c; while (is && is.get(c) && c!='<');
bool nameRead(false); bool nameRead(false);
for (char last(c); is && is.get(c) && c!='>'; last=c) switch (c) { for (char last(c); is && is.get(c) && c!='>'; last=c) switch (c) {
@ -559,6 +594,7 @@ namespace xml {
default: default:
if (tag.type==EMPTY) throw character_after_slash(position, is, tag, c); if (tag.type==EMPTY) throw character_after_slash(position, is, tag, c);
if (nameRead) { // read attribute if (nameRead) { // read attribute
if (tag.type!=START) throw attributes_in_end_tag(position, is, tag);
std::string attrname(1, c), attrvalue; std::string attrname(1, c), attrvalue;
while (is && is.get(c) && c!='=' && c!='>' && !ws(c)) attrname+=c; while (is && is.get(c) && c!='=' && c!='>' && !ws(c)) attrname+=c;
while (ws(c) && is && is.get(c)); // skip ws, search '=' while (ws(c) && is && is.get(c)); // skip ws, search '='
@ -569,16 +605,40 @@ namespace xml {
while (is && is.get(c) && c!='"') attrvalue+=c; while (is && is.get(c) && c!='"') attrvalue+=c;
if (c!='"') if (c!='"')
throw attribute_value_not_quoted(position, is, tag, c, attrname); throw attribute_value_not_quoted(position, is, tag, c, attrname);
} else { } else is.unget(); // read too far
is.unget();
}// read too far
if (tag.attributes.find(attrname)!=tag.attributes.end()) if (tag.attributes.find(attrname)!=tag.attributes.end())
throw duplicate_attribute(position, is, tag, attrname); 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; tag.attributes[attrname] = attrvalue;
} else { // part of a tag name } else { // part of a tag name
tag.name+=c; 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; return tag;
} }

@ -199,13 +199,16 @@ class ComplexTest: public CppUnit::TestFixture {
<<"<child/>" <<"<child/>"
<<"< otherchild >< / otherchild >< otherchild / >"<<std::endl <<"< otherchild >< / otherchild >< otherchild / >"<<std::endl
<<"</base>"; <<"</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>"); std::stringstream file("<base></xyz>");
CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_end_tag); CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_end_tag);
} { } {
std::stringstream file("<xyz></xyz>"); std::stringstream file("<xyz></xyz>");
CPPUNIT_ASSERT_THROW(factory.read(file), xml::wrong_start_tag); 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"); std::stringstream file("base");
CPPUNIT_ASSERT_THROW(factory.read(file), xml::tag_expected); 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); CPPUNIT_ASSERT_THROW(factory.read(file), xml::missing_end_tag);
} { } {
std::stringstream file; 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>"); std::stringstream file("<base><child a=b></base>");
CPPUNIT_ASSERT_THROW(factory.read(file), CPPUNIT_ASSERT_THROW(factory.read(file),
@ -249,14 +252,50 @@ class ComplexTest: public CppUnit::TestFixture {
} }
void attributes() { void attributes() {
xml::Factory factory(xml::Node("base") xml::Factory factory(xml::Node("base")
.attr("one", xml::mandatory)
.attr("two", xml::optional)
<<(xml::Node("child") <<(xml::Node("child")
<<xml::String("childofchild") <<xml::String("childofchild")
<<xml::UnsignedInteger("number")) <<xml::UnsignedInteger("number"))
<<xml::Node("otherchild")); <<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_SUITE(ComplexTest);
CPPUNIT_TEST(nodes); CPPUNIT_TEST(nodes);
CPPUNIT_TEST(attributes); CPPUNIT_TEST(attributes);
CPPUNIT_TEST(ranges);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
}; };
CPPUNIT_TEST_SUITE_REGISTRATION(ComplexTest); CPPUNIT_TEST_SUITE_REGISTRATION(ComplexTest);
@ -293,7 +332,7 @@ class FunTest: public CppUnit::TestFixture {
<<(xml::Node("head") <<(xml::Node("head")
<<xml::String("title")) <<xml::String("title"))
<<(xml::Node("body") <<(xml::Node("body")
<<xml::String("h1") <<xml::String("h1").attr("class", xml::optional)
<<xml::String("h2") <<xml::String("h2")
<<xml::String("p"))); <<xml::String("p")));
std::auto_ptr<xml::Node> read(factory.read(ss)); // read back the example std::auto_ptr<xml::Node> read(factory.read(ss)); // read back the example

Loading…
Cancel
Save