diff options
| author | Carl Hetherington <cth@carlh.net> | 2013-01-13 23:55:59 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2013-01-13 23:55:59 +0000 |
| commit | d14bbc2088ed9c0f9d77f15cb943968a70bb8198 (patch) | |
| tree | 5c1c766d3e82de3139710d76618293d610145f63 | |
Initial.
| -rw-r--r-- | libcxml.pc.in | 10 | ||||
| -rwxr-xr-x | run-tests.sh | 8 | ||||
| -rw-r--r-- | src/cxml.cc | 200 | ||||
| -rw-r--r-- | src/cxml.h | 170 | ||||
| -rw-r--r-- | src/wscript | 9 | ||||
| -rw-r--r-- | test/ref/a.xml | 8 | ||||
| -rw-r--r-- | test/tests.cc | 64 | ||||
| -rw-r--r-- | test/wscript | 20 | ||||
| -rwxr-xr-x | waf | bin | 0 -> 87683 bytes | |||
| -rw-r--r-- | wscript | 33 |
10 files changed, 522 insertions, 0 deletions
diff --git a/libcxml.pc.in b/libcxml.pc.in new file mode 100644 index 0000000..ebf8bbf --- /dev/null +++ b/libcxml.pc.in @@ -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 index 0000000..fda9bd3 --- /dev/null +++ b/run-tests.sh @@ -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 index 0000000..2a755bf --- /dev/null +++ b/src/cxml.cc @@ -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 index 0000000..bf2a2d3 --- /dev/null +++ b/src/cxml.h @@ -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 index 0000000..e1c405a --- /dev/null +++ b/src/wscript @@ -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 index 0000000..0a37114 --- /dev/null +++ b/test/ref/a.xml @@ -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 index 0000000..21466ca --- /dev/null +++ b/test/tests.cc @@ -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 index 0000000..8868a36 --- /dev/null +++ b/test/wscript @@ -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 = '' Binary files differ@@ -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') |
