From: Carl Hetherington Date: Thu, 9 Oct 2014 12:42:36 +0000 (+0100) Subject: Don't use xmlpp as the data storage; put things in cxml::Node members X-Git-Url: https://git.carlh.net/gitweb/?a=commitdiff_plain;h=fd7dada1f4f5668e56a7cf3a268d22702f6ba52d;p=libcxml.git Don't use xmlpp as the data storage; put things in cxml::Node members instead. Add short-cuts for creating XML using libcxml. --- diff --git a/src/cxml.cc b/src/cxml.cc index 810abf8..9a6a4e1 100644 --- a/src/cxml.cc +++ b/src/cxml.cc @@ -24,70 +24,107 @@ #include #include "cxml.h" -using namespace std; -using namespace boost; +using std::pair; +using std::list; +using std::istream; +using std::string; +using std::make_pair; +using std::map; +using std::stringstream; +using boost::shared_ptr; +using boost::optional; cxml::Node::Node () - : _node (0) { } -cxml::Node::Node (xmlpp::Node* node) - : _node (node) +cxml::Node::Node (xmlpp::Node const * node) { + read (node); +} +void +cxml::Node::read (xmlpp::Node const * node) +{ + _name = node->get_name (); + _namespace_uri = node->get_namespace_uri (); + _namespace_prefix = node->get_namespace_prefix (); + + xmlpp::Node::NodeList content = node->get_children (); + for (xmlpp::Node::NodeList::const_iterator i = content.begin(); i != content.end(); ++i) { + xmlpp::ContentNode const * v = dynamic_cast (*i); + if (v) { + _content += v->get_content (); + } else { + _children.push_back (cxml::NodePtr (new cxml::Node (*i))); + } + } + + xmlpp::Element const * element = dynamic_cast (node); + if (element) { + xmlpp::Element::AttributeList attributes = element->get_attributes (); + for (list::const_iterator i = attributes.begin(); i != attributes.end(); ++i) { + _attributes.push_back (make_pair ((*i)->get_name(), (*i)->get_value ())); + } + } } -string -cxml::Node::name () const +void +cxml::Node::write (xmlpp::Node* parent) const { - assert (_node); - return _node->get_name (); + xmlpp::Element* node = parent->add_child (_name); + + if (!_content.empty ()) { + node->add_child_text (_content); + } + + for (list >::const_iterator i = _attributes.begin(); i != _attributes.end(); ++i) { + node->set_attribute (i->first, i->second); + } + + for (NodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) { + (*i)->write (node); + } } -shared_ptr +cxml::NodePtr cxml::Node::node_child (string name) const { - list > n = node_children (name); + NodeList n = node_children (name); if (n.size() > 1) { throw cxml::Error ("duplicate XML tag " + name); } else if (n.empty ()) { - throw cxml::Error ("missing XML tag " + name + " in " + _node->get_name()); + throw cxml::Error ("missing XML tag " + name + " in " + _name); } return n.front (); } -shared_ptr +cxml::NodePtr cxml::Node::optional_node_child (string name) const { - list > n = node_children (name); + NodeList n = node_children (name); if (n.size() > 1) { throw cxml::Error ("duplicate XML tag " + name); } else if (n.empty ()) { - return shared_ptr (); + return NodePtr (); } return n.front (); } -list > +cxml::NodeList cxml::Node::node_children (string name) const { - /* XXX: using find / get_path should work here, but I can't follow - how get_path works. - */ - - xmlpp::Node::NodeList c = _node->get_children (); + NodeList n; - list > n; - for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) { - if ((*i)->get_name() == name) { - n.push_back (shared_ptr (new Node (*i))); + for (NodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) { + if ((*i)->name() == name) { + n.push_back (*i); } } - + _taken.push_back (name); return n; } @@ -101,7 +138,7 @@ cxml::Node::string_child (string c) const optional cxml::Node::optional_string_child (string c) const { - list > nodes = node_children (c); + NodeList nodes = node_children (c); if (nodes.size() > 1) { throw cxml::Error ("duplicate XML tag " + c); } @@ -140,33 +177,31 @@ cxml::Node::ignore_child (string name) const string cxml::Node::string_attribute (string name) const { - xmlpp::Element const * e = dynamic_cast (_node); - if (!e) { - throw cxml::Error ("missing attribute"); + list >::const_iterator i; + while (i != _attributes.end() && i->first != name) { + ++i; } - xmlpp::Attribute* a = e->get_attribute (name); - if (!a) { + if (i == _attributes.end ()) { throw cxml::Error ("missing attribute"); } - return a->get_value (); + return i->second; } optional cxml::Node::optional_string_attribute (string name) const { - xmlpp::Element const * e = dynamic_cast (_node); - if (!e) { - return optional (); + list >::const_iterator i; + while (i != _attributes.end() && i->first != name) { + ++i; } - xmlpp::Attribute* a = e->get_attribute (name); - if (!a) { + if (i == _attributes.end ()) { return optional (); } - return string (a->get_value ()); + return i->second; } bool @@ -190,10 +225,9 @@ cxml::Node::optional_bool_attribute (string name) const void cxml::Node::done () const { - xmlpp::Node::NodeList c = _node->get_children (); - for (xmlpp::Node::NodeList::iterator i = c.begin(); i != c.end(); ++i) { - if (dynamic_cast (*i) && find (_taken.begin(), _taken.end(), (*i)->get_name()) == _taken.end ()) { - throw cxml::Error ("unexpected XML node " + (*i)->get_name()); + for (NodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) { + if (find (_taken.begin(), _taken.end(), (*i)->name()) == _taken.end ()) { + throw cxml::Error ("unexpected XML node " + (*i)->name()); } } } @@ -201,92 +235,107 @@ cxml::Node::done () const string cxml::Node::content () const { - string content; - - xmlpp::Node::NodeList c = _node->get_children (); - for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) { - xmlpp::ContentNode const * v = dynamic_cast (*i); - if (v) { - content += v->get_content (); - } - } - - return content; + return _content; } string cxml::Node::namespace_uri () const { - return _node->get_namespace_uri (); + return _namespace_uri; } string cxml::Node::namespace_prefix () const { - return _node->get_namespace_prefix (); + return _namespace_prefix; } -cxml::Document::Document (string root_name) - : _root_name (root_name) +void +cxml::Node::set_string_content (string c) { - _parser = new xmlpp::DomParser; + _content = c; } -cxml::Document::Document (string root_name, boost::filesystem::path file) - : _root_name (root_name) +void +cxml::Node::set_bool_content (bool c) { - _parser = new xmlpp::DomParser (); - read_file (file); + _content = c ? "yes" : "no"; } -cxml::Document::Document () +cxml::NodePtr +cxml::Node::add_string (string name, string content) { - _parser = new xmlpp::DomParser (); + NodePtr n = add (name); + n->set_string_content (content); + return n; } - -cxml::Document::~Document () + +cxml::NodePtr +cxml::Node::add_bool (std::string name, bool content) { - delete _parser; + NodePtr n = add (name); + n->set_bool_content (content); + return n; } void -cxml::Document::read_file (filesystem::path file) +cxml::Document::read_file (boost::filesystem::path file) { - if (!filesystem::exists (file)) { + if (!boost::filesystem::exists (file)) { throw cxml::Error ("XML file does not exist"); } - - _parser->parse_file (file.string ()); - take_root_node (); + + xmlpp::DomParser parser; + parser.parse_file (file.string ()); + read (parser.get_document()->get_root_node ()); } void cxml::Document::read_stream (istream& stream) { - _parser->parse_stream (stream); - take_root_node (); + xmlpp::DomParser parser; + parser.parse_stream (stream); + read (parser.get_document()->get_root_node ()); } void cxml::Document::read_string (string s) { + xmlpp::DomParser parser; stringstream t (s); - _parser->parse_stream (t); - take_root_node (); + parser.parse_stream (t); + read (parser.get_document()->get_root_node ()); } void -cxml::Document::take_root_node () +cxml::Document::check_root_name (string r) { - if (!_parser) { - throw cxml::Error ("could not parse XML"); - } - - _node = _parser->get_document()->get_root_node (); - if (!_root_name.empty() && _node->get_name() != _root_name) { + if (name() != r) { throw cxml::Error ("unrecognised root node"); - } else if (_root_name.empty ()) { - _root_name = _node->get_name (); } } +void +cxml::Document::write_to_file_formatted (boost::filesystem::path path) const +{ + xmlpp::Document doc; + write_to_xmlpp_document (doc); + doc.write_to_file_formatted (path.string ()); +} + +void +cxml::Document::write_to_string (std::string coding) const +{ + xmlpp::Document doc; + write_to_xmlpp_document (doc); + doc.write_to_string (coding); +} + +void +cxml::Document::write_to_xmlpp_document (xmlpp::Document& doc) const +{ + xmlpp::Element* root = doc.create_root_node (name ()); + for (NodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) { + (*i)->write (root); + } +} diff --git a/src/cxml.h b/src/cxml.h index 8be978f..0542c4e 100644 --- a/src/cxml.h +++ b/src/cxml.h @@ -33,11 +33,8 @@ #undef check #endif -#include - namespace xmlpp { class Node; - class DomParser; } namespace cxml { @@ -67,21 +64,15 @@ private: }; class Node; -typedef std::list > NodeList; +typedef boost::shared_ptr NodePtr; +typedef std::list NodeList; +typedef boost::shared_ptr ConstNodePtr; -/** @brief A wrapper for a xmlpp::Node which simplifies parsing */ class Node { public: Node (); - - /** Construct a Node from an xmlpp::Node. This class will - * not destroy the xmlpp::Node. - * @param node xmlpp::Node. - */ - Node (xmlpp::Node* node); - - std::string name () const; + Node (xmlpp::Node const *); /* A set of methods which look up a child of this node by * its name, and return its contents as some type or other. @@ -142,14 +133,7 @@ public: return n; } - /** This will mark a child as to be ignored when calling done() */ - void ignore_child (std::string) const; - - /** Check whether all children of this Node have been looked up - * or passed to ignore_child(). If not, an exception is thrown. - */ - void done () const; - + /* These methods look for an attribute of this node, in the * same way as the child methods do. */ @@ -191,56 +175,108 @@ public: return n; } - /** @return The content of this node */ - std::string content () const; - /** @return namespace URI of this node */ - std::string namespace_uri () const; + /* Setting content */ - /** @return namespace prefix of this node */ - std::string namespace_prefix () const; + void set_string_content (std::string content); + void set_bool_content (bool content); + + template + void set_number_content (T content) + { + std::stringstream u; + u.imbue (std::locale::classic ()); + u << content; + u >> _content; + } + + + /* Short-cuts to add nodes with content */ + + NodePtr add_string (std::string name, std::string content); + NodePtr add_bool (std::string name, bool content); + + template + NodePtr add_number (std::string name, T content) + { + NodePtr n = add (name); + n->set_number_content (content); + return n; + } + + + /* Access to child nodes */ boost::shared_ptr node_child (std::string) const; boost::shared_ptr optional_node_child (std::string) const; - NodeList node_children (std::string) const; - xmlpp::Node* node () const { - return _node; + /** Add a child node with a given name */ + NodePtr add (std::string name) + { + NodePtr n (new cxml::Node ()); + n->set_name (name); + _children.push_back (n); + return n; } -protected: - xmlpp::Node* _node; + /** @return The content of this node */ + std::string content () const; + /** @return namespace URI of this node */ + std::string namespace_uri () const; + /** @return namespace prefix of this node */ + std::string namespace_prefix () const; + /** This will mark a child as to be ignored when calling done() */ + void ignore_child (std::string) const; + /** Check whether all children of this Node have been looked up + * or passed to ignore_child(). If not, an exception is thrown. + */ + void done () const; + /** Set the name of the node. + * @param n New name. + */ + void set_name (std::string n) { + _name = n; + } + /** @return Node name */ + std::string name () const { + return _name; + } + + /* We use xmlpp for parsing and writing XML out; these + methods help with that. + */ + void read (xmlpp::Node const *); + void write (xmlpp::Node *) const; + +protected: + NodeList _children; private: - mutable std::list _taken; + std::string _name; + std::string _content; + std::string _namespace_uri; + std::string _namespace_prefix; + std::list > _attributes; + mutable std::list _taken; }; -typedef boost::shared_ptr NodePtr; -typedef boost::shared_ptr ConstNodePtr; - class Document : public Node { public: - Document (); - Document (std::string root_name); - Document (std::string root_name, boost::filesystem::path); - - virtual ~Document (); + Document () {} void read_file (boost::filesystem::path); void read_stream (std::istream &); void read_string (std::string); - - std::string root_name () const { - return _root_name; - } - + + void check_root_name (std::string root_name); + + void write_to_file_formatted (boost::filesystem::path) const; + void write_to_string (std::string) const; + private: - void take_root_node (); - - xmlpp::DomParser* _parser; - std::string _root_name; + void write_to_xmlpp_document (xmlpp::Document &) const; }; } diff --git a/test/ref/a.xml b/test/ref/a.xml index e854aae..10900aa 100644 --- a/test/ref/a.xml +++ b/test/ref/a.xml @@ -1,3 +1,4 @@ + 42 fred @@ -5,6 +6,11 @@ yes 1 2 - testingmore testing - jim + + testing + more testing + + + jim + diff --git a/test/tests.cc b/test/tests.cc index 9d07e7c..32ea830 100644 --- a/test/tests.cc +++ b/test/tests.cc @@ -33,10 +33,70 @@ using std::vector; using std::list; using boost::shared_ptr; -BOOST_AUTO_TEST_CASE (test) + +void +check_xml (xmlpp::Element* ref, xmlpp::Element* test, list ignore) +{ + BOOST_CHECK_EQUAL (ref->get_name (), test->get_name ()); + BOOST_CHECK_EQUAL (ref->get_namespace_prefix (), test->get_namespace_prefix ()); + + if (find (ignore.begin(), ignore.end(), ref->get_name()) != ignore.end ()) { + return; + } + + xmlpp::Element::NodeList ref_children = ref->get_children (); + xmlpp::Element::NodeList test_children = test->get_children (); + BOOST_CHECK_EQUAL (ref_children.size (), test_children.size ()); + + xmlpp::Element::NodeList::iterator k = ref_children.begin (); + xmlpp::Element::NodeList::iterator l = test_children.begin (); + while (k != ref_children.end ()) { + + /* XXX: should be doing xmlpp::EntityReference, xmlpp::XIncludeEnd, xmlpp::XIncludeStart */ + + xmlpp::Element* ref_el = dynamic_cast (*k); + xmlpp::Element* test_el = dynamic_cast (*l); + BOOST_CHECK ((ref_el && test_el) || (!ref_el && !test_el)); + if (ref_el && test_el) { + check_xml (ref_el, test_el, ignore); + } + + xmlpp::ContentNode* ref_cn = dynamic_cast (*k); + xmlpp::ContentNode* test_cn = dynamic_cast (*l); + BOOST_CHECK ((ref_cn && test_cn) || (!ref_cn && !test_cn)); + if (ref_cn && test_cn) { + BOOST_CHECK_EQUAL (ref_cn->get_content(), test_cn->get_content ()); + } + + xmlpp::Attribute* ref_at = dynamic_cast (*k); + xmlpp::Attribute* test_at = dynamic_cast (*l); + BOOST_CHECK ((ref_at && test_at) || (!ref_at && !test_at)); + if (ref_at && test_at) { + BOOST_CHECK_EQUAL (ref_at->get_name(), test_at->get_name ()); + BOOST_CHECK_EQUAL (ref_at->get_value(), test_at->get_value ()); + } + + ++k; + ++l; + } +} + +void +check_xml (boost::filesystem::path ref, boost::filesystem::path test, list ignore) { - cxml::Document document ("A"); + xmlpp::DomParser* ref_parser = new xmlpp::DomParser (ref.string ()); + xmlpp::Element* ref_root = ref_parser->get_document()->get_root_node (); + xmlpp::DomParser* test_parser = new xmlpp::DomParser (test.string ()); + xmlpp::Element* test_root = test_parser->get_document()->get_root_node (); + + check_xml (ref_root, test_root, ignore); +} + +BOOST_AUTO_TEST_CASE (read_test) +{ + cxml::Document document; document.read_file ("test/ref/a.xml"); + document.check_root_name ("A"); BOOST_CHECK_EQUAL (document.string_child("B"), "42"); BOOST_CHECK_EQUAL (document.number_child("B"), 42); @@ -79,3 +139,24 @@ BOOST_AUTO_TEST_CASE (test) BOOST_CHECK_EQUAL (document.node_children("J").front()->node_children("K").size(), 1); BOOST_CHECK_EQUAL (document.node_children("J").front()->node_children("K").front()->content(), "jim"); } + +BOOST_AUTO_TEST_CASE (write_test) +{ + cxml::Document document; + document.set_name ("A"); + + document.add_number ("B", 42); + document.add_string ("C", "fred"); + document.add_number ("D", 42.9); + document.add_bool ("E", true); + document.add_number ("F", 1); + document.add_number ("F", 2); + cxml::NodePtr h = document.add ("H"); + h->add_string ("I", "testing"); + h->add_string ("I", "more testing"); + document.add ("J")->add_string ("K", "jim"); + + document.write_to_file_formatted ("build/test/b.xml"); + + check_xml ("test/ref/a.xml", "build/test/b.xml", list ()); +} diff --git a/wscript b/wscript index f54ff9c..2d1fd6a 100644 --- a/wscript +++ b/wscript @@ -4,6 +4,7 @@ API_VERSION = '0.0.0' def options(opt): opt.load('compiler_cxx') + opt.add_option('--enable-debug', action='store_true', default=False, help='build with debugging information and without optimisation') opt.add_option('--target-windows', action='store_true', default=False, help='set up to do a cross-compile to Windows') opt.add_option('--static', action='store_true', default=False, help='build statically') opt.add_option('--disable-tests', action='store_true', default=False, help='disable building of tests') @@ -12,6 +13,11 @@ def configure(conf): conf.load('compiler_cxx') conf.env.append_value('CXXFLAGS', ['-Wall', '-Wextra', '-O2']) + if conf.options.enable_debug: + conf.env.append_value('CXXFLAGS', '-g') + else: + conf.env.append_value('CXXFLAGS', '-O2') + conf.env.TARGET_WINDOWS = conf.options.target_windows conf.env.STATIC = conf.options.static conf.env.DISABLE_TESTS = conf.options.disable_tests