Use return {}.
[libcxml.git] / src / cxml.cc
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 #include "cxml.h"
22 #include <libxml++/libxml++.h>
23 #include <boost/filesystem.hpp>
24 #include <boost/algorithm/string.hpp>
25 #include <cstdio>
26
27 using std::shared_ptr;
28 using std::string;
29 using std::vector;
30 using boost::optional;
31
32 cxml::Node::Node ()
33         : _node (0)
34 {
35
36 }
37
38 cxml::Node::Node (xmlpp::Node* node)
39         : _node (node)
40 {
41
42 }
43
44 string
45 cxml::Node::name () const
46 {
47         if (!_node) {
48                 throw Error ("No node to read name from");
49         }
50         return _node->get_name ();
51 }
52
53 shared_ptr<cxml::Node>
54 cxml::Node::node_child (string name) const
55 {
56         auto const n = node_children (name);
57         if (n.size() > 1) {
58                 throw cxml::Error ("duplicate XML tag " + name);
59         } else if (n.empty ()) {
60                 throw cxml::Error ("missing XML tag " + name + " in " + _node->get_name());
61         }
62
63         return n.front ();
64 }
65
66 shared_ptr<cxml::Node>
67 cxml::Node::optional_node_child (string name) const
68 {
69         auto const n = node_children (name);
70         if (n.size() > 1) {
71                 throw cxml::Error ("duplicate XML tag " + name);
72         } else if (n.empty ()) {
73                 return {};
74         }
75
76         return n.front ();
77 }
78
79 vector<shared_ptr<cxml::Node>>
80 cxml::Node::node_children () const
81 {
82         if (!_node) {
83                 throw Error ("No node to read children from");
84         }
85
86         vector<shared_ptr<cxml::Node> > n;
87         for (auto i: _node->get_children()) {
88                 n.push_back (shared_ptr<Node> (new Node (i)));
89         }
90
91         return n;
92 }
93
94 vector<shared_ptr<cxml::Node>>
95 cxml::Node::node_children (string name) const
96 {
97         /* XXX: using find / get_path should work here, but I can't follow
98            how get_path works.
99         */
100
101         vector<shared_ptr<cxml::Node> > n;
102         for (auto i: _node->get_children()) {
103                 if (i->get_name() == name) {
104                         n.push_back (shared_ptr<Node> (new Node (i)));
105                 }
106         }
107
108         _taken.push_back (name);
109         return n;
110 }
111
112 string
113 cxml::Node::string_child (string c) const
114 {
115         return node_child(c)->content ();
116 }
117
118 optional<string>
119 cxml::Node::optional_string_child (string c) const
120 {
121         auto const nodes = node_children (c);
122         if (nodes.size() > 1) {
123                 throw cxml::Error ("duplicate XML tag " + c);
124         }
125
126         if (nodes.empty ()) {
127                 return {};
128         }
129
130         return nodes.front()->content();
131 }
132
133 bool
134 cxml::Node::bool_child (string c) const
135 {
136         auto const s = string_child (c);
137         return (s == "1" || s == "yes" || s == "True");
138 }
139
140 optional<bool>
141 cxml::Node::optional_bool_child (string c) const
142 {
143         auto const s = optional_string_child (c);
144         if (!s) {
145                 return {};
146         }
147
148         return (s.get() == "1" || s.get() == "yes" || s.get() == "True");
149 }
150
151 void
152 cxml::Node::ignore_child (string name) const
153 {
154         _taken.push_back (name);
155 }
156
157 string
158 cxml::Node::string_attribute (string name) const
159 {
160         auto e = dynamic_cast<const xmlpp::Element *> (_node);
161         if (!e) {
162                 throw cxml::Error ("missing attribute " + name);
163         }
164
165         auto a = e->get_attribute (name);
166         if (!a) {
167                 throw cxml::Error ("missing attribute " + name);
168         }
169
170         return a->get_value ();
171 }
172
173 optional<string>
174 cxml::Node::optional_string_attribute (string name) const
175 {
176         auto e = dynamic_cast<const xmlpp::Element *> (_node);
177         if (!e) {
178                 return {};
179         }
180
181         auto a = e->get_attribute (name);
182         if (!a) {
183                 return {};
184         }
185
186         return string (a->get_value ());
187 }
188
189 bool
190 cxml::Node::bool_attribute (string name) const
191 {
192         auto const s = string_attribute (name);
193         return (s == "1" || s == "yes");
194 }
195
196 optional<bool>
197 cxml::Node::optional_bool_attribute (string name) const
198 {
199         auto s = optional_string_attribute (name);
200         if (!s) {
201                 return {};
202         }
203
204         return (s.get() == "1" || s.get() == "yes");
205 }
206
207 void
208 cxml::Node::done () const
209 {
210         for (auto i: _node->get_children()) {
211                 if (dynamic_cast<xmlpp::Element *> (i) && find (_taken.begin(), _taken.end(), i->get_name()) == _taken.end ()) {
212                         throw cxml::Error ("unexpected XML node " + i->get_name());
213                 }
214         }
215 }
216
217 string
218 cxml::Node::content () const
219 {
220         string content;
221
222         for (auto i: _node->get_children()) {
223                 auto v = dynamic_cast<xmlpp::ContentNode const *> (i);
224                 if (v && dynamic_cast<xmlpp::TextNode const *>(v)) {
225                         content += v->get_content ();
226                 }
227         }
228
229         return content;
230 }
231
232 string
233 cxml::Node::namespace_uri () const
234 {
235         return _node->get_namespace_uri ();
236 }
237
238 string
239 cxml::Node::namespace_prefix () const
240 {
241         return _node->get_namespace_prefix ();
242 }
243
244 cxml::Document::Document (string root_name)
245         : _root_name (root_name)
246 {
247         _parser = new xmlpp::DomParser;
248 }
249
250 cxml::Document::Document (string root_name, boost::filesystem::path file)
251         : _root_name (root_name)
252 {
253         _parser = new xmlpp::DomParser ();
254         read_file (file);
255 }
256
257 cxml::Document::Document ()
258 {
259         _parser = new xmlpp::DomParser ();
260 }
261
262 cxml::Document::~Document ()
263 {
264         delete _parser;
265 }
266
267 void
268 cxml::Document::read_file (boost::filesystem::path file)
269 {
270         if (!boost::filesystem::exists (file)) {
271                 throw cxml::Error ("XML file " + file.string() + " does not exist");
272         }
273
274         _parser->parse_file (file.string ());
275         take_root_node ();
276 }
277
278 void
279 cxml::Document::read_string (string s)
280 {
281         _parser->parse_memory (s);
282         take_root_node ();
283 }
284
285 void
286 cxml::Document::take_root_node ()
287 {
288         if (!_parser) {
289                 throw cxml::Error ("could not parse XML");
290         }
291
292         _node = _parser->get_document()->get_root_node ();
293         if (!_root_name.empty() && _node->get_name() != _root_name) {
294                 throw cxml::Error ("unrecognised root node " + _node->get_name() + " (expecting " + _root_name + ")");
295         } else if (_root_name.empty ()) {
296                 _root_name = _node->get_name ();
297         }
298 }
299
300 static
301 string
302 make_local (string v)
303 {
304         auto lc = localeconv ();
305         boost::algorithm::replace_all (v, ".", lc->decimal_point);
306         /* We hope it's ok not to add in thousands separators here */
307         return v;
308 }
309
310 template <typename P, typename Q>
311 P
312 locale_convert (Q x)
313 {
314         /* We can't write a generic version of locale_convert; all required
315            versions must be specialised.
316         */
317         BOOST_STATIC_ASSERT (sizeof(Q) == 0);
318 }
319
320 template<>
321 int
322 locale_convert (string x)
323 {
324         int y = 0;
325         sscanf (x.c_str(), "%d", &y);
326         return y;
327 }
328
329 template<>
330 unsigned int
331 locale_convert (string x)
332 {
333         unsigned int y = 0;
334         sscanf (x.c_str(), "%u", &y);
335         return y;
336 }
337
338 template<>
339 long int
340 locale_convert (string x)
341 {
342         long int y = 0;
343         sscanf (x.c_str(), "%ld", &y);
344         return y;
345 }
346
347 template<>
348 long unsigned int
349 locale_convert (string x)
350 {
351         long unsigned int y = 0;
352 #ifdef LIBCXML_WINDOWS
353         __mingw_sscanf (x.c_str(), "%lud", &y);
354 #else
355         sscanf (x.c_str(), "%lud", &y);
356 #endif
357         return y;
358 }
359
360 template<>
361 long long
362 locale_convert (string x)
363 {
364         long long y = 0;
365 #ifdef LIBCXML_WINDOWS
366         __mingw_sscanf (x.c_str(), "%lld", &y);
367 #else
368         sscanf (x.c_str(), "%lld", &y);
369 #endif
370         return y;
371 }
372
373 template<>
374 long long unsigned
375 locale_convert (string x)
376 {
377         long long unsigned y = 0;
378 #ifdef LIBCXML_WINDOWS
379         __mingw_sscanf (x.c_str(), "%llud", &y);
380 #else
381         sscanf (x.c_str(), "%llud", &y);
382 #endif
383         return y;
384 }
385
386 template<>
387 float
388 locale_convert (string x)
389 {
390         float y = 0;
391         sscanf (x.c_str(), "%f", &y);
392         return y;
393 }
394
395 template <>
396 double
397 locale_convert (string x)
398 {
399         double y = 0;
400         sscanf (x.c_str(), "%lf", &y);
401         return y;
402 }
403
404 template <>
405 int
406 cxml::raw_convert (string v)
407 {
408         return locale_convert<int> (make_local(v));
409 }
410
411 template <>
412 unsigned int
413 cxml::raw_convert (string v)
414 {
415         return locale_convert<unsigned int> (make_local(v));
416 }
417
418 template <>
419 long int
420 cxml::raw_convert (string v)
421 {
422         return locale_convert<long int> (make_local(v));
423 }
424
425 template <>
426 long unsigned int
427 cxml::raw_convert (string v)
428 {
429         return locale_convert<long unsigned int> (make_local(v));
430 }
431
432 template <>
433 long long
434 cxml::raw_convert (string v)
435 {
436         return locale_convert<long long> (make_local(v));
437 }
438
439 template <>
440 long long unsigned
441 cxml::raw_convert (string v)
442 {
443         return locale_convert<long long unsigned> (make_local(v));
444 }
445
446 template <>
447 float
448 cxml::raw_convert (string v)
449 {
450         return locale_convert<float> (make_local(v));
451 }
452
453 template <>
454 double
455 cxml::raw_convert (string v)
456 {
457         return locale_convert<double> (make_local(v));
458 }