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