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