Optionally (with --c++17) use libxml++-4.0 and C++17.
[libcxml.git] / src / cxml.h
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libcxml.
5
6     libcxml is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libcxml is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libcxml.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #ifndef LIBCXML_CXML_H
22 #define LIBCXML_CXML_H
23
24 #include <glibmm/ustring.h>
25 #include <boost/optional.hpp>
26 #include <boost/filesystem.hpp>
27 #include <boost/algorithm/string/erase.hpp>
28 #include <exception>
29 #include <memory>
30 #include <string>
31 #include <vector>
32
33 /* Hack for OS X compile failure; see https://bugs.launchpad.net/hugin/+bug/910160 */
34 #ifdef check
35 #undef check
36 #endif
37
38 namespace xmlpp {
39         class DomParser;
40         class Element;
41         class Node;
42 }
43
44 namespace cxml {
45
46 /** @brief An error */
47 class Error : public std::exception
48 {
49 public:
50         /** Construct an Error exception.
51          *  @param message Error message.
52          */
53         Error (std::string const & message) : _message (message) {}
54
55         /** Error destructor */
56         ~Error () throw () {}
57
58         /** @return error message.  Caller must not free the returned
59          *  value.
60          */
61         char const * what () const noexcept override {
62                 return _message.c_str ();
63         }
64
65 private:
66         /** error message */
67         std::string _message;
68 };
69
70 /** A sort-of version of boost::lexical_cast that does uses the "C"
71  *  locale (i.e. no thousands separators and a . for the decimal separator).
72  */
73 template <typename P, typename Q>
74 P
75 raw_convert (Q)
76 {
77         /* We can't write a generic version of raw_convert; all required
78            versions must be specialised.
79         */
80         BOOST_STATIC_ASSERT (sizeof(Q) == 0);
81 }
82
83 template <>
84 int
85 raw_convert (std::string v);
86
87 template <>
88 unsigned int
89 raw_convert (std::string v);
90
91 template <>
92 long int
93 raw_convert (std::string v);
94
95 template <>
96 long unsigned int
97 raw_convert (std::string v);
98
99 template <>
100 long long
101 raw_convert (std::string v);
102
103 template <>
104 long long unsigned
105 raw_convert (std::string v);
106
107 template <>
108 float
109 raw_convert (std::string v);
110
111 template <>
112 double
113 raw_convert (std::string v);
114
115 /** @brief A wrapper for a xmlpp::Node which simplifies parsing */
116 class Node
117 {
118 public:
119         Node ();
120
121         /** Construct a Node from an xmlpp::Node.  This class will
122          *  not destroy the xmlpp::Node.
123          *  @param node xmlpp::Node.
124          */
125         Node (xmlpp::Node* node);
126
127         std::string name () const;
128
129         /* A set of methods which look up a child of this node by
130          * its name, and return its contents as some type or other.
131          *
132          * If, for example, this object has been created with
133          * a node named "Fred", we might have the following XML:
134          *
135          * <Fred>
136          *   <Jim>42</Jim>
137          * </Fred>
138          *
139          * string_child ("Jim") would return "42"
140          * number_child<int64_t> ("Jim") would return 42.
141          * ...and so on.
142          *
143          * The methods not marked "optional" will throw an exception
144          * if the child node is not present.  The "optional" methods
145          * will return an empty boost::optional<> in that case.
146          *
147          * All methods will also throw an exception if there is more
148          * than one of the specified child node.
149          */
150
151         std::string string_child (std::string c) const;
152         boost::optional<std::string> optional_string_child (std::string) const;
153
154         bool bool_child (std::string) const;
155         boost::optional<bool> optional_bool_child (std::string) const;
156
157         template <class T>
158         T number_child (std::string c) const
159         {
160                 auto s = string_child (c);
161                 boost::erase_all (s, " ");
162                 return raw_convert<T> (s);
163         }
164
165         template <class T>
166         boost::optional<T> optional_number_child (std::string c) const
167         {
168                 auto s = optional_string_child (c);
169                 if (!s) {
170                         return {};
171                 }
172
173                 auto t = s.get ();
174                 boost::erase_all (t, " ");
175                 return raw_convert<T> (t);
176         }
177
178         /** This will mark a child as to be ignored when calling done() */
179         void ignore_child (std::string) const;
180
181         /** Check whether all children of this Node have been looked up
182          *  or passed to ignore_child().  If not, an exception is thrown.
183          */
184         void done () const;
185
186         /* These methods look for an attribute of this node, in the
187          * same way as the child methods do.
188          */
189
190         std::string string_attribute (std::string) const;
191         boost::optional<std::string> optional_string_attribute (std::string) const;
192
193         bool bool_attribute (std::string) const;
194         boost::optional<bool> optional_bool_attribute (std::string) const;
195
196         template <class T>
197         T number_attribute (std::string c) const
198         {
199                 std::string s = string_attribute (c);
200                 boost::erase_all (s, " ");
201                 return raw_convert<T> (s);
202         }
203
204         template <class T>
205         boost::optional<T> optional_number_attribute (std::string c) const
206         {
207                 auto s = optional_string_attribute (c);
208                 if (!s) {
209                         return boost::optional<T> ();
210                 }
211
212                 auto t = s.get ();
213                 boost::erase_all (t, " ");
214                 return raw_convert<T> (t);
215         }
216
217         /** @return The text content of this node (excluding comments or CDATA) */
218         std::string content () const;
219
220         /** @return namespace URI of this node */
221         std::string namespace_uri () const;
222
223         /** @return namespace prefix of this node */
224         std::string namespace_prefix () const;
225
226         std::shared_ptr<Node> node_child (std::string) const;
227         std::shared_ptr<Node> optional_node_child (std::string) const;
228
229         std::vector<std::shared_ptr<Node>> node_children () const;
230         std::vector<std::shared_ptr<Node>> node_children (std::string) const;
231
232         xmlpp::Node* node () const {
233                 return _node;
234         }
235
236         bool is_text() const;
237
238 protected:
239         xmlpp::Node* _node;
240
241 private:
242         mutable std::vector<Glib::ustring> _taken;
243 };
244
245 typedef std::shared_ptr<cxml::Node> NodePtr;
246 typedef std::shared_ptr<const cxml::Node> ConstNodePtr;
247
248 class Document : public Node
249 {
250 public:
251         Document ();
252         explicit Document(std::string root_name);
253         Document (std::string root_name, boost::filesystem::path);
254
255         Document (Document const&) = delete;
256         Document& operator= (Document const&) = delete;
257
258         virtual ~Document ();
259
260         void read_file (boost::filesystem::path);
261         void read_string (std::string);
262
263         std::string root_name () const {
264                 return _root_name;
265         }
266
267 private:
268         void take_root_node ();
269
270         xmlpp::DomParser* _parser;
271         std::string _root_name;
272 };
273
274
275 xmlpp::Element* add_child(xmlpp::Element* parent, std::string const& name, std::string const& ns_prefix = {});
276 void add_text_child(xmlpp::Element* parent, std::string const& name, std::string const& text);
277
278
279 }
280
281 #endif