new tests, cleanup, prepared for node-limits

master
Marc Wäckerlin 15 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
#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;
};
}

@ -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

Loading…
Cancel
Save