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