Initial.
authorCarl Hetherington <cth@carlh.net>
Sun, 13 Jan 2013 23:55:59 +0000 (23:55 +0000)
committerCarl Hetherington <cth@carlh.net>
Sun, 13 Jan 2013 23:55:59 +0000 (23:55 +0000)
libcxml.pc.in [new file with mode: 0644]
run-tests.sh [new file with mode: 0755]
src/cxml.cc [new file with mode: 0644]
src/cxml.h [new file with mode: 0644]
src/wscript [new file with mode: 0644]
test/ref/a.xml [new file with mode: 0644]
test/tests.cc [new file with mode: 0644]
test/wscript [new file with mode: 0644]
waf [new file with mode: 0755]
wscript [new file with mode: 0644]

diff --git a/libcxml.pc.in b/libcxml.pc.in
new file mode 100644 (file)
index 0000000..ebf8bbf
--- /dev/null
@@ -0,0 +1,10 @@
+prefix=@prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libcxml
+Description: Library to simplify XML parsing with libxml++
+Version: @version@
+Requires: libxml++-2.6
+Libs: @libs@
+Cflags: -I${includedir}
diff --git a/run-tests.sh b/run-tests.sh
new file mode 100755 (executable)
index 0000000..fda9bd3
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/bash -e
+
+if [ "$1" == "--debug" ]; then
+  shift
+  LD_LIBRARY_PATH=build/src:build/asdcplib/src gdb --args build/test/tests
+else
+  LD_LIBRARY_PATH=build/src:build/asdcplib/src build/test/tests
+fi
diff --git a/src/cxml.cc b/src/cxml.cc
new file mode 100644 (file)
index 0000000..2a755bf
--- /dev/null
@@ -0,0 +1,200 @@
+#include <sstream>
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
+#include <libxml++/libxml++.h>
+#include "cxml.h"
+
+using namespace std;
+using namespace boost;
+
+cxml::Node::Node ()
+       : _node (0)
+{
+
+}
+
+cxml::Node::Node (xmlpp::Node const * node)
+       : _node (node)
+{
+
+}
+
+shared_ptr<cxml::Node>
+cxml::Node::node_child (string name)
+{
+       list<shared_ptr<cxml::Node> > 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());
+       }
+       
+       return n.front ();
+}
+
+list<shared_ptr<cxml::Node> >
+cxml::Node::node_children (string name)
+{
+       /* XXX: using find / get_path should work here, but I can't follow
+          how get_path works.
+       */
+
+       xmlpp::Node::NodeList c = _node->get_children ();
+       
+       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)));
+               }
+       }
+       
+       _taken.push_back (name);
+       return n;
+}
+
+string
+cxml::Node::string_child (string c)
+{
+       return node_child(c)->content ();
+}
+
+optional<string>
+cxml::Node::optional_string_child (string c)
+{
+       list<shared_ptr<Node> > nodes = node_children (c);
+       if (nodes.size() > 1) {
+               throw cxml::Error ("duplicate XML tag " + c);
+       }
+
+       if (nodes.empty ()) {
+               return optional<string> ();
+       }
+
+       return nodes.front()->content();
+}
+
+bool
+cxml::Node::bool_child (string c)
+{
+       string const s = string_child (c);
+       return (s == "1" || s == "yes");
+}
+
+optional<bool>
+cxml::Node::optional_bool_child (string c)
+{
+       optional<string> s = optional_string_child (c);
+       if (!s) {
+               return optional<bool> ();
+       }
+       
+       return (s.get() == "1" || s.get() == "yes");
+}
+
+void
+cxml::Node::ignore_child (string name)
+{
+       _taken.push_back (name);
+}
+
+string
+cxml::Node::string_attribute (string name)
+{
+       xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
+       if (!e) {
+               throw cxml::Error ("missing attribute");
+       }
+       
+       xmlpp::Attribute* a = e->get_attribute (name);
+       if (!a) {
+               throw cxml::Error ("missing attribute");
+       }
+
+       return a->get_value ();
+}
+
+optional<string>
+cxml::Node::optional_string_attribute (string name)
+{
+       xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
+       if (!e) {
+               return optional<string> ();
+       }
+       
+       xmlpp::Attribute* a = e->get_attribute (name);
+       if (!a) {
+               return optional<string> ();
+       }
+
+       return string (a->get_value ());
+}
+
+bool
+cxml::Node::bool_attribute (string name)
+{
+       string const s = string_attribute (name);
+       return (s == "1" || s == "yes");
+}
+
+optional<bool>
+cxml::Node::optional_bool_attribute (string name)
+{
+       optional<string> s = optional_string_attribute (name);
+       if (!s) {
+               return optional<bool> ();
+       }
+
+       return (s.get() == "1" || s.get() == "yes");
+}
+
+void
+cxml::Node::done ()
+{
+       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());
+               }
+       }
+}
+
+string
+cxml::Node::content ()
+{
+       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;
+}
+
+cxml::File::File (string file, string root_name)
+{
+       if (!filesystem::exists (file)) {
+               throw cxml::Error ("XML file does not exist");
+       }
+       
+       _parser = new xmlpp::DomParser;
+       _parser->parse_file (file);
+       if (!_parser) {
+               throw cxml::Error ("could not parse XML");
+       }
+
+       _node = _parser->get_document()->get_root_node ();
+       if (_node->get_name() != root_name) {
+               throw cxml::Error ("unrecognised root node");
+       }
+}
+
+cxml::File::~File ()
+{
+       delete _parser;
+}
diff --git a/src/cxml.h b/src/cxml.h
new file mode 100644 (file)
index 0000000..bf2a2d3
--- /dev/null
@@ -0,0 +1,170 @@
+#ifndef LIBCXML_CXML_H
+#define LIBCXML_CXML_H
+
+#include <string>
+#include <list>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/erase.hpp>
+
+namespace xmlpp {
+       class Node;
+       class DomParser;
+}
+
+namespace cxml {
+
+/** @brief An error */
+class Error : public std::exception
+{
+public:
+       /** Construct an Error exception.
+        *  @param message Error message.
+        */
+       Error (std::string const & message) : _message (message) {}
+
+       /** Error destructor */
+       ~Error () throw () {}
+
+       /** @return error message.  Caller must not free the returned
+        *  value.
+        */
+       char const * what () const throw () {
+               return _message.c_str ();
+       }
+
+private:
+       /** error message */
+       std::string _message;
+};
+
+/** @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 const * node);
+
+       /* A set of methods which look up a child of this node by
+        * its name, and return its contents as some type or other.
+        *
+        * If, for example, this object has been created with
+        * a node named "Fred", we might have the following XML:
+        *
+        * <Fred>
+        *   <Jim>42</Jim>
+        * </Fred>
+        *
+        * string_child ("Jim") would return "42"
+        * numerical_child<int64_t> ("Jim") would return 42.
+        * ...and so on.
+        *
+        * The methods not marked "optional" will throw an exception
+        * if the child node is not present.  The "optional" methods
+        * will return an empty boost::optional<> in that case.
+        *
+        * All methods will also throw an exception if there is more
+        * than one of the specified child node.
+        */
+
+       std::string string_child (std::string c);
+       boost::optional<std::string> optional_string_child (std::string);
+
+       bool bool_child (std::string);
+       boost::optional<bool> optional_bool_child (std::string);
+
+       template <class T>
+       T numerical_child (std::string c)
+       {
+               std::string s = string_child (c);
+               boost::erase_all (s, " ");
+               return boost::lexical_cast<T> (s);
+       }
+
+       template <class T>
+       boost::optional<T> optional_numerical_child (std::string c)
+       {
+               boost::optional<std::string> s = optional_string_child (c);
+               if (!s) {
+                       return boost::optional<T> ();
+               }
+
+               std::string t = s.get ();
+               boost::erase_all (t, " ");
+               return boost::optional<T> (boost::lexical_cast<T> (t));
+       }
+               
+       /** This will mark a child as to be ignored when calling done() */
+       void ignore_child (std::string);
+
+       /** Check whether all children of this Node have been looked up
+        *  or passed to ignore_child().  If not, an exception is thrown.
+        */
+       void done ();
+
+       /* These methods look for an attribute of this node, in the
+        * same way as the child methods do.
+        */
+
+       std::string string_attribute (std::string);
+       boost::optional<std::string> optional_string_attribute (std::string);
+
+       bool bool_attribute (std::string);
+       boost::optional<bool> optional_bool_attribute (std::string);
+
+       template <class T>
+       T numerical_attribute (std::string c)
+       {
+               std::string s = string_attribute (c);
+               boost::erase_all (s, " ");
+               return boost::lexical_cast<T> (s);
+       }
+
+       template <class T>
+       boost::optional<T> optional_numerical_attribute (std::string c)
+       {
+               boost::optional<std::string> s = optional_string_attribute (c);
+               if (!s) {
+                       return boost::optional<T> ();
+               }
+
+               std::string t = s.get ();
+               boost::erase_all (t, " ");
+               return boost::optional<T> (boost::lexical_cast<T> (t));
+       }
+
+       /** @return The content of this node */
+       std::string content ();
+
+       boost::shared_ptr<Node> node_child (std::string);
+       boost::shared_ptr<Node> optional_node_child (std::string);
+
+       std::list<boost::shared_ptr<Node> > node_children (std::string);
+       
+protected:
+       xmlpp::Node const * _node;
+       
+private:
+       std::list<Glib::ustring> _taken;
+};
+
+class File : public Node
+{
+public:
+       File (std::string file, std::string root_name);
+       virtual ~File ();
+
+private:
+       xmlpp::DomParser* _parser;
+};
+
+}
+
+#endif
diff --git a/src/wscript b/src/wscript
new file mode 100644 (file)
index 0000000..e1c405a
--- /dev/null
@@ -0,0 +1,9 @@
+def build(bld):
+    obj = bld(features = 'cxx cxxshlib')
+    obj.name = 'libcxml'
+    obj.target = 'cxml'
+    obj.export_includes = ['.']
+    obj.uselib = 'LIBXML++ BOOST_FILESYSTEM'
+    obj.source = "cxml.cc"
+
+    bld.install_files('${PREFIX}/include/libcxml', "cxml.h")
diff --git a/test/ref/a.xml b/test/ref/a.xml
new file mode 100644 (file)
index 0000000..0a37114
--- /dev/null
@@ -0,0 +1,8 @@
+<A>
+  <B>42</B>
+  <C>fred</C>
+  <D>42.9</D>
+  <E>yes</E>
+  <F>1</F>
+  <F>2</F>
+</A>
diff --git a/test/tests.cc b/test/tests.cc
new file mode 100644 (file)
index 0000000..21466ca
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <cmath>
+#include <boost/filesystem.hpp>
+#include <libxml++/libxml++.h>
+#include "cxml.h"
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE libdcp_test
+#include <boost/test/unit_test.hpp>
+
+using std::string;
+using std::cout;
+using std::vector;
+using std::list;
+using boost::shared_ptr;
+
+BOOST_AUTO_TEST_CASE (test)
+{
+       cxml::File file ("test/ref/a.xml", "A");
+
+       BOOST_CHECK_EQUAL (file.string_child("B"), "42");
+       BOOST_CHECK_EQUAL (file.numerical_child<int>("B"), 42);
+       BOOST_CHECK_EQUAL (file.numerical_child<float>("B"), 42);
+       BOOST_CHECK_EQUAL (file.string_child("C"), "fred");
+       BOOST_CHECK_EQUAL (file.numerical_child<double>("D"), 42.9);
+       BOOST_CHECK_EQUAL (file.string_child("E"), "yes");
+       BOOST_CHECK_EQUAL (file.bool_child("E"), true);
+       BOOST_CHECK_THROW (file.bool_child("F"), cxml::Error);
+
+       BOOST_CHECK (file.optional_string_child("B"));
+       BOOST_CHECK_EQUAL (file.optional_string_child("B").get(), "42");
+       BOOST_CHECK (file.optional_numerical_child<int>("B"));
+       BOOST_CHECK_EQUAL (file.optional_numerical_child<int>("B").get(), 42);
+       BOOST_CHECK (file.optional_numerical_child<float>("B"));
+       BOOST_CHECK_EQUAL (file.optional_numerical_child<float>("B").get(), 42);
+       BOOST_CHECK (file.optional_string_child("C"));
+       BOOST_CHECK_EQUAL (file.optional_string_child("C").get(), "fred");
+       BOOST_CHECK (file.optional_numerical_child<double>("D"));
+       BOOST_CHECK_EQUAL (file.optional_numerical_child<double>("D").get(), 42.9);
+       BOOST_CHECK (file.optional_string_child("E"));
+       BOOST_CHECK_EQUAL (file.optional_string_child("E").get(), "yes");
+       BOOST_CHECK (file.optional_bool_child("E"));
+       BOOST_CHECK_EQUAL (file.optional_bool_child("E").get(), true);
+       BOOST_CHECK_THROW (file.optional_bool_child("F"), cxml::Error);
+       BOOST_CHECK (!file.optional_bool_child("G"));
+}
diff --git a/test/wscript b/test/wscript
new file mode 100644 (file)
index 0000000..8868a36
--- /dev/null
@@ -0,0 +1,20 @@
+def configure(conf):
+    conf.check_cxx(fragment = """
+                              #define BOOST_TEST_MODULE Config test\n
+                             #include <boost/test/unit_test.hpp>\n
+                              int main() {}
+                              """,
+                              msg = 'Checking for boost unit testing library',
+                              lib = ['boost_unit_test_framework', 'boost_system'],
+                              uselib_store = 'BOOST_TEST')
+
+    conf.env.prepend_value('LINKFLAGS', '-Lsrc')
+
+def build(bld):
+    obj = bld(features = 'cxx cxxprogram')
+    obj.name   = 'tests'
+    obj.uselib = 'BOOST_TEST BOOST_FILESYSTEM'
+    obj.use    = 'libcxml'
+    obj.source = 'tests.cc'
+    obj.target = 'tests'
+    obj.install_path = ''
diff --git a/waf b/waf
new file mode 100755 (executable)
index 0000000..178461f
Binary files /dev/null and b/waf differ
diff --git a/wscript b/wscript
new file mode 100644 (file)
index 0000000..f1e0dd3
--- /dev/null
+++ b/wscript
@@ -0,0 +1,33 @@
+APPNAME = 'libcxml'
+VERSION = '0.01'
+
+def options(opt):
+    opt.load('compiler_cxx')
+
+def configure(conf):
+    conf.load('compiler_cxx')
+    conf.env.append_value('CXXFLAGS', ['-Wall', '-Wextra', '-O2'])
+
+    conf.check_cfg(package = 'libxml++-2.6', args = '--cflags --libs', uselib_store = 'LIBXML++', mandatory = True)
+
+    conf.check_cxx(fragment = """
+                  #include <boost/filesystem.hpp>\n
+                  int main() { boost::filesystem::copy_file ("a", "b"); }\n
+                  """,
+                   msg = 'Checking for boost filesystem library',
+                   libpath = '/usr/local/lib',
+                   lib = ['boost_filesystem', 'boost_system'],
+                   uselib_store = 'BOOST_FILESYSTEM')
+
+    conf.recurse('test')
+
+def build(bld):
+
+    bld(source = 'libcxml.pc.in',
+        version = VERSION,
+        includedir = '%s/include' % bld.env.PREFIX,
+        libs = "-L${libdir}",
+        install_path = '${LIBDIR}/pkgconfig')
+
+    bld.recurse('src')
+    bld.recurse('test')