return result;
     }
 
+
+    std::string xml_escape(const std::string & str)
+    {
+       std::string result;
+       std::size_t begin = 0;
+
+       for (;;)
+       {
+           std::size_t end = str.find_first_of("\"&'<>", begin);
+           result.append(str, begin, end - begin);
+           if (end == std::string::npos)
+               return result;
+
+           const char * entity = NULL;
+           switch (str[end])
+           {
+           case '"':  entity = """; break;
+           case '&':  entity = "&";  break;
+           case '\'': entity = "'"; break;
+           case '<':  entity = "<";   break;
+           case '>':  entity = ">";   break;
+           }
+           assert(entity);
+           result.append(entity);
+
+           begin = end + 1;
+       }
+    }
+
+    
     struct dvd_contents
     {
        enum pgc_type { menu_pgc, title_pgc };
        std::vector<menu> menus;
        std::vector<title> titles;
     };
-    
+
     class webdvd_window : public Gtk::Window
     {
     public:
                        filename + " is missing or not a regular file");
                vob_list
                    .append("<vob file='")
-                   // FIXME: Should XML-escape the path
-                   .append(filename)
+                   .append(xml_escape(filename))
                    .append("'/>\n");
            }
            else