1d5247383db78ef048f3fe203b635658588597db
[libdcp.git] / src / xml.cc
1 #include <sstream>
2 #include <iostream>
3 #include <boost/lexical_cast.hpp>
4 #include <boost/filesystem.hpp>
5 #include <boost/algorithm/string.hpp>
6 #include <libxml++/libxml++.h>
7 #include "xml.h"
8 #include "exceptions.h"
9 #include "util.h"
10
11 using namespace std;
12 using namespace boost;
13 using namespace libdcp;
14
15 XMLNode::XMLNode ()
16         : _node (0)
17 {
18
19 }
20
21 XMLNode::XMLNode (xmlpp::Node const * node)
22         : _node (node)
23 {
24         
25 }
26
27 xmlpp::Node *
28 XMLNode::xml_node (string name)
29 {
30         list<xmlpp::Node*> n = xml_nodes (name);
31         if (n.size() > 1) {
32                 throw XMLError ("duplicate XML tag " + name);
33         } else if (n.empty ()) {
34                 throw XMLError ("missing XML tag " + name);
35         }
36         
37         return n.front ();
38 }
39
40 list<xmlpp::Node*>
41 XMLNode::xml_nodes (string name)
42 {
43         /* XXX: using find / get_path should work here, but I can't follow
44            how get_path works.
45         */
46
47         xmlpp::Node::NodeList c = _node->get_children ();
48         
49         list<xmlpp::Node*> n;
50         for (xmlpp::Node::NodeList::iterator i = c.begin (); i != c.end(); ++i) {
51                 if ((*i)->get_name() == name) {
52                         n.push_back (*i);
53                 }
54         }
55         
56         _taken.push_back (name);
57         return n;
58 }
59
60 string
61 XMLNode::string_node (string name)
62 {
63         return XMLNode (xml_node (name)).content ();
64 }
65
66 string
67 XMLNode::optional_string_node (string name)
68 {
69         list<xmlpp::Node*> nodes = xml_nodes (name);
70         if (nodes.size() > 2) {
71                 throw XMLError ("duplicate XML tag " + name);
72         }
73
74         if (nodes.empty ()) {
75                 return "";
76         }
77
78         return string_node (name);
79 }
80
81 ContentKind
82 XMLNode::kind_node (string name)
83 {
84         return content_kind_from_string (string_node (name));
85 }
86
87 Fraction
88 XMLNode::fraction_node (string name)
89 {
90         return Fraction (string_node (name));
91 }
92
93 int64_t
94 XMLNode::int64_node (string name)
95 {
96         return lexical_cast<int64_t> (string_node (name));
97 }
98
99 int64_t
100 XMLNode::optional_int64_node (string name)
101 {
102         list<xmlpp::Node*> nodes = xml_nodes (name);
103         if (nodes.size() > 2) {
104                 throw XMLError ("duplicate XML tag " + name);
105         }
106
107         if (nodes.empty ()) {
108                 return 0;
109         }
110
111         return int64_node (name);
112 }
113
114 float
115 XMLNode::float_node (string name)
116 {
117         return lexical_cast<float> (string_node (name));
118 }
119
120 void
121 XMLNode::ignore_node (string name)
122 {
123         _taken.push_back (name);
124 }
125
126 Time
127 XMLNode::time_attribute (string name)
128 {
129         string const t = string_attribute (name);
130
131         vector<string> b;
132         boost::split (b, t, is_any_of (":"));
133         assert (b.size() == 4);
134
135         return Time (lexical_cast<int> (b[0]), lexical_cast<int> (b[1]), lexical_cast<int> (b[2]), lexical_cast<int> (b[3]));
136 }
137
138 string
139 XMLNode::string_attribute (string name)
140 {
141         xmlpp::Element const * e = dynamic_cast<const xmlpp::Element *> (_node);
142         if (!e) {
143                 return "";
144         }
145         
146         xmlpp::Attribute* a = e->get_attribute (name);
147         if (!a) {
148                 return "";
149         }
150
151         return a->get_value ();
152 }
153
154 float
155 XMLNode::float_attribute (string name)
156 {
157         return lexical_cast<float> (string_attribute (name));
158 }
159
160 int64_t
161 XMLNode::int64_attribute (string name)
162 {
163         return lexical_cast<int64_t> (string_attribute (name));
164 }
165
166 int64_t
167 XMLNode::optional_int64_attribute (string name)
168 {
169         string const s = string_attribute (name);
170         if (s.empty ()) {
171                 return 0;
172         }
173         
174         return lexical_cast<int64_t> (s);
175 }
176
177 optional<bool>
178 XMLNode::optional_bool_attribute (string name)
179 {
180         string const s = string_attribute (name);
181         if (s.empty ()) {
182                 return optional<bool> ();
183         }
184
185         if (s == "1" || s == "yes") {
186                 return optional<bool> (true);
187         }
188
189         return optional<bool> (false);
190 }
191
192 optional<Color>
193 XMLNode::optional_color_attribute (string name)
194 {
195         string const s = string_attribute (name);
196         if (s.empty ()) {
197                 return optional<Color> ();
198         }
199
200         return optional<Color> (Color (s));
201 }
202
203 void
204 XMLNode::done ()
205 {
206         xmlpp::Node::NodeList c = _node->get_children ();
207         for (xmlpp::Node::NodeList::iterator i = c.begin(); i != c.end(); ++i) {
208                 if (dynamic_cast<xmlpp::Element *> (*i) && find (_taken.begin(), _taken.end(), (*i)->get_name()) == _taken.end ()) {
209                         throw XMLError ("unexpected XML node " + (*i)->get_name());
210                 }
211         }
212 }
213
214 string
215 XMLNode::content ()
216 {
217         xmlpp::Node::NodeList c = _node->get_children ();
218
219         if (c.size() > 1) {
220                 throw XMLError ("unexpected content in XML node");
221         }
222
223         if (c.empty ()) {
224                 return "";
225         }
226         
227         xmlpp::ContentNode const * v = dynamic_cast<xmlpp::ContentNode const *> (c.front());
228         if (!v) {
229                 throw XMLError ("missing content in XML node");
230         }
231         
232         return v->get_content ();
233 }
234
235 XMLFile::XMLFile (string file, string root_name)
236 {
237         if (!filesystem::exists (file)) {
238                 throw FileError ("XML file does not exist", file);
239         }
240         
241         _parser = new xmlpp::DomParser;
242         _parser->parse_file (file);
243         if (!_parser) {
244                 throw XMLError ("could not parse XML");
245         }
246
247         _node = _parser->get_document()->get_root_node ();
248         if (_node->get_name() != root_name) {
249                 throw XMLError ("unrecognised root node");
250         }
251 }
252
253 XMLFile::~XMLFile ()
254 {
255         delete _parser;
256 }