diff --git a/src/path.cpp b/src/path.cpp index 72337a4..b6b7ce6 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -121,44 +121,85 @@ namespace quickbook // // Some info on file URLs at: // https://en.wikipedia.org/wiki/File_URI_scheme - std::string file_path_to_url(fs::path const& x) + std::string file_path_to_url_impl(fs::path const& x, bool is_dir) { - // TODO: Maybe some kind of error if this doesn't understand the path. - // TODO: Might need a special cygwin implementation. - // TODO: What if x.has_root_name() && !x.has_root_directory()? - // TODO: What does Boost.Filesystem do for '//localhost/c:/path'? - // Is that event allowed by windows? + fs::path::const_iterator it = x.begin(), end = x.end(); + if (it == end) { return is_dir ? "./" : ""; } + std::string result; + bool sep = false; + std::string part; if (x.has_root_name()) { - std::string root_name = detail::path_to_generic(x.root_name()); + // Handle network address (e.g. \\example.com) + part = detail::path_to_generic(*it); + if (part.size() >= 2 && part[0] == '/' && part[1] == '/') { + result = "file:" + detail::escape_uri(part); + sep = true; + ++it; + if (it != end && *it == "/") { + result += "/"; + sep = false; + ++it; + } + } else { + result = "file:///"; + } - if (root_name.size() > 2 && root_name[0] == '/' && root_name[1] == '/') { - // root_name is a network location. - return "file:" + detail::escape_uri(detail::path_to_generic(x)); + // Handle windows root (e.g. c:) + if (it != end) { + part = detail::path_to_generic(*it); + if (part.size() >= 2 && part[part.size() - 1] == ':') { + result += detail::escape_uri(part.substr(0, part.size()- 1)); + result += ':'; + sep = false; + ++it; + } } - else if (root_name.size() >= 2 && root_name[root_name.size() - 1] == ':') { - // root_name is a drive. - return "file:///" - + detail::escape_uri(root_name.substr(0, root_name.size() - 1)) - + ":/" // Somtimes "|/" is used, to avoid misinterpreting the ':'. - + detail::escape_uri(detail::path_to_generic(x.relative_path())); - } - else { - // Not sure what root_name is. - return detail::escape_uri(detail::path_to_generic(x)); + } else if (x.has_root_directory()) { + result = "file://"; + sep = true; + } else if (*it == ".") { + result = "."; + sep = true; + ++it; + } + + for (;it != end; ++it) + { + part = detail::path_to_generic(*it); + if (part == "/") { + result += "/"; + sep = false; + } else if (part == ".") { + // If the path has a trailing slash, write it out, + // even if is_dir is false. + if (sep) { + result += "/"; + sep = false; + } + } else { + if (sep) { + result += "/"; + } + result += detail::escape_uri(detail::path_to_generic(*it)); + sep = true; } } - else if (x.has_root_directory()) { - return "file://" + detail::escape_uri(detail::path_to_generic(x)); - } - else { - return detail::escape_uri(detail::path_to_generic(x)); + + if (is_dir && sep) { + result += "/"; } + + return result; + } + + std::string file_path_to_url(fs::path const& x) { + return file_path_to_url_impl(x, false); } std::string dir_path_to_url(fs::path const& x) { - return file_path_to_url(x) + "/"; + return file_path_to_url_impl(x, true); } namespace detail { diff --git a/test/unit/path_test.cpp b/test/unit/path_test.cpp index a08ad4c..ec0cc24 100644 --- a/test/unit/path_test.cpp +++ b/test/unit/path_test.cpp @@ -15,18 +15,67 @@ void file_path_to_url_tests() { using boost::filesystem::path; using quickbook::file_path_to_url; - BOOST_TEST_EQ("a/b", file_path_to_url(path("a/b"))); - BOOST_TEST_EQ("../a/b", file_path_to_url(path("../a/b"))); - BOOST_TEST_EQ("A%20B%2bC%2520", file_path_to_url(path("A B+C%20"))); - BOOST_TEST_EQ("file:///a/b", file_path_to_url(path("/a/b"))); + BOOST_TEST_EQ(std::string(), file_path_to_url(path())); + BOOST_TEST_EQ(std::string("."), file_path_to_url(path("."))); + BOOST_TEST_EQ(std::string("./"), file_path_to_url(path("./"))); + BOOST_TEST_EQ(std::string("a/b"), file_path_to_url(path("a/b"))); + BOOST_TEST_EQ(std::string("a/b/"), file_path_to_url(path("a/b/"))); + BOOST_TEST_EQ(std::string("./a/b"), file_path_to_url(path("./a/./././b"))); + BOOST_TEST_EQ(std::string("../a/b"), file_path_to_url(path("../a/b"))); + BOOST_TEST_EQ(std::string("A%20B%2bC%2520"), file_path_to_url(path("A B+C%20"))); + BOOST_TEST_EQ(std::string("file:///"), file_path_to_url(path("/"))); + BOOST_TEST_EQ(std::string("file:///a/b"), file_path_to_url(path("/a/b"))); + BOOST_TEST_EQ(std::string("file:///a/b/"), file_path_to_url(path("/a/b/"))); + BOOST_TEST_EQ(std::string("file://hello/a/b"), file_path_to_url(path("//hello/a/b"))); -#if BOOST_OS_WINDOWS - BOOST_TEST_EQ("file:///a", file_path_to_url(path("\\a"))); - BOOST_TEST_EQ("file:///c:/", file_path_to_url(path("c:\\"))); - BOOST_TEST_EQ("file:///c:/foo/bar", file_path_to_url(path("c:\\foo\\bar"))); +#if BOOST_OS_WINDOWS || BOOST_OS_CYGWIN + // Should this be file:///c:/x ? + BOOST_TEST_EQ(std::string("file://?/a:/x"), file_path_to_url(path("\\\\?\\a:\\x"))); + BOOST_TEST_EQ(std::string("file:///a"), file_path_to_url(path("\\a"))); + BOOST_TEST_EQ(std::string("file:///c:/"), file_path_to_url(path("c:\\"))); + BOOST_TEST_EQ(std::string("file:///c:/foo/bar"), file_path_to_url(path("c:\\foo\\bar"))); + BOOST_TEST_EQ(std::string("file://localhost/c:/foo/bar"), file_path_to_url(path("\\\\localhost\\c:\\foo\\bar"))); + + // Really not sure what to do with these examples. + // Maybe an error? + BOOST_TEST_EQ(std::string("file:///c:"), file_path_to_url(path("c:"))); + BOOST_TEST_EQ(std::string("file:///c:foo/bar"), file_path_to_url(path("c:foo\\bar"))); #endif } +void dir_path_to_url_tests() { + using boost::filesystem::path; + using quickbook::dir_path_to_url; + + BOOST_TEST_EQ(std::string("./"), dir_path_to_url(path())); + BOOST_TEST_EQ(std::string("./"), dir_path_to_url(path("."))); + BOOST_TEST_EQ(std::string("./"), dir_path_to_url(path("./"))); + BOOST_TEST_EQ(std::string("a/b/"), dir_path_to_url(path("a/b"))); + BOOST_TEST_EQ(std::string("a/b/"), dir_path_to_url(path("a/b/"))); + BOOST_TEST_EQ(std::string("./a/b/"), dir_path_to_url(path("./a/./././b"))); + BOOST_TEST_EQ(std::string("../a/b/"), dir_path_to_url(path("../a/b"))); + BOOST_TEST_EQ(std::string("A%20B%2bC%2520/"), dir_path_to_url(path("A B+C%20"))); + BOOST_TEST_EQ(std::string("file:///"), dir_path_to_url(path("/"))); + BOOST_TEST_EQ(std::string("file:///a/b/"), dir_path_to_url(path("/a/b"))); + BOOST_TEST_EQ(std::string("file:///a/b/"), dir_path_to_url(path("/a/b/"))); + BOOST_TEST_EQ(std::string("file://hello/a/b/"), dir_path_to_url(path("//hello/a/b"))); + +#if BOOST_OS_WINDOWS || BOOST_OS_CYGWIN + // Should this be file:///c:/x/ ? + BOOST_TEST_EQ(std::string("file://?/a:/x/"), dir_path_to_url(path("\\\\?\\a:\\x"))); + BOOST_TEST_EQ(std::string("file:///a/"), dir_path_to_url(path("\\a"))); + BOOST_TEST_EQ(std::string("file:///c:/"), dir_path_to_url(path("c:\\"))); + BOOST_TEST_EQ(std::string("file:///c:/foo/bar/"), dir_path_to_url(path("c:\\foo\\bar"))); + BOOST_TEST_EQ(std::string("file://localhost/c:/foo/bar/"), dir_path_to_url(path("\\\\localhost\\c:\\foo\\bar"))); + + // Really not sure what to do with these examples. + // Maybe an error? + BOOST_TEST_EQ(std::string("file:///c:"), dir_path_to_url(path("c:"))); // TODO + BOOST_TEST_EQ(std::string("file:///c:foo/bar/"), dir_path_to_url(path("c:foo\\bar"))); +#endif + +} + void path_difference_tests() { using boost::filesystem::current_path; using boost::filesystem::path; @@ -83,6 +132,7 @@ void path_difference_tests() { int main() { file_path_to_url_tests(); + dir_path_to_url_tests(); path_difference_tests(); return boost::report_errors(); }