+// Copyright 2006 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cerrno>
+#include <cstring>
+#include <sstream>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <expat.h>
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include "auto_fd.hpp"
+#include "auto_handle.hpp"
+#include "vob_list.hpp"
+
+namespace
+{
+ struct xml_parser_closer
+ {
+ void operator()(XML_Parser parser) const
+ {
+ if (parser)
+ XML_ParserFree(parser);
+ }
+ };
+
+ struct xml_parser_factory
+ {
+ XML_Parser operator()() const
+ {
+ return NULL;
+ }
+ };
+
+ typedef auto_handle<XML_Parser, xml_parser_closer, xml_parser_factory>
+ auto_xml_parser;
+
+ struct parse_context
+ {
+ parse_context(XML_Parser parser,
+ const std::string & list_path,
+ vob_list & list)
+ : parser(parser),
+ list_path(list_path),
+ base_path(Glib::path_get_dirname(list_path)),
+ list(list),
+ level(0)
+ {}
+ XML_Parser parser;
+ const std::string & list_path;
+ std::string base_path;
+ vob_list & list;
+ std::auto_ptr<xml_error> parse_error;
+ unsigned level;
+ };
+
+ void start_element_handler(void * user_data,
+ const char * name,
+ const char ** attributes)
+ {
+ parse_context & context = *static_cast<parse_context *>(user_data);
+
+ if (context.level == 0 && std::strcmp(name, "vob-list") == 0)
+ {
+ // We don't expect (and will ignore) any attributes.
+ }
+ else if (context.level == 1 && std::strcmp(name, "vob") == 0)
+ {
+ vob_ref ref;
+
+ while (attributes[0] != NULL)
+ {
+ if (std::strcmp(attributes[0], "file") == 0)
+ {
+ ref.file = attributes[1];
+ if (!Glib::path_is_absolute(ref.file))
+ ref.file = Glib::build_filename(context.base_path,
+ ref.file);
+ }
+ else if (std::strcmp(attributes[0], "chapters") == 0)
+ {
+ ref.chapters = attributes[1];
+ }
+ else if (std::strcmp(attributes[0], "pause") == 0)
+ {
+ ref.pause = attributes[1];
+ }
+
+ attributes += 2;
+ }
+
+ if (ref.file.empty())
+ {
+ context.parse_error.reset(
+ new xml_error(
+ context.list_path,
+ XML_GetCurrentLineNumber(context.parser),
+ "<vob> element missing file attribute"));
+ }
+ else
+ {
+ context.list.push_back(ref);
+ }
+ }
+ else // not root <vob-list> or child <vob>
+ {
+ context.parse_error.reset(
+ new xml_error(
+ context.list_path,
+ XML_GetCurrentLineNumber(context.parser),
+ std::string("unexpected element: <").append(name)
+ .append(">")));
+ }
+
+ ++context.level;
+ }
+
+ void end_element_handler(void * user_data,
+ const char * name)
+ {
+ parse_context & context = *static_cast<parse_context *>(user_data);
+ --context.level;
+ }
+}
+
+vob_list read_vob_list(const std::string & path)
+{
+ vob_list result;
+
+ auto_fd fd(open(path.c_str(), O_RDONLY, 0));
+ if (fd.get() < 0)
+ // FIXME: look up proper error code
+ throw Glib::FileError(Glib::FileError::FAILED,
+ std::strerror(errno));
+
+ auto_xml_parser parser(XML_ParserCreate(NULL));
+ if (parser.get() == NULL)
+ throw std::bad_alloc(); // any other reason?
+
+ parse_context context(parser.get(), path, result);
+ XML_SetUserData(parser.get(), &context);
+ XML_SetStartElementHandler(parser.get(), start_element_handler);
+ XML_SetEndElementHandler(parser.get(), end_element_handler);
+
+ for (;;)
+ {
+ static const int buffer_size = 1024;
+ void * buffer = XML_GetBuffer(parser.get(), buffer_size);
+ if (buffer == NULL)
+ throw std::bad_alloc();
+ int read_size = read(fd.get(), buffer, buffer_size);
+ if (read_size < 0)
+ // FIXME: look up proper error code
+ throw Glib::FileError(Glib::FileError::FAILED,
+ std::strerror(errno));
+ bool is_final = read_size < buffer_size;
+ if (XML_ParseBuffer(parser.get(), read_size, is_final)
+ == XML_STATUS_ERROR)
+ throw xml_error(path,
+ XML_GetCurrentLineNumber(parser.get()),
+ XML_ErrorString(XML_GetErrorCode(parser.get())));
+ if (context.parse_error.get())
+ throw *context.parse_error;
+ if (is_final)
+ break;
+ }
+
+ return result;
+}
+
+namespace
+{
+ std::string make_xml_error_message(const std::string & path, int line,
+ const std::string & message)
+ {
+ std::ostringstream os;
+ os << path << ":" << line << ": " << message;
+ return os.str();
+ }
+}
+
+xml_error::xml_error(const std::string & path, int line,
+ const std::string & message)
+ : std::runtime_error(make_xml_error_message(path, line, message))
+{}