086e1a032138f3e399a868c2bdc2a65af99b2b79
[videolink.git] / vob_list.cpp
1 // Copyright 2006 Ben Hutchings <ben@decadentplace.org.uk>.
2 // See the file "COPYING" for licence details.
3
4 #include <cerrno>
5 #include <cstring>
6 #include <sstream>
7
8 #include <unistd.h>
9 #include <fcntl.h>
10
11 #include <expat.h>
12
13 #include <glibmm/fileutils.h>
14 #include <glibmm/miscutils.h>
15
16 #include "auto_fd.hpp"
17 #include "auto_handle.hpp"
18 #include "vob_list.hpp"
19
20 namespace
21 {
22     struct xml_parser_closer
23     {
24         void operator()(XML_Parser parser) const
25             {
26                 if (parser)
27                     XML_ParserFree(parser);
28             }
29     };
30
31     struct xml_parser_factory
32     {
33         XML_Parser operator()() const
34             {
35                 return NULL;
36             }
37     };
38
39     typedef auto_handle<XML_Parser, xml_parser_closer, xml_parser_factory>
40     auto_xml_parser;
41
42     struct parse_context
43     {
44         parse_context(XML_Parser parser,
45                       const std::string & list_path,
46                       vob_list & list)
47                 : parser(parser),
48                   list_path(list_path),
49                   base_path(Glib::path_get_dirname(list_path)),
50                   list(list),
51                   level(0)
52             {}
53         XML_Parser parser;
54         const std::string & list_path;
55         std::string base_path;
56         vob_list & list;
57         std::auto_ptr<xml_error> parse_error;
58         unsigned level;
59     };
60
61     void start_element_handler(void * user_data,
62                                const char * name,
63                                const char ** attributes)
64     {
65         parse_context & context = *static_cast<parse_context *>(user_data);
66
67         if (context.level == 0 && std::strcmp(name, "vob-list") == 0)
68         {
69             // We don't expect (and will ignore) any attributes.
70         }
71         else if (context.level == 1 && std::strcmp(name, "vob") == 0)
72         {
73             vob_ref ref;
74
75             while (attributes[0] != NULL)
76             {
77                 if (std::strcmp(attributes[0], "file") == 0)
78                 {
79                     ref.file = attributes[1];
80                     if (!Glib::path_is_absolute(ref.file))
81                         ref.file = Glib::build_filename(context.base_path,
82                                                         ref.file);
83                 }
84                 else if (std::strcmp(attributes[0], "chapters") == 0)
85                 {
86                     ref.chapters = attributes[1];
87                 }
88                 else if (std::strcmp(attributes[0], "pause") == 0)
89                 {
90                     ref.pause = attributes[1];
91                 }
92
93                 attributes += 2;
94             }
95
96             if (ref.file.empty())
97             {
98                 context.parse_error.reset(
99                     new xml_error(
100                         context.list_path,
101                         XML_GetCurrentLineNumber(context.parser),
102                         "<vob> element missing file attribute"));
103             }
104             else
105             {
106                 context.list.push_back(ref);
107             }
108         }
109         else // not root <vob-list> or child <vob>
110         {
111             context.parse_error.reset(
112                 new xml_error(
113                     context.list_path,
114                     XML_GetCurrentLineNumber(context.parser),
115                     std::string("unexpected element: <").append(name)
116                     .append(">")));
117         }
118
119         ++context.level;
120     }
121
122     void end_element_handler(void * user_data,
123                              const char * name)
124     {
125         parse_context & context = *static_cast<parse_context *>(user_data);
126         --context.level;
127     }
128 }
129
130 vob_list read_vob_list(const std::string & path)
131 {
132     vob_list result;
133
134     auto_fd fd(open(path.c_str(), O_RDONLY, 0));
135     if (fd.get() < 0)
136         // FIXME: look up proper error code
137         throw Glib::FileError(Glib::FileError::FAILED,
138                               std::strerror(errno));
139
140     auto_xml_parser parser(XML_ParserCreate(NULL));
141     if (parser.get() == NULL)
142         throw std::bad_alloc(); // any other reason?
143
144     parse_context context(parser.get(), path, result);
145     XML_SetUserData(parser.get(), &context);
146     XML_SetStartElementHandler(parser.get(), start_element_handler);
147     XML_SetEndElementHandler(parser.get(), end_element_handler);
148
149     for (;;)
150     {
151         static const int buffer_size = 1024;
152         void * buffer = XML_GetBuffer(parser.get(), buffer_size);
153         if (buffer == NULL)
154             throw std::bad_alloc();
155         int read_size = read(fd.get(), buffer, buffer_size);
156         if (read_size < 0)
157             // FIXME: look up proper error code
158             throw Glib::FileError(Glib::FileError::FAILED,
159                                   std::strerror(errno));
160         bool is_final = read_size < buffer_size;
161         if (XML_ParseBuffer(parser.get(), read_size, is_final)
162             == XML_STATUS_ERROR)
163             throw xml_error(path,
164                             XML_GetCurrentLineNumber(parser.get()),
165                             XML_ErrorString(XML_GetErrorCode(parser.get())));
166         if (context.parse_error.get())
167             throw *context.parse_error;
168         if (is_final)
169             break;
170     }
171
172     return result;
173 }
174
175 namespace
176 {
177     std::string make_xml_error_message(const std::string & path, int line,
178                                        const std::string & message)
179     {
180         std::ostringstream os;
181         os << path << ":" << line << ": " << message;
182         return os.str();
183     }
184 }
185
186 xml_error::xml_error(const std::string & path, int line,
187                      const std::string & message)
188         : std::runtime_error(make_xml_error_message(path, line, message))
189 {}