Better error.
[libcxml.git] / src / cxml.cc
1 /*
2     Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <sstream>
21 #include <iostream>
22 #include <boost/filesystem.hpp>
23 #include <boost/algorithm/string.hpp>
24 #include <libxml++/libxml++.h>
25 #include "cxml.h"
26
27 using namespace std;
28 using namespace boost;
29
30 cxml::Node::Node ()
31         : _node (0)
32 {
33
34 }
35
36 cxml::Node::Node (xmlpp::Node* node)
37         : _node (node)
38 {
39
40 }
41
42 string
43 cxml::Node::name () const
44 {
45         assert (_node);
46         return _node->get_name ();
47 }
48
49 shared_ptr<cxml::Node>
50 cxml::Node::node_child (string name) const
51 {
52         list<shared_ptr<cxml::Node> > n = node_children (name);
53         if (n.size() > 1) {
54                 throw cxml::Error ("duplicate XML tag " + name);
55         } else if (n.empty ()) {
56                 throw cxml::Error ("missing XML tag " + name + " in " + _node->get_name());
57         }
58         
59         return n.front ();
60 }
61
62 shared_ptr<cxml::Node>
63 cxml::Node::optional_node_child (string name) const
64 {
65         list<shared_ptr<cxml::Node> > n = node_children (name);
66         if (n.size() > 1) {
67                 throw cxml::Error ("duplicate XML tag " + name);
68         } else if (n.empty ()) {
69                 return shared_ptr<cxml::Node> ();
70         }
71         
72         return n.front ();
73 }
74
75 list<shared_ptr<cxml::Node> >
76 cxml::Node::node_children (string name) const
77 {
78         /* XXX: using find / get_path should work here, but I can't follow
79            how get_path works.
80         */
81
82         xmlpp::Node::NodeList c = _node->get_children ();
83         
84         list<shared_ptr<cxml::Node> > n;
85         for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) {
86                 if ((*i)->get_name() == name) {
87                         n.push_back (shared_ptr<Node> (new Node (*i)));
88                 }
89         }
90         
91         _taken.push_back (name);
92         return n;
93 }
94
95 string
96 cxml::Node::string_child (string c) const
97 {
98         return node_child(c)->content ();
99 }
100
101 optional<string>
102 cxml::Node::optional_string_child (string c) const
103 {
104         list<shared_ptr<Node> > nodes = node_children (c);
105         if (nodes.size() > 1) {
106                 throw cxml::Error ("duplicate XML tag " + c);
107         }
108
109         if (nodes.empty ()) {
110                 return optional<string> ();
111         }
112
113         return nodes.front()->content();
114 }
115
116 bool
117 cxml::Node::bool_child (string c) const
118 {
119         string const s = string_child (c);
120         return (s == "1" || s == "yes");
121 }
122
123 optional<bool>
124 cxml::Node::optional_bool_child (string c) const
125 {
126         optional<string> s = optional_string_child (c);
127         if (!s) {
128                 return optional<bool> ();
129         }
130         
131         return (s.get() == "1" || s.get() == "yes");
132 }
133
134 void
135 cxml::Node::ignore_child (string name) const
136 {
137         _taken.push_back (name);
138 }
139
140 string
141 cxml::Node::string_attribute (string name) const
142 {
143         xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
144         if (!e) {
145                 throw cxml::Error ("missing attribute " + name);
146         }
147         
148         xmlpp::Attribute* a = e->get_attribute (name);
149         if (!a) {
150                 throw cxml::Error ("missing attribute " + name);
151         }
152
153         return a->get_value ();
154 }
155
156 optional<string>
157 cxml::Node::optional_string_attribute (string name) const
158 {
159         xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
160         if (!e) {
161                 return optional<string> ();
162         }
163         
164         xmlpp::Attribute* a = e->get_attribute (name);
165         if (!a) {
166                 return optional<string> ();
167         }
168
169         return string (a->get_value ());
170 }
171
172 bool
173 cxml::Node::bool_attribute (string name) const
174 {
175         string const s = string_attribute (name);
176         return (s == "1" || s == "yes");
177 }
178
179 optional<bool>
180 cxml::Node::optional_bool_attribute (string name) const
181 {
182         optional<string> s = optional_string_attribute (name);
183         if (!s) {
184                 return optional<bool> ();
185         }
186
187         return (s.get() == "1" || s.get() == "yes");
188 }
189
190 void
191 cxml::Node::done () const
192 {
193         xmlpp::Node::NodeList c = _node->get_children ();
194         for (xmlpp::Node::NodeList::iterator i = c.begin(); i != c.end(); ++i) {
195                 if (dynamic_cast<xmlpp::Element *> (*i) && find (_taken.begin(), _taken.end(), (*i)->get_name()) == _taken.end ()) {
196                         throw cxml::Error ("unexpected XML node " + (*i)->get_name());
197                 }
198         }
199 }
200
201 string
202 cxml::Node::content () const
203 {
204         string content;
205         
206         xmlpp::Node::NodeList c = _node->get_children ();
207         for (xmlpp::Node::NodeList::const_iterator i = c.begin(); i != c.end(); ++i) {
208                 xmlpp::ContentNode const * v = dynamic_cast<xmlpp::ContentNode const *> (*i);
209                 if (v) {
210                         content += v->get_content ();
211                 }
212         }
213
214         return content;
215 }
216
217 string
218 cxml::Node::namespace_uri () const
219 {
220         return _node->get_namespace_uri ();
221 }
222
223 string
224 cxml::Node::namespace_prefix () const
225 {
226         return _node->get_namespace_prefix ();
227 }
228
229 cxml::Document::Document (string root_name)
230         : _root_name (root_name)
231 {
232         _parser = new xmlpp::DomParser;
233 }
234
235 cxml::Document::Document (string root_name, boost::filesystem::path file)
236         : _root_name (root_name)
237 {
238         _parser = new xmlpp::DomParser ();
239         read_file (file);
240 }
241
242 cxml::Document::Document ()
243 {
244         _parser = new xmlpp::DomParser ();
245 }
246
247 cxml::Document::~Document ()
248 {
249         delete _parser;
250 }
251
252 void
253 cxml::Document::read_file (filesystem::path file)
254 {
255         if (!filesystem::exists (file)) {
256                 throw cxml::Error ("XML file " + file.string() + " does not exist");
257         }
258         
259         _parser->parse_file (file.string ());
260         take_root_node ();
261 }
262
263 void
264 cxml::Document::read_stream (istream& stream)
265 {
266         _parser->parse_stream (stream);
267         take_root_node ();
268 }
269
270 void
271 cxml::Document::read_string (string s)
272 {
273         stringstream t (s);
274         _parser->parse_stream (t);
275         take_root_node ();
276 }
277
278 void
279 cxml::Document::take_root_node ()
280 {
281         if (!_parser) {
282                 throw cxml::Error ("could not parse XML");
283         }
284
285         _node = _parser->get_document()->get_root_node ();
286         if (!_root_name.empty() && _node->get_name() != _root_name) {
287                 throw cxml::Error ("unrecognised root node");
288         } else if (_root_name.empty ()) {
289                 _root_name = _node->get_name ();
290         }
291 }
292