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