Don't use xmlpp as the data storage; put things in cxml::Node members
authorCarl Hetherington <cth@carlh.net>
Thu, 9 Oct 2014 12:42:36 +0000 (13:42 +0100)
committerCarl Hetherington <cth@carlh.net>
Thu, 9 Oct 2014 12:42:36 +0000 (13:42 +0100)
instead.  Add short-cuts for creating XML using libcxml.

src/cxml.cc
src/cxml.h
test/ref/a.xml
test/tests.cc
wscript

index 810abf81deae977c61ee11944382af10bff5eaf8..9a6a4e1307fb0e95961735cbe437d1779a4e69f5 100644 (file)
 #include <libxml++/libxml++.h>
 #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<xmlpp::ContentNode const *> (*i);
+               if (v) {
+                       _content += v->get_content ();
+               } else {
+                       _children.push_back (cxml::NodePtr (new cxml::Node (*i)));
+               }
+       }
+
+       xmlpp::Element const * element = dynamic_cast<xmlpp::Element const *> (node);
+       if (element) {
+               xmlpp::Element::AttributeList attributes = element->get_attributes ();
+               for (list<xmlpp::Attribute*>::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<pair<string, string> >::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::Node>
+cxml::NodePtr
 cxml::Node::node_child (string name) const
 {
-       list<shared_ptr<cxml::Node> > 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::Node>
+cxml::NodePtr
 cxml::Node::optional_node_child (string name) const
 {
-       list<shared_ptr<cxml::Node> > 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<cxml::Node> ();
+               return NodePtr ();
        }
        
        return n.front ();
 }
 
-list<shared_ptr<cxml::Node> >
+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<shared_ptr<cxml::Node> > n;
-       for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) {
-               if ((*i)->get_name() == name) {
-                       n.push_back (shared_ptr<Node> (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<string>
 cxml::Node::optional_string_child (string c) const
 {
-       list<shared_ptr<Node> > 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<const xmlpp::Element *> (_node);
-       if (!e) {
-               throw cxml::Error ("missing attribute");
+       list<pair<string, string> >::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<string>
 cxml::Node::optional_string_attribute (string name) const
 {
-       xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
-       if (!e) {
-               return optional<string> ();
+       list<pair<string, string> >::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<string> ();
        }
 
-       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<xmlpp::Element *> (*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<xmlpp::ContentNode const *> (*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);
+       }
+}
index 8be978f431e05314c0a4c3277c6a0a3300eeb725..0542c4e8d2a869e61b85b5b2f568aa62842a4052 100644 (file)
 #undef check
 #endif
 
-#include <glibmm.h>
-
 namespace xmlpp {
        class Node;
-       class DomParser;
 }
 
 namespace cxml {
@@ -67,21 +64,15 @@ private:
 };
 
 class Node;    
-typedef std::list<boost::shared_ptr<Node> > NodeList;  
+typedef boost::shared_ptr<cxml::Node> NodePtr;
+typedef std::list<NodePtr> NodeList;   
+typedef boost::shared_ptr<const cxml::Node> 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 <class T>
+       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 <class T>
+       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> node_child (std::string) const;
        boost::shared_ptr<Node> 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<Glib::ustring> _taken;
+       std::string _name;
+       std::string _content;
+       std::string _namespace_uri;
+       std::string _namespace_prefix;
+       std::list<std::pair<std::string, std::string> > _attributes;
+       mutable std::list<std::string> _taken;
 };
 
-typedef boost::shared_ptr<cxml::Node> NodePtr;
-typedef boost::shared_ptr<const cxml::Node> 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;
 };
 
 }
index e854aae06b40f842cf6aaf76e4e4fd28f9aba504..10900aa3b984e78e1d784286d7123130c1d01d57 100644 (file)
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <A>
   <B>42</B>
   <C>fred</C>
@@ -5,6 +6,11 @@
   <E>yes</E>
   <F>1</F>
   <F>2</F>
-  <H><I>testing</I><I>more testing</I></H>
-  <J><K>jim</K></J>
+  <H>
+    <I>testing</I>
+    <I>more testing</I>
+  </H>
+  <J>
+    <K>jim</K>
+  </J>
 </A>
index 9d07e7c19a7e6be7772327f344b4d317d6abb01c..32ea830453a8d9cebddd677b101ae182d69b1015 100644 (file)
@@ -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<string> 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<xmlpp::Element*> (*k);
+               xmlpp::Element* test_el = dynamic_cast<xmlpp::Element*> (*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<xmlpp::ContentNode*> (*k);
+               xmlpp::ContentNode* test_cn = dynamic_cast<xmlpp::ContentNode*> (*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<xmlpp::Attribute*> (*k);
+               xmlpp::Attribute* test_at = dynamic_cast<xmlpp::Attribute*> (*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<string> 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<int>("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<int> ("B", 42);
+       document.add_string ("C", "fred");
+       document.add_number<double> ("D", 42.9);
+       document.add_bool ("E", true);
+       document.add_number<int> ("F", 1);
+       document.add_number<int> ("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<string> ());
+}
diff --git a/wscript b/wscript
index f54ff9cbe426334a67358d4f9d9f7f78fc07d501..2d1fd6ae2221a41a874514e89fce1d5d4358dfb1 100644 (file)
--- 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