Use exceptions to report calls to Node with _node == 0.
[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 <boost/filesystem.hpp>
22 #include <boost/algorithm/string.hpp>
23 #include <libxml++/libxml++.h>
24 #include "cxml.h"
25
26 using std::string;
27 using std::list;
28 using boost::shared_ptr;
29 using boost::optional;
30
31 cxml::Node::Node ()
32         : _node (0)
33 {
34
35 }
36
37 cxml::Node::Node (xmlpp::Node* node)
38         : _node (node)
39 {
40
41 }
42
43 string
44 cxml::Node::name () const
45 {
46         if (!_node) {
47                 throw Error ("No node to read name from");
48         }
49         return _node->get_name ();
50 }
51
52 shared_ptr<cxml::Node>
53 cxml::Node::node_child (string name) const
54 {
55         list<shared_ptr<cxml::Node> > n = node_children (name);
56         if (n.size() > 1) {
57                 throw cxml::Error ("duplicate XML tag " + name);
58         } else if (n.empty ()) {
59                 throw cxml::Error ("missing XML tag " + name + " in " + _node->get_name());
60         }
61
62         return n.front ();
63 }
64
65 shared_ptr<cxml::Node>
66 cxml::Node::optional_node_child (string name) const
67 {
68         list<shared_ptr<cxml::Node> > n = node_children (name);
69         if (n.size() > 1) {
70                 throw cxml::Error ("duplicate XML tag " + name);
71         } else if (n.empty ()) {
72                 return shared_ptr<cxml::Node> ();
73         }
74
75         return n.front ();
76 }
77
78 list<shared_ptr<cxml::Node> >
79 cxml::Node::node_children () const
80 {
81         if (!_node) {
82                 throw Error ("No node to read children from");
83         }
84         xmlpp::Node::NodeList c = _node->get_children ();
85
86         list<shared_ptr<cxml::Node> > n;
87         for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) {
88                 n.push_back (shared_ptr<Node> (new Node (*i)));
89         }
90
91         return n;
92 }
93
94 list<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         xmlpp::Node::NodeList c = _node->get_children ();
102
103         list<shared_ptr<cxml::Node> > n;
104         for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) {
105                 if ((*i)->get_name() == name) {
106                         n.push_back (shared_ptr<Node> (new Node (*i)));
107                 }
108         }
109
110         _taken.push_back (name);
111         return n;
112 }
113
114 string
115 cxml::Node::string_child (string c) const
116 {
117         return node_child(c)->content ();
118 }
119
120 optional<string>
121 cxml::Node::optional_string_child (string c) const
122 {
123         list<shared_ptr<Node> > nodes = node_children (c);
124         if (nodes.size() > 1) {
125                 throw cxml::Error ("duplicate XML tag " + c);
126         }
127
128         if (nodes.empty ()) {
129                 return optional<string> ();
130         }
131
132         return nodes.front()->content();
133 }
134
135 bool
136 cxml::Node::bool_child (string c) const
137 {
138         string const s = string_child (c);
139         return (s == "1" || s == "yes" || s == "True");
140 }
141
142 optional<bool>
143 cxml::Node::optional_bool_child (string c) const
144 {
145         optional<string> s = optional_string_child (c);
146         if (!s) {
147                 return optional<bool> ();
148         }
149
150         return (s.get() == "1" || s.get() == "yes" || s.get() == "True");
151 }
152
153 void
154 cxml::Node::ignore_child (string name) const
155 {
156         _taken.push_back (name);
157 }
158
159 string
160 cxml::Node::string_attribute (string name) const
161 {
162         xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
163         if (!e) {
164                 throw cxml::Error ("missing attribute " + name);
165         }
166
167         xmlpp::Attribute* a = e->get_attribute (name);
168         if (!a) {
169                 throw cxml::Error ("missing attribute " + name);
170         }
171
172         return a->get_value ();
173 }
174
175 optional<string>
176 cxml::Node::optional_string_attribute (string name) const
177 {
178         xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
179         if (!e) {
180                 return optional<string> ();
181         }
182
183         xmlpp::Attribute* a = e->get_attribute (name);
184         if (!a) {
185                 return optional<string> ();
186         }
187
188         return string (a->get_value ());
189 }
190
191 bool
192 cxml::Node::bool_attribute (string name) const
193 {
194         string const s = string_attribute (name);
195         return (s == "1" || s == "yes");
196 }
197
198 optional<bool>
199 cxml::Node::optional_bool_attribute (string name) const
200 {
201         optional<string> s = optional_string_attribute (name);
202         if (!s) {
203                 return optional<bool> ();
204         }
205
206         return (s.get() == "1" || s.get() == "yes");
207 }
208
209 void
210 cxml::Node::done () const
211 {
212         xmlpp::Node::NodeList c = _node->get_children ();
213         for (xmlpp::Node::NodeList::iterator i = c.begin(); i != c.end(); ++i) {
214                 if (dynamic_cast<xmlpp::Element *> (*i) && find (_taken.begin(), _taken.end(), (*i)->get_name()) == _taken.end ()) {
215                         throw cxml::Error ("unexpected XML node " + (*i)->get_name());
216                 }
217         }
218 }
219
220 string
221 cxml::Node::content () const
222 {
223         string content;
224
225         xmlpp::Node::NodeList c = _node->get_children ();
226         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
227                 xmlpp::ContentNode const * 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 }