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