aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2013-03-15 08:22:58 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2013-03-15 08:22:58 +0200
commit927b97baaaf69e318ff7a0ce76d096375ec09da2 (patch)
tree074d633c92c99ec541e7f0631c2fd2a6c5378689
parent6664a24b003f3959e2efe2893628f725a5f6746f (diff)
Add support for returning XML attributes as map
-rw-r--r--cutl/makefile6
-rw-r--r--cutl/xml/parser.cxx112
-rw-r--r--cutl/xml/parser.hxx68
-rw-r--r--cutl/xml/parser.ixx35
-rw-r--r--cutl/xml/parser.txx30
-rw-r--r--cutl/xml/qname.cxx17
-rw-r--r--cutl/xml/qname.hxx5
-rw-r--r--cutl/xml/value-traits.cxx25
-rw-r--r--cutl/xml/value-traits.hxx10
-rw-r--r--cutl/xml/value-traits.txx13
-rw-r--r--tests/xml/parser/driver.cxx53
-rw-r--r--tests/xml/roundtrip/driver.cxx4
12 files changed, 341 insertions, 37 deletions
diff --git a/cutl/makefile b/cutl/makefile
index 61b47eb..61a8419 100644
--- a/cutl/makefile
+++ b/cutl/makefile
@@ -10,7 +10,11 @@ sources += fs/exception.cxx fs/path.cxx fs/auto-remove.cxx
sources += re/re.cxx
-sources += xml/qname.cxx xml/parser.cxx xml/serializer.cxx
+sources += \
+xml/qname.cxx \
+xml/parser.cxx \
+xml/serializer.cxx \
+xml/value-traits.cxx
sources += \
compiler/context.cxx \
diff --git a/cutl/xml/parser.cxx b/cutl/xml/parser.cxx
index 08f08ff..ee62a24 100644
--- a/cutl/xml/parser.cxx
+++ b/cutl/xml/parser.cxx
@@ -92,8 +92,12 @@ namespace cutl
: is_ (is), iname_ (iname), feature_ (f),
depth_ (0), state_ (state_next), event_ (eof), queue_ (eof),
pqname_ (&qname_), pvalue_ (&value_),
- attr_i_ (0), start_ns_i_ (0), end_ns_i_ (0)
+ attr_unhandled_ (0), attr_i_ (0), start_ns_i_ (0), end_ns_i_ (0)
{
+ if ((feature_ & receive_attributes_map) != 0 &&
+ (feature_ & receive_attributes_event) != 0)
+ feature_ &= ~receive_attributes_map;
+
// Allocate the parser. Make sure nothing else can throw after
// this call since otherwise we will leak it.
//
@@ -191,32 +195,56 @@ namespace cutl
istream::iostate old_state_;
};
+ const string& parser::
+ attribute (const qname_type& qn) const
+ {
+ attribute_map::const_iterator i (attr_map_.find (qn));
+
+ if (i != attr_map_.end ())
+ {
+ if (!i->second.handled)
+ {
+ i->second.handled = true;
+ attr_unhandled_--;
+ }
+ return i->second.value;
+ }
+ else
+ throw parsing (*this, "attribute '" + qn.string () + "' expected");
+ }
+
+ string parser::
+ attribute (const qname_type& qn, const string& dv) const
+ {
+ attribute_map::const_iterator i (attr_map_.find (qn));
+
+ if (i != attr_map_.end ())
+ {
+ if (!i->second.handled)
+ {
+ i->second.handled = true;
+ attr_unhandled_--;
+ }
+ return i->second.value;
+ }
+ else
+ return dv;
+ }
+
void parser::
next_expect (event_type e)
{
if (next () != e)
- throw parsing (*this, parser_event_str[e] + string (" expected"));
+ throw parsing (*this, string (parser_event_str[e]) + " expected");
}
void parser::
next_expect (event_type e, const string& ns, const string& n)
{
if (next () != e || namespace_ () != ns || name () != n)
- {
- string m (parser_event_str[e]);
- m += " '";
-
- if (!ns.empty ())
- {
- m += ns;
- m += '#';
- }
-
- m += n;
- m += "' expected";
-
- throw parsing (*this, m);
- }
+ throw parsing (*this,
+ string (parser_event_str[e]) + " '" +
+ qname_type (ns, n).string () + "' expected");
}
parser::event_type parser::
@@ -266,6 +294,29 @@ namespace cutl
parser::event_type parser::
next_body ()
{
+ // If the previous event is start_element and we return attributes
+ // as a map, make sure there are no unhandled attributes left. Also
+ // clear the map.
+ //
+ if (event_ == start_element && (feature_ & receive_attributes_map) != 0)
+ {
+ if (attr_unhandled_ != 0)
+ {
+ // Find the first unhandled attribute and report it.
+ //
+ for (attribute_map::const_iterator i (attr_map_.begin ());
+ i != attr_map_.end (); ++i)
+ {
+ if (!i->second.handled)
+ throw parsing (
+ *this, "unexpected attribute '" + i->first.string () + "'");
+ }
+ assert (false);
+ }
+
+ attr_map_.clear ();
+ }
+
// See if we have any start namespace declarations we need to return.
//
if (start_ns_i_ < start_ns_.size ())
@@ -299,7 +350,7 @@ namespace cutl
}
}
- // See if we have any attributes we need to return.
+ // See if we have any attributes we need to return as events.
//
if (attr_i_ < attr_.size ())
{
@@ -525,14 +576,31 @@ namespace cutl
// Handle attributes.
//
- if ((p.feature_ & receive_attributes) != 0)
+ bool am ((p.feature_ & receive_attributes_map) != 0);
+ bool ae ((p.feature_ & receive_attributes_event) != 0);
+ if (am || ae)
{
for (; *atts != 0; atts += 2)
{
- p.attr_.push_back (attribute ());
- split_name (*atts, p.attr_.back ().qname);
- p.attr_.back ().value = *(atts + 1);
+ if (am)
+ {
+ qname_type qn;
+ split_name (*atts, qn);
+ attribute_map::value_type v (qn, attribute_value ());
+ v.second.value = *(atts + 1);
+ v.second.handled = false;
+ p.attr_map_.insert (v);
+ }
+ else
+ {
+ p.attr_.push_back (attribute_type ());
+ split_name (*atts, p.attr_.back ().qname);
+ p.attr_.back ().value = *(atts + 1);
+ }
}
+
+ if (am)
+ p.attr_unhandled_ = p.attr_map_.size ();
}
XML_StopParser (p.p_, true);
diff --git a/cutl/xml/parser.hxx b/cutl/xml/parser.hxx
index 5d1e9e5..c84268a 100644
--- a/cutl/xml/parser.hxx
+++ b/cutl/xml/parser.hxx
@@ -5,8 +5,9 @@
#ifndef CUTL_XML_PARSER_HXX
#define CUTL_XML_PARSER_HXX
-#include <string>
+#include <map>
#include <vector>
+#include <string>
#include <iosfwd>
#include <cstddef> // std::size_t
@@ -82,14 +83,18 @@ namespace cutl
typedef xml::qname qname_type;
typedef unsigned short feature_type;
+ // If both receive_attributes_event and receive_attributes_map are
+ // specified, then receive_attributes_event is assumed.
+ //
static const feature_type receive_elements = 0x0001;
static const feature_type receive_characters = 0x0002;
- static const feature_type receive_attributes = 0x0004;
- static const feature_type receive_namespace_decls = 0x0008;
+ static const feature_type receive_attributes_map = 0x0004;
+ static const feature_type receive_attributes_event = 0x0008;
+ static const feature_type receive_namespace_decls = 0x0010;
static const feature_type receive_default = receive_elements |
receive_characters |
- receive_attributes;
+ receive_attributes_map;
// Parse std::istream. Input name is used in diagnostics to identify
// the document being parsed. std::ios_base::failure exception is
@@ -178,6 +183,42 @@ namespace cutl
unsigned long long line () const {return line_;}
unsigned long long column () const {return column_;}
+ // Attribute map lookup. If attribute is not found, then the version
+ // without the default value thows an appropriate parsing exception
+ // while the version with the default value returns that value.
+ //
+ // Note also that there is no attribute(ns,name) version since it
+ // would conflict with attribute(name,dv) (qualified attributes
+ // are not very common).
+ //
+ const std::string&
+ attribute (const std::string& name) const;
+
+ template <typename T>
+ T
+ attribute (const std::string& name) const;
+
+ std::string
+ attribute (const std::string& name, const std::string& dv) const;
+
+ template <typename T>
+ T
+ attribute (const std::string& name, const T& dv) const;
+
+ const std::string&
+ attribute (const qname_type& qname) const;
+
+ template <typename T>
+ T
+ attribute (const qname_type& qname) const;
+
+ std::string
+ attribute (const qname_type& qname, const std::string& dv) const;
+
+ template <typename T>
+ T
+ attribute (const qname_type& qname, const T& dv) const;
+
// Optional content processing.
//
public:
@@ -255,15 +296,27 @@ namespace cutl
unsigned long long line_;
unsigned long long column_;
- // Attributes.
+ // Attributes as a map.
+ //
+ struct attribute_value
+ {
+ std::string value;
+ mutable bool handled;
+ };
+
+ typedef std::map<qname_type, attribute_value> attribute_map;
+ attribute_map attr_map_;
+ mutable attribute_map::size_type attr_unhandled_;
+
+ // Attributes as events.
//
- struct attribute
+ struct attribute_type
{
qname_type qname;
std::string value;
};
- typedef std::vector<attribute> attributes;
+ typedef std::vector<attribute_type> attributes;
attributes attr_;
attributes::size_type attr_i_; // Index of the current attribute.
@@ -299,5 +352,6 @@ namespace cutl
}
#include <cutl/xml/parser.ixx>
+#include <cutl/xml/parser.txx>
#endif // CUTL_XML_PARSER_HXX
diff --git a/cutl/xml/parser.ixx b/cutl/xml/parser.ixx
index 61bb05b..fa5a7b6 100644
--- a/cutl/xml/parser.ixx
+++ b/cutl/xml/parser.ixx
@@ -2,10 +2,45 @@
// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC
// license : MIT; see accompanying LICENSE file
+#include <cutl/xml/value-traits.hxx>
+
namespace cutl
{
namespace xml
{
+ inline const std::string& parser::
+ attribute (const std::string& n) const
+ {
+ return attribute (qname_type (n));
+ }
+
+ template <typename T>
+ inline T parser::
+ attribute (const std::string& n) const
+ {
+ return attribute<T> (qname_type (n));
+ }
+
+ inline std::string parser::
+ attribute (const std::string& n, const std::string& dv) const
+ {
+ return attribute (qname_type (n), dv);
+ }
+
+ template <typename T>
+ inline T parser::
+ attribute (const std::string& n, const T& dv) const
+ {
+ return attribute<T> (qname_type (n), dv);
+ }
+
+ template <typename T>
+ inline T parser::
+ attribute (const qname_type& qn) const
+ {
+ return value_traits<T>::parse (attribute (qn), *this);
+ }
+
inline void parser::
next_expect (event_type e, const qname_type& qn)
{
diff --git a/cutl/xml/parser.txx b/cutl/xml/parser.txx
new file mode 100644
index 0000000..cf27f2c
--- /dev/null
+++ b/cutl/xml/parser.txx
@@ -0,0 +1,30 @@
+// file : cutl/xml/parser.txx
+// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <cutl/xml/value-traits.hxx>
+
+namespace cutl
+{
+ namespace xml
+ {
+ template <typename T>
+ T parser::
+ attribute (const qname_type& qn, const T& dv) const
+ {
+ attribute_map::const_iterator i (attr_map_.find (qn));
+
+ if (i != attr_map_.end ())
+ {
+ if (!i->second.handled)
+ {
+ i->second.handled = true;
+ attr_unhandled_--;
+ }
+ return value_traits<T>::parse (i->second.value, *this);
+ }
+ else
+ return dv;
+ }
+ }
+}
diff --git a/cutl/xml/qname.cxx b/cutl/xml/qname.cxx
index ce7cca1..a32add2 100644
--- a/cutl/xml/qname.cxx
+++ b/cutl/xml/qname.cxx
@@ -12,11 +12,24 @@ namespace cutl
{
namespace xml
{
+ string qname::
+ string () const
+ {
+ std::string r;
+ if (!ns_.empty ())
+ {
+ r += ns_;
+ r += '#';
+ }
+
+ r += name_;
+ return r;
+ }
+
ostream&
operator<< (ostream& os, const qname& qn)
{
- const string& ns (qn.namespace_ ());
- return os << ns << (ns.empty () ? "" : "#") << qn.name ();
+ return os << qn.string ();
}
}
}
diff --git a/cutl/xml/qname.hxx b/cutl/xml/qname.hxx
index ab1c8ea..0964705 100644
--- a/cutl/xml/qname.hxx
+++ b/cutl/xml/qname.hxx
@@ -38,6 +38,11 @@ namespace cutl
std::string& name () {return name_;}
std::string& prefix () {return prefix_;}
+ // Printable representation in the [<namespace>#]<name> form.
+ //
+ std::string
+ string () const;
+
// Note that comparison operators
//
public:
diff --git a/cutl/xml/value-traits.cxx b/cutl/xml/value-traits.cxx
new file mode 100644
index 0000000..7598645
--- /dev/null
+++ b/cutl/xml/value-traits.cxx
@@ -0,0 +1,25 @@
+// file : cutl/xml/value-traits.cxx
+// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC
+// license : MIT; see accompanying LICENSE file
+
+#include <cutl/xml/parser.hxx>
+#include <cutl/xml/serializer.hxx>
+
+using namespace std;
+
+namespace cutl
+{
+ namespace xml
+ {
+ bool default_value_traits<bool>::
+ parse (string s, const parser& p)
+ {
+ if (s == "true" || s == "1" || s == "True" || s == "TRUE")
+ return true;
+ else if (s == "false" || s == "0" || s == "False" || s == "FALSE")
+ return false;
+ else
+ throw parsing (p, "invalid bool value '" + s + "'");
+ }
+ }
+}
diff --git a/cutl/xml/value-traits.hxx b/cutl/xml/value-traits.hxx
index f79f67f..0b95205 100644
--- a/cutl/xml/value-traits.hxx
+++ b/cutl/xml/value-traits.hxx
@@ -6,6 +6,7 @@
#define CUTL_XML_VALUE_TRAITS_HXX
#include <string>
+#include <cstddef> // std::size_t
#include <cutl/details/export.hxx>
@@ -19,6 +20,9 @@ namespace cutl
template <typename T>
struct default_value_traits
{
+ static T
+ parse (std::string, const parser&);
+
static std::string
serialize (const T&, const serializer&);
};
@@ -26,6 +30,9 @@ namespace cutl
template <>
struct LIBCUTL_EXPORT default_value_traits<bool>
{
+ static bool
+ parse (std::string, const parser&);
+
static std::string
serialize (bool v, const serializer&)
{
@@ -35,6 +42,9 @@ namespace cutl
template <typename T>
struct value_traits: default_value_traits<T> {};
+
+ template <typename T, std::size_t N>
+ struct value_traits<T[N]>: default_value_traits<const T*> {};
}
}
diff --git a/cutl/xml/value-traits.txx b/cutl/xml/value-traits.txx
index 000d6be..4868dba 100644
--- a/cutl/xml/value-traits.txx
+++ b/cutl/xml/value-traits.txx
@@ -12,12 +12,23 @@ namespace cutl
namespace xml
{
template <typename T>
+ T default_value_traits<T>::
+ parse (std::string s, const parser& p)
+ {
+ T r;
+ std::istringstream is (s);
+ if (!(is >> r && is.eof ()) )
+ throw parsing (p, "invalid value '" + s + "'");
+ return r;
+ }
+
+ template <typename T>
std::string default_value_traits<T>::
serialize (const T& v, const serializer& s)
{
std::ostringstream os;
if (!(os << v))
- throw serialization (s.output_name (), "invalid value");
+ throw serialization (s, "invalid value");
return os.str ();
}
}
diff --git a/tests/xml/parser/driver.cxx b/tests/xml/parser/driver.cxx
index 787da50..4c85b77 100644
--- a/tests/xml/parser/driver.cxx
+++ b/tests/xml/parser/driver.cxx
@@ -81,11 +81,56 @@ main ()
// cerr << e.what () << endl;
}
+ // Test attribute maps.
+ //
+ {
+ istringstream is ("<root a='a' b='b' d='123' t='true'/>");
+ parser p (is, "test");
+ p.next_expect (parser::start_element, "root");
+
+ assert (p.attribute ("a") == "a");
+ assert (p.attribute ("b", "B") == "b");
+ assert (p.attribute ("c", "C") == "C");
+ assert (p.attribute<int> ("d") == 123);
+ assert (p.attribute<bool> ("t") == true);
+ assert (p.attribute ("f", false) == false);
+
+ p.next_expect (parser::end_element);
+ }
+
+ try
+ {
+ istringstream is ("<root a='a' b='b'/>");
+ parser p (is, "test");
+ p.next_expect (parser::start_element, "root");
+ assert (p.attribute ("a") == "a");
+ p.next_expect (parser::end_element);
+ assert (false);
+ }
+ catch (const xml::exception& e)
+ {
+ // cerr << e.what () << endl;
+ }
+
+ try
+ {
+ istringstream is ("<root a='abc'/>");
+ parser p (is, "test");
+ p.next_expect (parser::start_element, "root");
+ p.attribute<int> ("a");
+ assert (false);
+ }
+ catch (const xml::exception& e)
+ {
+ // cerr << e.what () << endl;
+ }
+
// Test peeking and getting the current event.
//
{
istringstream is ("<root x='x'>x<nested/></root>");
- parser p (is, "peek");
+ parser p (is, "peek",
+ parser::receive_default | parser::receive_attributes_event);
assert (p.event () == parser::eof);
@@ -133,7 +178,8 @@ main ()
//
{
istringstream is ("<root x=' x '> \n\t </root>");
- parser p (is, "empty");
+ parser p (is, "empty",
+ parser::receive_default | parser::receive_attributes_event);
assert (p.next () == parser::start_element);
p.content (parser::empty);
@@ -197,7 +243,8 @@ main ()
" <inner> X </inner>\n"
" </nested>\n"
"</root>\n");
- parser p (is, "complex");
+ parser p (is, "complex",
+ parser::receive_default | parser::receive_attributes_event);
assert (p.next () == parser::start_element); // root
p.content (parser::complex);
diff --git a/tests/xml/roundtrip/driver.cxx b/tests/xml/roundtrip/driver.cxx
index ec8cebe..b0b6497 100644
--- a/tests/xml/roundtrip/driver.cxx
+++ b/tests/xml/roundtrip/driver.cxx
@@ -33,7 +33,9 @@ main (int argc, char* argv[])
parser p (ifs,
argv[1],
- parser::receive_default | parser::receive_namespace_decls);
+ parser::receive_default |
+ parser::receive_attributes_event |
+ parser::receive_namespace_decls);
serializer s (cout, "out", 0);