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