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